Three new chassis design files for the SaltyTank continuous-track variant: • saltytank_chassis.scad — Deck plate (500×360×8mm Al, DXF export), 2× side track frames (6mm Al, CNC/laser), idler tensioner sliding block, 4× CSI corner camera mounts (45°/20°), D435i front bracket (8° tilt), stem collar (Ø25mm shared). Drive sprocket mounts accept hoverboard hub motors with caliper-verified D-cut bore (16.11mm/13mm flat) + 52mm BC hub flange bolt pattern. M6 tensioner bolt adjusts idler ±15mm for track tension. Shared FC 30.5×30.5mm + Jetson 58×49mm M3 patterns. Electronics bay footprint matches rover_electronics_bay.scad exactly. • saltytank_skid_plate.scad — Sacrificial underside skid panel (360×500mm). 4mm HDPE (DXF) or PETG print; countersunk M4 FHCS bolt-on. 4× drain/ inspection slots; optional printed ribs (RIB_PRINT=true). Ground clearance of hull between tracks: 90mm (exceeds 50mm requirement). • saltytank_BOM.md — Full BOM: deck plate, side frames, drive sprockets, idler wheels + tensioners, road wheels (2/side), track belts (1109mm circumference calc), skid plate, sensor brackets, electronics bay (rover_electronics_bay.scad reused unchanged). Frame mass ≈ 2.98 kg (just under 3 kg target). Assembly sequence and track tensioning procedure included. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
673 lines
30 KiB
OpenSCAD
673 lines
30 KiB
OpenSCAD
// ============================================================
|
||
// 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();
|
||
}
|