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