feat: SaltyTank tracked chassis — drive sprockets, tensioners, skid plate (#121) #131

Merged
sl-jetson merged 1 commits from sl-mechanical/issue-121-tank-chassis into main 2026-03-02 09:26:40 -05:00
3 changed files with 1269 additions and 0 deletions

383
chassis/saltytank_BOM.md Normal file
View File

@ -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 3540 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 × (sprocketidler 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 (M4M8 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 &
```

View File

@ -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();
}

View File

@ -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]
];