saltylab-firmware/chassis/stem_battery_clamp.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

379 lines
16 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 — Battery Stem Clamp (Part B)
// Agent: sl-mechanical | 2026-02-28
//
// HEIGHT-ADJUSTABLE battery carousel that slides on the central vertical stem.
// 24 battery packs stand UPRIGHT, arranged radially around the mast.
//
// HOW IT WORKS
// 1. Two collar halves clamp around the stem at the desired height.
// 2. Radial arms project outward from the collar (one per battery pack).
// 3. Each arm tip has a battery cradle — an upward-open U-channel that
// the pack slides into from above.
// 4. Velcro straps thread through cradle slots and cinch around the pack.
// 5. Loosen the M6 collar bolts → slide up/down for CG tuning.
// Tighten → locks in place.
//
// BATTERY (each pack, standing vertically):
// 420 mm tall × 88 mm wide × 56 mm deep (verified)
//
// ANGULAR LAYOUT
// BATT_COUNT = 2 → arms at 90° and 270° (±Y, balanced front/rear)
// BATT_COUNT = 4 → arms at 45°, 135°, 225°, 315° (each collar half owns 2)
// BATT_COUNT = 3 → arms at 90°, 210°, 330°
//
// PARTS (set RENDER= to export each)
// collar_half — 3D print × 2 (mirror pair, RENDER="collar_half")
// arm — laser-cut or print × BATT_COUNT (RENDER="arm_2d" for DXF)
// battery_cradle — 3D print × BATT_COUNT (RENDER="cradle")
//
// STEM
// 38.1 mm OD × 1.5 mm wall 6061-T6 aluminium tube (or 1.5" EMT conduit).
// Cut to ~1050 mm. Clamp can sit anywhere from 150 mm to 850 mm height.
// =============================================================================
$fn = 64;
// =============================================================================
// STEM
// =============================================================================
STEM_OD = 38.1;
STEM_BORE = STEM_OD + 0.5; // collar bore clearance
// =============================================================================
// COLLAR
// =============================================================================
COLLAR_H = 80.0; // mm taller = more grip / less slip risk
COLLAR_OD = 84.0; // mm outer diameter (wall = (8438.6)/2 ≈ 22.7 mm)
// Split plane: Y = 0 (each half is the +Y or Y side)
// Clamping bolts go through both halves at (±COLLAR_BOLT_X, 0, Z)
COLLAR_BOLT_X = 24.0; // mm bolt ±X from stem axis
COLLAR_BOLT_D = 6.5; // M6 clearance
COLLAR_NUT_D = 11.0; // M6 hex nut AF + 0.5 mm tolerance (point-to-point ≈ 10.4, use 11)
COLLAR_NUT_H = 5.2; // M6 standard nut height + 0.2 mm
// Height-lock / anti-rotation set screw (M6 thread on outer face of each half)
SETSCREW_D = 6.1; // through-hole for M6 set screw
// Arm attachment pads on collar exterior (flat boss, one per arm)
ARM_PAD_W = 32.0; // mm pad width (tangential)
ARM_PAD_H = 18.0; // mm pad height
ARM_PAD_T = 4.0; // mm pad protrusion from collar surface
ARM_BOLT_D = 4.3; // M4 clearance (arm-to-collar bolt)
ARM_BOLT_SPAN = 16.0; // mm C/L-to-C/L of two arm attachment bolts
// =============================================================================
// ARMS & BATTERIES
// =============================================================================
BATT_COUNT = 4; // 2, 3, or 4
BATT_L = 420.0; // mm pack height (vertical)
BATT_W = 88.0; // mm pack width (tangential)
BATT_D = 56.0; // mm pack depth (radial, into stem)
BATT_CL = 0.8; // mm all-round clearance in cradle
ARM_REACH = 55.0; // mm collar surface → battery near face
ARM_W = 28.0; // mm arm width
ARM_THICK = 8.0; // mm arm thickness (3D-print); 4 mm if laser-cut Al
ARM_CRADLE_D = 4.3; // M4 clearance (cradle-to-arm bolt)
// =============================================================================
// BATTERY CRADLE
// =============================================================================
CRADLE_H = 80.0; // mm cradle height (pack extends BATT_L-CRADLE_H above)
CRADLE_WALL = 4.5; // mm wall thickness
CRADLE_STRAP_W = 25.0; // mm Velcro strap slot width
CRADLE_STRAP_T = 6.0; // mm slot height
// =============================================================================
// ANGULAR PLACEMENT
// =============================================================================
// First arm angle chosen so all arms are clear of the Y=0 split plane
ARM_START = (BATT_COUNT == 2) ? 90 :
(BATT_COUNT == 4) ? 45 :
/* 3 */ 90 ;
// Helper: is arm i on the +Y half (side=+1) or Y half (side=1)?
// side = +1 → sin(angle) >= 0
// side = -1 → sin(angle) < 0
function arm_angle(i) = ARM_START + i * (360 / BATT_COUNT);
function arm_on_side(i, side) =
(side > 0) ? (sin(arm_angle(i)) >= -0.001) :
(sin(arm_angle(i)) <= 0.001);
// =============================================================================
// RENDER CONTROL
// =============================================================================
// "assembly" — full 3-D preview with ghosts
// "collar_half" — single collar half for printing (print 2, one mirrored)
// "arm" — single arm for printing or laser-cut
// "arm_2d" — 2-D DXF projection of arm
// "cradle" — single battery cradle for printing
RENDER = "assembly";
if (RENDER == "assembly") {
assembly();
} else if (RENDER == "collar_half") {
collar_half(side=1);
} else if (RENDER == "arm") {
arm();
} else if (RENDER == "arm_2d") {
projection(cut=true) translate([0, 0, -ARM_THICK/2]) arm();
} else if (RENDER == "cradle") {
battery_cradle();
}
// =============================================================================
// ASSEMBLY
// =============================================================================
module assembly() {
// Collar halves
color("LightSlateGray", 0.88) collar_half(side= 1);
color("SlateGray", 0.88) collar_half(side=-1);
// Arms + cradles at each battery position
for (i = [0 : BATT_COUNT - 1]) {
a = arm_angle(i);
rotate([0, 0, a]) {
// Arm: originates at collar surface, runs along +X
color("DimGray", 0.90)
translate([COLLAR_OD/2, 0, (COLLAR_H - ARM_THICK) / 2])
arm();
// Cradle: at arm tip
color("SteelBlue", 0.85)
translate([COLLAR_OD/2 + ARM_REACH,
-(BATT_W/2 + BATT_CL + CRADLE_WALL),
(COLLAR_H - CRADLE_H) / 2])
battery_cradle();
// Battery ghost (not for export)
%color("DarkGoldenrod", 0.30)
translate([COLLAR_OD/2 + ARM_REACH + CRADLE_WALL,
-(BATT_W/2 + BATT_CL),
(COLLAR_H - CRADLE_H) / 2])
cube([BATT_D + 2*BATT_CL, BATT_W + 2*BATT_CL, BATT_L]);
}
}
// Stem ghost
%color("Gray", 0.20)
translate([0, 0, -(COLLAR_H * 2)])
cylinder(d=STEM_OD, h=COLLAR_H * 14);
}
// =============================================================================
// COLLAR HALF
// =============================================================================
// Printed flat-side-down (split face = print bed).
// Print TWO: one as-is (side=+1), one mirrored in slicer (side=1).
// They are identical; the mirror instruction handles orientation.
//
// Bolt pattern:
// 4× M6 through the flat face (2 per half at ±COLLAR_BOLT_X)
// M6 hex nut pockets on the flat face (captured before assembly)
// 1× M6 set screw on the outer curved surface (height lock)
//
// Arm attachment:
// Raised pad on outer curved surface at each arm angle for this half.
// 2× M4 through-holes per pad; M4 hex nut pocket on inside of collar wall.
module collar_half(side = 1) {
mid_z = COLLAR_H / 2;
wall_t = (COLLAR_OD - STEM_BORE) / 2;
// Half of collar: the Y≥0 half (side=+1) or Y≤0 half (side=1)
difference() {
union() {
// ── Half-cylinder body ──────────────────────────────────────
intersection() {
cylinder(d=COLLAR_OD, h=COLLAR_H);
// Keep only the appropriate half
translate([-COLLAR_OD/2 - 1,
(side > 0) ? 0 : -COLLAR_OD - 1,
-1])
cube([COLLAR_OD + 2, COLLAR_OD + 1, COLLAR_H + 2]);
}
// ── Arm attachment pads ──────────────────────────────────────
for (i = [0 : BATT_COUNT - 1]) {
if (arm_on_side(i, side)) {
a = arm_angle(i);
rotate([0, 0, a])
translate([COLLAR_OD/2, -ARM_PAD_W/2,
mid_z - ARM_PAD_H/2])
cube([ARM_PAD_T, ARM_PAD_W, ARM_PAD_H]);
}
}
}
// ── Stem bore ────────────────────────────────────────────────────
translate([0, 0, -1])
cylinder(d=STEM_BORE, h=COLLAR_H + 2);
// ── Clamping bolt holes (2× through flat split face) ─────────────
// Bolt axis: along Y (perpendicular to split plane)
// Holes at (±COLLAR_BOLT_X, 0, COLLAR_H/3) and (±COLLAR_BOLT_X, 0, 2*COLLAR_H/3)
for (bx = [-COLLAR_BOLT_X, COLLAR_BOLT_X])
for (bz = [COLLAR_H/3, 2*COLLAR_H/3])
translate([bx, -1, bz])
rotate([-90, 0, 0])
cylinder(d=COLLAR_BOLT_D, h=COLLAR_OD/2 + 2);
// ── M6 nut pockets on outer flat face of each half ───────────────
// Pocket depth = COLLAR_NUT_H from the far curved side inward.
// This allows pre-installing the nuts before bolting the halves together.
for (bx = [-COLLAR_BOLT_X, COLLAR_BOLT_X])
for (bz = [COLLAR_H/3, 2*COLLAR_H/3])
translate([bx, side * (COLLAR_OD/2 - COLLAR_NUT_H), bz])
rotate([-90, 0, 0])
cylinder(d=COLLAR_NUT_D, h=COLLAR_NUT_H + 1, $fn=6);
// ── Set screw hole (M6, on curved outer surface at mid-height) ────
translate([0, side * (COLLAR_OD/2 + 1), COLLAR_H/2])
rotate([90, 0, 0])
cylinder(d=SETSCREW_D, h=COLLAR_OD/2 + 2);
// ── Arm bolt holes + nut pockets (through collar wall per arm) ────
for (i = [0 : BATT_COUNT - 1]) {
if (arm_on_side(i, side)) {
a = arm_angle(i);
for (dy = [-ARM_BOLT_SPAN/2, ARM_BOLT_SPAN/2])
rotate([0, 0, a])
translate([STEM_BORE/2 - 1, dy, mid_z])
rotate([0, 90, 0]) {
// Through-hole (M4 clearance all the way)
cylinder(d=ARM_BOLT_D,
h=COLLAR_OD/2 - STEM_BORE/2 + ARM_PAD_T + 2);
// Nut pocket on bore interior face
cylinder(d=10, h=4.5, $fn=6);
}
}
}
}
}
// =============================================================================
// ARM
// =============================================================================
// Flat bar, ARM_REACH × ARM_W × ARM_THICK.
// Collar end: 2× M4 clearance holes at ±ARM_BOLT_SPAN/2 in Y.
// Cradle end: 2× M4 clearance holes at ±(ARM_W/2 - 8) in Y.
// Can be laser-cut from 4 mm Al plate (reduce ARM_THICK to 4 in RENDER="arm_2d").
module arm() {
difference() {
translate([0, -ARM_W/2, 0])
cube([ARM_REACH, ARM_W, ARM_THICK]);
// Collar-end bolt holes (M4, match arm pad on collar)
for (dy = [-ARM_BOLT_SPAN/2, ARM_BOLT_SPAN/2])
translate([8, dy, -1])
cylinder(d=ARM_BOLT_D, h=ARM_THICK + 2);
// Cradle-end bolt holes (M4)
for (dy = [-(ARM_W/2 - 8), ARM_W/2 - 8])
translate([ARM_REACH - 10, dy, -1])
cylinder(d=ARM_CRADLE_D, h=ARM_THICK + 2);
// Lightening slot in centre (optional — reduces print material)
if (ARM_REACH > 40) {
slot_l = ARM_REACH - 34;
slot_w = ARM_W - 16;
translate([17, -slot_w/2, -1])
hull() {
translate([slot_w/2, slot_w/2, 0]) cylinder(d=slot_w/2*0.8, h=ARM_THICK+2);
translate([slot_l - slot_w/2, slot_w/2, 0]) cylinder(d=slot_w/2*0.8, h=ARM_THICK+2);
}
}
}
}
// =============================================================================
// BATTERY CRADLE
// =============================================================================
// U-channel, open top for pack insertion from above.
// Inner pocket: (BATT_D + 2*BATT_CL) radially × (BATT_W + 2*BATT_CL) tangentially.
// Cradle height CRADLE_H — battery extends (BATT_L CRADLE_H) above the cradle.
//
// Strap slots: 2× horizontal slots through front+rear walls (Velcro through-pass).
// Base bolt holes: 2× M4 for arm attachment (arm bolts up through arm into cradle).
module battery_cradle() {
cw = CRADLE_WALL;
id = BATT_D + 2*BATT_CL; // inner depth (radial, +X direction)
iw = BATT_W + 2*BATT_CL; // inner width (tangential, Y direction)
difference() {
// Outer block
cube([id + 2*cw, iw + 2*cw, CRADLE_H]);
// Battery slot (open top: subtract from cw to top + 1)
translate([cw, cw, -1])
cube([id, iw, CRADLE_H + 2]);
// Strap slots — through left and right walls (Y faces), 2 heights
for (sz = [CRADLE_H * 0.30, CRADLE_H * 0.65])
translate([-1, cw + (iw - CRADLE_STRAP_W) / 2, sz])
cube([id + 2*cw + 2, CRADLE_STRAP_W, CRADLE_STRAP_T]);
// Arm attachment holes in floor (2× M4)
for (dy = [cw + iw/2 - ARM_BOLT_SPAN/2,
cw + iw/2 + ARM_BOLT_SPAN/2])
translate([cw + id/2, dy, -1])
cylinder(d=ARM_CRADLE_D, h=cw + 2);
// Corner chamfers (front face — aids pack insertion)
chamfer_s = 5;
for (cy = [cw - 0.01, cw + iw - chamfer_s + 0.01])
translate([cw - 0.01, cy, CRADLE_H - chamfer_s])
rotate([0, 45, 0])
cube([chamfer_s * 1.42, chamfer_s, chamfer_s * 1.42]);
}
}
// =============================================================================
// DXF / PRINT EXPORT
// =============================================================================
//
// COLLAR HALF (3D print × 2 — print one as-is, mirror second in slicer):
// openscad stem_battery_clamp.scad -D 'RENDER="collar_half"' -o collar_half.stl
// Print settings: PETG, 5 perimeters, 40% infill, 0.2 mm layer, no supports needed
// (flat split face sits on bed; overhangs ≤ 45°)
//
// ARM — 3D print or laser-cut × BATT_COUNT:
// Print: openscad stem_battery_clamp.scad -D 'RENDER="arm"' -o arm.stl
// Laser (DXF): openscad stem_battery_clamp.scad \
// -D 'RENDER="arm_2d"' -D 'ARM_THICK=4' -o arm.dxf
// Laser material: 4 mm 5052-H32 aluminium
//
// BATTERY CRADLE (3D print × BATT_COUNT):
// openscad stem_battery_clamp.scad -D 'RENDER="cradle"' -o cradle.stl
// Print settings: PETG, 4 perimeters, 30% infill, 0.25 mm layer
//
// =============================================================================
//
// ASSEMBLY SEQUENCE
// 1. Print collar halves × 2, cradles × BATT_COUNT.
// 2. Laser-cut (or print) arms × BATT_COUNT.
// 3. Press M4 hex nuts into collar bore-face pockets.
// 4. Wrap collar halves around stem; thread M6 bolts through both halves.
// Do not fully tighten yet — position to desired height.
// 5. Bolt each arm to its collar pad (M4 × 20 SHCS from arm outward).
// 6. Bolt each cradle to its arm tip (M4 × 16 SHCS from below).
// 7. Drop battery packs into cradles from above; route Velcro straps.
// 8. Tighten M6 collar bolts (≈ 6 N·m each). Use M6 set screw for rotation lock.
//
// CG TUNING
// Loosen M6 collar bolts (do not fully remove). Slide entire carousel up/down.
// Re-tighten. Typical balance point: batteries at 400600 mm above base plate.
// =============================================================================