saltylab-firmware/chassis/rover_battery_tray.scad
sl-mechanical 3e4764b3eb feat: SaltyRover 4-wheel chassis design (#73)
Add parametric OpenSCAD designs for the SaltyRover stable 4-wheel
variant. Reuses existing 25mm stem, sensor head, and all SaltyLab
sensor mounts without modification.

Files:
- saltyrover_chassis.scad  480×500mm deck, stem collar, FC+Orin
  standoffs, motor attachment holes, battery tray opening; RENDER
  deck_2d for waterjet/CNC DXF
- rover_motor_mount.scad   L-bracket + axle clamp plate per motor;
  uses caliper-verified axle dims from BOM.md; dropout slot for
  tool-free motor swap; RENDER bracket_2d for CNC DXF
- rover_battery_tray.scad  Slide-out tray for 2-4 × 420×88×56mm
  packs laid flat (low CG); T-slot rails, spring latch
- rover_stem_adapter.scad  Flange + split clamp locks 25mm stem to
  deck collar; 550mm stem option for rover height
- rover_BOM.md             Assembly sequence, fastener table, mass
  estimate (~13.4kg), height stack diagram

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 01:21:46 -05:00

256 lines
11 KiB
OpenSCAD
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ============================================================
// rover_battery_tray.scad — SaltyRover Flat Battery Tray
// Rev A 2026-03-01 sl-mechanical
// ============================================================
// Slide-out tray for 24 × 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();
}