seedling tray: add water tray and pressing tool
This commit is contained in:
+43
-14
@@ -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.")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user