// =============================================================================
// SaltyBot — Battery Stem Clamp  (Part B)
// Agent: sl-mechanical  |  2026-02-28
//
// HEIGHT-ADJUSTABLE battery carousel that slides on the central vertical stem.
// 2–4 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 = (84−38.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 400–600 mm above base plate.
// =============================================================================
