// ============================================================
// battery_holder.scad — 6S LiPo Battery Holder for 2020 T-Slot Chassis
// Issue: #588  Agent: sl-mechanical  Date: 2026-03-14
// ============================================================
//
// Parametric bracket holding a 6S 5000 mAh LiPo pack on 2020 aluminium
// T-slot rails.  Designed for low centre-of-gravity mounting: pack sits
// flat between the two chassis rails, as close to ground as clearance
// allows.  Quick-release via captive Velcro straps — battery swap in
// under 60 s without tools.
//
// Architecture:
//   Tray        → flat floor + perimeter walls, battery sits inside
//   Rail saddles→ two T-nut feet drop onto 2020 rails, thumbscrew locks
//   Strap slots → four pairs of slots for 25 mm Velcro strap loops
//   XT60 window → cut-out in rear wall for XT60 connector exit
//   Balance port→ open channel in front wall for balance lead routing
//   QR tab      → front-edge pull tab for one-handed battery extraction
//
// Part catalogue:
//   Part 1 — battery_tray()    Main tray body (single-piece print)
//   Part 2 — rail_saddle()     T-nut saddle foot (print x2 per tray)
//   Part 3 — strap_guide()     25 mm Velcro strap guide (print x4)
//   Part 4 — assembly_preview()
//
// Hardware BOM:
//   2× M3 × 16 mm SHCS + M3 hex nut    T-nut rail clamp thumbscrews
//   2× 25 mm × 250 mm Velcro strap     battery retention (hook + loop)
//   1× XT60 female connector            (mounted on ESC/PDB harness)
//   — battery slides in from front, Velcro strap over top —
//
// 6S LiPo target pack (verify with calipers — packs vary by brand):
//   BATT_L = 155 mm  (length, X axis in tray)
//   BATT_W =  48 mm  (width,  Y axis in tray)
//   BATT_H =  52 mm  (height, Z axis in tray)
//   Clearance 1 mm each side added automatically (BATT_CLEAR)
//
// Mounting:
//   Rail span    : RAIL_SPAN — distance between 2020 rail centrelines
//                  Default 80 mm; adjust to chassis rail spacing
//   Saddle height: SADDLE_H — total height of saddle (tray floor above rail)
//                  Keep low for CoG; default 8 mm
//
// RENDER options:
//   "assembly"         full assembly preview (default)
//   "tray_stl"         Part 1 — battery tray
//   "saddle_stl"       Part 2 — rail saddle (print x2)
//   "strap_guide_stl"  Part 3 — strap guide (print x4)
//
// Export commands:
//   openscad battery_holder.scad -D 'RENDER="tray_stl"'        -o bh_tray.stl
//   openscad battery_holder.scad -D 'RENDER="saddle_stl"'      -o bh_saddle.stl
//   openscad battery_holder.scad -D 'RENDER="strap_guide_stl"' -o bh_strap_guide.stl
//
// Print settings (all parts):
//   Material   : PETG
//   Perimeters : 5 (tray, saddle), 3 (strap_guide)
//   Infill     : 40 % gyroid (tray floor, saddle), 20 % (strap_guide)
//   Orientation:
//     tray         — floor flat on bed (no supports needed)
//     saddle       — flat face on bed (no supports)
//     strap_guide  — flat face on bed (no supports)
// ============================================================

$fn = 64;
e   = 0.01;

// ── Battery pack dimensions (verify with calipers) ────────────────────────────
BATT_L     = 155.0;   // pack length (X)
BATT_W     =  48.0;   // pack width  (Y)
BATT_H     =  52.0;   // pack height (Z)
BATT_CLEAR =   1.0;   // per-side fit clearance

// ── Tray geometry ─────────────────────────────────────────────────────────────
TRAY_FLOOR_T   =  4.0;   // tray floor thickness
TRAY_WALL_T    =  4.0;   // tray perimeter wall thickness
TRAY_WALL_H    = 20.0;   // tray wall height (Z) — cradles lower half of pack
TRAY_FILLET_R  =  3.0;   // inner corner radius

// Inner tray cavity (battery + clearance)
TRAY_INN_L = BATT_L + 2*BATT_CLEAR;
TRAY_INN_W = BATT_W + 2*BATT_CLEAR;

// Outer tray footprint
TRAY_OUT_L = TRAY_INN_L + 2*TRAY_WALL_T;
TRAY_OUT_W = TRAY_INN_W + 2*TRAY_WALL_T;
TRAY_TOTAL_H = TRAY_FLOOR_T + TRAY_WALL_H;

// ── Rail interface ─────────────────────────────────────────────────────────────
RAIL_SPAN   =  80.0;   // distance between 2020 rail centrelines (Y)
RAIL_W      =  20.0;   // 2020 extrusion width
SLOT_NECK_H =   3.2;   // T-slot neck height
SLOT_OPEN   =   6.0;   // T-slot opening width
SLOT_INN_W  =  10.2;   // T-slot inner width
SLOT_INN_H  =   5.8;   // T-slot inner height

// ── T-nut / saddle geometry ───────────────────────────────────────────────────
TNUT_W       =  9.8;
TNUT_H       =  5.5;
TNUT_L       = 12.0;
TNUT_NUT_AF  =  5.5;   // M3 hex nut across-flats
TNUT_NUT_H   =  2.4;
TNUT_BOLT_D  =  3.3;   // M3 clearance

SADDLE_W     = 30.0;   // saddle foot width (X, along rail)
SADDLE_T     =  8.0;   // saddle body thickness (Z, above rail top face)
SADDLE_PAD_T =  2.0;   // rubber-pad recess depth (optional anti-slip)

// ── Velcro strap slots ────────────────────────────────────────────────────────
STRAP_W      = 26.0;   // 25 mm strap + 1 mm clearance
STRAP_T      =  4.0;   // slot through-thickness (tray wall)
// Four slot pairs: one near each end of tray (X), one each side (Y)
// Slots run through side walls (Y direction) — strap loops over battery top

// ── XT60 connector window (rear wall) ─────────────────────────────────────────
XT60_W       = 14.0;   // XT60 body width
XT60_H       = 18.0;   // XT60 body height (with cable exit)
XT60_OFFSET_Z = 4.0;   // height above tray floor

// ── Balance lead port (front wall) ────────────────────────────────────────────
BAL_W        = 40.0;   // balance lead bundle width (6S = 7 wires)
BAL_H        =  6.0;   // balance lead channel height
BAL_OFFSET_Z =  8.0;   // height above tray floor

// ── Quick-release pull tab (front edge) ──────────────────────────────────────
QR_TAB_W     = 30.0;   // tab width
QR_TAB_H     = 12.0;   // tab height above front wall top
QR_TAB_T     =  4.0;   // tab thickness
QR_HOLE_D    = 10.0;   // finger-loop hole diameter

// ── Strap guide clip ─────────────────────────────────────────────────────────
GUIDE_OD     = STRAP_W + 6.0;
GUIDE_T      =  3.0;
GUIDE_BODY_H = 14.0;

// ── Fasteners ─────────────────────────────────────────────────────────────────
M3_D = 3.3;

// ============================================================
// RENDER DISPATCH
// ============================================================
RENDER = "assembly";

if      (RENDER == "assembly")        assembly_preview();
else if (RENDER == "tray_stl")        battery_tray();
else if (RENDER == "saddle_stl")      rail_saddle();
else if (RENDER == "strap_guide_stl") strap_guide();

// ============================================================
// ASSEMBLY PREVIEW
// ============================================================
module assembly_preview() {
    // Ghost 2020 rails (Y direction, RAIL_SPAN apart)
    for (ry = [-RAIL_SPAN/2, RAIL_SPAN/2])
        %color("Silver", 0.28)
            translate([-TRAY_OUT_L/2 - 30, ry - RAIL_W/2, -SADDLE_T - TNUT_H])
                cube([TRAY_OUT_L + 60, RAIL_W, RAIL_W]);

    // Rail saddles (left and right)
    for (sy = [-RAIL_SPAN/2, RAIL_SPAN/2])
        color("DimGray", 0.85)
            translate([0, sy, -SADDLE_T])
                rail_saddle();

    // Battery tray (sitting on saddles)
    color("OliveDrab", 0.85)
        battery_tray();

    // Battery ghost
    %color("SaddleBrown", 0.35)
        translate([-BATT_L/2, -BATT_W/2, TRAY_FLOOR_T])
            cube([BATT_L, BATT_W, BATT_H]);

    // Strap guides (4×, two each end)
    for (sx = [-TRAY_OUT_L/2 + STRAP_W/2 + TRAY_WALL_T + 8,
                TRAY_OUT_L/2 - STRAP_W/2 - TRAY_WALL_T - 8])
    for (sy = [-1, 1])
        color("SteelBlue", 0.75)
            translate([sx, sy*(TRAY_OUT_W/2), TRAY_TOTAL_H + 2])
                rotate([sy > 0 ? 0 : 180, 0, 0])
                    strap_guide();
}

// ============================================================
// PART 1 — BATTERY TRAY
// ============================================================
// Single-piece tray: flat floor, four perimeter walls, T-nut saddle
// attachment bosses on underside, Velcro strap slots through side walls,
// XT60 window in rear wall, balance lead channel in front wall, and
// quick-release pull tab on front edge.
//
// Battery inserts from the front (−X end) — front wall is lower than
// rear wall so the pack slides in and the rear wall stops it.
// Velcro straps loop over the top of the pack through the side slots.
//
// Coordinate convention:
//   X: along battery length (−X = front/plug-end, +X = rear/balance-end)
//   Y: across battery width (centred, ±TRAY_OUT_W/2)
//   Z: vertical (Z=0 = tray floor top face; −Z = underside → saddles)
//
// Print: floor flat on bed, PETG, 5 perims, 40% gyroid. No supports.
module battery_tray() {
    // Short rear wall height (XT60 connector exits here — full wall height)
    // Front wall is lower to allow battery slide-in
    front_wall_h = TRAY_WALL_H * 0.55;   // 55% height — battery slides over

    difference() {
        union() {
            // ── Floor ────────────────────────────────────────────────────
            translate([-TRAY_OUT_L/2, -TRAY_OUT_W/2, -TRAY_FLOOR_T])
                cube([TRAY_OUT_L, TRAY_OUT_W, TRAY_FLOOR_T]);

            // ── Rear wall (+X, full height) ───────────────────────────────
            translate([TRAY_INN_L/2, -TRAY_OUT_W/2, 0])
                cube([TRAY_WALL_T, TRAY_OUT_W, TRAY_WALL_H]);

            // ── Front wall (−X, lowered for slide-in) ────────────────────
            translate([-TRAY_INN_L/2 - TRAY_WALL_T, -TRAY_OUT_W/2, 0])
                cube([TRAY_WALL_T, TRAY_OUT_W, front_wall_h]);

            // ── Side walls (±Y) ───────────────────────────────────────────
            for (sy = [-1, 1])
                translate([-TRAY_OUT_L/2,
                           sy*(TRAY_INN_W/2 + (sy>0 ? 0 : -TRAY_WALL_T)),
                           0])
                    cube([TRAY_OUT_L,
                          TRAY_WALL_T,
                          TRAY_WALL_H]);

            // ── Quick-release pull tab (front wall top edge) ──────────────
            translate([-TRAY_INN_L/2 - TRAY_WALL_T - e,
                       -QR_TAB_W/2, front_wall_h])
                cube([QR_TAB_T, QR_TAB_W, QR_TAB_H]);

            // ── Saddle attachment bosses (underside, one per rail) ─────────
            // Bosses drop into saddle sockets; M3 bolt through floor
            for (sy = [-RAIL_SPAN/2, RAIL_SPAN/2])
                translate([-SADDLE_W/2, sy - SADDLE_W/2, -TRAY_FLOOR_T - SADDLE_T/2])
                    cube([SADDLE_W, SADDLE_W, SADDLE_T/2 + e]);
        }

        // ── Battery cavity (hollow interior) ──────────────────────────────
        translate([-TRAY_INN_L/2, -TRAY_INN_W/2, -e])
            cube([TRAY_INN_L, TRAY_INN_W, TRAY_WALL_H + 2*e]);

        // ── XT60 connector window (rear wall) ─────────────────────────────
        // Centred on rear wall, low position so cable exits cleanly
        translate([TRAY_INN_L/2 - e, -XT60_W/2, XT60_OFFSET_Z])
            cube([TRAY_WALL_T + 2*e, XT60_W, XT60_H]);

        // ── Balance lead channel (front wall) ─────────────────────────────
        // Wide slot for 6S balance lead (7-pin JST-XH ribbon)
        translate([-TRAY_INN_L/2 - TRAY_WALL_T - e,
                   -BAL_W/2, BAL_OFFSET_Z])
            cube([TRAY_WALL_T + 2*e, BAL_W, BAL_H]);

        // ── Velcro strap slots (side walls, 2 pairs) ──────────────────────
        // Pair A: near front end (−X), Pair B: near rear end (+X)
        // Each slot runs through the wall in Y direction
        for (sx = [-TRAY_INN_L/2 + STRAP_W*0.5 + 10,
                    TRAY_INN_L/2 - STRAP_W*0.5 - 10])
        for (sy = [-1, 1]) {
            translate([sx - STRAP_W/2,
                       sy*(TRAY_INN_W/2) - (sy > 0 ? TRAY_WALL_T + e : -e),
                       TRAY_WALL_H * 0.35])
                cube([STRAP_W, TRAY_WALL_T + 2*e, STRAP_T]);
        }

        // ── QR tab finger-loop hole ────────────────────────────────────────
        translate([-TRAY_INN_L/2 - TRAY_WALL_T/2,
                   0, front_wall_h + QR_TAB_H * 0.55])
            rotate([0, 90, 0])
                cylinder(d = QR_HOLE_D, h = QR_TAB_T + 2*e, center = true);

        // ── Saddle bolt holes (M3 through floor into saddle boss) ─────────
        for (sy = [-RAIL_SPAN/2, RAIL_SPAN/2])
            translate([0, sy, -TRAY_FLOOR_T - e])
                cylinder(d = M3_D, h = TRAY_FLOOR_T + 2*e);

        // ── Floor lightening grid (non-structural area) ───────────────────
        // 2D grid of pockets reduces weight without weakening battery support
        for (gx = [-40, 0, 40])
        for (gy = [-12, 12])
            translate([gx, gy, -TRAY_FLOOR_T - e])
                cylinder(d = 14, h = TRAY_FLOOR_T - 1.5 + e);

        // ── Inner corner chamfers (battery slide-in guidance) ─────────────
        // 45° chamfers at bottom-front inner corners
        translate([-TRAY_INN_L/2, -TRAY_INN_W/2 - e, -e])
            rotate([0, 0, 45])
                cube([4, 4, TRAY_WALL_H * 0.3 + e]);
        translate([-TRAY_INN_L/2, TRAY_INN_W/2 + e, -e])
            rotate([0, 0, -45])
                cube([4, 4, TRAY_WALL_H * 0.3 + e]);
    }
}

// ============================================================
// PART 2 — RAIL SADDLE
// ============================================================
// T-nut foot that clamps to the top face of a 2020 T-slot rail.
// Battery tray boss drops into saddle socket; M3 bolt through tray
// floor and saddle body locks everything together.
// M3 thumbscrew through side of saddle body grips the rail T-groove
// (same thumbscrew interface as all other SaltyLab rail brackets).
//
// Saddle sits on top of rail (no T-nut tongue needed — saddle clamps
// from the top using a T-nut inserted into the rail T-groove from the
// end).  Low profile keeps battery CoG as low as possible.
//
// Print: flat base on bed, PETG, 5 perims, 50% gyroid.
module rail_saddle() {
    sock_d = SADDLE_W - 4;   // tray boss socket diameter

    difference() {
        union() {
            // ── Main saddle body ──────────────────────────────────────────
            translate([-SADDLE_W/2, -SADDLE_W/2, 0])
                cube([SADDLE_W, SADDLE_W, SADDLE_T]);

            // ── T-nut tongue (enters rail T-groove from above) ────────────
            translate([-TNUT_W/2, -TNUT_L/2, -SLOT_NECK_H])
                cube([TNUT_W, TNUT_L, SLOT_NECK_H + e]);

            // ── T-nut inner body (locks in groove) ────────────────────────
            translate([-TNUT_W/2, -TNUT_L/2, -SLOT_NECK_H - (TNUT_H - SLOT_NECK_H)])
                cube([TNUT_W, TNUT_L, TNUT_H - SLOT_NECK_H + e]);
        }

        // ── Rail channel clearance (bottom of saddle straddles rail) ──────
        // Saddle body has a channel that sits over the rail top face
        translate([-RAIL_W/2 - e, -SADDLE_W/2 - e, -e])
            cube([RAIL_W + 2*e, SADDLE_W + 2*e, 2.0]);

        // ── M3 clamp bolt bore (through saddle body into T-nut) ───────────
        translate([0, 0, -SLOT_NECK_H - TNUT_H - e])
            cylinder(d = TNUT_BOLT_D, h = SADDLE_T + TNUT_H + 2*e);

        // ── M3 hex nut pocket (top face of saddle for thumbscrew) ─────────
        translate([0, 0, SADDLE_T - TNUT_NUT_H - 0.5])
            cylinder(d = TNUT_NUT_AF / cos(30),
                     h = TNUT_NUT_H + 0.6, $fn = 6);

        // ── Tray boss socket (top face of saddle, tray boss nests here) ───
        // Cylindrical socket receives tray underside boss; M3 bolt centres
        translate([0, 0, SADDLE_T - 3])
            cylinder(d = sock_d + 0.4, h = 3 + e);

        // ── M3 tray bolt bore (vertical, through saddle top) ──────────────
        translate([0, 0, SADDLE_T - 3 - e])
            cylinder(d = M3_D, h = SADDLE_T + e);

        // ── Anti-slip pad recess (bottom face, optional rubber adhesive) ──
        translate([0, 0, -e])
            cylinder(d = SADDLE_W - 8, h = SADDLE_PAD_T + e);

        // ── Lightening pockets ─────────────────────────────────────────────
        for (lx = [-1, 1], ly = [-1, 1])
            translate([lx*8, ly*8, -e])
                cylinder(d = 5, h = SADDLE_T - 3 - 1 + e);
    }
}

// ============================================================
// PART 3 — STRAP GUIDE
// ============================================================
// Snap-on guide that sits on top of tray wall at each strap slot,
// directing the 25 mm Velcro strap from the side slot up and over
// the battery top.  Four per tray, one at each slot exit.
// Curved lip prevents strap from cutting into PETG wall edge.
// Push-fit onto tray wall top; no fasteners required.
//
// Print: flat base on bed, PETG, 3 perims, 20% infill.
module strap_guide() {
    strap_w_clr = STRAP_W + 0.5;   // strap slot with clearance
    lip_r       = 3.0;              // guide lip radius

    difference() {
        union() {
            // ── Body (sits on tray wall top edge) ─────────────────────────
            translate([-GUIDE_OD/2, 0, 0])
                cube([GUIDE_OD, GUIDE_T, GUIDE_BODY_H]);

            // ── Curved guide lip (top of body, strap bends around this) ───
            translate([0, GUIDE_T/2, GUIDE_BODY_H])
                rotate([0, 90, 0])
                    cylinder(r = lip_r, h = GUIDE_OD, center = true);

            // ── Wall engagement tabs (snap over tray wall top) ────────────
            for (sy = [0, -(TRAY_WALL_T + GUIDE_T)])
                translate([-strap_w_clr/2 - 3, sy - GUIDE_T, 0])
                    cube([strap_w_clr + 6, GUIDE_T, GUIDE_BODY_H * 0.4]);
        }

        // ── Strap slot (through body) ──────────────────────────────────────
        translate([-strap_w_clr/2, -e, -e])
            cube([strap_w_clr, GUIDE_T + 2*e, GUIDE_BODY_H + 2*e]);

        // ── Wall clearance slot (body slides over tray wall) ──────────────
        translate([-strap_w_clr/2 - 3 - e,
                   -TRAY_WALL_T - GUIDE_T, -e])
            cube([strap_w_clr + 6 + 2*e,
                  TRAY_WALL_T, GUIDE_BODY_H * 0.4 + 2*e]);

        // ── Lightening pockets on side faces ──────────────────────────────
        for (lx = [-GUIDE_OD/4, GUIDE_OD/4])
            translate([lx, GUIDE_T/2, GUIDE_BODY_H/2])
                cube([6, GUIDE_T + 2*e, GUIDE_BODY_H * 0.5], center = true);
    }
}
