saltylab-firmware/chassis/rover_spring_arm.scad
sl-mechanical 2fa72e169e feat: SaltyRover chassis Rev 2 — 4-wheel rover with spring suspension (#109)
New chassis design files for the SaltyRover rough-terrain variant:

• saltyrover_chassis_r2.scad — Deck plate (500×480×6mm Al, laser-cut DXF),
  4× M3-adjustable pivot brackets, 4× CSI corner camera mounts (45° outward,
  20° down), D435i front bracket (8° tilt), stem collar. All RENDER modes
  for STL and DXF export included.

• rover_spring_arm.scad — Trailing-arm spring suspension (×4). Pivot on M8
  bolt; captured 14mm OD compression spring (50mm free, ~5 N/mm); open-end
  axle dropout slot with retainer cap. Provides 25mm bump + 15mm droop travel.
  Bearing-seat recess for caliper-verified 37.8mm collar OD.

• rover_electronics_bay.scad — PETG electronics bay (240×200×80mm internal).
  FC standoffs 30.5×30.5mm M3 and Jetson Orin 58×49mm M3 — shared SaltyLab
  swappable pattern. Ventilation slots all 4 walls + lid. Lid integrates
  100mm RPLIDAR A1M8 tower (58mm BC, matched to rplidar_mount.scad).
  Split-print halves for 220mm beds included.

• rover_chassis_r2_BOM.md — Full BOM, mass estimate (frame ~2.15kg; reduce
  to <2kg by setting DECK_T=5), assembly sequence, critical dimensions.

Sensor positions: RPLIDAR top-centre on bay lid, D435i front, 4× IMX219 at
deck corners. Shares 30.5mm FC + 58mm Jetson + Ø25mm stem patterns with
SaltyLab for swappable electronics.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 08:42:44 -05:00

273 lines
12 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_spring_arm.scad — SaltyRover Spring Suspension Arm
// Issue: #109 Agent: sl-mechanical Date: 2026-03-01
// ============================================================
//
// Trailing-arm spring suspension for rough terrain.
// One arm per wheel (print 4×).
//
// Mechanical principle:
// The arm pivots on an M8 bolt through the pivot bracket
// (saltyrover_chassis_r2.scad → pivot_bracket).
// A captured compression spring between the pivot bracket's
// spring boss and the arm's spring pocket provides restoring
// force. When a wheel strikes a bump, the arm swings upward
// (rotating around the pivot) and compresses the spring.
//
// Pivot bracket (chassis-fixed)
// │
// [M8 pivot]──────────────────[Motor axle dropout]
// │ [Trailing arm] │
// └──[Spring upper seat] │
// │ │
// [Spring] │
// │ │
// [Spring pocket in arm] │
//
// Spring spec (wire form compression, standard size):
// OD : 14 mm (slides over bracket's 14 mm guide boss)
// ID : ~10 mm
// Free length : 50 mm
// Solid height: ~20 mm
// Travel (max): 25 mm → spring compressed to 25 mm
// Spring rate : ~5 N/mm (soft — adjust to robot mass)
// Part no. : e.g. Lee Spring LCI 014M 05 S (or equivalent)
//
// Wheel travel:
// Bump (compression): 25 mm (spring coils bind before hard stop)
// Droop (extension) : 15 mm (limited by pivot bracket flange)
// Total travel : 40 mm
//
// Axle-to-ground height at full compression (worst case bump):
// AXLE_H TRAVEL_BUMP = 127 25 = 102 mm above ground ✓
//
// Print:
// Material : PETG or PC (PC recommended for structural rigidity)
// Settings : 5 perimeters, 60 % gyroid infill
// Orientation: pivot boss flat face on build plate; no supports needed
// Qty: 4×
//
// Export commands:
// STL (main arm × 4):
// openscad rover_spring_arm.scad -D 'RENDER="arm_stl"' -o rover_spring_arm.stl
// STL (spring retainer cap × 4):
// openscad rover_spring_arm.scad -D 'RENDER="retainer_stl"' -o rover_spring_retainer.stl
// Assembly preview:
// openscad rover_spring_arm.scad -D 'RENDER="assembly"'
// ============================================================
$fn = 64;
e = 0.01;
// ── Motor axle (BOM.md caliper-verified) ────────────────────────────────────
AXLE_D = 16.11; // axle base section OD (caliper)
AXLE_FLAT = 13.00; // D-cut chord width (caliper)
AXLE_D_DCUT = 15.95; // D-cut section OD (caliper)
BEARING_OD = 37.80; // bearing seat collar OD (caliper)
BEARING_RECESS_H = 8.0; // recess depth for bearing seat on inboard face
// ── Arm geometry ─────────────────────────────────────────────────────────────
// Pivot end is at X=0, Y=0, Z=0 (pivot CL)
// Motor axle end is at X = +ARM_REACH (outboard, positive X = outboard)
ARM_REACH = 75.0; // pivot CL to motor axle CL (outboard reach)
ARM_W = 38.0; // arm width (fore-aft / Y direction)
ARM_T = 14.0; // arm thickness (vertical / Z direction)
ARM_TAPER = 4.0; // taper at motor end (arm narrows by this amount)
// Pivot boss
PIV_BOSS_OD = ARM_W; // boss is as wide as arm for structural continuity
PIV_BOSS_L = ARM_T; // boss length = arm thickness
PIV_D = 8.5; // M8 clearance bore through pivot
// ── Suspension spring parameters ────────────────────────────────────────────
SPG_OD = 14.0; // spring OD (matches bracket guide boss OD)
SPG_FREE_L = 50.0; // spring free length (see spec above)
SPG_TRAVEL = 25.0; // max bump travel / spring compression
SPG_POCKET_D = SPG_OD + 1.5; // pocket bore (spring slides in with clearance)
SPG_POCKET_H = SPG_TRAVEL + 5; // pocket depth (captures spring bottom)
// Spring pocket CL from pivot (along arm)
SPG_POS_X = ARM_REACH * 0.45; // ~45% along arm from pivot
// ── Motor axle dropout slot ──────────────────────────────────────────────────
// Open-end slot at motor end of arm. Retained by spring_retainer_cap.
DROP_W = AXLE_D + 1.0; // slot width (snug but not interference)
DROP_DEPTH = AXLE_D + 4.0; // slot depth from arm end inward
// ── Spring retainer cap ──────────────────────────────────────────────────────
// Small cap that closes the open axle slot from below, screwing onto the arm.
// Prevents axle from dropping out; provides second bearing recess face.
RET_T = 6.0; // cap thickness
RET_W = ARM_W + 4.0; // cap wider than arm for alignment lip
RET_BOLT_D = M3_D; // 2× M3 bolts retain the cap
M2_D = 2.3;
M3_D = 3.3;
M4_D = 4.3;
M5_D = 5.3;
M8_D = 8.5;
// ============================================================
// RENDER DISPATCH
// ============================================================
RENDER = "assembly";
if (RENDER == "assembly") {
assembly();
} else if (RENDER == "arm_stl") {
spring_arm();
} else if (RENDER == "retainer_stl") {
spring_retainer_cap();
}
// ============================================================
// FULL ASSEMBLY PREVIEW
// ============================================================
module assembly() {
color("SteelBlue", 0.85) spring_arm();
color("CornflowerBlue", 0.80)
translate([ARM_REACH, 0, -(ARM_T/2 + RET_T)])
spring_retainer_cap();
// Phantom spring (compressed)
%color("LimeGreen", 0.5)
translate([SPG_POS_X, 0, ARM_T/2])
cylinder(d = SPG_OD, h = SPG_FREE_L - SPG_TRAVEL);
// Phantom motor axle
%color("Tomato", 0.3)
translate([ARM_REACH, 0, 0])
rotate([0, 90, 0])
cylinder(d = AXLE_D, h = 120, center = true);
// Phantom pivot bolt (M8)
%color("Gray", 0.5)
rotate([0, 90, 0])
cylinder(d = 8, h = ARM_T + 20, center = true);
// Phantom bracket guide boss (from pivot_bracket)
%color("DarkGray", 0.4)
translate([SPG_POS_X, 0, ARM_T/2])
cylinder(d = SPG_OD, h = 20);
}
// ============================================================
// SPRING ARM
// ============================================================
// Pivot CL at (0, 0, 0). Arm extends toward +X.
// Pivots around Y-axis (M8 bolt runs in Y direction).
// Spring acts in Z (vertical) at SPG_POS_X along arm.
// Motor axle runs in Y direction at (ARM_REACH, 0, 0).
// ============================================================
module spring_arm() {
w_motor = ARM_W - ARM_TAPER; // narrower at motor end
difference() {
union() {
// ── Main arm body (tapered hull) ──────────────────────────
hull() {
// Pivot end — full-width rectangular section
translate([0, -ARM_W/2, -ARM_T/2])
cube([e, ARM_W, ARM_T]);
// Motor end — slightly narrower
translate([ARM_REACH - DROP_DEPTH, -w_motor/2, -ARM_T/2])
cube([e, w_motor, ARM_T]);
}
// ── Pivot boss (cylindrical for hinge strength) ───────────
// Cylindrical boss sits at pivot CL; flange provides washer seat.
rotate([90, 0, 0]) {
difference() {
cylinder(d = PIV_BOSS_OD, h = PIV_BOSS_L, center = true);
}
}
// ── Spring pocket boss (raised above arm top face) ─────────
// Boss rises to meet the bracket's spring guide boss.
// The compression spring is captured between the two bosses.
translate([SPG_POS_X, 0, ARM_T/2])
cylinder(d = SPG_OD + 8, h = 8);
// ── Axle retention lug at motor end (prevents side loading) ─
translate([ARM_REACH - ARM_T, -w_motor/2, -ARM_T/2])
cube([ARM_T, w_motor, ARM_T - BEARING_RECESS_H]);
}
// ── M8 pivot bore (through pivot boss in Y direction) ─────────
rotate([90, 0, 0])
cylinder(d = PIV_D, h = PIV_BOSS_L + 2*e, center = true);
// ── Spring pocket bore (from top, captures spring bottom) ─────
// Bore is slightly larger than spring OD for easy insertion.
translate([SPG_POS_X, 0, ARM_T/2 - e])
cylinder(d = SPG_POCKET_D, h = SPG_POCKET_H + e);
// ── Spring pocket access slot (allows spring preload assembly) ─
// Lateral slot lets spring be pressed in from side during assembly.
translate([SPG_POS_X - SPG_OD/2, -SPG_OD/2 - 0.5, ARM_T/2 - e])
cube([SPG_OD, SPG_OD + 1, SPG_POCKET_H + e]);
// ── Motor axle dropout slot (open at arm tip end, +X) ─────────
// Slot width = axle OD + 1 mm; depth = DROP_DEPTH inward.
// Motor axle slides in from the open end.
translate([ARM_REACH - DROP_DEPTH, -DROP_W/2, -ARM_T/2 - e])
cube([DROP_DEPTH + e, DROP_W, ARM_T + 2*e]);
// Rounded bore at inner end of dropout slot (distributes load)
translate([ARM_REACH - DROP_DEPTH, 0, -ARM_T/2 - e])
cylinder(d = DROP_W, h = ARM_T + 2*e);
// ── Bearing seat recess (inboard face of axle slot) ──────────
// Prevents bearing collar (Ø37.8) from clashing with arm face.
translate([ARM_REACH - DROP_DEPTH - BEARING_RECESS_H, 0, -ARM_T/2 - e])
cylinder(d = BEARING_OD + 1.5, h = BEARING_RECESS_H + e);
// ── Retainer cap M3 bolt holes (2×, for spring_retainer_cap) ──
for (dy = [-ARM_W/4, ARM_W/4])
translate([ARM_REACH - DROP_DEPTH/2, dy, -ARM_T/2 - e])
cylinder(d = M3_D - 0.3, h = ARM_T/2 + e);
// Slightly tight bore — M3 self-taps into PETG at 3.0 mm
// ── Lightening slot (mid-arm, between pivot boss and spring) ──
lx1 = PIV_BOSS_OD/2 + 5;
lx2 = SPG_POS_X - SPG_OD/2 - 5;
translate([lx1, -(ARM_W/4), -ARM_T/2 - e])
cube([max(lx2 - lx1, 1), ARM_W/2, ARM_T + 2*e]);
}
}
// ============================================================
// SPRING RETAINER CAP
// ============================================================
// Clips onto the open axle slot at the arm tip.
// Prevents motor axle from falling out of the dropout slot.
// 2× M3 bolts thread into the arm's self-tap holes.
// Also provides the outboard bearing-seat face.
// ============================================================
module spring_retainer_cap() {
w_cap = ARM_W - ARM_TAPER + 2;
difference() {
union() {
cube([DROP_DEPTH, w_cap, RET_T], center = true);
// Alignment lips (engage the arm slot edges)
for (dy = [-1, 1])
translate([0, dy * (w_cap/2 + 1), 0])
cube([DROP_DEPTH - 2, 2, RET_T + 4], center = true);
}
// Axle bore (clearance) — round section
cylinder(d = AXLE_D + 0.8, h = RET_T + 2*e, center = true);
// Bearing seat recess (outboard face)
translate([0, 0, RET_T/2 - BEARING_RECESS_H/2])
cylinder(d = BEARING_OD + 1.5, h = BEARING_RECESS_H + e, center = true);
// 2× M3 bolt clearance holes
for (dy = [-ARM_W/4, ARM_W/4])
translate([0, dy, 0])
cylinder(d = M3_D, h = RET_T + 2*e, center = true);
}
}