$fn = 64; // --- PARAMETERS --- rows = 2; cols = 3; cell_pitch = 40; // Distance between cell centers cell_height = 45; // Total height of the tray cell_bottom_inner = 25; // Inner width of the cell at the bottom wall_thickness = 1.2; // Wall thickness drain_hole_dia = 8; // Diameter of bottom drainage holes lip_width = 5; // Width of the top rim lip_height = 2; // Height/thickness of the top rim // --- DERIVED VARIABLES --- cell_top_inner = cell_pitch - (wall_thickness * 2); w_top_outer = cell_pitch + 0.1; // 0.1mm overlap to ensure manifold geometry w_base_outer = cell_bottom_inner + (wall_thickness * 2); // --- MODULES --- module cell_outer() { hull() { translate([0, 0, 0]) cube([w_base_outer, w_base_outer, 0.01], center=true); translate([0, 0, cell_height]) cube([w_top_outer, w_top_outer, 0.01], center=true); } } module cell_inner() { hull() { translate([0, 0, wall_thickness]) cube([cell_bottom_inner, cell_bottom_inner, 0.01], center=true); translate([0, 0, cell_height + 0.1]) cube([cell_top_inner, cell_top_inner, 0.01], center=true); } } module seedling_tray() { difference() { // Main Body union() { // Outer cell bodies for(x = [0 : cols - 1]) { for(y = [0 : rows - 1]) { translate([x * cell_pitch, y * cell_pitch, 0]) cell_outer(); } } // Top Lip translate([ ((cols - 1) * cell_pitch) / 2, ((rows - 1) * cell_pitch) / 2, cell_height - (lip_height / 2) ]) cube([ (cols * cell_pitch) + (lip_width * 2), (rows * cell_pitch) + (lip_width * 2), lip_height ], center=true); } // Cutouts for(x = [0 : cols - 1]) { for(y = [0 : rows - 1]) { translate([x * cell_pitch, y * cell_pitch, 0]) { // Hollow out the cell cell_inner(); // Bottom drainage hole translate([0, 0, -1]) cylinder(d=drain_hole_dia, h=wall_thickness + 2); } } } } } // --- RENDER --- seedling_tray();