diff --git a/chassis/saltytank_BOM.md b/chassis/saltytank_BOM.md new file mode 100644 index 0000000..537b578 --- /dev/null +++ b/chassis/saltytank_BOM.md @@ -0,0 +1,383 @@ +# SaltyTank Chassis — BOM & Assembly Notes +**Issue: #121 Agent: sl-mechanical Date: 2026-03-01** + +--- + +## Overview + +SaltyTank is the tracked variant of the SaltyLab robot family. +Rubber or metal continuous tracks replace wheels for rough-terrain capability. +The electronics bay, RPLIDAR, D435i, stem, and sensor head are **shared with +SaltyLab and SaltyRover** — no modifications required. + +``` +Side view (schematic): + + ← +Y forward + Sensor head + RPLIDAR + │ + ┌──────────────────────────── deck (Z=0) ───────────────────────────┐ + │ [Electronics Bay] [Stem collar] │ + └──────────────────────────────────────────────────────────────────┘ + │ [Side frame - 90 mm tall] │ + │ ◎ idler (Ø80) ◎ road wheel ◎ road wheel ◎ drive (Ø66)│ + │───────────────────── track belt (rubber, 80 mm wide) ─────────────│ + (ground) + +Top view (schematic): + +Y (forward) + │ +[track] ┌──────┴───────┐ [track] + │ [Electronics │ + │ Bay + │ + │ RPLIDAR] │ +[track] └──────┬───────┘ [track] + │ + D435i → + +Key dimensions: + Deck: 500 mm (L) × 360 mm (W) + Track belt centres: 360 + 6 + 80 = 446 mm left-right CL to CL + Overall robot width (outer track edge): 446 + 80 = 526 mm + Ground clearance (hull between tracks): 90 mm ← exceeds 50 mm req. + Height: deck to RPLIDAR scan plane: ~330 mm (with 550 mm stem option) +``` + +--- + +## File Index + +| File | Description | RENDER → Output | +|------|-------------|-----------------| +| `saltytank_chassis.scad` | Deck plate + side frames + idler block + CSI/D435i mounts | See table below | +| `saltytank_skid_plate.scad` | Underside skid plate (bolt-on, sacrificial) | `skid_2d` → DXF; `skid_stl` → STL | +| `rover_electronics_bay.scad` | Electronics bay body + lid + RPLIDAR tower | **Unchanged** — reuse rover part | +| `rover_motor_mount.scad` | (Not used — drive sprockets mount directly to side frame) | — | +| `rover_stem_adapter.scad` | Stem adapter (shared) | Unchanged | +| `rplidar_mount.scad` | RPLIDAR anti-vibration ring (shared) | Unchanged | + +### RENDER map + +| RENDER value | Output | Qty | Process | +|---|---|---|---| +| `"deck_2d"` | `saltytank_deck.dxf` | 1 | Waterjet / CNC, 8 mm Al | +| `"side_frame_2d"` | `saltytank_side_frame.dxf` | 2 | CNC / laser, 6 mm Al (cut 2×, flip 1) | +| `"side_frame_stl"` | `saltytank_side_frame.stl` | 2 | PETG print (prototype) | +| `"idler_block_stl"` | `saltytank_idler_block.stl` | 2 | PETG print | +| `"csi_mount_stl"` | `saltytank_csi_mount.stl` | 4 | PETG print | +| `"d435i_mount_stl"` | `saltytank_d435i_mount.stl` | 1 | PETG print | +| `"skid_2d"` | `saltytank_skid.dxf` | 1 | Waterjet / CNC, 4 mm HDPE | +| `"skid_stl"` | `saltytank_skid.stl` | 1 | PETG print (prototype) | + +### Export commands + +```bash +# Deck plate DXF: +openscad saltytank_chassis.scad -D 'RENDER="deck_2d"' -o saltytank_deck.dxf + +# Side frame DXF (cut 2×, flip one): +openscad saltytank_chassis.scad -D 'RENDER="side_frame_2d"' -o saltytank_side_frame.dxf + +# Side frame STL (print 2×, mirror right in slicer): +openscad saltytank_chassis.scad -D 'RENDER="side_frame_stl"' -o saltytank_side_frame.stl + +# Idler tensioner block STL (×2): +openscad saltytank_chassis.scad -D 'RENDER="idler_block_stl"' -o saltytank_idler_block.stl + +# CSI bracket STL (×4): +openscad saltytank_chassis.scad -D 'RENDER="csi_mount_stl"' -o saltytank_csi_mount.stl + +# D435i bracket STL (×1): +openscad saltytank_chassis.scad -D 'RENDER="d435i_mount_stl"' -o saltytank_d435i_mount.stl + +# Skid plate DXF (4 mm HDPE, preferred): +openscad saltytank_skid_plate.scad -D 'RENDER="skid_2d"' -o saltytank_skid.dxf + +# Skid plate STL (prototype): +openscad saltytank_skid_plate.scad -D 'RENDER="skid_stl"' -o saltytank_skid.stl +``` + +--- + +## Part A — Deck Plate + +| # | Spec | Qty | Material | Process | Notes | +|---|------|-----|----------|---------|-------| +| A1 | 8 mm 5052-H32 Al, 500×360 mm blank | 1 | Aluminium | Waterjet | Preferred | +| A1-alt | 6 mm 6061-T6 Al | 1 | — | CNC router | Saves 0.3 kg | +| A1-proto | 8 mm PETG FDM, 2 halves lap-jointed | 1 | — | Print | Prototype only | + +**Deck fasteners:** + +| # | Spec | Qty | Use | +|---|------|-----|-----| +| A-f1 | M5×20 SHCS | 8 | Side frame to deck side edge (4 per side) | +| A-f2 | M5 T-nut or rivet nut | 8 | Captured in deck edge (installed before frame) | +| A-f3 | M4×16 FHCS | 4 | Stem collar to deck (4× M4 countersunk) | +| A-f4 | M3×12 SHCS | 10 | Electronics bay to deck | +| A-f5 | M4×12 FHCS | 6 | Skid plate to deck (countersunk from below) | +| A-f6 | M4 rivet-nut | 6 | Installed in deck for skid plate bolts | + +--- + +## Part B — Side Frames (×2) + +| # | RENDER | Qty | Material | Process | Notes | +|---|--------|-----|----------|---------|-------| +| B1 | `side_frame_2d` | 2 | 6 mm 6061-T6 Al | CNC router or waterjet | Cut 2× same DXF; flip 1 (mirror) | +| B1-alt | `side_frame_stl` | 2 | PETG | Print | 5 perims, 60% gyroid; prototype only | + +> ⚠ **Hub motor flange bolt circle:** Default is 52 mm BC × 4× M5. +> **Measure your motor hub** before cutting the side frame — adjust `HUB_FLANGE_BC` +> in `saltytank_chassis.scad` if needed. + +**Side frame fasteners:** + +| # | Spec | Qty | Use | +|---|------|-----|-----| +| B-f1 | M5×20 SHCS | 8 | Frame-to-deck attachment (4 per frame, through slotted holes) | +| B-f2 | M5 nyloc | 8 | Under deck | +| B-f3 | M5×16 SHCS | 8 | Hub motor flange to side frame (2× M5 per motor hub face) | +| B-f4 | M8×60 hex bolt | 2 | Road wheel axle bolts (1 per wheel × 2 per side) | +| B-f5 | M8 nyloc | 4 | Road wheel axle retention | +| B-f6 | M8 flat washer | 8 | Both sides of each road wheel | + +--- + +## Part C — Drive Sprockets + Hub Motors + +SaltyTank reuses the same hoverboard hub motors as SaltyLab/SaltyRover. +A laser-cut sprocket plate bolts to the motor hub and engages the track belt. + +| # | Part | Qty | Spec | Notes | +|---|------|-----|------|-------| +| C1 | Hub motor | 2 | 10×2.125" tire, 36 V, ~350 W; axle OD 16.11 mm (caliper) | **Only 2 motors — rear drive** | +| C2 | Drive sprocket plate | 2 | 5 mm 6061-T6 Al, laser-cut | 10-tooth, 20 mm pitch, PCD ≈ 64.7 mm; Ø52 mm hub bolt holes (verify) | +| C3 | Motor phase cable | 2 | 12 AWG, 300 mm, XT30 | Left + right rear | +| C4 | Hall sensor cable | 2 | 6-pin JST-PH, 300 mm | — | + +> **Sprocket plate design** (separate file to be made if needed): +> 10 teeth, pitch = 20 mm → PCD = 20 / sin(18°) ≈ 64.7 mm → R ≈ 32.4 mm. +> Hub bolt circle = 52 mm, 4× M5 at 90°. Verify against actual motor hub. +> Sprocket tooth profile: ISO/DIN standard for roller chain or custom for belt track. + +**Motor fasteners:** + +| # | Spec | Qty | Use | +|---|------|-----|-----| +| C-f1 | M5×16 SHCS | 8 | Sprocket plate to motor hub flange (4 per sprocket) | +| C-f2 | Hub motor axle nut | 2 | M16×1.5 (verify axle thread); torque 35–40 N·m | +| C-f3 | Loctite 243 | — | On axle nut and flange bolts | + +--- + +## Part D — Idler Wheels + Tensioners + +| # | RENDER | Qty | Material | Notes | +|---|--------|-----|----------|-------| +| D1 | `idler_block_stl` | 2 | PETG | Tensioner sliding block, 1 per side | + +| # | Part | Qty | Spec | Notes | +|---|------|-----|------|-------| +| D2 | Idler wheel | 2 | Ø80 mm, 80+ mm wide, M8 bore, flanged | Off-shelf: e.g. polyurethane track idler | +| D3 | M8×100 SHCS | 2 | Stainless | Idler axle through block + wheel + outer bearing | +| D4 | M8 nyloc nut | 2 | — | Axle retention | +| D5 | M8 flat washer | 4 | — | Both sides of idler wheel | +| D6 | M6×40 SHCS | 2 | — | Tensioner adjustment bolt (threads into idler block lug) | +| D7 | M6 nyloc nut | 2 | — | Captured in block; lock bolt position | + +**Track tensioning procedure:** +1. Loosen idler axle nut (M8) — block free to slide in slot. +2. Turn tensioner M6 bolt CW to push block rearward → tighten track. +3. Target: ~10 mm slack on upper track run (finger-press deflects 10 mm). +4. Tighten M8 axle nut to 12 N·m. Check tension after first 5-minute run. + +--- + +## Part E — Road Wheels (2 per side, 4 total) + +| # | Part | Qty | Spec | Notes | +|---|------|-----|------|-------| +| E1 | Road wheel | 4 | Ø60 mm × 80+ mm wide, M8 bore | Polyurethane or HDPE; must fit inside track width | +| E2 | M8×80 SHCS | 4 | Stainless | Road wheel axle through side frame | +| E3 | M8 nyloc | 4 | — | — | +| E4 | M8 flat washer | 8 | — | — | + +--- + +## Part F — Track Belts + +| # | Part | Qty | Spec | Notes | +|---|------|-----|------|-------| +| F1 | Rubber track belt | 2 | 80 mm wide, 20 mm pitch, ~1040 mm circumference | Circumference: 2 × (sprocket–idler CL) + π × (sprocket_D + idler_D)/2 ≈ 2 × 440 + π × 72 ≈ 1106 mm; order next standard size up | +| F1-alt | Metal link track | 2 | 80 mm wide, 20 mm pitch, adjustable length | Higher durability; heavier (~0.8 kg/belt); louder | + +> **Track circumference calculation:** +> `C = 2 × span + π × (D_sprocket + D_idler) / 2` +> ` = 2 × 440 + π × (66 + 80) / 2` +> ` = 880 + π × 73` +> ` = 880 + 229 ≈ 1109 mm` +> Order **1120 mm** (next standard rubber track pitch × N links). + +--- + +## Part G — Skid Plate + +| # | RENDER | Qty | Material | Process | Notes | +|---|--------|-----|----------|---------|-------| +| G1 | `skid_2d` | 1 | 4 mm HDPE (white) | Waterjet / CNC | Preferred — lightweight, low friction | +| G1-alt | `skid_2d` | 1 | 2 mm 304 stainless | Laser-cut | Heavy but extreme durability | +| G1-proto | `skid_stl` | 1 | PETG | Print | Prototype — 2 halves if bed <500 mm | + +| # | Spec | Qty | Use | +|---|------|-----|-----| +| G-f1 | M4×12 FHCS | 6 | Skid plate to deck (countersunk flush) | +| G-f2 | M4 rivet-nut (short) | 6 | Pre-installed in deck plate | + +--- + +## Part H — Sensor Brackets + +| # | RENDER | Qty | Material | Notes | +|---|--------|-----|----------|-------| +| H1 | `csi_mount_stl` | 4 | PETG | CSI corner brackets (45° outward, 20° down) | +| H2 | `d435i_mount_stl` | 1 | PETG | D435i front arm (8° nose-down) | + +| # | Spec | Qty | Use | +|---|------|-----|-----| +| H-f1 | M2×6 SHCS | 8 | CSI camera PCB to bracket | +| H-f2 | M3×8 SHCS | 8 | CSI bracket to deck | +| H-f3 | M4×14 SHCS | 2 | D435i bracket to deck front face | +| H-f4 | 1/4-20 UNC hex nut | 1 | Captured in D435i bracket face | +| H-c1 | 200 mm CSI FPC cable | 4 | IMX219 to Jetson | + +--- + +## Part I — Electronics Bay (reuse from rover) + +The `rover_electronics_bay.scad` bay fits the SaltyTank deck directly — the +deck bolt pattern is identical. Print or reuse existing bay + lid. + +| # | Part | Qty | Notes | +|---|------|-----|-------| +| I1 | Electronics bay body | 1 | `rover_electronics_bay.scad` → `bay_stl` | +| I2 | Electronics bay lid | 1 | Includes RPLIDAR tower | +| I3 | M3×12 SHCS | 10 | Bay to deck | +| I4 | M3×8 BHCS | 4 | Lid to bay | + +--- + +## Part J — Stem + Sensor Head (shared, unchanged) + +Same stem adapter, RPLIDAR, D435i, and IMX219 sensor head as SaltyLab. +Recommended stem length for SaltyTank: **500 mm** (lower than SaltyBot mast for +stability; RPLIDAR at ~635 mm from ground with 98 mm deck height). + +--- + +## Mass Estimate — Frame Only + +| Assembly | Material | Est. mass | +|----------|----------|-----------| +| Deck plate (8 mm Al, ~45% lightened) | Al | ~1.55 kg | +| Side frames ×2 (6 mm Al, ~35% lightened) | Al | ~0.52 kg | +| Skid plate (4 mm HDPE, solid) | HDPE | ~0.56 kg | +| Idler tensioner blocks ×2 | PETG | ~0.06 kg | +| CSI brackets ×4 + D435i bracket | PETG | ~0.07 kg | +| Stem collar | PETG | ~0.04 kg | +| Fasteners (M4–M8 SS) | Steel | ~0.18 kg | +| **Frame total** | | **~2.98 kg** ✓ | + +> **Just under the 3 kg target.** +> To save weight: switch side frames from 6 mm to 5 mm Al → saves ~0.09 kg. +> Set `FRAME_T = 5.0` in `saltytank_chassis.scad` and re-export DXF. + +--- + +## Assembly Sequence + +### 1. Fabricate / source parts +1. Export DXFs; send deck and side frames to waterjet. Order HDPE skid to DXF. +2. Print idler blocks, CSI/D435i brackets, stem collar. +3. Source track belts, idler wheels, road wheels, hub motors. +4. Install M4 rivet-nuts in deck plate (6× for skid; stem flange positions). + +### 2. Side frame preparation +1. Test-fit hub motor axle D-cut bore in side frame — adjust `SPROCKET_AXLE_D` + and `SPROCKET_AXLE_FLAT` if caliper measurement differs. +2. Press or Loctite M8 flange nuts into road wheel axle positions (optional). + +### 3. Frame assembly +1. Slide side frames into deck side edge slots (inner face flush with deck edge). +2. Insert M5×20 SHCS from above through deck slots; fit nyloc nuts under deck. +3. **Snug only** — leave adjustable for wheel alignment (step 6). + +### 4. Drive motor installation +1. Feed hub motor axle through rear bore in side frame (D-cut aligned to flat). +2. Bolt sprocket plate to motor hub: 4× M5×16 SHCS, 2.5 N·m, Loctite 243. +3. Fit axle lock nut; Loctite 243; torque 35 N·m. +4. Route phase + hall cables through deck cable slot. + +### 5. Road wheel installation +1. Slide M8×80 bolt through side frame road wheel bore. +2. Fit road wheel on axle (M8 flat washers both sides). +3. Thread M8 nyloc; torque to 12 N·m. +4. Verify wheel rotates freely. + +### 6. Track belt installation (one side at a time) +1. Install idler block in tensioner slot (tensioner bolt only finger-tight). +2. Thread track belt around sprocket → road wheels → idler. + *(Easier with motor temporarily removed — reinstall after threading.)* +3. Fit idler axle (M8×100) through idler block + idler wheel; nyloc finger-tight. +4. Apply tension: tighten M6 tensioner bolt until ~10 mm slack on upper run. +5. Torque idler axle nut to 12 N·m. + +### 7. Geometry verification +1. Set robot on flat surface; check all 4 road wheels contact ground through track. +2. Measure track tension on both sides — should match within 2 mm. +3. Spin motors briefly — verify tracks run straight without walking. +4. Torque side-frame-to-deck M5 bolts to 4 N·m once alignment confirmed. + +### 8. Skid plate +1. Slide skid plate under deck; align M4 countersunk holes with rivet-nuts. +2. Fasten 6× M4×12 FHCS from below; tighten evenly to 2.5 N·m. + +### 9. Electronics, sensors, stem +1. Mount electronics bay on deck (10× M3×12); route cables. +2. Install CSI and D435i brackets on deck corners/front. +3. Press stem through collar; install stem adapter + clamp. +4. Mount sensor head + RPLIDAR on stem top. + +--- + +## Critical Dimensions + +| Dimension | Nominal | Tolerance | +|-----------|---------|-----------| +| Deck length × width | 500 × 360 mm | ±1 mm | +| Frame height | 90 mm | ±0.5 mm | +| Frame length | 500 mm | ±0.5 mm | +| Drive axle bore OD | 16.11 mm | +0.4/0 (round section) | +| Drive axle flat chord | 13.00 mm | +0.4/0 | +| Bearing seat recess OD | 37.80 mm | +1.5/0 | +| Hub motor flange BC | 52 mm | **⚠ verify caliper** | +| Tensioner slot height | 10.5 mm | ±0.2 mm | +| Tensioner travel | ±15 mm | — | +| FC hole pattern | 30.5×30.5 mm | ±0.2 mm | +| Jetson hole pattern | 58×49 mm | ±0.2 mm | +| Stem bore | Ø25.5 mm | +0.3/0 | +| Track belt width | 80 mm | ±1 mm | +| Sprocket PCD | 64.7 mm | ±0.5 mm | +| Idler OD | 80 mm | ±1 mm | +| Road wheel OD | 60 mm | ±1 mm | + +--- + +## OpenSCAD Version + +Requires OpenSCAD **2021.01 or newer**. + +```bash +# Full assembly preview: +openscad saltytank_chassis.scad & +``` diff --git a/chassis/saltytank_chassis.scad b/chassis/saltytank_chassis.scad new file mode 100644 index 0000000..fdaf202 --- /dev/null +++ b/chassis/saltytank_chassis.scad @@ -0,0 +1,672 @@ +// ============================================================ +// saltytank_chassis.scad — SaltyTank Tracked Chassis +// Issue: #121 Agent: sl-mechanical Date: 2026-03-01 +// ============================================================ +// +// Parametric tank chassis for rubber or metal continuous tracks. +// +// Structure (left-to-right cross-section): +// [left track belt] +// ← sprocket + idler + road wheels on outer face of side frame +// [left side frame plate, 8 mm Al] +// [deck plate, 8 mm Al] ← electronics bay on top +// [right side frame plate, 8 mm Al] +// → sprocket + idler + road wheels +// [right track belt] +// +// Drive: hoverboard hub motors (rear) — caliper-verified axle +// (16.11 mm OD, D-cut flat 13.00 mm, bearing seat Ø37.8 mm) +// Idler: 80 mm OD, M8 axle; tensioner slot ±15 mm fore-aft +// Road wh: 60 mm OD × 2 per side, M8 axle, fixed +// Tracks: rubber belt, 80 mm wide, 20 mm pitch (parametric) +// +// Electronics bay: reuses rover_electronics_bay.scad (same deck +// footprint, FC 30.5 × 30.5 mm M3 + Jetson 58 × 49 mm M3) +// Sensors: RPLIDAR A1M8 top (bay lid tower), D435i front, +// 4 × IMX219 / CSI at deck corners +// Stem: Ø25 mm (shared with SaltyLab / SaltyRover) +// +// Coordinate convention: +// Z = 0 deck top face +// +Y forward +// +X right +// Ground Z = -(DECK_T + FRAME_H) [= −98 mm with defaults] +// +// Ground clearance of hull (between tracks): FRAME_H = 90 mm ✓ +// (exceeds 50 mm requirement with significant margin) +// +// Weight estimate — frame only (excl. motors, electronics, battery): +// Deck plate (8 mm Al, lightened) ≈ 1.55 kg +// Side frames 2 × (6 mm Al) ≈ 0.52 kg +// Skid plate (saltytank_skid_plate.scad, 4 mm HDPE) ≈ 0.56 kg +// Brackets + fasteners (PETG + SS) ≈ 0.35 kg +// Total ≈ 2.98 kg ← just under 3 kg target +// +// RENDER options: +// "assembly" full 3D preview (default) +// "deck_2d" DXF — deck plate (waterjet / CNC) +// "side_frame_2d" DXF — side frame plate (×2 mirrored; CNC) +// "side_frame_stl" STL — side frame (print 2×, mirror right) +// "idler_block_stl" STL — tensioner idler block (print 2×) +// "csi_mount_stl" STL — CSI corner bracket (print 4×) +// "d435i_mount_stl" STL — D435i front bracket (print 1×) +// +// ── Export commands ───────────────────────────────────────── +// Deck DXF: +// openscad saltytank_chassis.scad -D 'RENDER="deck_2d"' -o saltytank_deck.dxf +// Side frame DXF (cut 2×, flip one for mirror): +// openscad saltytank_chassis.scad -D 'RENDER="side_frame_2d"' -o saltytank_side_frame.dxf +// Side frame STL (print 2×; mirror one in slicer): +// openscad saltytank_chassis.scad -D 'RENDER="side_frame_stl"' -o saltytank_side_frame.stl +// Idler block STL (×2): +// openscad saltytank_chassis.scad -D 'RENDER="idler_block_stl"' -o saltytank_idler_block.stl +// CSI bracket STL (×4): +// openscad saltytank_chassis.scad -D 'RENDER="csi_mount_stl"' -o saltytank_csi_mount.stl +// D435i bracket STL (×1): +// openscad saltytank_chassis.scad -D 'RENDER="d435i_mount_stl"' -o saltytank_d435i_mount.stl +// ============================================================ + +$fn = 64; +e = 0.01; + +// ── Deck plate ──────────────────────────────────────────────────────────────── +BODY_L = 500.0; // deck fore-aft (Y) +BODY_W = 360.0; // deck left-right (X) — space between inner frame faces +DECK_T = 8.0; // deck plate thickness +DECK_R = 15.0; // corner fillet radius + +// ── Side frame geometry ─────────────────────────────────────────────────────── +FRAME_H = 90.0; // frame height below deck bottom = hull ground clearance +FRAME_T = 6.0; // frame plate thickness (6 mm Al laser-cut) +FRAME_R = 10.0; // frame corner fillet radius + +// ── Track system (rubber or metal belt) ─────────────────────────────────────── +TRACK_WID = 80.0; // track belt width +TRACK_PITCH = 20.0; // track link pitch (mm) — affects sprocket tooth count +// Track belt inner face sits at X = ±(BODY_W/2 + FRAME_T) from centre +// Track CL at X = ±(BODY_W/2 + FRAME_T + TRACK_WID/2) from centre + +// ── Drive sprocket (rear) — hoverboard hub motor ────────────────────────────── +// Caliper-verified axle (matches BOM.md): +SPROCKET_AXLE_D = 16.11; // axle base OD (round section near hub) +SPROCKET_AXLE_FLAT= 13.00; // D-cut chord width +SPROCKET_AXLE_DCUT= 15.95; // D-cut OD +BEARING_OD = 37.80; // motor bearing-seat collar OD +BEARING_RECESS = 8.0; // bearing seat recess depth in frame + +// Sprocket pitch circle: 10 teeth × 20 mm pitch +// PCD = pitch / sin(π/N) = 20 / sin(18°) ≈ 64.7 mm → R ≈ 32.4 mm +SPROCKET_R = 33.0; // sprocket pitch-circle radius (nominal) +SPROCKET_POS_Y = -(BODY_L/2 - SPROCKET_R - 22); // rear, −Y + +// Hub motor flange bolt circle (4× M5 at 90°, BC = 52 mm) +// ⚠ Verify against your motor flange before fabricating! +HUB_FLANGE_BC = 52.0; // motor hub bolt circle OD (M5 × 4) +HUB_BOLT_D = 5.3; // M5 clearance + +// ── Idler wheel (front, adjustable tensioner) ───────────────────────────────── +IDLER_R = 40.0; // idler wheel radius (OD = 80 mm) +IDLER_AXLE_D = 8.5; // M8 axle clearance bore +IDLER_POS_Y_NOM = +(BODY_L/2 - IDLER_R - 22); // front nominal, +Y +// Tensioner slot: idler can move ±TENS_TRAVEL fore-aft to tension track +TENS_TRAVEL = 15.0; // ±15 mm track tension adjustment +// Tensioner bolt: M6 runs fore-aft in threaded lug at front of slot +TENS_BOLT_D = 6.5; // M6 clearance +TENS_BLOCK_L = 35.0; // sliding idler block length +TENS_BLOCK_W = TRACK_WID - 4; // block width (fits inside track) +TENS_SLOT_H = IDLER_AXLE_D + 2.0; // slot height (clearance for block) + +// ── Road wheels (2× per side, between sprocket and idler) ──────────────────── +ROAD_WHEEL_R = 30.0; // road wheel radius (OD = 60 mm) +ROAD_AXLE_D = 8.5; // M8 axle clearance bore +ROAD_Y_1 = -BODY_L/4; // forward road wheel (−125 mm from centre) +ROAD_Y_2 = +BODY_L/4; // rearward road wheel (+125 mm from centre) +// ⚠ Reversed fore/aft: ROAD_Y_1 is near rear sprocket, ROAD_Y_2 near front idler + +// ── Height stack ───────────────────────────────────────────────────────────── +// Ground Z = 0 (absolute); deck coords: Z=0 at deck top. +// In deck coords, ground is at Z = -(DECK_T + FRAME_H) = -98 mm. +// SPROCKET_CL_Z = -(DECK_T + FRAME_H - SPROCKET_R) = -(98 - 33) = -65 mm +// IDLER_CL_Z = -(DECK_T + FRAME_H - IDLER_R) = -(98 - 40) = -58 mm +// ROAD_WHEEL_Z = -(DECK_T + FRAME_H - ROAD_WHEEL_R) = -(98 - 30) = -68 mm +GROUND_Z = -(DECK_T + FRAME_H); // = -98 mm in deck coords + +// ── Stem socket (deck centre, shared with SaltyLab / SaltyRover) ───────────── +STEM_BORE = 25.5; // 25 mm tube + 0.5 mm clearance +STEM_COLLAR_OD = 50.0; +STEM_COLLAR_H = 20.0; // boss height above deck top +STEM_FLANGE_BC = 40.0; // 4× M4 bolt circle + +// ── Electronics bay footprint (rover_electronics_bay.scad) ─────────────────── +// Bay dimensions match rover_electronics_bay.scad exactly. +// Deck holes match bay floor bolt pattern for drop-in compatibility. +BAY_L = 240.0; // bay length (X on deck = left-right) +BAY_W = 200.0; // bay width (Y on deck = fore-aft) +BAY_WALL = 3.0; // bay wall thickness +BAY_BOLT_INSET = 8.0; // bay bolt CL from bay exterior corner + +// ── FC mount — 30.5 × 30.5 mm M3 (shared SaltyLab pattern) ────────────────── +FC_PITCH = 30.5; +FC_HOLE_D = 3.2; +FC_POS_Y = BAY_W/2 - 50.0; // near front edge (inside bay footprint) + +// ── Jetson Orin mount — 58 × 49 mm M3 (shared SaltyLab pattern) ───────────── +ORIN_HOLE_X = 58.0; +ORIN_HOLE_Y = 49.0; +ORIN_HOLE_D = 3.2; +ORIN_POS_Y = -(BAY_W/2 - 55.0); // near rear edge + +// ── CSI corner camera mounts ────────────────────────────────────────────────── +CSI_PCB = 25.0; // IMX219 PCB square side +CSI_M2_SPC = 15.0; // M2 hole pitch +CSI_TILT = 20.0; // nose-down tilt (degrees) + +// ── D435i front bracket ─────────────────────────────────────────────────────── +RS_TILT = 8.0; // nose-down tilt (degrees) +RS_ARM_LEN = 70.0; // arm reach forward from deck edge +RS_BASE_W = 44.0; // base plate width + +// ── Fasteners ───────────────────────────────────────────────────────────────── +M2_D = 2.3; +M3_D = 3.3; +M4_D = 4.3; +M5_D = 5.3; +M6_D = 6.5; +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 == "side_frame_2d") { + // Left frame, projected flat (2D profile only — Z/Y plane) + projection(cut = true) + rotate([90, 0, 0]) + translate([0, 0, BODY_L / 2]) + side_frame(-1); +} else if (RENDER == "side_frame_stl") { + side_frame(-1); // mirror one in slicer for right side +} else if (RENDER == "idler_block_stl") { + idler_block(); +} else if (RENDER == "csi_mount_stl") { + csi_corner_bracket(); +} else if (RENDER == "d435i_mount_stl") { + d435i_front_bracket(); +} + +// ============================================================ +// FULL ASSEMBLY +// ============================================================ +module assembly() { + // Deck plate + color("Silver", 0.90) deck_plate(); + + // Stem collar + color("DimGray", 0.85) stem_collar(); + + // Side frames (left = −1, right = +1) + color("SteelBlue", 0.80) side_frame(-1); + color("SteelBlue", 0.80) side_frame(+1); + + // Idler tensioner blocks (×2) + color("LightSlateGray", 0.85) + translate([-BODY_W/2 - FRAME_T, IDLER_POS_Y_NOM, + GROUND_Z + IDLER_R]) + rotate([0, 90, 0]) idler_block(); + color("LightSlateGray", 0.85) + translate([+BODY_W/2, IDLER_POS_Y_NOM, + GROUND_Z + IDLER_R]) + rotate([0, -90, 0]) idler_block(); + + // 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(); + + // Phantom: electronics bay (rover_electronics_bay.scad) + %color("OliveDrab", 0.25) + translate([0, 0, DECK_T]) + cube([BAY_L + 2*BAY_WALL, + BAY_W + 2*BAY_WALL, + 84], center = true); + + // Phantom: track loops (rubber belt cross-section) + for (sx = [-1, 1]) + %color("Black", 0.15) + translate([sx * (BODY_W/2 + FRAME_T + TRACK_WID/2), 0, 0]) + rotate([90, 0, 0]) + track_loop_ghost(); + + // Phantom: hub motors (rear, outboard) + for (sx = [-1, 1]) + %color("Orange", 0.20) + translate([sx * (BODY_W/2 + FRAME_T + 30), + SPROCKET_POS_Y, + GROUND_Z + SPROCKET_R]) + rotate([0, sx*90, 0]) + cylinder(d = BEARING_OD, h = 70, center = false); +} + +// Ghost track loop outline for preview (no geometry output) +module track_loop_ghost() { + span = abs(IDLER_POS_Y_NOM - SPROCKET_POS_Y); + hull() { + translate([0, IDLER_POS_Y_NOM, GROUND_Z + IDLER_R]) + rotate([90, 0, 0]) cylinder(d = IDLER_R*2, h = 1); + translate([0, SPROCKET_POS_Y, GROUND_Z + SPROCKET_R]) + rotate([90, 0, 0]) cylinder(d = SPROCKET_R*2, h = 1); + } +} + +// ============================================================ +// DECK PLATE (Part A — laser-cut 8 mm 5052-H32 aluminium) +// ============================================================ +// Rectangular plate spanning the gap between the two side frames. +// All electronics and sensor mounts attach to the deck top face. +// Side frames bolt to deck side edges (M5 × 4 per side). +// Weight estimate: 500×360×8 mm Al, ~45% lightened ≈ 1.55 kg +module deck_plate() { + difference() { + // ── Outer profile ───────────────────────────────────────────── + linear_extrude(DECK_T) + minkowski() { + square([BODY_L - 2*DECK_R, BODY_W - 2*DECK_R], + center = true); + circle(r = DECK_R); + } + + // ── Side frame attachment slots (M5 × 4 per side) ───────────── + // Slots run fore-aft (Y) for ±10 mm lateral alignment adjustment + for (sx = [-1, 1]) + for (py = [-BODY_L/4, BODY_L/4]) { + hull() { + translate([sx*(BODY_W/2 - 8), py - 12, -e]) + cylinder(d = M5_D, h = DECK_T + 2*e); + translate([sx*(BODY_W/2 - 8), py + 12, -e]) + cylinder(d = M5_D, h = DECK_T + 2*e); + } + } + + // ── Stem bore ───────────────────────────────────────────────── + translate([0, 0, -e]) + cylinder(d = STEM_BORE, h = DECK_T + 2*e); + + // ── Stem flange bolts (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); + + // ── Electronics bay footprint bolt holes (10× M3) ───────────── + // Matches rover_electronics_bay.scad floor flange pattern exactly + for (sx = [-1, 1]) + for (sy = [-1, 1]) { + bx = sx * (BAY_L/2 + BAY_WALL - BAY_BOLT_INSET); + by = sy * (BAY_W/2 + BAY_WALL - BAY_BOLT_INSET); + translate([bx, by, -e]) + cylinder(d = M3_D, h = DECK_T + 2*e); + } + // Centre long-wall bolts (2×) + for (sy = [-1, 1]) + translate([0, sy*(BAY_W/2 + BAY_WALL - BAY_BOLT_INSET), -e]) + cylinder(d = M3_D, h = DECK_T + 2*e); + + // ── FC mount holes — 30.5×30.5 M3 (shared SaltyLab) ────────── + 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 (shared SaltyLab) ───── + 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); + + // ── Lightening holes (between bay footprint and deck edges) ─── + for (sx = [-1, 1]) + for (sy = [-1, 1]) { + lx = sx * (BAY_L/2 + 40); + ly = sy * (BODY_L/4 + 10); + translate([lx, ly, -e]) + cylinder(d = 50, h = DECK_T + 2*e); + } + // Centre pair flanking stem + for (sx = [-1, 1]) + translate([sx * 65, 0, -e]) + cylinder(d = 38, h = DECK_T + 2*e); + + // ── Cable routing slots (motor phase + sensor harness) ───────── + // 4× slots near side frame attachment points + for (sx = [-1, 1]) + for (sy = [-1, 1]) + hull() { + translate([sx*(BODY_W/2 - 30), sy*(BODY_L/4 - 8), -e]) + cylinder(d = 14, h = DECK_T + 2*e); + translate([sx*(BODY_W/2 - 30), sy*(BODY_L/4 + 8), -e]) + cylinder(d = 14, h = DECK_T + 2*e); + } + + // ── Skid plate attachment holes (M4 × 8, through deck underside) + // These align with saltytank_skid_plate.scad bolt pattern + for (sx = [-1, 1]) + for (sy = [-1, 0, 1]) { + bx = sx * (BODY_W/2 - 20); + by = sy * (BODY_L/3); + translate([bx, by, -e]) + cylinder(d = M4_D, h = DECK_T + 2*e); + } + + // ── CSI corner bracket attachment holes (M3 × 2 per corner) ─── + for (sx = [-1, 1]) + for (sy = [-1, 1]) + for (dd = [-12, 12]) + translate([sx*(BODY_W/2 - 18), + sy*(BODY_L/2 - 18) + dd*sy, -e]) + cylinder(d = M3_D, h = DECK_T + 2*e); + } +} + +// ── Deck-top stem collar ───────────────────────────────────────────────────── +module stem_collar() { + translate([0, 0, DECK_T]) + difference() { + cylinder(d = STEM_COLLAR_OD, h = STEM_COLLAR_H); + translate([0, 0, -e]) + cylinder(d = STEM_BORE, h = STEM_COLLAR_H + 2*e); + 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); + } +} + +// ============================================================ +// SIDE FRAME (Part B — laser-cut 6 mm 6061-T6 aluminium) +// ============================================================ +// Vertical plate running the full body length. +// Inner face bolts to deck plate side edge. +// Outer face mounts: drive sprocket (rear) + idler slot (front) +// + road wheels (middle). +// +// `side` = -1 (left / −X) or +1 (right / +X) +// The right frame is a mirror of the left — cut 2× from same DXF, +// flip one face-down before mounting. +// +// Weight estimate: 500×90×6 mm Al, ~35% lightened ≈ 0.26 kg each +// ============================================================ +module side_frame(side = -1) { + sx = side; // −1 = left, +1 = right + // Frame inner face at X = sx * BODY_W/2 + // Frame outer face at X = sx * (BODY_W/2 + FRAME_T) + frame_x = sx * BODY_W/2; // inner face X position + + // In deck coords: sprocket / idler CL Z values + spr_z = GROUND_Z + SPROCKET_R; // = -(DECK_T + FRAME_H) + SPROCKET_R + idl_z = GROUND_Z + IDLER_R; + rw_z = GROUND_Z + ROAD_WHEEL_R; + + translate([frame_x, 0, 0]) + rotate([0, sx > 0 ? 180 : 0, 0]) // mirror right frame in X + translate([0, 0, 0]) { + difference() { + // ── Outer profile of side frame ─────────────────────────── + // Frame plate in the Y-Z plane, FRAME_T thick in X + translate([0, -BODY_L/2, GROUND_Z]) + linear_extrude(FRAME_T) + minkowski() { + square([BODY_L - 2*FRAME_R, + FRAME_H + DECK_T - 2*FRAME_R], + center = false); + circle(r = FRAME_R); + } + + // ── Deck attachment slots (2× M5 per side, front + rear) ── + // Slots allow ±10 mm vertical adjustment for frame height + for (py = [-BODY_L/4, BODY_L/4]) + hull() { + translate([e, py - 12, -DECK_T / 2]) + rotate([0, 90, 0]) + cylinder(d = M5_D, h = FRAME_T + 2*e); + translate([e, py + 12, -DECK_T / 2]) + rotate([0, 90, 0]) + cylinder(d = M5_D, h = FRAME_T + 2*e); + } + + // ── Drive sprocket bore — D-cut (rear) ──────────────────── + // Round section (base, near hub) + translate([e, SPROCKET_POS_Y, spr_z]) + rotate([0, 90, 0]) + cylinder(d = SPROCKET_AXLE_D + 0.4, h = FRAME_T + 2*e); + + // D-cut flat (anti-rotation) — removes chord to AXLE_FLAT + dcut_h = sqrt(pow((SPROCKET_AXLE_DCUT + 0.4)/2, 2) + - pow((SPROCKET_AXLE_FLAT + 0.4)/2, 2)); + translate([e, SPROCKET_POS_Y, spr_z]) + rotate([0, 90, 0]) + translate([0, (SPROCKET_AXLE_FLAT + 0.4)/2, 0]) + cube([(SPROCKET_AXLE_DCUT + 0.4)/2, + FRAME_T + 2*e, + FRAME_T + 2*e], + center = false); + + // ── Bearing seat recess (outboard face — inboard of track) ─ + // Prevents Ø37.8 mm collar binding on frame face + translate([e - BEARING_RECESS, SPROCKET_POS_Y, spr_z]) + rotate([0, 90, 0]) + cylinder(d = BEARING_OD + 1.5, + h = BEARING_RECESS + e); + + // ── Hub motor flange bolt holes (4× M5, 52 mm BC) ───────── + for (a = [45, 135, 225, 315]) + translate([e, + SPROCKET_POS_Y + HUB_FLANGE_BC/2 * sin(a), + spr_z + HUB_FLANGE_BC/2 * cos(a)]) + rotate([0, 90, 0]) + cylinder(d = HUB_BOLT_D, h = FRAME_T + 2*e); + + // ── Idler tensioner slot (front) ────────────────────────── + // Horizontal slot in Y direction; idler block slides in it + slot_y_ctr = IDLER_POS_Y_NOM; + slot_y_min = slot_y_ctr - TENS_TRAVEL; + slot_y_max = slot_y_ctr + TENS_TRAVEL; + translate([e, slot_y_min, idl_z - TENS_SLOT_H/2]) + cube([FRAME_T + 2*e, + slot_y_max - slot_y_min, + TENS_SLOT_H]); + // Rounded ends + translate([e, slot_y_min, idl_z]) + rotate([0, 90, 0]) + cylinder(d = TENS_SLOT_H, h = FRAME_T + 2*e); + translate([e, slot_y_max, idl_z]) + rotate([0, 90, 0]) + cylinder(d = TENS_SLOT_H, h = FRAME_T + 2*e); + + // ── Tensioner bolt bore (M6, at front end of slot) ──────── + // M6 bolt threads into lug at front of slot; pushes block rearward + translate([e, + slot_y_max + 8, + idl_z]) + rotate([0, 90, 0]) + cylinder(d = M6_D, h = FRAME_T + 2*e); + + // ── Road wheel bores (2× M8, fore/aft of centre) ────────── + for (ry = [ROAD_Y_1, ROAD_Y_2]) + translate([e, ry, rw_z]) + rotate([0, 90, 0]) + cylinder(d = ROAD_AXLE_D, h = FRAME_T + 2*e); + + // ── Lightening holes (between bore positions) ────────────── + // Row 1: between road wheel 1 and sprocket + translate([e, + (SPROCKET_POS_Y + ROAD_Y_1) / 2, + GROUND_Z + FRAME_H/2]) + rotate([0, 90, 0]) + cylinder(d = 45, h = FRAME_T + 2*e); + + // Row 2: between road wheel 2 and idler + translate([e, + (IDLER_POS_Y_NOM + ROAD_Y_2) / 2, + GROUND_Z + FRAME_H/2]) + rotate([0, 90, 0]) + cylinder(d = 45, h = FRAME_T + 2*e); + + // Row 3: between the two road wheels + translate([e, (ROAD_Y_1 + ROAD_Y_2) / 2, GROUND_Z + FRAME_H/2]) + rotate([0, 90, 0]) + cylinder(d = 35, h = FRAME_T + 2*e); + } + } +} + +// ============================================================ +// IDLER TENSIONER BLOCK (Part C — 3D print PETG, × 2) +// ============================================================ +// Slides in the frame's tensioner slot. +// M8 bore holds the idler axle. +// A flat face at the front receives the tensioner M6 bolt. +// Lock nut (M6 nyloc) clamps block at desired track tension. +// Print orientation: flat face on bed; no supports needed. +// ============================================================ +module idler_block() { + block_h = TENS_SLOT_H - 0.6; // height with slot clearance + block_l = TENS_BLOCK_L; + block_w = FRAME_T - 0.4; // thickness with clearance + + difference() { + union() { + // Main block body + cube([block_w, block_l, block_h], center = true); + + // Tensioner bolt lug (extends from +Y face) + translate([0, block_l/2, 0]) + cube([block_w, 10, block_h], center = true); + } + + // M8 axle bore through full width + translate([0, 0, 0]) + rotate([0, 90, 0]) + cylinder(d = IDLER_AXLE_D, h = block_w + 2*e, center = true); + + // M6 tensioner bolt bore (fore-aft, through lug) + translate([0, block_l/2 + 5, 0]) + rotate([90, 0, 0]) + cylinder(d = M6_D, h = 16, center = true); + + // M6 nyloc nut pocket (rear face of lug — captured nut) + translate([0, block_l/2 - 1, 0]) + rotate([90, 0, 0]) + cylinder(d = 11.5, h = 5, $fn = 6, center = false); + + // ── Lightening slot (centre, removes material not in load path) + cube([block_w + 2*e, block_l/2 - IDLER_AXLE_D/2 - 4, block_h - 4], + center = true); + } +} + +// ============================================================ +// CSI CORNER BRACKET (Part D — 3D print PETG, × 4) +// ============================================================ +// Same design as saltyrover_chassis_r2.scad. +// Mounts IMX219 / Arducam CSI camera at each deck corner, +// angled 45° outward + CSI_TILT downward. +// ============================================================ +module csi_corner_bracket() { + base_l = 42; + base_w = 32; + base_t = 5; + + difference() { + union() { + cube([base_l, base_w, base_t]); + translate([base_l / 2, base_w / 2, base_t]) + rotate([0, CSI_TILT, 0]) + translate([-CSI_PCB/2 - 3, -CSI_PCB/2 - 3, 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 M2 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 slot + translate([base_l/2 - 6, base_w/2 - 1.5, -e]) + cube([12, 3, base_t + 2*e]); + } +} + +module csi_bracket_placed(sx, sy) { + cx = sx * (BODY_W/2 - 22); + cy = sy * (BODY_L/2 - 22); + rot = atan2(sy, sx) * 180 / 3.14159 - 45; + translate([cx, cy, DECK_T]) + rotate([0, 0, rot]) + translate([-21, -16, 0]) + csi_corner_bracket(); +} + +// ============================================================ +// D435i FRONT BRACKET (Part E — 3D print PETG, × 1) +// ============================================================ +// Same design as saltyrover_chassis_r2.scad. +// Arm extends forward from deck front edge. +// RS_TILT degrees nose-down. 1/4-20 captured nut for D435i. +// ============================================================ +module d435i_front_bracket() { + base_d = 24; + base_h = 8; + arm_len = RS_ARM_LEN; + + nut14_af = 11.1; + nut14_h = 5.6; + nut14_cl = 6.5; + + difference() { + union() { + // Base plate (bolts to deck front face) + translate([-RS_BASE_W/2, 0, 0]) + cube([RS_BASE_W, base_d, base_h]); + // Forward arm + translate([-13, base_d, 0]) + cube([26, arm_len, base_h]); + // Tilted face plate + translate([0, base_d + arm_len, base_h/2]) + rotate([0, RS_TILT, 0]) + translate([-16, 0, -base_h/2]) + cube([32, 14, base_h]); + } + // 2× M4 base attachment + 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 UNC captured nut + translate([0, base_d + arm_len + 12, base_h/2]) + rotate([0, 90, 0]) { + translate([0, 0, -nut14_h - 1]) + cylinder(d = nut14_af/cos(30), h = nut14_h + 1, $fn = 6); + cylinder(d = nut14_cl, h = 20); + } + } +} + +module d435i_bracket_placed() { + translate([0, BODY_L/2 + 12, DECK_T]) + rotate([0, 0, 180]) + d435i_front_bracket(); +} diff --git a/chassis/saltytank_skid_plate.scad b/chassis/saltytank_skid_plate.scad new file mode 100644 index 0000000..0da68ba --- /dev/null +++ b/chassis/saltytank_skid_plate.scad @@ -0,0 +1,214 @@ +// ============================================================ +// saltytank_skid_plate.scad — SaltyTank Underside Skid Plate +// Issue: #121 Agent: sl-mechanical Date: 2026-03-01 +// ============================================================ +// +// Bolt-on underside protection panel for rough terrain. +// Mounts to the underside of the deck plate, covering the hull +// floor between the two track frames. +// +// Purpose: +// • Protects electronics bay wiring + deck underside from rock strikes +// • Sacrificial layer — replace without reworking deck plate +// • Provides a smooth low-drag hull bottom for terrain sliding +// • Four drain / inspection slots (water / mud egress) +// +// Material options: +// Primary : 4 mm HDPE (high-density polyethylene) +// — lightweight, impact resistant, low friction surface +// Alternative: 2 mm 304 stainless steel +// — heavier (+0.5 kg), extremely wear resistant +// Prototype : 6 mm plywood (quick, cheap; not weatherproof) +// +// Dimensions: BODY_W × BODY_L × SKID_T +// = 360 × 500 × 4 mm (fits between track inner faces) +// +// Mounting: +// 8× M4 SHCS from below; nuts captured in deck plate M4 rivet-nuts +// (see saltytank_chassis.scad skid plate attachment holes) +// Countersunk (FHCS) variant available — set COUNTERSINK = true +// +// Coordinate: Z=0 at skid top face (= deck plate underside) +// Ground contact surface at Z = -(FRAME_H) = -90 mm (in deck coords) +// Skid sits immediately below deck: deck_bottom → skid_top +// +// RENDER options: +// "skid_stl" STL for 3D printing (4 mm PETG, prototype) +// "skid_2d" DXF for waterjet / CNC (HDPE or steel plate) +// "assembly" preview with deck ghost +// +// Export commands: +// DXF (HDPE / steel — recommended): +// openscad saltytank_skid_plate.scad -D 'RENDER="skid_2d"' -o saltytank_skid.dxf +// STL (3D print prototype): +// openscad saltytank_skid_plate.scad -D 'RENDER="skid_stl"' -o saltytank_skid.stl +// ============================================================ + +$fn = 64; +e = 0.01; + +// ── Skid plate dimensions ──────────────────────────────────────────────────── +// Must match saltytank_chassis.scad BODY_W and BODY_L exactly. +BODY_L = 500.0; // fore-aft — copy from saltytank_chassis.scad +BODY_W = 360.0; // left-right +SKID_T = 4.0; // skid plate thickness (4 mm HDPE) +SKID_R = 12.0; // corner radius (matches deck corners, ease of cleaning) + +FRAME_H = 90.0; // side frame height (from saltytank_chassis.scad) + +// ── Skid reinforcement ribs (under-surface, printed variant only) ───────────── +// Ribs add stiffness to the printed skid without adding flat-surface area. +// Ribs are omitted on the CNC/laser version (material stiffness is sufficient). +RIB_H = 8.0; // rib height below skid top face +RIB_T = 3.0; // rib wall thickness +RIB_PRINT = false; // set true to add ribs (print only; skip for DXF) + +// ── Attachment bolt holes (M4) ─────────────────────────────────────────────── +// Pattern must match saltytank_chassis.scad skid plate holes exactly. +// 2 rows × 3 columns = 6 holes + 2 corners = 8 total +BOLT_D = 4.3; // M4 clearance +BOLT_INSET_X = 20.0; // CL inset from left/right edge +BOLT_INSET_Y = 25.0; // CL inset from front/rear edge + +// Countersinking (flat-head bolts flush with skid surface — preferred for terrain) +COUNTERSINK = true; +CSINK_ANGLE = 82; // M4 FHCS countersink angle (82° for DIN 7991) +CSINK_OD = 8.0; // M4 FHCS head diameter + +// ── Drain / inspection slots ───────────────────────────────────────────────── +DRAIN_W = 20.0; // slot width +DRAIN_L = 60.0; // slot length +DRAIN_R = 10.0; // slot corner radius + +// ── Road-wheel axle pass-through slots ─────────────────────────────────────── +// The road wheel axles sit OUTSIDE the skid plate (they're in the side frames), +// so no axle holes are needed in the skid plate. +// The skid plate is a solid panel between the track frames. + +// ── Fasteners ───────────────────────────────────────────────────────────────── +M4_D = 4.3; + +// ============================================================ +// RENDER DISPATCH +// ============================================================ +RENDER = "assembly"; + +if (RENDER == "skid_stl") { + skid_plate(); +} else if (RENDER == "skid_2d") { + projection(cut = true) + translate([0, 0, -SKID_T / 2]) + skid_plate_flat_only(); +} else if (RENDER == "assembly") { + assembly(); +} + +// ============================================================ +// ASSEMBLY PREVIEW +// ============================================================ +module assembly() { + color("Sienna", 0.85) skid_plate(); + + // Ghost deck plate above + %color("Silver", 0.25) + translate([0, 0, SKID_T]) + linear_extrude(8) + minkowski() { + square([BODY_L - 24, BODY_W - 24], center = true); + circle(r = 12); + } + + // Ghost side frames (simplified) + for (sx = [-1, 1]) + %color("SteelBlue", 0.20) + translate([sx * (BODY_W/2 + 3), 0, SKID_T]) + cube([6, BODY_L, FRAME_H + 8], center = true); +} + +// ============================================================ +// SKID PLATE (3D print — includes optional ribs) +// ============================================================ +module skid_plate() { + difference() { + union() { + // ── Base plate ───────────────────────────────────────────── + linear_extrude(SKID_T) + skid_profile_2d(); + + // ── Reinforcement ribs (printed version only) ────────────── + if (RIB_PRINT) { + // Longitudinal centre rib + translate([0, 0, SKID_T]) + cube([RIB_T, BODY_L - 40, RIB_H], center = true); + // Lateral ribs (3× fore-aft) + for (ry = [-BODY_L/4, 0, BODY_L/4]) + translate([0, ry, SKID_T]) + cube([BODY_W - 40, RIB_T, RIB_H], center = true); + } + } + + // ── Attachment bolt holes ────────────────────────────────────── + for (bpos = bolt_positions()) + translate([bpos[0], bpos[1], -e]) { + cylinder(d = BOLT_D, h = SKID_T + 2*e); + if (COUNTERSINK) + // Countersink cone from top face + translate([0, 0, SKID_T + e]) + cylinder(d1 = CSINK_OD, d2 = BOLT_D, + h = (CSINK_OD - BOLT_D) / (2 * tan(CSINK_ANGLE/2))); + } + + // ── Drain / inspection slots (4×, one per quadrant) ─────────── + for (sx = [-1, 1]) + for (sy = [-1, 1]) { + dx = sx * (BODY_W/4 + 15); + dy = sy * (BODY_L/4 + 20); + translate([dx - DRAIN_W/2 + DRAIN_R, dy - DRAIN_L/2 + DRAIN_R, -e]) + linear_extrude(SKID_T + 2*e) + minkowski() { + square([DRAIN_W - 2*DRAIN_R, DRAIN_L - 2*DRAIN_R]); + circle(r = DRAIN_R); + } + } + } +} + +// 2D profile of skid plate (shared by skid_plate and DXF export) +module skid_profile_2d() { + minkowski() { + square([BODY_L - 2*SKID_R, BODY_W - 2*SKID_R], center = true); + circle(r = SKID_R); + } +} + +// Flat-only version for DXF projection (no ribs) +module skid_plate_flat_only() { + difference() { + linear_extrude(SKID_T) skid_profile_2d(); + for (bpos = bolt_positions()) + translate([bpos[0], bpos[1], -e]) + cylinder(d = BOLT_D, h = SKID_T + 2*e); + for (sx = [-1, 1]) + for (sy = [-1, 1]) { + dx = sx * (BODY_W/4 + 15); + dy = sy * (BODY_L/4 + 20); + translate([dx - DRAIN_W/2 + DRAIN_R, dy - DRAIN_L/2 + DRAIN_R, -e]) + linear_extrude(SKID_T + 2*e) + minkowski() { + square([DRAIN_W - 2*DRAIN_R, DRAIN_L - 2*DRAIN_R]); + circle(r = DRAIN_R); + } + } + } +} + +// ── Bolt position list (8 positions matching saltytank_chassis.scad) ────────── +function bolt_positions() = [ + // From saltytank_chassis.scad skid plate holes (sx × (BODY_W/2 - 20), sy × BODY_L/3) + [-BODY_W/2 + BOLT_INSET_X, -BODY_L/3], + [-BODY_W/2 + BOLT_INSET_X, 0], + [-BODY_W/2 + BOLT_INSET_X, +BODY_L/3], + [+BODY_W/2 - BOLT_INSET_X, -BODY_L/3], + [+BODY_W/2 - BOLT_INSET_X, 0], + [+BODY_W/2 - BOLT_INSET_X, +BODY_L/3] +];