saltylab-firmware/chassis/prototype_baseplate.scad
sl-mechanical 914afbc1ca feat: vertical stem architecture — compact baseplate + battery carousel
ARCHITECTURE CHANGE: batteries no longer sit flat on the base plate.
They mount VERTICALLY on a central mast via a height-adjustable carousel.
CG is tuned by sliding the carousel up/down the stem.

Part A — prototype_baseplate.scad (Rev C):
- PLATE_DEPTH: 210mm → 130mm (no battery footprint constraint)
- Removed all battery tray geometry (holes, strap slots, expansion mounts)
- Added central stem socket: Ø38.6mm bore + 4x M5 flange bolt holes on Ø66mm BC
- Added stem_flange() module: laser-cut ring (qty 2, one each side of plate)
- Wiring pass-through slots flanking stem centre
- FC mount relocated to FC_X_OFFSET = -40mm (front of plate, clear of stem)
- New RENDER="stem_flange_2d" DXF export option

Part B — stem_battery_clamp.scad (new):
- Collar: two 3D-printed D-shaped halves, split at Y=0
  - Ø38.6mm bore (1.5" EMT / 6061-T6 tube)
  - 4x M6 clamping bolts + hex nut pockets
  - 1x M6 set screw per half for height/rotation lock
  - Arm attachment pads with M4 through-holes + nut pockets
- Arms: flat bars, laser-cut or printed, ARM_REACH=55mm
- Battery cradles: U-channel, open top, Velcro strap slots at 30% + 65% height
- BATT_COUNT param: 2 (180°), 3 (120°), or 4 (90°) radial batteries
- ARM_START_ANGLE chosen per BATT_COUNT to keep all arms clear of Y=0 split
- Battery ghosts in assembly for visualisation
- Full RENDER control: assembly / collar_half / arm / arm_2d / cradle
- Assembly sequence + CG tuning notes in file footer

BOM.md → Rev C:
- Part A table updated (5 laser-cut parts + stem tube)
- Part B table added (collar halves, arms, cradles, fasteners)
- Battery section: flat-deck layout replaced with vertical stem guide
- Fastener table updated to match new architecture

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 14:57:30 -05:00

349 lines
14 KiB
OpenSCAD
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// =============================================================================
// SaltyBot — Prototype Base Plate (Rev C — compact, stem-mount)
// Agent: sl-mechanical | 2026-02-28
//
// Laser-cut or CNC-routed flat plate (6 mm Al / 8 mm acrylic).
// Uses CALIPER-VERIFIED hub motor axle measurements (see PR #7 / #11).
//
// ARCHITECTURE CHANGE (Rev C):
// Batteries are NO LONGER on the base plate.
// They stand vertically on a central stem via stem_battery_clamp.scad.
// The base plate is now compact — only axle dropouts + stem socket + FC mount.
//
// ── AXLE PROFILE (stepped D-cut, caliper-verified) ───────────────────────────
// Zone │ Feature │ Ø / Width │ Length
// ───────┼──────────────────┼────────────────────┼──────────
// Base │ Round (near hub) │ Ø 16.11 mm │ 15.00 mm
// D-cut │ Round OD │ Ø 15.95 mm │ 43.35 mm
// │ Flat chord │ 13.00 mm │
// Tip │ Shoulder/end │ — │ 3.00 mm
// Total │ Hub face → tip │ — │ 65.50 mm
// Bearing seat collar: Ø 37.8 mm
// Tire: 10 × 2.125" pneumatic (Ø 254 mm, 35 PSI)
// =============================================================================
$fn = 128;
// =============================================================================
// AXLE PARAMETERS — caliper-verified
// =============================================================================
AXLE_BASE_DIA = 16.11;
AXLE_BASE_LEN = 15.00;
AXLE_DCUT_DIA = 15.95;
AXLE_DCUT_FLAT = 13.00;
AXLE_DCUT_LEN = 43.35;
AXLE_TIP_LEN = 3.00;
AXLE_TOTAL = 65.50;
BEARING_SEAT_OD = 37.80;
TIRE_OD = 254.0;
AXLE_CL_HEIGHT = TIRE_OD / 2; // 127 mm above ground
// =============================================================================
// PLATE PARAMETERS
// =============================================================================
WHEELBASE = 600.0; // mm axle C/L to axle C/L
// Plate depth now driven only by structural + FC needs (no battery footprint).
PLATE_DEPTH = 130.0; // mm front-to-rear
PLATE_OVERHANG = 40.0; // mm plate past axle C/L each side
PLATE_THICK = 6.0; // mm
// Fork slot
FORK_W = AXLE_BASE_DIA + 0.4; // 16.51 mm
FORK_DEPTH = 50.0;
// Dropout clamp (two-piece sandwich)
CLAMP_L = 80.0;
CLAMP_H = 60.0;
CLAMP_THICK = 8.0;
CLAMP_BOLT_D = 5.3; // M5
CLAMP_BOLT_DX = 22.0;
CLAMP_BOLT_DY = 22.0;
CLAMP_ALIGN_D = 4.1; // Ø4 pin
// D-cut bore clearance
DCUT_CL = 0.3;
// FC mount — MAMBA F722S 30.5 × 30.5 mm M3
FC_PITCH = 30.5;
FC_HOLE_D = 3.2;
// FC is offset toward front of plate (away from stem)
FC_X_OFFSET = -40.0; // mm from plate centre (negative = front/motor side)
// =============================================================================
// STEM SOCKET PARAMETERS
// =============================================================================
STEM_OD = 38.1; // mm 1.5" EMT conduit OD
STEM_BORE = STEM_OD + 0.5; // 38.6 mm with clearance
// Flange ring (laser-cut, bolts above + below plate to grip tube):
STEM_FLANGE_OD = 82.0; // mm flange outer diameter
STEM_FLANGE_BC = 66.0; // mm bolt circle diameter (4× M5 at 90°)
STEM_FLANGE_T = 6.0; // mm = PLATE_THICK (flush-mount)
// Stem position: at plate centre (X=0, Y=0)
// =============================================================================
// UTILITIES
// =============================================================================
M3 = 3.2; M4 = 4.3; M5 = 5.3;
PLATE_X_HALF = WHEELBASE/2 + PLATE_OVERHANG; // ± 340 mm
DCUT_R = (AXLE_DCUT_DIA + 2*DCUT_CL) / 2;
DCUT_FC = AXLE_DCUT_FLAT + 2*DCUT_CL;
// =============================================================================
// RENDER CONTROL
// =============================================================================
//
// "assembly" full 3-D preview
// "plate_2d" DXF — base plate
// "clamp_lower_2d" DXF — lower dropout clamp (× 2)
// "clamp_upper_2d" DXF — upper dropout clamp (× 2)
// "stem_flange_2d" DXF — stem flange ring (× 2, one above + one below plate)
RENDER = "assembly";
if (RENDER == "assembly") {
assembly();
} else if (RENDER == "plate_2d") {
projection(cut=true) translate([0,0,-PLATE_THICK/2]) base_plate();
} else if (RENDER == "clamp_lower_2d") {
projection(cut=true) translate([0,0,-CLAMP_THICK/2]) clamp_lower();
} else if (RENDER == "clamp_upper_2d") {
projection(cut=true) translate([0,0,-CLAMP_THICK/2]) clamp_upper();
} else if (RENDER == "stem_flange_2d") {
projection(cut=true) translate([0,0,-STEM_FLANGE_T/2]) stem_flange();
}
// =============================================================================
// ASSEMBLY
// =============================================================================
module assembly() {
color("Silver", 0.85) base_plate();
for (side = [-1, 1]) {
color("SteelBlue", 0.80)
translate([side * WHEELBASE/2, 0, PLATE_THICK])
clamp_lower();
color("CornflowerBlue", 0.80)
translate([side * WHEELBASE/2, 0, PLATE_THICK + CLAMP_THICK])
clamp_upper();
}
// Stem flange rings (above and below plate)
color("DimGray", 0.70)
translate([0, 0, -STEM_FLANGE_T])
stem_flange();
color("DimGray", 0.70)
translate([0, 0, PLATE_THICK])
stem_flange();
// Reference ghosts
%color("Orange", 0.25)
translate([0, 0, PLATE_THICK + STEM_FLANGE_T])
cylinder(d=STEM_OD, h=800); // vertical stem
%for (side = [-1, 1])
color("Tomato", 0.2)
translate([side * WHEELBASE/2, 0, 0])
rotate([0, side*90, 0])
axle_ghost();
}
// =============================================================================
// BASE PLATE (Part A — compact)
// =============================================================================
module base_plate() {
R = 12; // corner radius
difference() {
// ── Outer profile ──
linear_extrude(PLATE_THICK)
minkowski() {
square([2*(PLATE_X_HALF - R), PLATE_DEPTH - 2*R], center=true);
circle(r=R);
}
// ── Fork slots (open at ±X edges, semicircular tip) ──────────────
for (side = [-1, 1]) {
translate([side*(PLATE_X_HALF - FORK_DEPTH), -FORK_W/2, -1])
cube([FORK_DEPTH + 1, FORK_W, PLATE_THICK + 2]);
translate([side*(PLATE_X_HALF - FORK_DEPTH), 0, -1])
cylinder(d=FORK_W, h=PLATE_THICK + 2);
}
// ── Bearing seat relief (prevents Ø37.8 mm collar binding on edge) ─
for (side = [-1, 1])
translate([side*PLATE_X_HALF - side*(BEARING_SEAT_OD/2 + 1),
-BEARING_SEAT_OD/2, -1])
cube([BEARING_SEAT_OD/2 + 2, BEARING_SEAT_OD, PLATE_THICK + 2]);
// ── Dropout clamp bolt through-holes ─────────────────────────────
for (side = [-1, 1])
for (dx = [-CLAMP_BOLT_DX, CLAMP_BOLT_DX])
for (dy = [-CLAMP_BOLT_DY, CLAMP_BOLT_DY])
translate([side*WHEELBASE/2 + dx, dy, -1])
cylinder(d=CLAMP_BOLT_D, h=PLATE_THICK + 2);
// ── Alignment pin holes (Ø4) ──────────────────────────────────────
for (side = [-1, 1])
for (dy = [-CLAMP_BOLT_DY + 8, CLAMP_BOLT_DY - 8])
translate([side*WHEELBASE/2, dy, -1])
cylinder(d=CLAMP_ALIGN_D, h=PLATE_THICK + 2);
// ── Stem socket bore ──────────────────────────────────────────────
translate([0, 0, -1])
cylinder(d=STEM_BORE, h=PLATE_THICK + 2);
// ── Stem flange bolt holes (4× M5, 90° pattern on STEM_FLANGE_BC) ─
for (a = [0, 90, 180, 270])
rotate([0, 0, a])
translate([STEM_FLANGE_BC/2, 0, -1])
cylinder(d=M5, h=PLATE_THICK + 2);
// ── FC mount (MAMBA F722S 30.5 × 30.5 M3) ────────────────────────
for (x = [FC_X_OFFSET - FC_PITCH/2, FC_X_OFFSET + FC_PITCH/2])
for (y = [-FC_PITCH/2, FC_PITCH/2])
translate([x, y, -1])
cylinder(d=FC_HOLE_D, h=PLATE_THICK + 2);
// ── Wiring / cable pass-through slots (2×, flanking stem) ─────────
for (dy = [-30, 30])
hull() {
translate([15, dy, -1]) cylinder(d=14, h=PLATE_THICK + 2);
translate([-15, dy, -1]) cylinder(d=14, h=PLATE_THICK + 2);
}
// ── Lightening slots (between FC zone and dropout zones) ──────────
for (sx = [-1, 1]) {
// One slot each side of stem, in the structural corridor
lx = sx * (WHEELBASE/4);
hull() {
translate([lx, -(PLATE_DEPTH/2 - 22), -1]) cylinder(d=18, h=PLATE_THICK+2);
translate([lx, (PLATE_DEPTH/2 - 22), -1]) cylinder(d=18, h=PLATE_THICK+2);
}
}
}
}
// =============================================================================
// STEM FLANGE RING (laser-cut, qty 2 — one above, one below plate)
// =============================================================================
module stem_flange() {
difference() {
cylinder(d=STEM_FLANGE_OD, h=STEM_FLANGE_T);
// Stem bore (tight fit — tube presses into flange)
translate([0, 0, -1])
cylinder(d=STEM_BORE, h=STEM_FLANGE_T + 2);
// 4× M5 flange bolts
for (a = [0, 90, 180, 270])
rotate([0, 0, a])
translate([STEM_FLANGE_BC/2, 0, -1])
cylinder(d=M5, h=STEM_FLANGE_T + 2);
}
}
// =============================================================================
// DROPOUT CLAMP — LOWER (round bore, base-section diameter)
// =============================================================================
module clamp_lower() {
difference() {
hull() {
translate([-CLAMP_L/2 + CLAMP_H/2, 0, 0]) cylinder(d=CLAMP_H, h=CLAMP_THICK);
translate([ CLAMP_L/2 - CLAMP_H/2, 0, 0]) cylinder(d=CLAMP_H, h=CLAMP_THICK);
}
// Round bore (base zone)
translate([0, 0, -1]) cylinder(d=AXLE_BASE_DIA + 0.4, h=CLAMP_THICK + 2);
// Slide-in slot (open toward wheel side)
translate([-CLAMP_L/2 - 1, -FORK_W/2, -1])
cube([CLAMP_L/2 + 1, FORK_W, CLAMP_THICK + 2]);
// Clamp bolts
for (dx = [-CLAMP_BOLT_DX, CLAMP_BOLT_DX])
for (dy = [-CLAMP_BOLT_DY, CLAMP_BOLT_DY])
translate([dx, dy, -1]) cylinder(d=CLAMP_BOLT_D, h=CLAMP_THICK + 2);
// Alignment pins
for (dy = [-CLAMP_BOLT_DY + 8, CLAMP_BOLT_DY - 8])
translate([0, dy, -1]) cylinder(d=CLAMP_ALIGN_D, h=CLAMP_THICK + 2);
}
}
// =============================================================================
// DROPOUT CLAMP — UPPER (D-cut bore, anti-rotation)
// =============================================================================
module clamp_upper() {
dcut_r = DCUT_R;
dcut_d = sqrt(pow(dcut_r, 2) - pow(DCUT_FC/2, 2));
difference() {
hull() {
translate([-CLAMP_L/2 + CLAMP_H/2, 0, 0]) cylinder(d=CLAMP_H, h=CLAMP_THICK);
translate([ CLAMP_L/2 - CLAMP_H/2, 0, 0]) cylinder(d=CLAMP_H, h=CLAMP_THICK);
}
// D-cut bore
translate([0, 0, -1])
linear_extrude(CLAMP_THICK + 2)
dcut_profile_2d(dcut_r, dcut_d);
// Slide-in slot
translate([-CLAMP_L/2 - 1, -FORK_W/2, -1])
cube([CLAMP_L/2 + 1, FORK_W, CLAMP_THICK + 2]);
// Clamp bolts
for (dx = [-CLAMP_BOLT_DX, CLAMP_BOLT_DX])
for (dy = [-CLAMP_BOLT_DY, CLAMP_BOLT_DY])
translate([dx, dy, -1]) cylinder(d=CLAMP_BOLT_D, h=CLAMP_THICK + 2);
// Alignment pins
for (dy = [-CLAMP_BOLT_DY + 8, CLAMP_BOLT_DY - 8])
translate([0, dy, -1]) cylinder(d=CLAMP_ALIGN_D, h=CLAMP_THICK + 2);
// Orientation emboss
translate([0, dcut_d + 1.5, CLAMP_THICK - 0.8])
linear_extrude(1)
polygon([[0,0],[-3,-5],[3,-5]]);
}
}
// ── D-cut 2D profile helper ───────────────────────────────────────────────────
module dcut_profile_2d(r, flat_d) {
intersection() {
circle(r=r);
translate([-r - 1, -r - 1])
square([2*(r+1), r + 1 + flat_d]);
}
}
// =============================================================================
// AXLE GHOST (visualisation only)
// =============================================================================
module axle_ghost() {
cylinder(d=BEARING_SEAT_OD, h=12);
translate([0,0,12]) cylinder(d=AXLE_BASE_DIA, h=AXLE_BASE_LEN);
translate([0,0,12+AXLE_BASE_LEN]) cylinder(d=AXLE_DCUT_DIA, h=AXLE_DCUT_LEN);
}
// =============================================================================
// DXF EXPORT
// =============================================================================
//
// Part 1 — Base plate:
// openscad prototype_baseplate.scad -D 'RENDER="plate_2d"' -o baseplate.dxf
//
// Part 2 — Dropout clamp, lower (× 2):
// openscad prototype_baseplate.scad -D 'RENDER="clamp_lower_2d"' -o clamp_lower.dxf
//
// Part 3 — Dropout clamp, upper (× 2):
// openscad prototype_baseplate.scad -D 'RENDER="clamp_upper_2d"' -o clamp_upper.dxf
//
// Part 4 — Stem flange ring (× 2, one each side of plate):
// openscad prototype_baseplate.scad -D 'RENDER="stem_flange_2d"' -o stem_flange.dxf
//
// Materials:
// Plate + flanges : 6 mm 5052-H32 aluminium (preferred)
// 8 mm clear acrylic (quick proto)
// Dropout clamps : 8 mm 6061-T6 aluminium
// Stem tube : 38.1 mm OD × 1.5 mm wall 6061-T6 (or 1.5" EMT)
// Cut stem to ~1050 mm — allows batteries from ~100 mm to ~950 mm height.
// =============================================================================