seedling tray: add water tray and pressing tool

This commit is contained in:
2026-09-18 16:15:34 +02:00
parent a20eb904a4
commit 07e8105cce
2 changed files with 147 additions and 46 deletions
+43 -14
View File
@@ -20,28 +20,30 @@ def render_scad(
rows: int, rows: int,
cell_size: float, cell_size: float,
tray_height: float, tray_height: float,
water_tray_height: float,
render_mode: str, render_mode: str,
) -> None: ) -> None:
cmd = ( cmd = [
f'openscad ' "openscad",
f'-D cols={cols} ' "-D", f"cols={cols}",
f'-D rows={rows} ' "-D", f"rows={rows}",
f'-D cell_size={cell_size} ' "-D", f"cell_size={cell_size}",
f'-D tray_height={tray_height} ' "-D", f"tray_height={tray_height}",
f'-D \'render_mode="{render_mode}"\' ' "-D", f"water_tray_height={water_tray_height}",
f'-o "{out_file}" ' "-D", f'render_mode="{render_mode}"',
f'"{scad_file}"' "-o", str(out_file),
) str(scad_file),
]
result = subprocess.run( result = subprocess.run(
["bash", "-lc", cmd], cmd,
cwd=BASE_DIR, cwd=BASE_DIR,
text=True, text=True,
capture_output=True, capture_output=True,
) )
if result.returncode != 0: if result.returncode != 0:
print(f"\nFailed to render {scad_file.name}") print(f"\nFailed to render {scad_file.name} ({render_mode})")
if result.stdout: if result.stdout:
print(result.stdout) print(result.stdout)
if result.stderr: if result.stderr:
@@ -61,6 +63,7 @@ def main() -> None:
rows = ask_int("Number of rows (rows): ") rows = ask_int("Number of rows (rows): ")
cell_size = ask_float("Inner cell size in mm (cell_size): ") cell_size = ask_float("Inner cell size in mm (cell_size): ")
tray_height = ask_float("Tray height in mm (tray_height): ") tray_height = ask_float("Tray height in mm (tray_height): ")
water_tray_height = ask_float("Water tray wall height in mm (water_tray_height): ")
OUT_DIR.mkdir(parents=True, exist_ok=True) OUT_DIR.mkdir(parents=True, exist_ok=True)
@@ -68,7 +71,7 @@ def main() -> None:
if not scad_file.exists(): if not scad_file.exists():
raise SystemExit(f"Missing file: {scad_file}") raise SystemExit(f"Missing file: {scad_file}")
# Render the tray only # seedling tray
render_scad( render_scad(
scad_file, scad_file,
OUT_DIR / "seedlings-tray.stl", OUT_DIR / "seedlings-tray.stl",
@@ -76,10 +79,11 @@ def main() -> None:
rows, rows,
cell_size, cell_size,
tray_height, tray_height,
water_tray_height,
"tray_only", "tray_only",
) )
# Render all plates in a grid # removable plates
render_scad( render_scad(
scad_file, scad_file,
OUT_DIR / "plates.stl", OUT_DIR / "plates.stl",
@@ -87,9 +91,34 @@ def main() -> None:
rows, rows,
cell_size, cell_size,
tray_height, tray_height,
water_tray_height,
"plates_only", "plates_only",
) )
# water tray
render_scad(
scad_file,
OUT_DIR / "water-tray.stl",
cols,
rows,
cell_size,
tray_height,
water_tray_height,
"water_tray_only",
)
# pressing tool
render_scad(
scad_file,
OUT_DIR / "pusher-tool.stl",
cols,
rows,
cell_size,
tray_height,
water_tray_height,
"tool_only",
)
print("\nDone.") print("\nDone.")
+104 -32
View File
@@ -1,27 +1,37 @@
// ======================================================== // ========================================================
// PARAMETRIC SEEDLING TRAY & PUSHABLE PLATES // PARAMETRIC SEEDLING TRAY SYSTEM
// Includes: Tray, Plates, Angled Water Tray, & Ball-Pusher Tool
// ======================================================== // ========================================================
/* [Grid Configuration] */ /* [Grid Configuration] */
cols = 3; // Number of columns cols = 4; // Number of columns
rows = 2; // Number of rows rows = 3; // Number of rows
cell_size = 35; // Inner width/length of each square cell (mm) cell_size = 35; // Inner width/length of each square cell (mm)
tray_height = 50; // Total height of the tray (mm) tray_height = 50; // Total height of the tray (mm)
/* [Wall & Ledge Dimensions] */ /* [Wall & Ledge Dimensions] */
wall_thickness = 1.6; // Thickness of the walls (mm) wall_thickness = 1.6; // Thickness of outer and interior walls (mm)
bottom_lip = 4.0; // Width of the bottom ledge holding the plate (mm) bottom_lip = 4.0; // Width of the bottom ledge holding the plate (mm)
floor_thickness = 2.0; // Thickness of the bottom lip (mm) floor_thickness = 2.0; // Height/thickness of the bottom lip (mm)
/* [Pusher Plate Settings] */ /* [Pusher Plate Settings] */
plate_thickness = 2.0; // Thickness of the pushable plate (mm) plate_thickness = 2.0; // Thickness of the removable plate (mm)
clearance = 0.4; // Gap between cell walls and plate for easy sliding (mm) clearance = 0.4; // Gap between cell walls and plate for easy sliding (mm)
drain_hole_dia = 4.5; // Diameter of drainage holes in the pushable plate (mm) drain_hole_dia = 4.5; // Diameter of drainage holes in the plate (mm)
/* [Angled Water Tray Settings] */
water_tray_height = 25; // Height of the bottom drip tray (mm)
wt_clearance = 1.5; // Extra room around main tray for easy removal (mm)
wt_flare = 3.5; // Outward wall expansion at top (mm)
/* [Single Pressing Tool Settings] */
tool_plate_thick = 3.0; // Thickness of the square pusher plate (mm)
stem_dia = 10.0; // Diameter of the stick (mm)
stem_length = 45.0; // Length of the stick shaft (mm)
handle_ball_dia = 20.0; // Diameter of the handle ball on top (mm)
/* [Render Mode] */ /* [Render Mode] */
// ["both_layout", "assembled", "tray_only", "plates_only"] render_mode = "all_layout"; // ["all_layout", "assembled", "tray_only", "plates_only", "water_tray_only", "tool_only"]
render_mode = "both_layout";
// --- DERIVED CALCULATIONS --- // --- DERIVED CALCULATIONS ---
@@ -30,28 +40,61 @@ total_width = cols * pitch + wall_thickness;
total_length = rows * pitch + wall_thickness; total_length = rows * pitch + wall_thickness;
// --- MAIN DISPLAY --- // --- MAIN DISPLAY EXECUTION ---
if (render_mode == "tray_only") { if (render_mode == "tray_only") {
seedling_tray(); seedling_tray();
} else if (render_mode == "plates_only") { } else if (render_mode == "plates_only") {
plate_grid(); plate_grid();
} else if (render_mode == "both_layout") { } else if (render_mode == "water_tray_only") {
water_tray();
} else if (render_mode == "tool_only") {
single_pressing_tool();
} else if (render_mode == "all_layout") {
// Top Left: Main Tray
seedling_tray(); seedling_tray();
translate([0, total_length + 10, 0])
// Bottom Left: Plates Grid
translate([0, -total_length - 20, 0])
plate_grid(); plate_grid();
// Top Right: Angled Water Tray
translate([total_width + 25, 0, 0])
water_tray();
// Bottom Right: Single Pressing Tool
translate([total_width + 25 + (total_width / 2), -total_length / 2 - 20, 0])
single_pressing_tool();
} else if (render_mode == "assembled") { } else if (render_mode == "assembled") {
// Visual preview // 1. Water tray at base
seedling_tray(); color("DarkSlateGray") water_tray();
for (r = [0 : rows - 1]) {
for (c = [0 : cols - 1]) { // 2. Seedling tray inside water tray
translate([ translate([wall_thickness + wt_clearance/2, wall_thickness + wt_clearance/2, floor_thickness]) {
wall_thickness + c * pitch + clearance/2, color("ForestGreen", 0.95) seedling_tray();
wall_thickness + r * pitch + clearance/2,
floor_thickness // Plates on bottom ledges
]) color("SaddleBrown") {
pusher_plate(); for (r = [0 : rows - 1]) {
for (c = [0 : cols - 1]) {
translate([
wall_thickness + c * pitch + clearance/2,
wall_thickness + r * pitch + clearance/2,
floor_thickness
])
pusher_plate();
}
}
} }
} }
// 3. Pressing tool
translate([
wall_thickness + wt_clearance/2 + wall_thickness + (cell_size / 2),
wall_thickness + wt_clearance/2 + wall_thickness + (cell_size / 2),
-stem_length / 2
])
color("Goldenrod") single_pressing_tool();
} }
@@ -59,23 +102,19 @@ if (render_mode == "tray_only") {
// MODULES // MODULES
// ======================================================== // ========================================================
// Main seedling tray body // 1. Main seedling tray body
module seedling_tray() { module seedling_tray() {
difference() { difference() {
// Outer solid volume
cube([total_width, total_length, tray_height]); cube([total_width, total_length, tray_height]);
// Cut out each cell pocket and bottom push hole
for (r = [0 : rows - 1]) { for (r = [0 : rows - 1]) {
for (c = [0 : cols - 1]) { for (c = [0 : cols - 1]) {
x_pos = wall_thickness + c * pitch; x_pos = wall_thickness + c * pitch;
y_pos = wall_thickness + r * pitch; y_pos = wall_thickness + r * pitch;
// 1. Main cell compartment
translate([x_pos, y_pos, floor_thickness]) translate([x_pos, y_pos, floor_thickness])
cube([cell_size, cell_size, tray_height]); cube([cell_size, cell_size, tray_height]);
// 2. Bottom push-through opening
translate([x_pos + bottom_lip, y_pos + bottom_lip, -1]) translate([x_pos + bottom_lip, y_pos + bottom_lip, -1])
cube([ cube([
cell_size - (2 * bottom_lip), cell_size - (2 * bottom_lip),
@@ -87,16 +126,14 @@ module seedling_tray() {
} }
} }
// Single pushable bottom plate // 2. Single removable bottom plate
module pusher_plate() { module pusher_plate() {
p_size = cell_size - clearance; p_size = cell_size - clearance;
offset_margin = p_size / 3.5; offset_margin = p_size / 3.5;
difference() { difference() {
// Plate body
cube([p_size, p_size, plate_thickness]); cube([p_size, p_size, plate_thickness]);
// 5-points drainage hole pattern
translate([p_size / 2, p_size / 2, -1]) translate([p_size / 2, p_size / 2, -1])
cylinder(h = plate_thickness + 2, d = drain_hole_dia, $fn = 20); cylinder(h = plate_thickness + 2, d = drain_hole_dia, $fn = 20);
@@ -109,7 +146,7 @@ module pusher_plate() {
} }
} }
// Plate grid // 3. Print layout grid matching exact tray cell count
module plate_grid() { module plate_grid() {
p_size = cell_size - clearance; p_size = cell_size - clearance;
spacing = 2; spacing = 2;
@@ -121,3 +158,38 @@ module plate_grid() {
} }
} }
} }
// 4. Water Tray with Angled Outward Walls
module water_tray() {
wt_inner_w = total_width + wt_clearance;
wt_inner_l = total_length + wt_clearance;
wt_outer_w = wt_inner_w + (2 * wall_thickness);
wt_outer_l = wt_inner_l + (2 * wall_thickness);
difference() {
translate([wt_outer_w / 2, wt_outer_l / 2, 0])
linear_extrude(height = water_tray_height, scale = [(wt_outer_w + 2 * wt_flare) / wt_outer_w, (wt_outer_l + 2 * wt_flare) / wt_outer_l])
square([wt_outer_w, wt_outer_l], center = true);
translate([wt_outer_w / 2, wt_outer_l / 2, floor_thickness])
linear_extrude(height = water_tray_height, scale = [(wt_inner_w + 2 * wt_flare) / wt_inner_w, (wt_inner_l + 2 * wt_flare) / wt_inner_l])
square([wt_inner_w, wt_inner_l], center = true);
}
}
// 5. Pressing Tool
module single_pressing_tool() {
p_size = (cell_size - clearance);
// Square pusher plate at base
translate([0, 0, tool_plate_thick / 2])
cube([p_size, p_size, tool_plate_thick], center = true);
// Vertical stick
translate([0, 0, tool_plate_thick])
cylinder(h = stem_length, d = stem_dia, $fn = 32);
// Palm ball handle on top
translate([0, 0, tool_plate_thick + stem_length])
sphere(d = handle_ball_dia, $fn = 40);
}