diff --git a/chassis/ASSEMBLY.md b/chassis/ASSEMBLY.md new file mode 100644 index 0000000..e7f10bc --- /dev/null +++ b/chassis/ASSEMBLY.md @@ -0,0 +1,121 @@ +# SaltyBot Chassis — Assembly Notes +**Task:** bd-1iy5 — Rev A — 2026-02-28 + +--- + +## Overview + +``` + [Front bumper rail — 22mm EMT] + ├─ bumper_bracket(front=+1) ──────────────────────┐ + │ │ + ┌───────┴──────────── Main Deck (640×220×6mm Al) ─────────┴───────┐ + │ ← Jetson mount plate (rear/+X) FC mount (front/−X) → │ + │ [Battery tray hanging below centre] │ + └───┬──────────────────────────────────────────────────────────┬───┘ + │ │ + Motor fork (L) Motor fork (R) + Hub motor CL: ±300mm from deck centre │ + Axle height: 310mm above ground │ + ├─ bumper_bracket(front=-1) ──────────────────────┘ + [Rear bumper rail — 22mm EMT] +``` + +--- + +## Step-by-step Assembly + +### 1 Fabricate / print parts +- Send deck plate DXF (export from OpenSCAD → DXF) to CNC router or waterjet cutter. +- Print motor fork brackets in PETG 5 perimeters / 40% gyroid for prototype, or send STEP to machine shop. +- Print battery tray, FC pad, Jetson plate, bumper brackets in PETG. +- Export STEP: OpenSCAD → Render (F6) → Export as STL, then convert with FreeCAD for STEP. + +### 2 Verify motor axle dimensions +- Measure actual hoverboard motor axle: diameter and flat-to-flat. +- Adjust `MOTOR_AXLE_D` and `MOTOR_AXLE_FLAT` in `chassis_frame.scad`. +- Re-export fork dropout slot. + +### 3 Motor forks → deck +1. Thread M5 T-nuts into underside of deck edge slots (or use M5 rivet nuts). +2. Align fork bracket to deck edge; fasten with 4× M5×16 SHCS + flat washer each side. +3. Apply Loctite 243 to threads. Torque to 4 N·m. + +### 4 Motors into forks +1. Slide hub motor axle into dropout slot (flat side aligns with slot). +2. Fit flat washer then flanged M14 axle nut; torque to 35–40 N·m. +3. Feed motor phase wires and hall-sensor cable through deck cable slot. + +### 5 Longitudinal ribs +1. Align ribs with edge grooves on deck underside. +2. Secure with M4×12 SHCS through pre-drilled holes; torque 2.5 N·m. + +### 6 Battery tray +1. Pass any wiring harness through deck wire-pass hole before mounting tray. +2. Align tray mounting ears to deck M4 threaded inserts (or rivet nuts). +3. Fasten 4× M4×12 SHCS. Torque 2.5 N·m. +4. Insert battery pack; route Velcro straps through slots and cinch. + +### 7 FC mount (MAMBA F722S) +1. Place silicone anti-vibration grommets onto nylon M3 standoffs. +2. Lower FC onto standoffs; secure with M3×6 BHCS. Snug only — do not over-torque. +3. Orient USB-C port toward front of robot for cable access. + +### 8 Jetson Nano mount plate +1. Press or thread M3 nylon standoffs (8mm) into plate holes. +2. Bolt plate to deck: 4× M3×10 SHCS at deck corners. +3. Set Jetson Nano B01 carrier onto plate standoffs; fasten M3×6 BHCS. + +### 9 Bumper brackets +1. Slide 22mm EMT conduit through saddle clamp openings. +2. Bolt bracket to deck edge: 4× M5×16 SHCS per bracket. +3. Position bumper rail flush with motor OD outer edge; tighten saddle clamp bolts. + +### 10 Cable routing +- Route motor phase cables along longitudinal ribs; secure with cable clips. +- FC ↔ ESC/VESC harness exits through front cable slot. +- Jetson USB/UART ribbon exits through rear cable slot. +- Power harness (battery XT60 → BMS → 24V bus) runs under deck along centreline. + +--- + +## Critical Dimensions (verify before machining) + +| Dimension | Nominal | Tolerance | +|-----------|---------|-----------| +| Wheelbase (axle C/L to C/L) | 600 mm | ±1 mm | +| Motor fork slot width | 24 mm | +0.5 / 0 | +| Motor fork dropout depth | 60 mm | ±0.5 mm | +| FC hole pattern | 30.5 × 30.5 mm | ±0.2 mm | +| Jetson hole pattern | 58 × 58 mm | ±0.2 mm | +| Battery tray inner | 185 × 72 × 52 mm | +2 / 0 mm | + +--- + +## OpenSCAD Rendering Notes + +```bash +# Render full assembly preview (F5 in GUI) +openscad chassis_frame.scad + +# Export individual part STL for slicing +openscad chassis_frame.scad -D "PART=\"motor_fork\"" -o motor_fork_right.stl + +# Export deck DXF (set DECK_THICKNESS=0.01 for 2D projection) +openscad chassis_frame.scad -D "RENDER_2D=true" -o deck_plate.dxf +``` + +**Slicing profile (PETG, structural parts):** +- Nozzle: 0.4mm | Layer: 0.2mm +- Perimeters: 5 | Infill: 40% gyroid +- Supports: Yes (motor fork dropout slot) +- Orientation: Fork bracket printed vertically (load axis = layer direction) + +--- + +## Safety Notes + +- Verify axle nut torque after first 10-minute ride — hub motors vibrate and may back off. +- Battery tray is rated for packs ≤185×72×52mm; confirm dimensions before ordering cells. +- Ground all aluminium chassis parts to power-common to avoid RF interference with FC. +- Do **not** run robot without bumpers fitted — uncontrolled runaway risk. diff --git a/chassis/BOM.md b/chassis/BOM.md new file mode 100644 index 0000000..56d4588 --- /dev/null +++ b/chassis/BOM.md @@ -0,0 +1,102 @@ +# SaltyBot Chassis — Bill of Materials +**Task:** bd-1iy5 +**Rev:** A — 2026-02-28 +**Agent:** sl-mechanical + +--- + +## Structural / Fabricated Parts + +| # | Part | Qty | Material | Notes | +|---|------|-----|----------|-------| +| 1 | Main deck plate | 1 | 6mm 5052 aluminium plate, 640×220mm | CNC router or waterjet; all holes per `chassis_frame.scad` | +| 2 | Longitudinal rib (front) | 1 | 4mm 5052 aluminium, 600×40mm | Laser-cut; press-fit into deck slots | +| 3 | Longitudinal rib (rear) | 1 | 4mm 5052 aluminium, 600×40mm | Same file, symmetric | +| 4 | Motor fork bracket (L) | 1 | 8mm 6061 aluminium | CNC mill or FDM PETG @100% infill for prototyping | +| 5 | Motor fork bracket (R) | 1 | 8mm 6061 aluminium | Mirror of item 4 | +| 6 | Battery tray | 1 | 3mm PETG FDM or 3mm aluminium fold | `chassis_frame.scad` — `battery_tray()` module | +| 7 | FC mount plate / standoffs | 1 set | PETG or nylon FDM | Includes 4× M3 nylon standoffs, 6mm height | +| 8 | Jetson Nano mount plate | 1 | 4mm 5052 aluminium or 4mm PETG FDM | B01 58×58mm hole pattern | +| 9 | Front bumper bracket | 1 | 5mm PETG FDM | Saddle clamps for 22mm EMT conduit | +| 10 | Rear bumper bracket | 1 | 5mm PETG FDM | Mirror of item 9 | + +--- + +## Motors + +| # | Part | Qty | Source / Spec | Notes | +|---|------|-----|---------------|-------| +| 11 | Hoverboard hub motor | 2 | Generic 6.5" / 170mm OD, 36V nominal | ~350W each; 14mm flat axle; confirm exact OD before cutting fork slots | +| 12 | Motor hall-sensor cable extension | 2 | 6-pin JST-PH 300mm | Route through deck cable slot | + +--- + +## Electronics Mounts + +| # | Part | Qty | Spec | Notes | +|---|------|-----|------|-------| +| 13 | STM32 MAMBA F722S FC | 1 | 36×36mm PCB, 30.5×30.5mm M3 mount | Oriented USB-C port toward front | +| 14 | Nylon M3 standoff 6mm | 4 | F/F nylon | FC vibration isolation | +| 15 | Anti-vibration grommet M3 | 4 | Ø6mm silicone | Under FC mount pads | +| 16 | Jetson Nano B01 module | 1 | 69.6×45mm module + carrier | 58×58mm M3 carrier hole pattern | +| 17 | Nylon M3 standoff 8mm | 4 | F/F nylon | Jetson board standoffs | + +--- + +## Battery + +| # | Part | Qty | Spec | Notes | +|---|------|-----|------|-------| +| 18 | LiPo / LiFePO4 pack | 1 | 24V (6S LiPo or 8S LiFePO4), 4Ah, ≤185×72×52mm | Confirm dims before printing tray; add 2mm clearance | +| 19 | Velcro strap 20mm wide | 2 | 300mm length | Route through tray strap slots | +| 20 | BMS board | 1 | Matched to cell chemistry | Mount externally on rear inner face of tray wall | + +--- + +## Bumper Rail + +| # | Part | Qty | Spec | Notes | +|---|------|-----|------|-------| +| 21 | 3/4" EMT conduit | 2 | 22mm OD, 660mm length | Front and rear bumper rail; bent or straight | +| 22 | Conduit saddle clamp M4 | 6 | 3D-printed integral to bracket (items 9/10) | Back-up: standard pipe clamp 22mm | + +--- + +## Fasteners + +| # | Part | Qty | Spec | +|---|------|-----|------| +| 23 | M5×16 SHCS | 24 | ISO 4762, SS | +| 24 | M5 hex nut | 24 | ISO 4032, SS | +| 25 | M4×12 SHCS | 12 | ISO 4762, SS | +| 26 | M4 hex nut | 12 | ISO 4032, SS | +| 27 | M3×10 SHCS | 20 | ISO 4762, SS | +| 28 | M3 hex nut | 20 | ISO 4032, SS | +| 29 | M3×6 BHCS | 8 | FC + Jetson board bolts | +| 30 | M14×1.5 axle nut | 4 | One each side per motor, flanged | Confirm axle thread pitch on actual motors | +| 31 | Serrated washer M14 | 4 | Axle anti-rotation | +| 32 | Flat washer M5 | 48 | SS | + +--- + +## Tools Required for Assembly + +- Torque wrench (M14 axle nuts: 35–40 N·m; M5: 4 N·m) +- M2.5 / M3 / M4 / M5 hex drivers +- Thread locker (Loctite 243 blue for all structural fasteners) +- Dial caliper — verify motor OD, axle flat before machining fork slot + +--- + +## Estimated Masses + +| Assembly | Est. mass | +|----------|-----------| +| Aluminium deck + ribs | ~1.1 kg | +| Motor forks (×2 Al) | ~0.4 kg | +| Motors (×2) | ~3.6 kg | +| Battery (6S 4Ah LiPo) | ~0.7 kg | +| Electronics (FC + Jetson + wiring) | ~0.5 kg | +| Bumpers + brackets | ~0.3 kg | +| Fasteners | ~0.2 kg | +| **Total estimate** | **~6.8 kg** | diff --git a/chassis/chassis_frame.scad b/chassis/chassis_frame.scad new file mode 100644 index 0000000..8c96c82 --- /dev/null +++ b/chassis/chassis_frame.scad @@ -0,0 +1,340 @@ +// ============================================================================= +// SaltyBot — Parametric Chassis Frame +// Task: bd-1iy5 +// Agent: sl-mechanical +// Date: 2026-02-28 +// +// Self-balancing two-wheeled robot chassis +// Requirements: +// - 600mm wheelbase +// - 2x hoverboard hub motors (170mm OD) +// - STM32 MAMBA F722S FC mount (30.5x30.5mm pattern) +// - Battery tray (24V 4Ah — ~180x70x50mm pack) +// - Jetson Nano B01 mount plate (100x80mm, M3 holes) +// - Front/rear bumper brackets +// ============================================================================= + +// ─── RENDER QUALITY ────────────────────────────────────────────────────────── +$fn = 64; + +// ============================================================================= +// PARAMETERS — edit here to adjust the whole model +// ============================================================================= + +// ── Wheelbase / overall geometry ───────────────────────────────────────────── +WHEELBASE = 600; // mm, center-to-center axle distance (lateral) +FRAME_WIDTH = 220; // mm, front-to-back depth of main deck +DECK_THICKNESS = 6; // mm, main deck plate thickness +WALL_T = 4; // mm, general wall thickness for brackets/ribs + +// ── Hub motor parameters ───────────────────────────────────────────────────── +MOTOR_OD = 170; // mm, motor housing outer diameter +MOTOR_AXLE_D = 14; // mm, axle bolt diameter +MOTOR_AXLE_FLAT = 10; // mm, axle flat-to-flat (for anti-rotation) +MOTOR_FORK_WIDTH = 24; // mm, dropout slot width (fits 10-14mm axle + spacers) +MOTOR_FORK_DEPTH = 60; // mm, dropout slot depth from fork tip +MOTOR_FORK_H = 80; // mm, total height of motor fork bracket +MOTOR_FORK_T = 8; // mm, fork plate thickness +AXLE_HEIGHT = 310; // mm, axle CL above ground (motor radius + clearance) + +// ── FC mount (MAMBA F722S — 30.5 × 30.5 mm M3 pattern) ────────────────────── +FC_MOUNT_SPACING = 30.5; // mm, hole pattern pitch +FC_MOUNT_HOLE_D = 3.2; // mm, M3 clearance +FC_STANDOFF_H = 6; // mm, standoff height +FC_PAD_T = 3; // mm, mounting pad thickness + +// ── Battery tray (24V 4Ah LiPo / LiFePO4) ─────────────────────────────────── +BATT_L = 185; // mm, cell pack length +BATT_W = 72; // mm, cell pack width +BATT_H = 52; // mm, cell pack height +BATT_WALL = 3; // mm, tray wall thickness +BATT_FLOOR = 4; // mm, tray floor thickness +BATT_STRAP_W = 20; // mm, Velcro strap slot width +BATT_STRAP_T = 2; // mm, strap slot depth + +// ── Jetson Nano B01 mount plate ────────────────────────────────────────────── +// B01 carrier board hole pattern: 58 x 58 mm M3 (inner) + corner pass-throughs +JETSON_HOLE_PITCH = 58; // mm, M3 mounting hole pattern +JETSON_HOLE_D = 3.2; // mm +JETSON_PLATE_L = 105; // mm, plate length +JETSON_PLATE_W = 90; // mm, plate width +JETSON_PLATE_T = 4; // mm, plate thickness +JETSON_STANDOFF_H = 8; // mm + +// ── Bumper bracket ──────────────────────────────────────────────────────────── +BUMPER_L = WHEELBASE + 60; // mm, bumper rail length (overhangs wheel CL) +BUMPER_H = 40; // mm, bracket vertical height +BUMPER_T = 5; // mm, bracket plate thickness +BUMPER_TUBE_OD = 22; // mm, 3/4" EMT conduit OD for bumper rail + +// ── Rib / gusset parameters ─────────────────────────────────────────────────── +RIB_W = 20; // mm, longitudinal rib width +RIB_H = 40; // mm, rib height below deck +RIB_T = 4; // mm, rib plate thickness (laser/router cut) + +// ── Fastener helpers ───────────────────────────────────────────────────────── +M3_D = 3.2; +M4_D = 4.3; +M5_D = 5.3; +M6_D = 6.5; + +// ============================================================================= +// MAIN ASSEMBLY — comment/uncomment parts as needed +// ============================================================================= + +color("Silver", 0.9) main_deck(); +color("DimGray") motor_fork(side= 1); // right (+X) +color("DimGray") motor_fork(side=-1); // left (-X) +color("SteelBlue") battery_tray(); +color("OliveDrab") fc_mount_plate(); +color("DarkOrange") jetson_mount_plate(); +color("Tomato") bumper_bracket(front= 1); +color("Tomato") bumper_bracket(front=-1); +color("WhiteSmoke") longitudinal_ribs(); + +// ============================================================================= +// MODULES +// ============================================================================= + +// ─── Main deck plate ───────────────────────────────────────────────────────── +module main_deck() { + difference() { + // Deck plate + translate([-WHEELBASE/2 - MOTOR_FORK_T, -FRAME_WIDTH/2, 0]) + cube([WHEELBASE + 2*MOTOR_FORK_T, FRAME_WIDTH, DECK_THICKNESS]); + + // Lightening holes — 3 rows × 4 cols + for (x = [-WHEELBASE/3, 0, WHEELBASE/3]) + for (y = [-FRAME_WIDTH/4, FRAME_WIDTH/4]) + translate([x, y, -1]) + cylinder(d=50, h=DECK_THICKNESS+2); + + // FC mount holes + fc_mount_holes(z_offset=-1, depth=DECK_THICKNESS+2); + + // Battery tray floor cutout for wire pass-through + translate([-BATT_L/2 + 40, -BATT_W/2 + 40, -1]) + cube([BATT_L - 80, BATT_W - 80, DECK_THICKNESS+2]); + + // Cable routing slots + for (x = [-60, 60]) + translate([x, -FRAME_WIDTH/2 - 1, -1]) + cube([14, 18, DECK_THICKNESS+2]); + } +} + +// ─── Longitudinal ribs (×2, symmetric F/R) ─────────────────────────────────── +module longitudinal_ribs() { + for (y = [-FRAME_WIDTH/2 + RIB_W/2 + WALL_T, + FRAME_WIDTH/2 - RIB_W/2 - WALL_T]) + translate([-WHEELBASE/2, y - RIB_T/2, -RIB_H]) + cube([WHEELBASE, RIB_T, RIB_H]); +} + +// ─── Motor fork dropout bracket ────────────────────────────────────────────── +// Mounts to deck edge; provides a CNC-milled or FDM dropout slot for the axle. +// `side` = +1 (right/+X) or -1 (left/-X) +module motor_fork(side = 1) { + x_pos = side * (WHEELBASE/2); + + translate([x_pos, 0, 0]) { + difference() { + union() { + // Vertical fork body + translate([side*(DECK_THICKNESS/2), -MOTOR_FORK_WIDTH/2 - 4, -MOTOR_FORK_H]) + cube([side * MOTOR_FORK_T, MOTOR_FORK_WIDTH + 8, MOTOR_FORK_H + DECK_THICKNESS]); + + // Gusset triangles + for (g = [-1, 1]) + translate([side*(DECK_THICKNESS/2), g*(MOTOR_FORK_WIDTH/2 + 2), -MOTOR_FORK_H]) + linear_extrude(height=MOTOR_FORK_T) + polygon([[0,0],[0, g*20],[side*-30, 0]]); + } + + // Axle dropout slot (open at bottom) + translate([side*(DECK_THICKNESS/2) - 1, + -MOTOR_AXLE_FLAT/2, + -MOTOR_FORK_DEPTH - MOTOR_AXLE_D/2]) + cube([MOTOR_FORK_T + 2, MOTOR_AXLE_FLAT, MOTOR_FORK_DEPTH + 1]); + + // Axle through-hole at slot top + translate([side*(DECK_THICKNESS/2) - 1, 0, + -MOTOR_FORK_DEPTH - MOTOR_AXLE_D/2]) + rotate([0, 90, 0]) + cylinder(d=MOTOR_AXLE_D + 1, h=MOTOR_FORK_T + 2); + + // Bolt holes for deck attachment (M5 × 4) + for (y = [-20, 20]) + for (z = [4, 12]) + translate([side*(DECK_THICKNESS/2) - 1, y, z - MOTOR_FORK_H + MOTOR_FORK_H/2]) + rotate([0, 90, 0]) + cylinder(d=M5_D, h=MOTOR_FORK_T + 2); + } + } +} + +// ─── Battery tray ───────────────────────────────────────────────────────────── +// Positioned in centre of deck, recessed 10mm below deck surface +module battery_tray() { + z_base = -BATT_FLOOR - BATT_H - 5; // hang below deck + translate([-BATT_L/2 - BATT_WALL, -BATT_W/2 - BATT_WALL, z_base]) { + difference() { + // Outer tray body + cube([BATT_L + 2*BATT_WALL, + BATT_W + 2*BATT_WALL, + BATT_H + BATT_FLOOR]); + + // Inner cavity + translate([BATT_WALL, BATT_WALL, BATT_FLOOR]) + cube([BATT_L, BATT_W, BATT_H + 1]); + + // Strap slots (2× longitudinal) + for (x = [BATT_L/2 - BATT_STRAP_W*2, + BATT_L/2 + BATT_STRAP_W]) + translate([x, -1, BATT_FLOOR + BATT_H/2 - BATT_STRAP_W/2]) + cube([BATT_STRAP_W, BATT_W + 2*BATT_WALL + 2, BATT_STRAP_W]); + + // Ventilation slots bottom (3×) + for (i = [0:2]) + translate([BATT_WALL + 20 + i*50, BATT_WALL + 10, -1]) + cube([30, BATT_W - 20, BATT_FLOOR + 2]); + + // Mount holes to deck (M4 × 4 corners) + for (x = [10, BATT_L + BATT_WALL - 2]) + for (y = [10, BATT_W + BATT_WALL - 2]) + translate([x, y, BATT_H + BATT_FLOOR - 1]) + cylinder(d=M4_D, h=BATT_FLOOR + 2); + } + } +} + +// ─── FC mount holes helper ──────────────────────────────────────────────────── +module fc_mount_holes(z_offset=0, depth=10) { + // MAMBA F722S: 30.5×30.5 mm M3 pattern, centred at origin + for (x = [-FC_MOUNT_SPACING/2, FC_MOUNT_SPACING/2]) + for (y = [-FC_MOUNT_SPACING/2, FC_MOUNT_SPACING/2]) + translate([x, y, z_offset]) + cylinder(d=FC_MOUNT_HOLE_D, h=depth); +} + +// ─── FC mount plate (raised 40mm above deck centre) ────────────────────────── +module fc_mount_plate() { + fc_x = -50; // offset toward front from deck centre + fc_y = 0; + plate_z = DECK_THICKNESS + FC_STANDOFF_H; + + translate([fc_x, fc_y, 0]) { + difference() { + union() { + // Mount pads with standoffs + for (x = [-FC_MOUNT_SPACING/2, FC_MOUNT_SPACING/2]) + for (y = [-FC_MOUNT_SPACING/2, FC_MOUNT_SPACING/2]) { + translate([x, y, DECK_THICKNESS]) + cylinder(d=8, h=FC_STANDOFF_H); // standoff column + translate([x - 5, y - 5, DECK_THICKNESS]) + cube([10, 10, FC_PAD_T]); // pad base + } + // Base plate + translate([-FC_MOUNT_SPACING/2 - 8, -FC_MOUNT_SPACING/2 - 8, DECK_THICKNESS]) + cube([FC_MOUNT_SPACING + 16, FC_MOUNT_SPACING + 16, FC_PAD_T]); + } + // M3 through-holes in standoffs + fc_mount_holes(z_offset=DECK_THICKNESS - 1, depth=FC_STANDOFF_H + FC_PAD_T + 2); + + // Deck anchor holes + fc_mount_holes(z_offset=-1, depth=DECK_THICKNESS + 2); + } + } +} + +// ─── Jetson Nano B01 mount plate ───────────────────────────────────────────── +// Positioned rear of deck, elevated on standoffs +module jetson_mount_plate() { + jet_x = 60; // offset toward rear + jet_y = 0; + plate_z = DECK_THICKNESS + JETSON_STANDOFF_H; + + translate([jet_x, jet_y, 0]) { + difference() { + union() { + // Mounting plate + translate([-JETSON_PLATE_L/2, -JETSON_PLATE_W/2, DECK_THICKNESS + JETSON_STANDOFF_H]) + cube([JETSON_PLATE_L, JETSON_PLATE_W, JETSON_PLATE_T]); + + // Four standoff columns + for (x = [-JETSON_HOLE_PITCH/2, JETSON_HOLE_PITCH/2]) + for (y = [-JETSON_HOLE_PITCH/2, JETSON_HOLE_PITCH/2]) + translate([x, y, DECK_THICKNESS]) + cylinder(d=6, h=JETSON_STANDOFF_H); + } + + // B01 M3 hole pattern (58×58 mm) + for (x = [-JETSON_HOLE_PITCH/2, JETSON_HOLE_PITCH/2]) + for (y = [-JETSON_HOLE_PITCH/2, JETSON_HOLE_PITCH/2]) + translate([x, y, DECK_THICKNESS - 1]) + cylinder(d=JETSON_HOLE_D, h=JETSON_STANDOFF_H + JETSON_PLATE_T + 2); + + // Ventilation / cable routing slots in plate + for (i = [-1, 0, 1]) + translate([i*25 - 10, -JETSON_PLATE_W/2 + 10, + DECK_THICKNESS + JETSON_STANDOFF_H - 1]) + cube([20, JETSON_PLATE_W - 20, JETSON_PLATE_T + 2]); + + // Deck anchor holes (M3 × 4 corners of plate) + for (x = [-JETSON_PLATE_L/2 + 8, JETSON_PLATE_L/2 - 8]) + for (y = [-JETSON_PLATE_W/2 + 8, JETSON_PLATE_W/2 - 8]) + translate([x, y, -1]) + cylinder(d=M3_D, h=DECK_THICKNESS + 2); + } + } +} + +// ─── Bumper bracket ─────────────────────────────────────────────────────────── +// `front` = +1 (front) or -1 (rear) +module bumper_bracket(front = 1) { + y_pos = front * (FRAME_WIDTH/2); + + translate([0, y_pos, 0]) { + difference() { + union() { + // Horizontal mounting rail plate + translate([-BUMPER_L/2, front*(BUMPER_T/2), 0]) + cube([BUMPER_L, BUMPER_T, BUMPER_H]); + + // Vertical gussets at ends + for (x = [-BUMPER_L/2 + 5, BUMPER_L/2 - BUMPER_T - 5]) + translate([x, front*(BUMPER_T), 0]) + cube([BUMPER_T, 20, BUMPER_H]); + + // Bumper tube saddle clamps (×3 evenly spaced) + for (x = [-BUMPER_L/3, 0, BUMPER_L/3]) + translate([x - BUMPER_TUBE_OD/2 - 2, + front*(BUMPER_T + 15), + BUMPER_H - BUMPER_TUBE_OD/2 - 5]) + difference() { + cube([BUMPER_TUBE_OD + 4, BUMPER_TUBE_OD/2 + 4, BUMPER_TUBE_OD + 4]); + translate([BUMPER_TUBE_OD/2 + 2, -1, BUMPER_TUBE_OD/2 + 2]) + rotate([-90, 0, 0]) + cylinder(d=BUMPER_TUBE_OD, h=BUMPER_TUBE_OD/2 + 6); + } + } + + // Deck mounting holes (M5 × 6 along length) + for (x = [-BUMPER_L/2 + 20, -BUMPER_L/6, BUMPER_L/6, BUMPER_L/2 - 20]) + translate([x, y_pos * 0.001, -1]) + cylinder(d=M5_D, h=BUMPER_H + 2); + } + } +} + +// ============================================================================= +// DIMENSIONS / ANNOTATIONS (2D cross-section reference) +// ============================================================================= +// Uncomment to render a side-view dimension guide: +// +// %translate([0, FRAME_WIDTH/2 + 30, 0]) { +// color("Blue") { +// cube([WHEELBASE, 1, 1]); // wheelbase span +// translate([0,0,0]) text(str("Wheelbase: ", WHEELBASE, "mm"), size=12); +// } +// }