$fn=64; // --- Parameters --- columns = 3; rows = 2; cell_top_width = 40; cell_bottom_width = 28; cell_height = 50; wall_thickness = 1.2; drain_hole_dia = 8; lip_width = 3; lip_height = 3; corner_radius = 4; // --- Modules --- module rounded_square(w, r) { safe_r = max(0.01, min(r, w/2 - 0.01)); hull() { translate([ w/2-safe_r, w/2-safe_r]) circle(safe_r); translate([-w/2+safe_r, w/2-safe_r]) circle(safe_r); translate([ w/2-safe_r, -w/2+safe_r]) circle(safe_r); translate([-w/2+safe_r, -w/2+safe_r]) circle(safe_r); } } module cell_outer() { hull() { linear_extrude(0.01) rounded_square(cell_bottom_width, corner_radius); translate([0, 0, cell_height - 0.01]) linear_extrude(0.01) rounded_square(cell_top_width, corner_radius); } } module cell_inner() { hull() { translate([0, 0, wall_thickness]) linear_extrude(0.01) rounded_square(cell_bottom_width - 2*wall_thickness, max(0.1, corner_radius - wall_thickness)); translate([0, 0, cell_height + 1]) linear_extrude(0.01) rounded_square(cell_top_width - 2*wall_thickness, max(0.1, corner_radius - wall_thickness)); } } module starter_tray() { difference() { // Main Body union() { // Individual tapered cells for(x=[0:columns-1], y=[0:rows-1]) { translate([x*cell_top_width, y*cell_top_width, 0]) cell_outer(); } // Top deck / lip (chamfered for support-free 3D printing) hull() { // Base of the lip for(x=[0, columns-1], y=[0, rows-1]) { translate([x*cell_top_width, y*cell_top_width, cell_height - lip_height]) linear_extrude(0.01) rounded_square(cell_top_width, corner_radius); } // Top edge of the lip for(x=[0, columns-1], y=[0, rows-1]) { translate([x*cell_top_width, y*cell_top_width, cell_height - 0.01]) linear_extrude(0.01) rounded_square(cell_top_width + 2*lip_width, corner_radius + lip_width); } } } // Hollow out cells and add drainage holes for(x=[0:columns-1], y=[0:rows-1]) { translate([x*cell_top_width, y*cell_top_width, 0]) { cell_inner(); // Drainage hole translate([0, 0, -1]) cylinder(h=wall_thickness + 2, d=drain_hole_dia); } } } } // Center the tray on the origin translate([-(columns-1)*cell_top_width/2, -(rows-1)*cell_top_width/2, 0]) starter_tray();