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

203 lines
8.3 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_stem_adapter.scad — SaltyRover Vertical Stem Adapter
// Rev A 2026-03-01 sl-mechanical
// ============================================================
// Secures the 25 mm OD vertical stem to the rover deck.
//
// Two-part system:
// base_flange() — annular flange plate bolts to deck top
// (4× M4 SHCS into deck; deck already has
// 25 mm stem bore through its centre collar)
// stem_clamp() — split collar clamps on stem above the
// deck collar; two M4 clamping bolts
// lock stem position / rotation
//
// Reuses the existing sensor head, RPLIDAR, camera mounts, and
// roll cage from SaltyLab — stem OD 25 mm is unchanged.
//
// Stem length options:
// SaltyLab 1000 mm (balance robot — tall for CG)
// SaltyRover 550 mm (rover — sensors visible, compact)
// The adapter is identical; only the purchased tube differs.
//
// ⚠ Ensure the deck stem collar (saltyrover_chassis.scad,
// STEM_COLLAR_OD=50 mm, H=22 mm) is the primary lateral
// support. The flange + clamp provide torque/axial lock.
//
// RENDER options:
// "assembly" flange + clamp + stem stub
// "base_flange" flange plate for printing
// "clamp_front" clamp front half for printing
// "clamp_rear" clamp rear half for printing
// "flange_2d" flange projection → DXF
// ============================================================
RENDER = "assembly";
// ── Stem ─────────────────────────────────────────────────
STEM_OD = 25.0;
STEM_BORE = 25.4; // +0.4 clearance (same as sensor mounts)
STEM_L_ROVER = 550; // recommended rover stem length (mm)
// ── Base flange ───────────────────────────────────────────
// Sits on deck collar top. 4× M4 bolt through flange into deck.
FLANGE_OD = 80.0; // outer diameter
FLANGE_T = 6.0; // plate thickness
FLANGE_BOLT_BC = 65.0; // M4 bolt circle diameter
FLANGE_BOLT_D = 4.3; // M4 clearance
FLANGE_BOLT_N = 4; // number of bolts (at 90°)
// Deck collar height (must match saltyrover_chassis.scad STEM_COLLAR_H)
DECK_COLLAR_H = 22.0;
// ── Split stem clamp ─────────────────────────────────────
// Sits on top of flange; clamped M4 bolts lock stem.
COL_OD = 52.0; // clamp outer diameter
COL_H = 28.0; // clamp height (above flange)
COL_BOLT_X = 19.0; // M4 clamping bolt CL from stem axis
COL_BOLT_D = 4.5; // M4 clearance hole
COL_NUT_W = 7.0; // M4 hex nut across-flats
COL_NUT_H = 3.4; // hex nut height
// Set screw for rotation lock (front half)
SET_SCREW_D = 4.5; // M4 set screw
// ── Fasteners ─────────────────────────────────────────────
M4_D = 4.3;
$fn = 64;
e = 0.01;
// ─────────────────────────────────────────────────────────
// base_flange()
// Sits on top of the deck stem collar.
// Z=0 at deck top (collar rises from here).
// ─────────────────────────────────────────────────────────
module base_flange() {
difference() {
union() {
// Annular flange plate (sits on collar top)
translate([0, 0, DECK_COLLAR_H])
cylinder(d=FLANGE_OD, h=FLANGE_T);
// Short skirt that drops inside/over collar
translate([0, 0, DECK_COLLAR_H - 4])
cylinder(d=FLANGE_OD - 8, h=4);
}
// Stem bore
translate([0, 0, DECK_COLLAR_H - 4 - e])
cylinder(d=STEM_BORE, h=FLANGE_T + 4 + 2*e);
// M4 bolts through flange + down into deck (×4)
for (ang=[0, 90, 180, 270])
rotate([0, 0, ang])
translate([FLANGE_BOLT_BC/2, 0, DECK_COLLAR_H - e])
cylinder(d=FLANGE_BOLT_D, h=FLANGE_T + 2*e);
// Countersink on top face
for (ang=[0, 90, 180, 270])
rotate([0, 0, ang])
translate([FLANGE_BOLT_BC/2, 0, DECK_COLLAR_H + FLANGE_T - 3.5])
cylinder(d1=FLANGE_BOLT_D, d2=8.5, h=3.5 + e);
}
}
// ─────────────────────────────────────────────────────────
// stem_clamp_half(side)
// Split collar clamps on stem above the flange.
// Print flat-face-down. side = "front" | "rear"
// ─────────────────────────────────────────────────────────
module stem_clamp_half(side="front") {
y_front = (side == "front");
// Clamp Z origin: on top of flange
clamp_z0 = DECK_COLLAR_H + FLANGE_T;
difference() {
// D-shaped half
intersection() {
translate([0, 0, clamp_z0])
cylinder(d=COL_OD, h=COL_H);
translate([-COL_OD/2,
y_front ? 0 : -COL_OD/2,
clamp_z0])
cube([COL_OD, COL_OD/2, COL_H]);
}
// Stem bore
translate([0, 0, clamp_z0 - e])
cylinder(d=STEM_BORE, h=COL_H + 2*e);
// M4 clamping bolt holes (Y direction, through front half)
for (bx=[-COL_BOLT_X, COL_BOLT_X])
translate([bx,
y_front ? COL_OD/2 : 0,
clamp_z0 + COL_H/2])
rotate([90, 0, 0])
cylinder(d=COL_BOLT_D, h=COL_OD/2 + e);
// M4 hex nut pockets in rear half
if (!y_front)
for (bx=[-COL_BOLT_X, COL_BOLT_X])
translate([bx,
-(COL_OD/4 + e),
clamp_z0 + COL_H/2])
rotate([90, 0, 0])
cylinder(d=COL_NUT_W/cos(30),
h=COL_NUT_H + e, $fn=6);
// M4 set screw hole (front half, mid-height, horizontal)
if (y_front)
translate([0, COL_OD/2,
clamp_z0 + COL_H * 0.65])
rotate([90, 0, 0])
cylinder(d=SET_SCREW_D,
h=COL_OD/2 - STEM_BORE/2 + e);
// Mating face chamfer (0.2 mm, prevents elephant-foot binding)
translate([0, 0, clamp_z0 - e])
rotate([0, 0, y_front ? 0 : 180])
translate([-COL_OD/2, -0.2, 0])
cube([COL_OD, 0.2, COL_H + 2*e]);
}
}
// ─────────────────────────────────────────────────────────
// Render selector
// ─────────────────────────────────────────────────────────
if (RENDER == "assembly") {
// Phantom deck collar reference
color("Gray", 0.15)
difference() {
cylinder(d=50, h=DECK_COLLAR_H);
translate([0,0,-e]) cylinder(d=STEM_BORE, h=DECK_COLLAR_H+2*e);
}
color("SteelBlue", 0.90) base_flange();
color("CornflowerBlue", 0.90) stem_clamp_half("front");
color("SlateBlue", 0.90)
mirror([0,1,0]) stem_clamp_half("rear");
// Phantom stem stub
color("Silver", 0.30)
translate([0, 0, DECK_COLLAR_H + FLANGE_T + COL_H])
cylinder(d=STEM_OD, h=STEM_L_ROVER);
} else if (RENDER == "base_flange") {
// Print flat; rotate flange down
translate([0, 0, -(DECK_COLLAR_H - 4)])
base_flange();
} else if (RENDER == "clamp_front") {
translate([0, 0, -(DECK_COLLAR_H + FLANGE_T)])
stem_clamp_half("front");
} else if (RENDER == "clamp_rear") {
translate([0, 0, -(DECK_COLLAR_H + FLANGE_T)])
stem_clamp_half("rear");
} else if (RENDER == "flange_2d") {
projection(cut=true)
translate([0, 0, -(DECK_COLLAR_H + FLANGE_T/2)])
base_flange();
}