saltylab-firmware/chassis/saltyrover_chassis.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

219 lines
9.6 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.

// ============================================================
// saltyrover_chassis.scad — SaltyRover 4-Wheel Base Plate
// Rev A 2026-03-01 sl-mechanical
// ============================================================
// Parametric deck plate for the stable 4-wheel SaltyRover.
// Low/wide/stable design — reuses sensor head + 25 mm stem.
//
// Coordinate convention:
// Z = 0 deck top face
// +Y forward
// +X right (motor side)
//
// Battery orientation:
// Packs laid FLAT (56 mm tall, 420 mm running left-right,
// 88 mm per pack fore-aft). N packs arranged fore-aft.
// 2-pack tray: 430 × 186 mm opening.
// 4-pack tray: 430 × 362 mm opening.
//
// Motor positions are at ±TRACK_W/2 (X), ±AXLE_BASE/2 (Y).
// Motor brackets (rover_motor_mount.scad) bolt to deck edge
// and extend BRKT_REACH mm outward to hold axle at AXLE_H.
//
// RENDER options:
// "assembly" full deck + phantom batteries + stem stub
// "deck" deck plate only (review)
// "deck_2d" floor projection → DXF for waterjet/CNC
// ============================================================
RENDER = "assembly";
// ── Deck footprint ────────────────────────────────────────
ROVER_L = 500.0; // fore-aft (Y)
ROVER_W = 480.0; // left-right (X) — sized around battery width
DECK_T = 8.0; // deck thickness
DECK_R = 15.0; // corner fillet radius
// ── Drive geometry (caliper-verified motor data from BOM.md) ──
TRACK_W = 540.0; // motor axle CL to CL, left-right
AXLE_BASE = 340.0; // motor axle CL to CL, fore-aft
AXLE_H = 127.0; // axle CL above ground (10×2.125" wheel, caliper)
GND_CLR = 50.0; // minimum chassis ground clearance
// Height of deck bottom above ground:
// GND_CLR + battery_tray_floor + battery_height
BATT_FLOOR_T = 3.0;
BATT_PACK_H = 56.0; // flat-laid pack height
DECK_BOT_H = GND_CLR + BATT_FLOOR_T + BATT_PACK_H; // 109 mm
// Axle above deck top = AXLE_H - (DECK_BOT_H + DECK_T) ≈ +10 mm
// ── Battery packs (BOM.md caliper-verified: 420×88×56 mm) ──
// Laid flat, 420 mm running left-right (X), 88 mm per pack (Y),
// 56 mm tall (Z). BATT_N packs arranged side-by-side fore-aft.
BATT_X_DIM = 420.0;
BATT_Y_DIM = 88.0; // per-pack fore-aft depth
BATT_N = 2; // packs fore-aft (2 = 176 mm; 4 = 352 mm)
TRAY_MARGIN = 5.0; // extra margin on each side of tray opening
// ── Stem socket (deck centre) ─────────────────────────────
STEM_BORE = 25.5; // 25 mm tube + 0.5 clearance
STEM_COLLAR_OD = 50.0;
STEM_COLLAR_H = 22.0; // raised boss height above deck top
// ── FC mount (MAMBA F722S, 30.5×30.5 M3) ─────────────────
FC_SPACING = 30.5;
FC_HOLE_D = 3.2;
FC_STANDOFF_H= 6.0;
// FC centred forward of battery tray
FC_X = 0.0;
FC_Y = ROVER_L/2 - 60.0; // near front edge
// ── Jetson Orin NX mount ─────────────────────────────────
// ⚠ Verify hole pattern against your carrier board.
// Default: 58×49 mm M3 (same as RPi HAT pattern used on Orin NX cards).
ORIN_HOLE_X = 58.0;
ORIN_HOLE_Y = 49.0;
ORIN_HOLE_D = 3.2;
ORIN_STANDOFF = 8.0;
ORIN_X = 0.0;
ORIN_Y = -(ROVER_L/2 - 55.0); // near rear edge
// ── Motor bracket attachment bolt pattern ────────────────
// 2× M5 bolts through deck edge at each motor corner.
MOT_BOLT_D = 5.3; // M5 clearance
MOT_BOLT_SPC = 30.0; // bolt spacing fore-aft at each motor position
// ── Lightening holes ──────────────────────────────────────
LH_D = 55.0;
// ── Fasteners ─────────────────────────────────────────────
M3_D = 3.2;
M4_D = 4.3;
M5_D = 5.3;
$fn = 64;
e = 0.01;
// ─────────────────────────────────────────────────────────
// rounded_plate_2d()
// ─────────────────────────────────────────────────────────
module rounded_plate_2d(l, w, r) {
offset(r=r, $fn=32) offset(r=-r)
square([l, w], center=true);
}
// ─────────────────────────────────────────────────────────
// rover_deck()
// ─────────────────────────────────────────────────────────
module rover_deck() {
tray_w = BATT_X_DIM + 2*TRAY_MARGIN; // 430 mm
tray_l = BATT_Y_DIM * BATT_N + 2*TRAY_MARGIN; // 186 or 362 mm
difference() {
// ── Deck plate ────────────────────────────────
translate([0, 0, -DECK_T])
linear_extrude(DECK_T)
rounded_plate_2d(ROVER_W, ROVER_L, DECK_R);
// ── Battery tray opening (centred, through deck) ──
translate([-tray_w/2, -tray_l/2, -DECK_T - e])
cube([tray_w, tray_l, DECK_T + 2*e]);
// ── Lightening holes ──────────────────────────
// 3 columns × 2 rows, outside battery opening
for (hx = [-ROVER_W/4, 0, ROVER_W/4])
for (hy = [-(tray_l/2 + LH_D/2 + 20),
(tray_l/2 + LH_D/2 + 20)])
if (abs(hy) < ROVER_L/2 - LH_D/2 - 10)
translate([hx, hy, -DECK_T - e])
cylinder(d=LH_D, h=DECK_T + 2*e);
// ── FC mount holes ────────────────────────────
for (fx=[-FC_SPACING/2, FC_SPACING/2])
for (fy=[-FC_SPACING/2, FC_SPACING/2])
translate([FC_X + fx, FC_Y + fy, -DECK_T - e])
cylinder(d=FC_HOLE_D, h=DECK_T + 2*e);
// ── Orin mount holes ──────────────────────────
for (ox=[-ORIN_HOLE_X/2, ORIN_HOLE_X/2])
for (oy=[-ORIN_HOLE_Y/2, ORIN_HOLE_Y/2])
translate([ORIN_X + ox, ORIN_Y + oy, -DECK_T - e])
cylinder(d=ORIN_HOLE_D, h=DECK_T + 2*e);
// ── Motor bracket bolt holes (4 corners × 2 bolts) ──
for (sx=[-1,1], sy=[-1,1])
for (by=[-MOT_BOLT_SPC/2, MOT_BOLT_SPC/2])
translate([sx*ROVER_W/2 - sx*(DECK_T/2 + e),
sy*AXLE_BASE/2 + by,
-DECK_T - e])
rotate([0, 90, 0])
cylinder(d=MOT_BOLT_D, h=DECK_T + 2*e);
// ── Stem bore through deck ────────────────────
translate([0, 0, -DECK_T - e])
cylinder(d=STEM_BORE, h=DECK_T + 2*e);
// ── Cable routing slots (×2 per side, fore and aft) ──
for (cy=[-ROVER_L/2 - e, ROVER_L/2 - 16])
for (cx=[-55, 55])
translate([cx - 7, cy, -DECK_T - e])
cube([14, 16, DECK_T + 2*e]);
}
// ── Stem collar (raised boss) ─────────────────────
difference() {
cylinder(d=STEM_COLLAR_OD, h=STEM_COLLAR_H);
translate([0, 0, -e])
cylinder(d=STEM_BORE, h=STEM_COLLAR_H + 2*e);
}
// ── FC standoff posts (×4) ────────────────────────
for (fx=[-FC_SPACING/2, FC_SPACING/2])
for (fy=[-FC_SPACING/2, FC_SPACING/2])
translate([FC_X + fx, FC_Y + fy, 0])
cylinder(d=6, h=FC_STANDOFF_H);
// ── Orin standoff posts (×4) ──────────────────────
for (ox=[-ORIN_HOLE_X/2, ORIN_HOLE_X/2])
for (oy=[-ORIN_HOLE_Y/2, ORIN_HOLE_Y/2])
translate([ORIN_X + ox, ORIN_Y + oy, 0])
cylinder(d=6, h=ORIN_STANDOFF);
}
// ─────────────────────────────────────────────────────────
// Render selector
// ─────────────────────────────────────────────────────────
if (RENDER == "assembly") {
color("Silver", 0.90) rover_deck();
// Phantom battery packs (2 × laid flat)
tray_w = BATT_X_DIM + 2*TRAY_MARGIN;
tray_l = BATT_Y_DIM * BATT_N + 2*TRAY_MARGIN;
for (pn = [0 : BATT_N - 1])
color("Gold", 0.30)
translate([-BATT_X_DIM/2,
-BATT_Y_DIM*BATT_N/2 + pn*BATT_Y_DIM,
-(DECK_T + BATT_FLOOR_T + BATT_PACK_H)])
cube([BATT_X_DIM, BATT_Y_DIM, BATT_PACK_H]);
// Phantom stem stub
color("Silver", 0.25)
translate([0, 0, STEM_COLLAR_H])
cylinder(d=25, h=400);
// Motor position markers (spheres at axle CLs)
axle_z = AXLE_H - (DECK_BOT_H + DECK_T); // above deck top
for (sx=[-1,1], sy=[-1,1])
color("Tomato", 0.5)
translate([sx*TRACK_W/2, sy*AXLE_BASE/2, axle_z])
sphere(d=20);
} else if (RENDER == "deck") {
rover_deck();
} else if (RENDER == "deck_2d") {
projection(cut=true)
translate([0, 0, -DECK_T/2])
rover_deck();
}