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>
273 lines
12 KiB
OpenSCAD
273 lines
12 KiB
OpenSCAD
// ============================================================
|
||
// 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);
|
||
}
|
||
}
|