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