$fa = 5; $fs = 0.25; // ===== Parameters ===== gridx = 2; // number of bases along x-axis gridy = 2; // number of bases along y-axis gridz = 3; // unit height along z-axis (2, 3, or 6, but can be anything) n_div = 2; // number of compartments (ideally, coprime w/ gridx) // set n_div to 0 for a solid bin (for custom bins) length = 42; // base unit (if you want to go rogue ig) // type of tab style. alignment only matters if tabs are large enough // 0:full, 1:automatic, 2:right, 3:center, 4:left, 5:none style_tab = 0; enable_scoop = true; // the rounded edge that allows for easy removal enable_holes = true; // holes on the base for magnet / screw enable_holeslit = true; // extra cut within holes for better slicing // ===== Info ===== // the red plane that is the top of the internal bin is d_height+h_base above z=0 // the tab cutter object causes serious lag in the preview, I think it has something to do with cutting the same surfaces as other cutting objects, but I cannot seem to fix it, apologies // the magnet holes have an extra cut in them to make it easier to print without supports // ===== Dimensions ===== // base 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 // base holes 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 // fillets r_f1 = 0.6; // top edge fillet radius r_f2 = 2.8; // internal fillet radius r_f3 = 0.6; // lip fillet radius // misc d_div = 1.2; // width of divider between compartments d_wall = 0.95; // minimum wall thickness d_clear = 0.25; // tolerance fit factor // tabs d_tabh = 15.85; // height of tab (yaxis, measured from inner wall) d_tabw = length; // maximum width of tab a_tab = 32; // calculations d_height = (gridz-1)*7 + 2; r_scoop = length*gridz/12; // scoop radius d_wall2 = r_base-r_c1-d_clear*sqrt(2); d_width = (gridx*length-2*d_wall-(n_div-1)*d_div)/n_div; b_notab = style_tab == 5 || gridz < 3; // magic numbers (cutter parameters) v_tab = [r_f2, r_f3, d_height-h_bot-(d_tabh-d_wall)*tan(a_tab), d_tabh-d_wall-r_f3/tan(a_tab/2), d_height-h_bot-r_f3, 179, a_tab, r_f2]; v_edg = [r_f2, 0, d_height-h_bot-d_wall2, d_wall2-d_wall, d_height-h_bot-d_wall, 90, 45]; v_slo = [r_scoop, 0, 2*d_height, 0, 0, 30, 10]; gridfinity(); // ===== Modules ===== module gridfinity() { difference() { // solid bin block_bottom(d_height); // subtraction blocks block_cutter(); } block_base(); block_wall(); } 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() { color("orange") pattern_linear(gridx, gridy, length) render() union() { sweep_rounded(length-2*r_base,length-2*r_base) profile_base(); pattern_circular(4) difference() { linear_extrude(h_base) square(length/2-r_base); if (enable_holes) translate([d_hole/2, d_hole/2, 0]) union() { cylinder(h = 3*h_base, r = r_hole1, center=true); cylinder(h = 2*h_hole, r = r_hole2, center=true); if (enable_holeslit) intersection() { cylinder(h = 2*(h_hole+0.2), r = r_hole2, center=true); cube([r_hole1*2,r_hole2*3,2*(h_hole+0.4)], center=true); } } } } } module profile_wall_sub() { difference() { polygon([ [0,0], [d_wall/2,0], [d_wall/2,d_height-d_wall2-d_wall/2], [d_wall2,d_height-d_wall], [d_wall2,d_height+h_base], [0,d_height+h_base] ]); offset(delta = 0.25) translate([r_base,d_height,0]) mirror([1,0,0]) profile_base(); } } 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() { color("royalblue") translate([0,0,h_base]) sweep_rounded(gridx*length-2*r_base, gridy*length-2*r_base) profile_wall(); } module block_bottom( h = 2.2 ) { color("firebrick") translate([0,0,h_base]) hull() sweep_rounded(gridx*length-2*r_base, gridy*length-2*r_base) translate([r_base-0.1,0,0]) mirror([1,0,0]) square([d_wall, d_height]); } module profile_cutter(r = 0, width = 1, stretch = 0) { extent = width/2-r_f2+0.1; translate([r-r_f2,0,0]) union() { difference() { union() { translate([0,extent,0]) circle(r=r_f2); square([r_f2,extent]); } translate([-r_f2,0,0]) square([r_f2,extent+r_f2]); } mirror([1,0,0]) square([stretch,extent+r_f2]); } } module part_bend(t=[0,0], rot, ang, rad, width, s = 0) { translate([t.x,t.y,0]) rotate([0,0,rot]) rotate_extrude(angle = ang, convexity = 4) profile_cutter(rad,width,s); } module part_line(t=[0,0], rot, d, width, s = 0) { translate([t.x,t.y,0]) rotate([90,0,rot]) translate([r_f2,0,0]) linear_extrude(d) profile_cutter(0,width,s); } module cutter_main(arr, width, offset = 0) { r1 = arr[0]; r2 = arr[1]; a_end = arr[5]; a_slo = arr[6]; p_y3 = arr[2] - r1*tan((90-a_slo)/2); p_x4 = arr[3]; p_y4 = arr[4]; d_width = width; d_extent = gridy*length/2 - d_wall + 0.1; l_angle = ([p_x4-r1,p_y4-p_y3] * [[cos(a_slo), -sin(a_slo)], [sin(a_slo), cos(a_slo)]])[0]; translate([0,gridy*length/2-d_wall,h_base+h_bot]) rotate([90,0,-90]) union() copy_mirror([0,0,1]) translate([offset,0,-0.1]) { // outside of hull because of its concave geometry if (p_x4 != 0 || p_y4 != 0) difference() { union() { render() part_bend([p_x4, p_y4], a_slo+90, a_end-a_slo, -r2, d_width, 2*r_f2); part_line([p_x4, p_y4]+(r_f2+r2)*ta(a_end-90)-0.1*ta(a_end), a_end+90, d_extent, d_width, 2*r_f2); part_line([r1, p_y3]+(r1-r_f2)*-ta(-a_slo)+l_angle*ta(a_slo), a_slo+90, l_angle, d_width, 2*r_f2); } copy_mirror([0,1,0]) translate([0,-0.1,-0.1]) cube([d_extent+0.1,r_f2,d_width]); } hull() { // left bottom, angle, and scoop fillets part_bend([r1, r1], -180, 90, r1, d_width); part_bend([r1, p_y3], 90+a_slo, 90-a_slo, r1, d_width); // bottom, left, right, angle (thin), angle (thicc) part_line([d_extent, r_f2], -90, d_extent - r1, d_width); part_line([r_f2, r1], 180, p_y3 - r1, d_width); part_line([r1, p_y3]+(r1-r_f2)*-ta(-a_slo), a_slo+90, (d_extent-r1+(r1-r_f2)*cos(a_slo))/cos(a_slo), d_width); } } } module cutter_tab(s = 1) { d_divw = (gridx*length-2*d_wall-(n_div-1)*d_div)/n_div; if ((d_divw > d_tabw && s != 0 && d_divw - d_tabw > 4*r_f2 ) || s == 5) { d_w = (d_divw - (s==5?0:length)) / (s==3?2:1); mirror([s==2?1:0, 0, 0]) copy_mirror([s==3?1:0, 0, 0]) translate([(d_divw-d_w)/2,0,0]) cutter_main(v_edg, d_w); } } module block_cutter() { if (n_div > 0) { pattern_linear(n_div, 1, d_width + d_div) cutter_main(b_notab ? v_edg : v_tab, d_width); if (!b_notab) { for (i = [1:n_div]) translate([((i-1)-(n_div-1)/2)*(d_width + d_div),0,0]) cutter_tab(style_tab==1?(i==1?4:(i==n_div?2:3)):style_tab); } mirror([0,1,0]) for (i = [1:n_div]) translate(((i-1)-(n_div-1)/2)*(d_width + d_div)*[1,0,0]) cutter_main(enable_scoop?v_slo:v_edg, d_width, enable_scoop?d_wall2-d_wall : 0); } } // ==== Utilities ===== ta = function (a) [cos(a), sin(a)]; module copy_mirror(vec=[0,1,0]) { children(); mirror(vec) children(); } 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(); } }