// ============================================================ // saltyrover_chassis_r2.scad — SaltyRover 4-Wheel Chassis Rev 2 // Issue: #109 Agent: sl-mechanical Date: 2026-03-01 // ============================================================ // // Complete parametric chassis assembly for the SaltyRover 4-wheel // rough-terrain variant. Designed to be printed (PETG), laser-cut // (6 mm 5052-H32 Al), or CNC-routed. // // NEW vs Rev 1 (issue #73 / saltyrover_chassis.scad): // • 4× trailing-arm spring-suspension corners // • Enclosed electronics bay (rover_electronics_bay.scad) // • M3-slot-adjustable pivot brackets (replaces fixed M5 flanges) // • CSI camera corner brackets (4×, 45° outward tilt) // • RPLIDAR tower stub on electronics bay lid // • D435i front bracket arm // • Weight target: <2 kg frame (excl. motors/electronics) // // Shared SaltyLab patterns (swappable electronics): // FC : 30.5 × 30.5 mm M3 (MAMBA F722S / Pixhawk) // Jetson: 58 × 49 mm M3 (Orin NX / Nano carrier board) // Stem : Ø25 mm bore (sensor head unchanged) // // Coordinate convention (all modules): // Z = 0 deck top face // +Y forward // +X right // Ground at Z ≈ −(GND_CLR + BATT_PACK_H + BATT_FLOOR_T + DECK_T) // // RENDER options: // "assembly" full 3D preview (default) // "deck_2d" DXF — deck plate for waterjet / CNC // "pivot_bracket_2d" DXF — pivot bracket for CNC / laser (×4) // "pivot_bracket_stl" STL — pivot bracket (print 4×) // "csi_mount_stl" STL — CSI corner bracket (print 4×) // "d435i_mount_stl" STL — D435i front bracket (print 1×) // // ── Export commands ───────────────────────────────────────── // Deck DXF: // openscad saltyrover_chassis_r2.scad -D 'RENDER="deck_2d"' -o saltyrover_r2_deck.dxf // Pivot bracket DXF (×4): // openscad saltyrover_chassis_r2.scad -D 'RENDER="pivot_bracket_2d"' -o rover_pivot_bracket.dxf // Pivot bracket STL (×4): // openscad saltyrover_chassis_r2.scad -D 'RENDER="pivot_bracket_stl"' -o rover_pivot_bracket.stl // CSI mount STL (×4): // openscad saltyrover_chassis_r2.scad -D 'RENDER="csi_mount_stl"' -o rover_csi_mount.stl // D435i mount STL (×1): // openscad saltyrover_chassis_r2.scad -D 'RENDER="d435i_mount_stl"' -o rover_d435i_mount.stl // ============================================================ $fn = 64; e = 0.01; // ── Deck footprint ──────────────────────────────────────────────────────────── ROVER_L = 500.0; // deck fore-aft (Y) ROVER_W = 480.0; // deck left-right (X) DECK_T = 6.0; // deck plate thickness (6 mm Al — weight-optimised) DECK_R = 15.0; // corner fillet radius // ── Drive geometry ──────────────────────────────────────────────────────────── // Hoverboard hub motors — caliper-verified (matches BOM.md / rover_motor_mount.scad) TRACK_W = 540.0; // motor axle CL to CL, left-right (X) AXLE_BASE = 340.0; // motor axle CL to CL, fore-aft (Y) AXLE_H = 127.0; // axle CL above ground (10×2.125" tire, r=127mm) AXLE_D = 16.11; // axle base section OD (caliper) AXLE_FLAT = 13.00; // D-cut chord width (caliper) BEARING_OD = 37.80; // bearing seat collar OD (caliper) // ── Height stack ───────────────────────────────────────────────────────────── GND_CLR = 55.0; // min ground clearance (at static suspension sag) BATT_FLOOR_T = 3.0; // battery tray floor thickness BATT_PACK_H = 56.0; // battery pack height (420×88×56mm, laid flat) DECK_BOT_H = GND_CLR + BATT_FLOOR_T + BATT_PACK_H; // 114 mm DECK_TOP_H = DECK_BOT_H + DECK_T; // 120 mm // Axle above deck top (in chassis SCAD coords = positive Z): // AXLE_H - DECK_TOP_H = 127 - 120 = +7 mm ← axle is 7 mm above deck top // ── Battery packs (under-deck, laid flat) ──────────────────────────────────── BATT_X_DIM = 420.0; // pack long side (left-right) BATT_Y_DIM = 88.0; // pack fore-aft per pack BATT_N = 2; // number of packs fore-aft (2 = 176 mm; 4 = 352 mm) TRAY_MARGIN = 5.0; // opening margin each side // ── Stem socket (deck centre) ───────────────────────────────────────────────── STEM_BORE = 25.5; // 25 mm tube + 0.5 mm FDM clearance STEM_COLLAR_OD = 50.0; STEM_COLLAR_H = 20.0; // raised boss height above deck top STEM_FLANGE_BC = 40.0; // 4× M4 bolt circle for stem adapter // ── FC mount — MAMBA F722S / Pixhawk (30.5 × 30.5 mm M3) ──────────────────── // Shared with SaltyLab — swappable electronics FC_PITCH = 30.5; FC_HOLE_D = 3.2; FC_POS_Y = ROVER_L/2 - 65.0; // near front edge // ── Jetson Orin NX / Nano mount (58 × 49 mm M3) ────────────────────────────── // Shared with SaltyLab — swappable electronics ORIN_HOLE_X = 58.0; ORIN_HOLE_Y = 49.0; ORIN_HOLE_D = 3.2; ORIN_POS_Y = -(ROVER_L/2 - 60.0); // near rear edge // ── Pivot bracket (motor corner mount, adjustable) ─────────────────────────── // Each corner: one pivot bracket bolted to deck top at motor CL fore-aft. // M3 slotted holes allow ±15 mm fore-aft and ±10 mm lateral adjustment. PBK_L = 80.0; // bracket plate length (fore-aft / Y) PBK_W = 55.0; // bracket plate width (lateral / X from deck edge) PBK_T = 8.0; // bracket plate thickness PBK_FLANGE_H = 20.0; // vertical flange height below deck bottom face // M3 adjustment slots (4× per bracket on the deck-top flange) ADJ_SLOT_L = 25.0; // slot length (allows ±12 mm adjustment) ADJ_M3_D = 3.3; // M3 clearance ADJ_INSET_X = 12.0; // slot CL from lateral edge of bracket ADJ_INSET_Y = 16.0; // slot CL from fore/aft ends // Pivot pin (M8 through-bolt; arm swings around this) PIV_D = 8.5; // M8 clearance bore PIV_POS_X = ROVER_W/2 + 5.0; // pivot CL from deck centre (just at edge) // Pivot fore-aft at each motor corner (±AXLE_BASE/2) // Spring guide boss on bracket underside SPG_GUIDE_OD = 14.0; // spring guide boss OD (spring slides over this) SPG_GUIDE_H = 15.0; // guide boss height below bracket bottom // ── CSI camera corner brackets ──────────────────────────────────────────────── CSI_PCB = 25.0; // IMX219 / CSI module PCB width (square) CSI_M2_SPC = 15.0; // M2 hole pitch (±7.5 mm from centre) CSI_TILT = 20.0; // downward tilt (degrees) for terrain view CSI_ANGLE = 45.0; // outward rotation at each corner // ── D435i front bracket ─────────────────────────────────────────────────────── RS_TILT = 8.0; // nose-down tilt (degrees) RS_ARM_LEN = 65.0; // arm length from deck front edge to camera CL RS_BASE_W = 40.0; // base width (left-right) // ── Fasteners ───────────────────────────────────────────────────────────────── 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 == "deck_2d") { projection(cut = true) translate([0, 0, -DECK_T / 2]) deck_plate(); } else if (RENDER == "pivot_bracket_2d") { projection(cut = true) translate([0, 0, -PBK_T / 2]) pivot_bracket_flat(); } else if (RENDER == "pivot_bracket_stl") { pivot_bracket(); } else if (RENDER == "csi_mount_stl") { csi_corner_bracket(); } else if (RENDER == "d435i_mount_stl") { d435i_front_bracket(); } // ============================================================ // FULL ASSEMBLY // ============================================================ module assembly() { color("Silver", 0.90) deck_plate(); color("DimGray", 0.85) stem_collar(); // 4× pivot brackets at motor corners for (sx = [-1, 1]) for (sy = [-1, 1]) color("SteelBlue", 0.85) translate([sx * TRACK_W/2, sy * AXLE_BASE/2, 0]) rotate([0, 0, sx > 0 ? 0 : 180]) pivot_bracket(); // 4× CSI corner brackets for (sx = [-1, 1]) for (sy = [-1, 1]) color("Teal", 0.85) csi_bracket_placed(sx, sy); // D435i front bracket color("DarkSlateGray", 0.85) d435i_bracket_placed(); // Electronics bay reference ghost (from rover_electronics_bay.scad) %color("OliveDrab", 0.30) translate([0, 0, DECK_T + 0.5]) cube([240, 200, 80], center = true); // Ghost motor axle positions for (sx = [-1, 1]) for (sy = [-1, 1]) %color("Tomato", 0.25) translate([sx * TRACK_W/2, sy * AXLE_BASE/2, AXLE_H - DECK_TOP_H]) rotate([90, 0, 0]) cylinder(d = AXLE_D, h = 80, center = true); // Ghost tyre outlines for (sx = [-1, 1]) for (sy = [-1, 1]) %color("Black", 0.15) translate([sx * TRACK_W/2, sy * AXLE_BASE/2, -(DECK_TOP_H - AXLE_H)]) rotate([90, 0, 0]) cylinder(d = 254, h = 55, center = true); } // ============================================================ // DECK PLATE (Part A — laser-cut 6 mm 5052-H32 aluminium) // ============================================================ // Weight estimate: 480×500×6 mm Al, ~50% lightened ≈ 1.35 kg module deck_plate() { difference() { // ── Outer profile — rounded rectangle ───────────────────────── linear_extrude(DECK_T) minkowski() { square([ROVER_L - 2*DECK_R, ROVER_W - 2*DECK_R], center = true); circle(r = DECK_R); } // ── Battery tray opening (under-deck, centred) ───────────────── batt_open_x = BATT_X_DIM + 2*TRAY_MARGIN; batt_open_y = BATT_Y_DIM * BATT_N + 2*TRAY_MARGIN; translate([0, 0, -e]) cube([batt_open_x, batt_open_y, DECK_T + 2*e], center = true); // ── Stem bore ───────────────────────────────────────────────── translate([0, 0, -e]) cylinder(d = STEM_BORE, h = DECK_T + 2*e); // ── Stem collar bolt circle (4× M4 at 90°) ──────────────────── for (a = [0, 90, 180, 270]) rotate([0, 0, a]) translate([STEM_FLANGE_BC/2, 0, -e]) cylinder(d = M4_D, h = DECK_T + 2*e); // ── FC mount holes — 30.5×30.5 M3 (shared SaltyLab pattern) ── for (dx = [-FC_PITCH/2, FC_PITCH/2]) for (dy = [-FC_PITCH/2, FC_PITCH/2]) translate([dx, FC_POS_Y + dy, -e]) cylinder(d = FC_HOLE_D, h = DECK_T + 2*e); // ── Jetson Orin mount holes — 58×49 M3 ─────────────────────── for (dx = [-ORIN_HOLE_X/2, ORIN_HOLE_X/2]) for (dy = [-ORIN_HOLE_Y/2, ORIN_HOLE_Y/2]) translate([dx, ORIN_POS_Y + dy, -e]) cylinder(d = ORIN_HOLE_D, h = DECK_T + 2*e); // ── Pivot bracket M3 attachment slots (4× corners) ──────────── // Two slotted holes per corner at the deck attachment flange for (sx = [-1, 1]) for (sy = [-1, 1]) { bx = sx * (ROVER_W/2 - ADJ_INSET_X); by = sy * AXLE_BASE/2; for (offset = [-ADJ_INSET_Y, ADJ_INSET_Y]) hull() { translate([bx, by + offset - ADJ_SLOT_L/2, -e]) cylinder(d = ADJ_M3_D, h = DECK_T + 2*e); translate([bx, by + offset + ADJ_SLOT_L/2, -e]) cylinder(d = ADJ_M3_D, h = DECK_T + 2*e); } } // ── Lightening holes — 55 mm dia, in structural corridors ───── // Row between battery opening and pivot brackets for (sx = [-1, 1]) for (sy = [-1, 1]) { lx = sx * (ROVER_W/4 + 20); ly = sy * (ROVER_L/4 + 15); translate([lx, ly, -e]) cylinder(d = 55, h = DECK_T + 2*e); } // Additional pair flanking stem for (sx = [-1, 1]) translate([sx * 65, 0, -e]) cylinder(d = 40, h = DECK_T + 2*e); // ── Cable routing slots (4× around electronics bay footprint) ── for (sy = [-1, 1]) hull() { translate([-20, sy * 105, -e]) cylinder(d = 14, h = DECK_T+2*e); translate([ 20, sy * 105, -e]) cylinder(d = 14, h = DECK_T+2*e); } for (sx = [-1, 1]) hull() { translate([sx * 125, -18, -e]) cylinder(d = 14, h = DECK_T+2*e); translate([sx * 125, 18, -e]) cylinder(d = 14, h = DECK_T+2*e); } // ── Motor phase cable pass-throughs at each corner ──────────── for (sx = [-1, 1]) for (sy = [-1, 1]) translate([sx * (ROVER_W/2 - 25), sy * (ROVER_L/2 - 25), -e]) cylinder(d = 18, h = DECK_T + 2*e); } } // ── Deck-top stem collar (raised boss, 25 mm bore) ───────────────────────── module stem_collar() { translate([0, 0, DECK_T]) difference() { cylinder(d = STEM_COLLAR_OD, h = STEM_COLLAR_H); // Bore translate([0, 0, -e]) cylinder(d = STEM_BORE, h = STEM_COLLAR_H + 2*e); // Flange bolt holes for (a = [0, 90, 180, 270]) rotate([0, 0, a]) translate([STEM_FLANGE_BC/2, 0, -e]) cylinder(d = M4_D, h = STEM_COLLAR_H + 2*e); } } // ============================================================ // PIVOT BRACKET (Part B — M3-adjustable motor corner mount) // ============================================================ // One per corner (×4). Mounts to deck top face via 2× M3 SHCS // through slotted deck holes (allows ±12 mm fore/aft adjustment). // Provides: // • M8 pivot pin bore for suspension trailing arm // • Spring upper seat (captured spring guide boss, 14 mm OD) // • Vertical flange to deck edge for lateral stiffness // // Print: PETG 5 perims 60% infill, flat base down. // Alt : CNC 8 mm 6061-T6 Al from pivot_bracket_2d DXF. // // Coordinate: bracket centred at motor corner (sx*TRACK_W/2, sy*AXLE_BASE/2) // The deck-edge flange is on the -X side (inner face toward deck centre). // ============================================================ module pivot_bracket() { // Deck-top flat base plate translate([-PBK_W/2, -PBK_L/2, 0]) difference() { cube([PBK_W, PBK_L, PBK_T]); // 2× M3 adjustment slots (fore-aft direction) for (s = [-1, 1]) hull() { translate([ADJ_INSET_X, PBK_L/2 + s*ADJ_INSET_Y - ADJ_SLOT_L/2, -e]) cylinder(d = ADJ_M3_D, h = PBK_T + 2*e); translate([ADJ_INSET_X, PBK_L/2 + s*ADJ_INSET_Y + ADJ_SLOT_L/2, -e]) cylinder(d = ADJ_M3_D, h = PBK_T + 2*e); } // Lightening slot (centre of bracket base) translate([ADJ_INSET_X + 8, PBK_L/2 - 18, -e]) cube([PBK_W - ADJ_INSET_X - 14, 36, PBK_T + 2*e]); } // Outer vertical flange (at +X edge — outboard side, toward motor) // This flange drops below the deck to form the suspension pivot clevis. translate([PBK_W/2 - PBK_T, -PBK_L/2, -(PBK_FLANGE_H)]) difference() { cube([PBK_T, PBK_L, PBK_FLANGE_H + PBK_T]); // M8 pivot pin bore — at mid-height of flange, centred fore-aft // The trailing arm will pivot on an M8 bolt through this hole. pivot_z = PBK_FLANGE_H / 2; translate([-e, PBK_L/2, pivot_z]) rotate([0, 90, 0]) cylinder(d = PIV_D, h = PBK_T + 2*e); } // Spring upper seat boss (below bracket base, outboard side) // Compression spring (Ø14 OD) slides over this guide boss. // When arm swings up (bump), spring is compressed between this boss // and the matching pocket in the trailing arm. translate([PBK_W/2 - PBK_T/2, 0, -e]) cylinder(d = SPG_GUIDE_OD, h = SPG_GUIDE_H + e); } // Flat (2D projection source) version of pivot bracket — same profile module pivot_bracket_flat() { difference() { cube([PBK_W, PBK_L, PBK_T], center = true); // M3 slots for (s = [-1, 1]) hull() { translate([0, s*ADJ_INSET_Y - ADJ_SLOT_L/2, 0]) cylinder(d = ADJ_M3_D, h = PBK_T + 2*e, center = true); translate([0, s*ADJ_INSET_Y + ADJ_SLOT_L/2, 0]) cylinder(d = ADJ_M3_D, h = PBK_T + 2*e, center = true); } // M8 pivot bore cylinder(d = PIV_D, h = PBK_T + 2*e, center = true); } } // ── Place pivot brackets at correct corners ─────────────────────────────────── // Called from assembly() with (sx, sy) = (±1, ±1) // ============================================================ // CSI CAMERA CORNER BRACKET (Part C — 4× corners) // ============================================================ // Mounts an IMX219 / Arducam CSI module at each deck corner, // angled 45° outward and CSI_TILT° downward for terrain coverage. // 2× M2 bolts hold camera PCB (15 mm square hole pattern). // 2× M3 bolts mount bracket to deck top. // // Print: PETG 4 perims 30% infill, flat base down. // ============================================================ module csi_corner_bracket() { base_l = 40; base_w = 30; base_t = 5; arm_l = 30; difference() { union() { // Deck-top base plate cube([base_l, base_w, base_t]); // Angled arm + camera face plate translate([base_l / 2, base_w / 2, base_t]) rotate([0, CSI_TILT, 0]) translate([-CSI_PCB/2, -CSI_PCB/2, 0]) cube([CSI_PCB + 6, CSI_PCB + 6, base_t]); } // 2× M3 base attachment holes for (dx = [8, base_l - 8]) translate([dx, base_w / 2, -e]) cylinder(d = M3_D, h = base_t + 2*e); // CSI camera M2 mounting holes (15 × 15 mm pattern) translate([base_l / 2, base_w / 2, base_t]) rotate([0, CSI_TILT, 0]) for (cx = [-CSI_M2_SPC/2, CSI_M2_SPC/2]) for (cy = [-CSI_M2_SPC/2, CSI_M2_SPC/2]) translate([cx, cy, -e]) cylinder(d = M2_D, h = base_t + 2*e); // CSI ribbon cable slot (3 mm wide, 12 mm long, centred) translate([base_l/2 - 6, base_w/2 - 1.5, -e]) cube([12, 3, base_t + 2*e]); } } module csi_bracket_placed(sx, sy) { // Corner position cx = sx * (ROVER_W/2 - 25); cy = sy * (ROVER_L/2 - 25); // Rotate so camera faces outward from corner rot = atan2(sy, sx) * 180 / 3.14159 - 45; translate([cx, cy, DECK_T]) rotate([0, 0, rot]) translate([-20, -15, 0]) csi_corner_bracket(); } // ============================================================ // D435i FRONT BRACKET (Part D — 1× front mount) // ============================================================ // Arm extends forward from deck front edge. // Camera face tilted RS_TILT° nose-down. // 1/4-20 UNC captured hex nut for D435i tripod socket. // 2× M4 bolts mount base to deck front face. // // Print: PETG 5 perims 40% infill, arm flat on bed. // ============================================================ module d435i_front_bracket() { base_d = 22; // base depth (Y direction, into deck) base_h = 8; // base/arm thickness arm_len = RS_ARM_LEN; // 1/4-20 UNC geometry nut14_af = 11.1; // across-flats nut14_h = 5.6; // nut thickness nut14_cl = 6.5; // bolt clearance bore difference() { union() { // Rear base plate (bolts to deck front face) translate([-RS_BASE_W/2, 0, 0]) cube([RS_BASE_W, base_d, base_h]); // Forward arm (+ direction is forward / +Y) translate([-12, base_d, 0]) cube([24, arm_len, base_h]); // Camera face plate (tilted RS_TILT° downward) translate([0, base_d + arm_len, base_h / 2]) rotate([0, RS_TILT, 0]) translate([-15, 0, -base_h / 2]) cube([30, 14, base_h]); } // 2× M4 base attachment holes for (dx = [-RS_BASE_W/2 + 10, RS_BASE_W/2 - 10]) translate([dx, base_d / 2, -e]) cylinder(d = M4_D, h = base_h + 2*e); // 1/4-20 captured nut pocket in face plate translate([0, base_d + arm_len + 12, base_h / 2]) rotate([0, 90, 0]) { // Hex nut pocket (from back) translate([0, 0, -nut14_h - 1]) cylinder(d = nut14_af / cos(30), h = nut14_h + 1, $fn = 6); // Camera bolt clearance bore cylinder(d = nut14_cl, h = 20); } } } module d435i_bracket_placed() { // Mount to deck front edge, centred left-right, at deck level translate([0, ROVER_L/2 + 10, DECK_T]) rotate([0, 0, 180]) d435i_front_bracket(); }