saltylab-firmware/chassis/battery_holder.scad

411 lines
20 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.

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