gridfinity-rebuilt-openscad.../gridfinity-rebuilt.scad

505 lines
16 KiB
OpenSCAD
Raw Normal View History

// ===== Info =====
// IMPORTANT: rendering will be better for analyzing the model if fast-csg is enabled. As of writing, this feature is only available in the development builds and not the official release of OpenSCAD, but it makes rendering only take a couple seconds, even for comically large bins. Enable it in Edit > Preferences > Features > fast-csg
// the plane that is the top of the internal bin solid is d_height+h_base above z=0
// the magnet holes can have an extra cut in them to make it easier to print without supports
// tabs will automatically be disabled when gridz is less than 3, as the tabs take up too much space
// examples are at the end of the file
// ===============
$fa = 8;
2022-07-31 06:13:47 +00:00
$fs = 0.25;
gridx = 3; // number of bases along x-axis
gridy = 3; // number of bases along y-axis
gridz = 6; // unit height along z-axis (2, 3, or 6, but can be any)
length = 42;// base unit (if you want to go rogue ig)
2022-07-31 06:13:47 +00:00
enable_holes = true; // holes on the base for magnet / screw
enable_hole_slit = true; // extra cut within holes for better slicing
2022-07-31 06:13:47 +00:00
color("tomato")
gridfinityEqual(n_divx = 2, n_divy = 2, style_tab = 1, enable_scoop = true);
2022-07-31 06:13:47 +00:00
// ===== User Modules =====
// Creates an equally divided gridfinity bin.
//
// n_divx: number of x compartments (ideally, coprime w/ gridx)
// n_divy: number of y compartments (ideally, coprime w/ gridy)
// set n_div values to 0 for a solid bin
// style_tab: tab style for all compartments. see cut()
// enable_scoop: scoop toggle for all compartments. see cut()
module gridfinityEqual(n_divx=1, n_divy=1, style_tab=1, enable_scoop=true) {
gridfinityCustom()
for (i = [1:n_divx])
for (j = [1:n_divy])
cut((i-1)*gridx/n_divx,(j-1)*gridy/n_divy, gridx/n_divx, gridy/n_divy, style_tab, enable_scoop);
}
// wrapper module
// DOES NOT CHECK FOR VALID COMPARTMENT STRUCTURE
module gridfinityCustom() {
difference() {
color("firebrick") block_bottom(d_height);
children();
}
color("orange") block_base();
color("royalblue") block_wall();
}
// Function to include in the custom() module to individually slice bins
// Will try to clamp values to fit inside the provided base size
//
// x: start coord. x=1 is the left side of the bin.
// y: start coord. y=1 is the bottom side of the bin.
// w: width of compartment, in # of bases covered
// h: height of compartment, in # of basese covered
// t: tab style of this specific compartment.
// alignment only matters if the compartment size is larger than d_tabw
// 0:full, 1:auto, 2:left, 3:center, 4:right, 5:none
// Automatic alignment will use left tabs for bins on the left edge, right tabs for bins on the right edge, and center tabs everywhere else.
// s: toggle the rounded back corner that allows for easy removal
module cut(x=0, y=0, w=1, h=1, t=1, s=true) {
cut_move(x,y,w,h)
block_cutter(clp(x,0,gridx), clp(y,0,gridy), clp(w,0,gridx-x), clp(h,0,gridy-y), t, s);
}
// Translates an object from the origin point to the center of the requested compartment block, can be used to add custom cuts in the bin
// See cut() module for parameter descriptions
module cut_move(x, y, w, h) {
cut_move_unsafe(clp(x,0,gridx), clp(y,0,gridy), clp(w,0,gridx-x), clp(h,0,gridy-y))
children();
}
2022-07-31 06:13:47 +00:00
2022-07-31 06:13:47 +00:00
// ===== Dimensions =====
h_base = 5; // height of the base
r_base = 4; // outside rounded radius of bin
r_c1 = 0.8; // lower base chamfer "radius"
r_c2 = 2.4; // upper base chamfer "radius"
h_bot = 2.2; // bottom thiccness of bin
r_fo1 = 7.5; // outside radii
r_fo2 = 3.2;
r_fo3 = 1.6;
2022-07-31 06:13:47 +00:00
r_hole1 = 1.5; // screw hole radius
r_hole2 = 3.25; // magnet hole radius
d_hole = 26; // center-to-center distance between holes
h_hole = 2.4; // magnet hole depth
r_f1 = 0.6; // top edge fillet radius
2022-08-04 16:42:52 +00:00
r_f2 = 2.8; // internal fillet radius
2022-07-31 06:13:47 +00:00
d_div = 1.2; // width of divider between compartments
d_wall = 0.95; // minimum wall thickness
d_clear = 0.25; // tolerance fit factor
d_tabh = 15.85; // height of tab (yaxis, measured from inner wall)
d_tabw = length; // maximum width of tab
a_tab = 36;
2022-07-31 06:13:47 +00:00
d_height = (gridz-1)*7 + 2;
r_scoop = length*gridz/12 - r_f2; // scoop radius
2022-07-31 06:13:47 +00:00
d_wall2 = r_base-r_c1-d_clear*sqrt(2);
xl = gridx*length-0.5-2*d_wall+d_div;
yl = gridy*length-0.5-2*d_wall+d_div;
2022-07-31 06:13:47 +00:00
// ===== Modules =====
module profile_base() {
polygon([
[0,0],
[0,h_base],
[r_base,h_base],
[r_base-r_c2,h_base-r_c2],
[r_base-r_c2,r_c1],
[r_base-r_c2-r_c1,0]
]);
}
module block_base() {
translate([0,0,h_base])
rounded_rectangle(gridx*length-0.5+0.002, gridy*length-0.5+0.002, h_bot/1.5, r_fo1/2+0.001);
2022-07-31 06:13:47 +00:00
pattern_linear(gridx, gridy, length)
render()
difference() {
translate([0,0,h_base])
mirror([0,0,1])
union() {
hull() {
rounded_square(length-0.5-2*r_c2-2*r_c1, h_base, r_fo3/2);
rounded_square(length-0.5-2*r_c2, h_base-r_c1, r_fo2/2);
}
hull() {
rounded_square(length-0.5-2*r_c2, r_c2, r_fo2/2);
mirror([0,0,1])
rounded_square(length-0.5, h_bot/2, r_fo1/2);
}
}
if (enable_holes)
pattern_circular(4)
translate([d_hole/2, d_hole/2, 0]) {
union() {
difference() {
cylinder(h = 2*(h_hole+(enable_hole_slit?0.2:0)), r = r_hole2, center=true);
if (enable_hole_slit)
copy_mirror([0,1,0])
translate([-1.5*r_hole2,r_hole1+0.1,h_hole])
cube([r_hole2*3,r_hole2*3, 0.4]);
2022-07-31 06:13:47 +00:00
}
cylinder(h = 3*h_base, r = r_hole1, center=true);
2022-07-31 06:13:47 +00:00
}
}
}
}
module profile_wall_sub() {
difference() {
polygon([
[0,0],
[d_wall/2,0],
[d_wall/2,d_height-1.2-d_wall2+d_wall/2],
[d_wall2,d_height-1.2],
2022-07-31 06:13:47 +00:00
[d_wall2,d_height+h_base],
[0,d_height+h_base]
]);
color("red")
2022-07-31 06:13:47 +00:00
offset(delta = 0.25)
translate([r_base,d_height,0])
mirror([1,0,0])
profile_base();
square([d_wall,0.1]);
2022-07-31 06:13:47 +00:00
}
}
module profile_wall() {
translate([r_base,0,0])
mirror([1,0,0])
difference() {
profile_wall_sub();
difference() {
translate([0, d_height+h_base-d_clear*sqrt(2), 0])
circle(r_base/2);
offset(r = r_f1)
offset(delta = -r_f1)
profile_wall_sub();
}
}
}
module block_wall() {
translate([0,0,h_base])
sweep_rounded(gridx*length-2*r_base-0.5-0.001, gridy*length-2*r_base-0.5-0.001)
2022-07-31 06:13:47 +00:00
profile_wall();
}
module block_bottom( h = 2.2 ) {
translate([0,0,h_base+0.1])
rounded_rectangle(gridx*length-0.5-d_wall/4, gridy*length-0.5-d_wall/4, d_height-0.1, r_base+0.01);
2022-07-31 06:13:47 +00:00
}
module cut_move_unsafe(x, y, w, h) {
translate([(x)*xl/gridx,(y)*yl/gridy,0])
translate([(-xl+d_div)/2,(-yl+d_div)/2,0])
translate([(w*xl/gridx-d_div)/2,(h*yl/gridy-d_div)/2,0])
children();
2022-07-31 06:13:47 +00:00
}
module block_cutter(x,y,w,h,t,s) {
v_len_tab = d_tabh;
v_len_lip = d_wall2-d_wall+1.2;
v_cut_tab = d_tabh - (2*r_f1)/tan(a_tab);
v_cut_lip = d_wall2-d_wall;
v_ang_tab = a_tab;
v_ang_lip = 45;
ylast = abs(y+h-gridy)<0.001;
xlast = abs(x+w-gridx)<0.001;
ylen = h*yl/gridy-d_div;
xlen = w*xl/gridx-d_div;
2022-07-31 06:13:47 +00:00
height = d_height;
extent = (s && y==0 ? d_wall2-d_wall : 0);
tab = (gridz < 3 || t == 5) ? (ylast?v_len_lip:0) : v_len_tab;
ang = (gridz < 3 || t == 5) ? (ylast?v_ang_lip:0) : v_ang_tab;
cut = (gridz < 3 || t == 5) ? (ylast?v_cut_lip:0) : v_cut_tab;
style = (t > 1 && t < 5) ? t-3 : (x == 0 ? -1 : xlast ? 1 : 0);
2022-07-31 06:13:47 +00:00
translate([0,ylen/2,h_base+h_bot])
rotate([90,0,-90]) {
if (gridz >= 3 && xlen - d_tabw > 4*r_f2 && t != 0) {
fillet_cutter(3,"bisque")
difference() {
transform_tab(style, xlen, ((x==0&&style==-1)||(xlast&&style==1))?v_cut_lip:0)
translate([ylast?d_wall2-d_wall:0,0])
profile_cutter(height-h_bot, ylen/2, s);
if (x==0)
translate([0,0,(xlen/2-r_f2)-v_cut_lip])
cube([ylen,height,v_cut_lip*2]);
if (xlast)
translate([0,0,-(xlen/2-r_f2)-v_cut_lip])
cube([ylen,height,v_cut_lip*2]);
}
if (t != 0 && t != 5)
fillet_cutter(2,"indigo")
difference() {
transform_tab(style, xlen, ((x==0&&style==-1)||(xlast&&style==1))?v_cut_lip:0)
difference() {
intersection() {
profile_cutter(height-h_bot, ylen-extent, s);
profile_cutter_tab(height-h_bot, v_len_tab, v_ang_tab);
}
if (ylast) profile_cutter_tab(height-h_bot, v_len_lip, 45);
}
if (x == 0)
translate([ylen/2,0,xlen/2])
rotate([0,90,0])
transform_main(2*ylen)
profile_cutter_tab(height-h_bot, v_len_lip, v_ang_lip);
if (xlast)
translate([ylen/2,0,-xlen/2])
rotate([0,-90,0])
transform_main(2*ylen)
profile_cutter_tab(height-h_bot, v_len_lip, v_ang_lip);
}
}
2022-07-31 06:13:47 +00:00
fillet_cutter(1,"seagreen")
translate([0,0,xlast?v_cut_lip/2:0])
translate([0,0,x==0?-v_cut_lip/2:0])
transform_main(xlen-(x==0?v_cut_lip:0)-(xlast?v_cut_lip:0))
translate([cut,0])
profile_cutter(height-h_bot, ylen-extent-cut-(!s&&y==0?v_cut_lip:0), s);
2022-07-31 06:13:47 +00:00
fillet_cutter(0,"hotpink")
difference() {
transform_main(xlen)
difference() {
profile_cutter(height-h_bot, ylen-extent, s);
if (!((gridz < 3 || t == 5) && !ylast))
profile_cutter_tab(height-h_bot, tab, ang);
if (!s && y == 0)
translate([ylen-extent,0,0])
mirror([1,0,0])
profile_cutter_tab(height-h_bot, v_len_lip, v_ang_lip);
}
if (x == 0)
color("indigo")
translate([ylen/2,0,xlen/2])
rotate([0,90,0])
transform_main(2*ylen)
profile_cutter_tab(height-h_bot, v_len_lip, v_ang_lip);
if (xlast)
color("indigo")
translate([ylen/2,0,-xlen/2])
rotate([0,-90,0])
transform_main(2*ylen)
profile_cutter_tab(height-h_bot, v_len_lip, v_ang_lip);
2022-07-31 06:13:47 +00:00
}
}
2022-07-31 06:13:47 +00:00
}
module transform_main(xlen) {
translate([0,0,-(xlen-2*r_f2)/2])
linear_extrude(xlen-2*r_f2)
children();
}
module transform_tab(type, xlen, cut) {
mirror([0,0,type==1?1:0])
copy_mirror([0,0,-(abs(type)-1)])
translate([0,0,-(xlen)/2])
translate([0,0,r_f2])
linear_extrude((xlen-d_tabw-abs(cut))/(1-(abs(type)-1))-2*r_f2)
children();
}
module fillet_cutter(t = 0, c = "goldenrod") {
color(c)
minkowski() {
children();
sphere(r = r_f2-t/1000);
2022-07-31 06:13:47 +00:00
}
}
module profile_cutter(h, length, s) {
scoop = s ? r_scoop : 0;
translate([r_f2,r_f2])
hull() {
if (length-scoop-2*r_f2 > 0)
square(0.1);
if (scoop < h) {
translate([length-2*r_f2,h-r_f2/2])
mirror([1,1])
square(0.1);
translate([0,h-r_f2/2])
mirror([0,1])
square(0.1);
}
difference() {
translate([length-scoop-2*r_f2, scoop])
if (scoop != 0) {
intersection() {
circle(scoop);
mirror([0,1]) square(2*scoop);
}
} else mirror([1,0]) square(0.1);
translate([length-scoop-2*r_f2,-1])
square([-(length-scoop-2*r_f2),2*h]);
translate([0,h])
square([2*length,scoop]);
}
2022-07-31 06:13:47 +00:00
}
}
module profile_cutter_tab(h, tab, ang) {
if (tab > 0)
color("blue")
offset(delta = r_f2)
polygon([[0,h],[tab,h],[0,h-tab*tan(ang)]]);
}
2022-07-31 06:13:47 +00:00
// ==== Utilities =====
function clp(x,a,b) = min(max(x,a),b);
module rounded_rectangle(length, width, height, rad) {
linear_extrude(height)
offset(rad)
offset(-rad)
square([length,width], center = true);
}
module rounded_square(length, height, rad) {
rounded_rectangle(length, length, height, rad);
}
2022-07-31 06:13:47 +00:00
module copy_mirror(vec=[0,1,0]) {
children();
if (vec != [0,0,0])
mirror(vec)
children();
2022-07-31 06:13:47 +00:00
}
module pattern_linear(x = 1, y = 1, spacing = 0) {
translate([-(x-1)*spacing/2,-(y-1)*spacing/2,0])
for (i = [1:x])
for (j = [1:y])
translate([(i-1)*spacing,(j-1)*spacing,0])
children();
}
module pattern_circular(n=2) {
for (i = [1:n])
rotate(i*360/n)
children();
}
module sweep_rounded(w=10, h=10) {
union() pattern_circular(2) {
copy_mirror([1,0,0])
translate([w/2,h/2,0])
rotate_extrude(angle = 90, convexity = 4)
children();
translate([w/2,0,0])
rotate([90,0,0])
linear_extrude(height = h, center = true)
children();
rotate([0,0,90])
translate([h/2,0,0])
rotate([90,0,0])
linear_extrude(height = w, center = true)
children();
}
}
// ===== Examples =====
// All examples assume gridx == 3, gridy == 3, and gridz == 6, but some may work with other settings
// 3x3 even spaced grid
//gridfinityEqual(3, 3, 0, true);
// Compartments can be placed anywhere (this includes non-integer positions like 1/2 or 1/3). The grid is defined as (0,0) being the bottom left corner of the bin, with each unit being 1 base long. Each cut() module is a compartment, with the first four values defining the area that should be made into a compartment (X coord, Y coord, width, and height). These values should all be positive. t is the tab style of the compartment (0:full, 1:auto, 2:left, 3:center, 4:right, 5:none). s is a toggle for the bottom scoop.
/*
gridfinityCustom() {
cut(x=0, y=0, w=1.5, h=0.5, t=5, s=false);
cut(0, 0.5, 1.5, 0.5, 5, false);
cut(0, 1, 1.5, 0.5, 5, false);
cut(0,1.5,0.5,1.5,5,false);
cut(0.5,1.5,0.5,1.5,5,false);
cut(1,1.5,0.5,1.5,5,false);
cut(1.5, 0, 1.5, 5/3, 2);
cut(1.5, 5/3, 1.5, 4/3, 4);
}*/
// Compartments can overlap! This allows for weirdly shaped compartments, such as this "2" bin.
/*
gridfinityCustom() {
cut(0,2,2,1,5,false);
cut(1,0,1,3,5);
cut(1,0,2,1,5);
cut(0,0,1,2);
cut(2,1,1,2);
}*/
// Areas without a compartment are solid material, where you can put your own cutout shapes. using the cut_move() function, you can select an area, and any child shapes will be moved from the origin to the center of that area, and subtracted from the block. For example, a pattern of three cylinderical holes.
/*
gridfinityCustom() {
cut(x=0, y=0, w=2, h=3);
cut(x=0, y=0, w=3, h=1, t=5);
cut_move(x=2, y=1, w=1, h=2)
pattern_linear(x=1, y=3, spacing=length/2)
cylinder(r=5, h=10*d_height, center=true);
}*/
// You can use loops as well as the bin dimensions to make different parametric functions, such as this one, which divides the box into columns, with a small 1x1 top compartment and a long vertical compartment below
/*gridfinityCustom() {
for(i=[0:gridx-1]) {
cut(i,0,1,gridx-1);
cut(i,gridx-1,1,1);
}
}*/
// Pyramid scheme bin
/*
gridfinityCustom() {
for (i = [0:gridx-1])
for (j = [0:i])
cut(j*gridx/(i+1),gridy-i-1,gridx/(i+1),1,0);
}*/