// ============================================================ // rover_battery_tray.scad — SaltyRover Flat Battery Tray // Rev A 2026-03-01 sl-mechanical // ============================================================ // Slide-out tray for 2–4 × 420×88×56 mm battery packs // laid FLAT below the rover deck plate. // // Packs orient with 420 mm left-right (X), 88 mm fore-aft // per pack (Y), 56 mm tall (Z). BATT_N packs stack fore-aft. // // Tray slides out to the RIGHT (+X) for battery swap. // Two slide rails (T-slot profile) run fore-aft inside // the deck opening. Tray body rides on these rails. // A spring-clip latch on the +X end retains the tray. // // Base plate attachment: // Rail mounts bolt to deck underside (4× M4 per rail, // counter-sunk flat-head). // // ⚠ VERIFY: // BATT_PACK_L / W / H from caliper. // RAIL_SLOT_W / H from your chosen extrusion or print. // // RENDER options: // "assembly" tray + phantom packs (default) // "tray" tray body for printing // "rail" one T-slot slide rail (print 2×) // "latch" spring latch for tray end (print 1×) // "tray_2d" floor projection → DXF // ============================================================ RENDER = "assembly"; // ── Battery packs (BOM.md caliper-verified) ─────────────── BATT_PACK_L = 420.0; // left-right (X) BATT_PACK_W = 88.0; // fore-aft per pack (Y) BATT_PACK_H = 56.0; // height when flat (Z) BATT_N = 2; // packs arranged fore-aft (2 or 4) // ── Tray geometry ───────────────────────────────────────── TRAY_WALL = 2.5; // wall thickness TRAY_FLOOR = 3.0; // floor thickness TRAY_PAD = 2.0; // per-side clearance inside tray (pack-to-wall) // Inner cavity TRAY_INN_W = BATT_PACK_L + 2*TRAY_PAD; TRAY_INN_D = BATT_PACK_W * BATT_N + 2*TRAY_PAD; TRAY_INN_H = BATT_PACK_H + TRAY_PAD; // open top // Outer tray body TRAY_OUT_W = TRAY_INN_W + 2*TRAY_WALL; TRAY_OUT_D = TRAY_INN_D + 2*TRAY_WALL; TRAY_OUT_H = TRAY_INN_H + TRAY_FLOOR; // ── Slide rail ──────────────────────────────────────────── // T-profile rail runs fore-aft (Y), mounts to deck underside. // Tray has matching T-slots on its side walls. RAIL_L = TRAY_OUT_D + 40.0; // rail longer than tray for stop RAIL_W = 14.0; // rail body width RAIL_H = 8.0; // rail height below deck RAIL_T_W = 10.0; // T-slot head width on tray side RAIL_T_H = 4.0; // T-slot head height (captured in tray slot) RAIL_CL = 0.3; // running clearance RAIL_M4_SPC = 80.0; // M4 deck attachment bolt spacing along rail // Tray T-slot channel (cut into tray outer wall) SLOT_W = RAIL_T_W + 2*RAIL_CL; SLOT_H = RAIL_T_H + RAIL_CL; // Rail Y-spacing: rails at ±RAIL_Y from tray centre (fore-aft) RAIL_Y_SPC = TRAY_INN_W - 20.0; // rails near X edges of tray // Note: rails run in Y, so RAIL_Y_SPC is the X spread between them // ── Strap slots ─────────────────────────────────────────── STRAP_W = 20.0; // Velcro strap width STRAP_T = 3.0; // slot depth // ── Latch ───────────────────────────────────────────────── LATCH_L = 40.0; // latch body length LATCH_W = 20.0; LATCH_T = 4.0; // plate thickness LATCH_FLEX = 25.0; // spring arm length LATCH_TAB_H = 5.0; // retention tab height (hooks over deck edge) LATCH_BOLT_D= 3.3; // M3 attachment bolt // ── Fasteners ───────────────────────────────────────────── M3_D = 3.2; M4_D = 4.3; M4_CS_D = 7.0; // flat-head countersink diam $fn = 48; e = 0.01; // ───────────────────────────────────────────────────────── // battery_tray() // Z=0 at tray bottom face. Slides in +X direction. // Inner cavity origin at (TRAY_WALL, TRAY_WALL, TRAY_FLOOR). // ───────────────────────────────────────────────────────── module battery_tray() { difference() { union() { // ── Outer tray body ──────────────────────── cube([TRAY_OUT_W, TRAY_OUT_D, TRAY_OUT_H]); // ── Pull tab on +X face ──────────────────── translate([TRAY_OUT_W, TRAY_OUT_D/2 - 15, TRAY_FLOOR]) cube([18, 30, TRAY_OUT_H - TRAY_FLOOR]); } // ── Inner cavity (open top) ──────────────────── translate([TRAY_WALL, TRAY_WALL, TRAY_FLOOR]) cube([TRAY_INN_W, TRAY_INN_D, TRAY_INN_H + e]); // ── Pack divider slot (for multi-pack separation) ── if (BATT_N > 1) for (pn = [1 : BATT_N - 1]) translate([TRAY_WALL - e, TRAY_WALL + BATT_PACK_W * pn - 1, TRAY_FLOOR]) cube([TRAY_INN_W + 2*e, 2, TRAY_INN_H + e]); // ── Strap slots through floor (×2 per pack) ─── for (pn = [0 : BATT_N - 1]) for (sx = [TRAY_WALL + TRAY_INN_W*0.25, TRAY_WALL + TRAY_INN_W*0.75]) translate([sx - STRAP_W/2, TRAY_WALL + BATT_PACK_W * pn + 10, -e]) cube([STRAP_W, BATT_PACK_W - 20, TRAY_FLOOR + 2*e]); // ── T-slot channels on left (+Y) and right (-Y) faces ── // Tray left wall: Y+ face — rail rides in from -X end for (face_y=[TRAY_OUT_D - TRAY_WALL - e, -e]) { translate([TRAY_OUT_W*0.2 - SLOT_W/2, face_y, TRAY_FLOOR + TRAY_INN_H/2 - SLOT_H/2]) cube([TRAY_OUT_W*0.6, TRAY_WALL + 2*e, SLOT_H]); // T-head channel translate([TRAY_OUT_W*0.2 - RAIL_T_W/2 - RAIL_CL, face_y, TRAY_FLOOR + TRAY_INN_H/2 - SLOT_H/2 - RAIL_CL]) cube([RAIL_T_W + 2*RAIL_CL, TRAY_WALL + 2*e, SLOT_H + 2*RAIL_CL]); } // ── Ventilation / weight-save holes in floor (×4) ── for (vx=[TRAY_OUT_W*0.25, TRAY_OUT_W*0.75]) for (vy=[TRAY_WALL + TRAY_INN_D*0.25, TRAY_WALL + TRAY_INN_D*0.75]) translate([vx, vy, -e]) cylinder(d=25, h=TRAY_FLOOR + 2*e); } } // ───────────────────────────────────────────────────────── // slide_rail() // Mounts to deck underside. Runs fore-aft (Y). // T-head protrudes inward (X direction) into tray slot. // ───────────────────────────────────────────────────────── module slide_rail() { difference() { union() { // Rail body translate([-RAIL_W/2, 0, 0]) cube([RAIL_W, RAIL_L, RAIL_H]); // T-head flange (protrudes toward tray) translate([-RAIL_T_W/2, 0, RAIL_H - RAIL_T_H]) cube([RAIL_T_W, RAIL_L, RAIL_T_H]); } // M4 countersunk attachment holes to deck (×3 along rail) for (ry = [15, RAIL_L/2, RAIL_L - 15]) translate([0, ry, RAIL_H + e]) rotate([180, 0, 0]) { cylinder(d=M4_D, h=RAIL_H + 2*e); cylinder(d1=M4_CS_D, d2=M4_D, h=3.5 + e); } // Rail end stop slot (tray cannot slide past rail end) translate([-RAIL_W/2 - e, RAIL_L - 8, -e]) cube([RAIL_W + 2*e, 8 + e, RAIL_H - RAIL_T_H + e]); } } // ───────────────────────────────────────────────────────── // spring_latch() // Clips to tray +X end face. Spring arm hooks over // deck opening edge to retain tray. Squeeze tab to release. // ───────────────────────────────────────────────────────── module spring_latch() { difference() { union() { // Base plate (bolts to tray end face) cube([LATCH_W, LATCH_T, LATCH_L]); // Spring arm translate([LATCH_W/2 - 4, LATCH_T, LATCH_L - LATCH_FLEX]) cube([8, LATCH_T + LATCH_TAB_H, LATCH_FLEX + LATCH_TAB_H]); // Retention tab translate([LATCH_W/2 - 6, LATCH_T, LATCH_L - 2]) cube([12, LATCH_T + LATCH_TAB_H + 4, LATCH_TAB_H + 2]); } // M3 attachment holes (×2) for (lz=[10, LATCH_L - 20]) translate([LATCH_W/2, -e, lz]) rotate([-90,0,0]) cylinder(d=LATCH_BOLT_D, h=LATCH_T + 2*e); } } // ───────────────────────────────────────────────────────── // Render selector // ───────────────────────────────────────────────────────── if (RENDER == "assembly") { color("SteelBlue", 0.88) battery_tray(); // Phantom battery packs inside tray for (pn = [0 : BATT_N - 1]) color("Gold", 0.35) translate([TRAY_WALL + TRAY_PAD, TRAY_WALL + TRAY_PAD + pn * BATT_PACK_W, TRAY_FLOOR]) cube([BATT_PACK_L, BATT_PACK_W, BATT_PACK_H]); // Slide rails color("Silver", 0.80) { translate([0, -20, TRAY_OUT_H]) rotate([0, 90, 0]) slide_rail(); translate([0, TRAY_OUT_D + 20 - RAIL_L, TRAY_OUT_H]) rotate([0, 90, 0]) slide_rail(); } // Latch at +X end color("OrangeRed", 0.85) translate([TRAY_OUT_W + 18, TRAY_OUT_D/2 - LATCH_W/2, TRAY_OUT_H/2 - LATCH_L/2]) rotate([0, -90, 0]) spring_latch(); } else if (RENDER == "tray") { battery_tray(); } else if (RENDER == "rail") { slide_rail(); } else if (RENDER == "latch") { spring_latch(); } else if (RENDER == "tray_2d") { projection(cut=true) translate([0, 0, -TRAY_FLOOR/2]) battery_tray(); }