holder: expose all 59 scad parameters auto-extracted from the .scad source
The bundled hex_cell.scad has many more knobs than the dozen we used to expose (box clearances, cap/box wall, vertical stacking, busbar template, etc). Maintaining a hand-curated PARAMS list in Python next to a SCAD file that already documents every variable inline was always going to drift. Sync + auto-extract approach: - scad/hex_cell.scad: replace with the upstream master file from Albert Phan's Hex-Cell-Holder fork (adds the BUSBAR TEMPLATE section and the "busbar template" part option). - holder.py: PARAMS now built at import time by _scan_scad(), which regexes top-level `name = literal; // help` lines into Param entries up until the // END OF CONFIGURATION marker. Scan handles bool / number / string literals; derived expressions and helper variables are skipped automatically. - Manual maps stay small and explicit: _SELECT_OPTIONS for the few string-enum params (part, pack_style, box_style, template_outline, template_hole_style, etc.), _GROUP_RULES for UI sectioning, and _NUMBER_HINTS for sensible min/max/step on the most-tweaked numbers. UI: - holder-app.js: extra GROUP_ORDER / GROUP_LABELS for the new sections (cap, box, insulator, bolts, wires, stacking, template, advanced). Group titles are now click-to-collapse; the advanced groups start collapsed so the form isn't a wall of inputs on load. - holder.css: caret marker on the group title, smooth rotate on collapse, hides the body via .collapsed class. Net effect: every variable in the .scad — including the new busbar template knobs — is editable from the page, with helpful comments copied straight from the .scad source.
This commit is contained in:
@@ -62,46 +62,142 @@ class Param:
|
|||||||
help: str | None = None
|
help: str | None = None
|
||||||
|
|
||||||
|
|
||||||
PARAMS: list[Param] = [
|
# ----- automated scan of the bundled .scad ----------------------------------
|
||||||
# Pack geometry
|
# We extract top-level `name = literal; // help` lines into Param entries.
|
||||||
Param("part", "Part", "select", "holder", group="part",
|
# Lines whose RHS isn't a constant number / bool / string (e.g. derived
|
||||||
options=["holder", "cap", "box lid", "box bottom", "insulator"],
|
# expressions like `box_total_height = get_mock_pack_height() + ...`) are
|
||||||
help="Which piece to generate."),
|
# skipped — those would still take their default value from the .scad itself.
|
||||||
Param("pack_style", "Pack style", "select", "rect", group="part",
|
|
||||||
options=["rect", "para", "tria"],
|
|
||||||
help="Cell arrangement."),
|
|
||||||
Param("wire_style", "Wire style", "select", "strip", group="part",
|
|
||||||
options=["strip", "bus"],
|
|
||||||
help="strip = nickel strips between cells; bus = bus wires between rows."),
|
|
||||||
|
|
||||||
# Cell
|
# Param categories — used both for grouping in the UI and for picking the
|
||||||
Param("cell_dia", "Cell diameter (mm)", "number", 21.2, group="cell",
|
# select-typed params (those with a fixed option set). Keep keys in scad
|
||||||
min=10, max=40, step=0.1, help="21.2 for 21700, 18.4 for 18650, 26.5 for 26650."),
|
# variable-name space; group order/labels are the rendering hint.
|
||||||
Param("cell_height", "Cell height (mm)", "number", 70.0, group="cell",
|
_SELECT_OPTIONS: dict[str, list[str]] = {
|
||||||
min=30, max=200, step=1),
|
"part": ["holder", "cap", "box lid", "box bottom",
|
||||||
Param("wall", "Wall thickness (mm)", "number", 0.8, group="cell",
|
"wire clamp", "insulator",
|
||||||
min=0.4, max=3.0, step=0.1,
|
"vertical box section", "busbar template"],
|
||||||
help="Wall around one cell; spacing between cells is 2× this."),
|
"part_type": ["normal", "mirrored", "both", "assembled"],
|
||||||
|
"pack_style": ["rect", "para", "tria"],
|
||||||
|
"wire_style": ["strip", "bus"],
|
||||||
|
"box_style": ["bolt", "ziptie", "both"],
|
||||||
|
"template_outline": ["rect", "hull"],
|
||||||
|
"template_hole_style": ["engrave", "through", "center"],
|
||||||
|
}
|
||||||
|
|
||||||
# Pack size
|
# How to group each scad var into UI sections. First match wins; "advanced"
|
||||||
Param("num_rows", "Rows", "number", 6, group="size", min=1, max=40, step=1),
|
# catches the rest.
|
||||||
Param("num_cols", "Columns", "number", 12, group="size", min=1, max=40, step=1),
|
_GROUP_RULES: list[tuple[str, list[str]]] = [
|
||||||
|
("part", ["part", "part_type", "pack_style", "wire_style", "box_style"]),
|
||||||
# Holder
|
("cell", ["cell_dia", "cell_height", "wall"]),
|
||||||
Param("holder_height", "Holder height (mm)", "number", 10.0, group="holder",
|
("size", ["num_rows", "num_cols"]),
|
||||||
min=4, max=30, step=0.5),
|
("holder", ["holder_height", "slot_height", "col_slot_width",
|
||||||
Param("slot_height", "Slot height (mm)", "number", 3.0, group="holder",
|
"row_slot_width", "cell_top_overlap"]),
|
||||||
min=0, max=10, step=0.5,
|
("cap", ["cap_wall", "cap_clearance"]),
|
||||||
help="Height of all wire slots (set 0 for no slots)."),
|
("box", ["box_lip", "wire_clamp_add", "box_wall", "box_clearance",
|
||||||
Param("col_slot_width", "Column slot width (mm)", "number", 8.0, group="holder",
|
"bms_clearance", "box_bottom_clearance",
|
||||||
min=0, max=20, step=0.5),
|
"box_wire_side_clearance", "box_nonwire_side_clearance"]),
|
||||||
Param("row_slot_width", "Row slot width (mm)", "number", 8.0, group="holder",
|
("insulator",["insulator_as_support", "support_z_gap", "insulator_tolerance"]),
|
||||||
min=0, max=20, step=0.5),
|
("bolts", ["bolt_dia", "bolt_head_dia", "bolt_head_thickness",
|
||||||
Param("cell_top_overlap", "Cell top overlap (mm)","number", 3.0, group="holder",
|
"ziptie_width", "ziptie_thickness", "bolt_dia_clearance",
|
||||||
min=0, max=10, step=0.5,
|
"wire_clamp_bolt_dia"]),
|
||||||
help="Opening dia = cell_dia − 2 × this. Welding-window size."),
|
("wires", ["wire_diameter", "clamp_factor", "wire_hole_width",
|
||||||
|
"wire_hole_length", "wire_top_wall", "clamp_plate_height"]),
|
||||||
|
("stacking", ["stacking_pins", "stacking_pin_dia", "stacking_pin_alt_style",
|
||||||
|
"stacking_bolts", "stacking_bolt_dia", "num_pack_stacks",
|
||||||
|
"stacking_pins_tolerance"]),
|
||||||
|
("template", ["template_2d", "template_outline", "template_thickness",
|
||||||
|
"template_margin", "template_mark_dia", "template_hole_style",
|
||||||
|
"template_line_width", "template_engrave_depth",
|
||||||
|
"template_center_mark", "template_center_mark_dia"]),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Tighter numeric input hints (min/max/step) for selected params. Anything
|
||||||
|
# not listed here gets a free-form number input.
|
||||||
|
_NUMBER_HINTS: dict[str, dict[str, float]] = {
|
||||||
|
"cell_dia": {"min": 10, "max": 40, "step": 0.1},
|
||||||
|
"cell_height": {"min": 30, "max": 200, "step": 1},
|
||||||
|
"wall": {"min": 0.2, "max": 3, "step": 0.1},
|
||||||
|
"num_rows": {"min": 1, "max": 50, "step": 1},
|
||||||
|
"num_cols": {"min": 1, "max": 50, "step": 1},
|
||||||
|
"holder_height": {"min": 4, "max": 30, "step": 0.5},
|
||||||
|
"slot_height": {"min": 0, "max": 10, "step": 0.5},
|
||||||
|
"col_slot_width": {"min": 0, "max": 20, "step": 0.5},
|
||||||
|
"row_slot_width": {"min": 0, "max": 20, "step": 0.5},
|
||||||
|
"cell_top_overlap": {"min": 0, "max": 10, "step": 0.5},
|
||||||
|
"cap_wall": {"min": 0.4, "max": 5, "step": 0.1},
|
||||||
|
"box_wall": {"min": 0.4, "max": 10, "step": 0.1},
|
||||||
|
"num_pack_stacks": {"min": 1, "max": 10, "step": 1},
|
||||||
|
}
|
||||||
|
|
||||||
|
_LITERAL_RE = re.compile(
|
||||||
|
r"^\s*([a-z_][a-z0-9_]*)\s*=\s*"
|
||||||
|
r"(?P<rhs>true|false|\"[^\"]*\"|[-+]?\d+(?:\.\d+)?(?:[eE][-+]?\d+)?)\s*;"
|
||||||
|
r"(?:\s*//\s*(?P<comment>.*))?\s*$"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _group_for(name: str) -> str:
|
||||||
|
for grp, names in _GROUP_RULES:
|
||||||
|
if name in names:
|
||||||
|
return grp
|
||||||
|
return "advanced"
|
||||||
|
|
||||||
|
|
||||||
|
def _pretty_label(name: str) -> str:
|
||||||
|
return name.replace("_", " ").capitalize()
|
||||||
|
|
||||||
|
|
||||||
|
def _scan_scad(path: Path) -> list[Param]:
|
||||||
|
out: list[Param] = []
|
||||||
|
seen: set[str] = set()
|
||||||
|
if not path.is_file():
|
||||||
|
return out
|
||||||
|
for raw in path.read_text(encoding="utf-8", errors="replace").splitlines():
|
||||||
|
# Stop at the explicit end-of-config marker so we don't pick up
|
||||||
|
# internal helpers (hextra, spacing, etc.).
|
||||||
|
if "END OF CONFIGURATION" in raw or "NON-Configurable" in raw:
|
||||||
|
break
|
||||||
|
m = _LITERAL_RE.match(raw)
|
||||||
|
if not m:
|
||||||
|
continue
|
||||||
|
name = m.group(1)
|
||||||
|
if name in seen:
|
||||||
|
continue
|
||||||
|
rhs = m.group("rhs")
|
||||||
|
help_text = (m.group("comment") or "").strip() or None
|
||||||
|
|
||||||
|
if rhs in ("true", "false"):
|
||||||
|
kind, default = "bool", (rhs == "true")
|
||||||
|
elif rhs.startswith('"'):
|
||||||
|
literal = rhs[1:-1]
|
||||||
|
if name in _SELECT_OPTIONS:
|
||||||
|
kind, default = "select", literal
|
||||||
|
else:
|
||||||
|
# Plain free-text strings aren't worth a form field.
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
num = float(rhs)
|
||||||
|
default = int(num) if num.is_integer() else num
|
||||||
|
kind = "number"
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
p = Param(
|
||||||
|
name=name, label=_pretty_label(name), kind=kind,
|
||||||
|
default=default, group=_group_for(name), help=help_text,
|
||||||
|
)
|
||||||
|
if kind == "select":
|
||||||
|
p.options = _SELECT_OPTIONS[name]
|
||||||
|
if kind == "number" and name in _NUMBER_HINTS:
|
||||||
|
h = _NUMBER_HINTS[name]
|
||||||
|
p.min, p.max, p.step = h.get("min"), h.get("max"), h.get("step")
|
||||||
|
out.append(p)
|
||||||
|
seen.add(name)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
PARAMS: list[Param] = _scan_scad(SCAD_FILE)
|
||||||
|
|
||||||
|
|
||||||
def default_params() -> dict[str, Any]:
|
def default_params() -> dict[str, Any]:
|
||||||
return {p.name: p.default for p in PARAMS}
|
return {p.name: p.default for p in PARAMS}
|
||||||
|
|||||||
+152
-13
@@ -15,16 +15,16 @@
|
|||||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
cell_dia = 21.2; // Cell diameter default = 18.4 for 18650s **PRINT OUT TEST FIT PIECE STL FIRST**
|
cell_dia = 21.4; // Cell diameter default = 18.4 for 18650s **PRINT OUT TEST FIT PIECE STL FIRST**
|
||||||
cell_height = 70; // Cell height default = 65 for 18650s
|
cell_height = 70; // Cell height default = 65 for 18650s
|
||||||
wall = 0.8; // Wall thickness around a single cell. Make as a multiple of the nozzle diameter. Spacing between cells is twice this amount. default = 1.2
|
wall = 0.2; // Wall thickness around a single cell. Make as a multiple of the nozzle diameter. Spacing between cells is twice this amount. default = 1.2
|
||||||
// If using bought injection molded hexes and printing out the boxes, take the distance between the centers of 2 cells and divide by two for the wall thickness (((((pitch - diameter)/2). Add space for the protuding interlocking tabs in the cap or box clearances.
|
// If using bought injection molded hexes and printing out the boxes, take the distance between the centers of 2 cells and divide by two for the wall thickness (((((pitch - diameter)/2). Add space for the protuding interlocking tabs in the cap or box clearances.
|
||||||
|
|
||||||
num_rows = 6;
|
num_rows = 7;
|
||||||
num_cols = 12;
|
num_cols = 3;
|
||||||
|
|
||||||
holder_height = 10; // Height of cell holder default = 10 (not including slot_height)
|
holder_height = 10; // Height of cell holder default = 10 (not including slot_height)
|
||||||
slot_height = 3; // Height of all slots default = 3 mm (set to 0 for no slots but that allows you to print without support)
|
slot_height = 1; // Height of all slots default = 3 mm (set to 0 for no slots but that allows you to print without support)
|
||||||
|
|
||||||
col_slot_width = 8; // Width of slots between rows default = 8
|
col_slot_width = 8; // Width of slots between rows default = 8
|
||||||
row_slot_width = 8; // Width of slots along rows default = 8
|
row_slot_width = 8; // Width of slots along rows default = 8
|
||||||
@@ -35,39 +35,40 @@ pack_style = "rect"; // "rect" for rectangular pack, "para" for parallelogram, "
|
|||||||
wire_style = "strip"; // "strip" to make space to run nickel strips between cells. Default usage
|
wire_style = "strip"; // "strip" to make space to run nickel strips between cells. Default usage
|
||||||
// "bus" to make space for bus wires between rows
|
// "bus" to make space for bus wires between rows
|
||||||
|
|
||||||
box_style = "both"; // "bolt" for bolting the box pack together
|
box_style = "bolt"; // "bolt" for bolting the box pack together
|
||||||
// "ziptie" for using zipties to fasten the box together. (ziptie heads will stick out),
|
// "ziptie" for using zipties to fasten the box together. (ziptie heads will stick out),
|
||||||
// "both" default: uses bolts for the 4 corners and zipties inbetween. Useful for mounting the pack to something with zipties but while still using bolts to hold it together
|
// "both" default: uses bolts for the 4 corners and zipties inbetween. Useful for mounting the pack to something with zipties but while still using bolts to hold it together
|
||||||
|
|
||||||
part_type = "normal"; // "normal","mirrored", or "both". "assembled" is used for debugging. You'll want a mirrored piece if the tops and bottom are different ( ie. When there are even rows in rectangular style or any number of rows in parallelogram. The Console will tell you if you need a mirrored piece).
|
part_type = "normal"; // "normal","mirrored", or "both". "assembled" is used for debugging. You'll want a mirrored piece if the tops and bottom are different ( ie. When there are even rows in rectangular style or any number of rows in parallelogram. The Console will tell you if you need a mirrored piece).
|
||||||
|
|
||||||
part = "box bottom"; // "holder" to generate cell holders,
|
part = "holder"; // "holder" to generate cell holders,
|
||||||
// "cap" to generate pack end caps,
|
// "cap" to generate pack end caps,
|
||||||
// "box lid" to generate box lid
|
// "box lid" to generate box lid
|
||||||
// "box bottom" for box bottom
|
// "box bottom" for box bottom
|
||||||
// "wire clamp" for strain relief clamp
|
// "wire clamp" for strain relief clamp
|
||||||
// "insulator" for insulator piece to fit over the nickel strips
|
// "insulator" for insulator piece to fit over the nickel strips
|
||||||
// "vertical box section" for vertical battery stacking boxes (print 1 section for every additional stacked pack)
|
// "vertical box section" for vertical battery stacking boxes (print 1 section for every additional stacked pack)
|
||||||
|
// "busbar template" to generate a flat template/drawing of the exact cell positions for making nickel strips / bus bars
|
||||||
|
|
||||||
box_lip = true; // Adds a lip to the box pieces. default = true.
|
box_lip = true; // Adds a lip to the box pieces. default = true.
|
||||||
wire_clamp_add = true; // Adds a wire exit hole out the side of the box lid.
|
wire_clamp_add = true; // Adds a wire exit hole out the side of the box lid.
|
||||||
insulator_as_support = true; // Print the insulator as a part of the holder support material.
|
insulator_as_support = true; // Print the insulator as a part of the holder support material.
|
||||||
|
|
||||||
cap_wall = 1.2; // Cap wall thickness (default = 1.2 recommend to make a multiple of nozzle dia)
|
cap_wall = 0.8; // Cap wall thickness (default = 1.2 recommend to make a multiple of nozzle dia)
|
||||||
cap_clearance = 0.2; // Clearance between holder and caps default = 0.2
|
cap_clearance = 0.2; // Clearance between holder and caps default = 0.2
|
||||||
|
|
||||||
box_wall = 2; // Box wall thickness (default = 2.0 recommend to make at least 4 * multiple of nozzle dia)
|
box_wall = 1.2; // Box wall thickness (default = 2.0 recommend to make at least 4 * multiple of nozzle dia)
|
||||||
box_clearance = 0.2; // Clearance between holder and box default = 0.2
|
box_clearance = 0.2; // Clearance between holder and box default = 0.2
|
||||||
|
|
||||||
|
|
||||||
// Box clearances for wires
|
// Box clearances for wires
|
||||||
bms_clearance = 10; // Vertical space for the battery management system (bms) on top of holders, set to 0 for no extra space
|
bms_clearance = 0; // Vertical space for the battery management system (bms) on top of holders, set to 0 for no extra space
|
||||||
box_bottom_clearance = 0; // Vertical space for wires on bottom of box
|
box_bottom_clearance = 0; // Vertical space for wires on bottom of box
|
||||||
box_wire_side_clearance = 3; // Horizontal space from right side (side with wire hole opening) to the box wall for wires
|
box_wire_side_clearance = 15; // Horizontal space from right side (side with wire hole opening) to the box wall for wires
|
||||||
box_nonwire_side_clearance = 0; // Horizontal space from left side (opposite of wire hole) to the box wall for wires
|
box_nonwire_side_clearance = 0; // Horizontal space from left side (opposite of wire hole) to the box wall for wires
|
||||||
|
|
||||||
support_z_gap = 0.3; // Insulator gap to holder. default 0.3
|
support_z_gap = 0.3; // Insulator gap to holder. default 0.3
|
||||||
insulator_tolerance = 1.5; // How much smaller to make the width of the insulator default 1.5
|
insulator_tolerance = 0.4; // How much smaller to make the width of the insulator default 1.5
|
||||||
insulator_thickness = (slot_height-support_z_gap); // Thickness of insulator
|
insulator_thickness = (slot_height-support_z_gap); // Thickness of insulator
|
||||||
|
|
||||||
wire_diameter = 5; // Diameter of 1 power wire used in the strain relief clamps default = 5 for 10 awg stranded silicon wire
|
wire_diameter = 5; // Diameter of 1 power wire used in the strain relief clamps default = 5 for 10 awg stranded silicon wire
|
||||||
@@ -106,7 +107,7 @@ num_pack_stacks = 1; // How many additional packs you will stack vertically. Aff
|
|||||||
|
|
||||||
cell_top_overlap = 3; // How big the opening overlaps the cell default = 3
|
cell_top_overlap = 3; // How big the opening overlaps the cell default = 3
|
||||||
opening_dia = cell_dia-cell_top_overlap*2; // Circular opening to expose cell
|
opening_dia = cell_dia-cell_top_overlap*2; // Circular opening to expose cell
|
||||||
separation = 1; // Separation between cell top and wire slots (aka tab thickness) default = 1
|
separation = 0.6; // Separation between cell top and wire slots (aka tab thickness) default = 1
|
||||||
wire_hole_width = 15; // Width of wire hole default = 15
|
wire_hole_width = 15; // Width of wire hole default = 15
|
||||||
wire_hole_length = 10; // Length of the wireclamp that sticks out default = 10
|
wire_hole_length = 10; // Length of the wireclamp that sticks out default = 10
|
||||||
wire_top_wall = 4; // Thickness of top wire wall default = 4mm
|
wire_top_wall = 4; // Thickness of top wire wall default = 4mm
|
||||||
@@ -120,6 +121,23 @@ spacer_overhang = box_clearance + 3; // Amount of spacer overhang to hold the
|
|||||||
flip_holders = false; // Mostly used for taking pngs
|
flip_holders = false; // Mostly used for taking pngs
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// BUSBAR / WELDING TEMPLATE OPTIONS (used when part = "busbar template")
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
template_2d = false; // false = printable 3D plate; true = flat 2D drawing for DXF/SVG export (F6 -> Export as DXF / SVG)
|
||||||
|
template_outline = "rect"; // "rect" = rectangular plate (easy to cut into strips); "hull" = follows the pack outline
|
||||||
|
template_thickness = 1.5; // Plate thickness for the 3D version
|
||||||
|
template_margin = 3; // Extra material added around the outermost cells
|
||||||
|
template_mark_dia = cell_dia; // Diameter of the marked circle at each cell. Set to opening_dia to mark only the exposed weld window
|
||||||
|
template_hole_style = "engrave";// "engrave" = ring scribed into the top, plate stays solid (best for tracing/marking)
|
||||||
|
// "through" = holes all the way through (use as a drill / spray-mark jig)
|
||||||
|
// "center" = only a tiny center hole at each cell (precise punch/drill centers)
|
||||||
|
template_line_width = 0.8; // Width of the engraved ring line (make a multiple of nozzle dia)
|
||||||
|
template_engrave_depth = 0.6; // Depth of the engraved ring for "engrave" style
|
||||||
|
template_center_mark = true; // With "engrave": also add a tiny center hole at each cell for punching exact centers
|
||||||
|
template_center_mark_dia = 1; // Diameter of the center mark hole
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// cell_tab_width = 5; // Width of tab that keeps the cell in the holder default = 5
|
// cell_tab_width = 5; // Width of tab that keeps the cell in the holder default = 5
|
||||||
// cell_tab_length = 3; // Approx Length of tab that keeps the cell in the holder default = 3
|
// cell_tab_length = 3; // Approx Length of tab that keeps the cell in the holder default = 3
|
||||||
@@ -388,6 +406,11 @@ wire_clamp_nib_dia = 5;
|
|||||||
{
|
{
|
||||||
vertical_box_section(num_pack_stacks);
|
vertical_box_section(num_pack_stacks);
|
||||||
}
|
}
|
||||||
|
else if(part == "busbar template")
|
||||||
|
{
|
||||||
|
busbar_template();
|
||||||
|
echo_cell_coordinates();
|
||||||
|
}
|
||||||
else if(part == "flipped holder png")
|
else if(part == "flipped holder png")
|
||||||
{
|
{
|
||||||
rotate([0,180,0])
|
rotate([0,180,0])
|
||||||
@@ -1495,3 +1518,119 @@ function get_pin_list_rect(num_rows,num_cols)
|
|||||||
]
|
]
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// BUSBAR / WELDING TEMPLATE
|
||||||
|
// Generates a flat plate (or 2D drawing) with the exact cell positions so it
|
||||||
|
// can be printed, cut, and used as a template for nickel strips / bus bars,
|
||||||
|
// or exported as a dimensioned drawing (DXF/SVG).
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// returns the list of all hex/cell center points for the current pack_style
|
||||||
|
function get_all_hex_centers()
|
||||||
|
= pack_style == "rect" ? get_hex_center_points_rect(num_rows,num_cols)
|
||||||
|
: pack_style == "para" ? get_hex_center_points_para(num_rows,num_cols)
|
||||||
|
: get_hex_center_points_tria(num_rows,num_cols);
|
||||||
|
|
||||||
|
// Echoes every cell center coordinate plus key dimensions to the console.
|
||||||
|
module echo_cell_coordinates()
|
||||||
|
{
|
||||||
|
centers = get_all_hex_centers();
|
||||||
|
echo("===== CELL CENTER COORDINATES (mm) =====");
|
||||||
|
echo(str("pack_style = ", pack_style, ", rows = ", num_rows, ", cols = ", num_cols, ", total cells = ", len(centers)));
|
||||||
|
echo(str("Origin (0,0) = center of first cell. cell_dia = ", cell_dia, ", opening_dia = ", opening_dia));
|
||||||
|
echo(str("Pitch X (same row, center-to-center) = ", hex_w));
|
||||||
|
echo(str("Row offset X (between rows) = ", hex_w/2));
|
||||||
|
echo(str("Pitch Y (row to row, center-to-center)= ", hex_pt*1.5));
|
||||||
|
for(i = [0:len(centers)-1])
|
||||||
|
echo(str("Cell ", i+1, ": x = ", centers[i].x, " , y = ", centers[i].y));
|
||||||
|
echo("========================================");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Small flat ring used for engraving cell outlines
|
||||||
|
module template_ring(outer_d, line_width, h)
|
||||||
|
{
|
||||||
|
difference()
|
||||||
|
{
|
||||||
|
cylinder(d = outer_d, h = h);
|
||||||
|
translate([0,0,-extra])
|
||||||
|
cylinder(d = max(outer_d - 2*line_width, 0.1), h = h + 2*extra);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2D footprint of the template plate
|
||||||
|
module template_plate_shape(centers)
|
||||||
|
{
|
||||||
|
if(template_outline == "hull")
|
||||||
|
{
|
||||||
|
hull()
|
||||||
|
for(p = centers)
|
||||||
|
translate([p.x,p.y])
|
||||||
|
offset(r = template_margin)
|
||||||
|
polygon([for(a=[0:5])[hex_pt*sin(a*60),hex_pt*cos(a*60)]]);
|
||||||
|
}
|
||||||
|
else // rectangular bounding plate
|
||||||
|
{
|
||||||
|
xs = [for(p = centers) p.x];
|
||||||
|
ys = [for(p = centers) p.y];
|
||||||
|
min_x = min(xs) - cell_radius - template_margin;
|
||||||
|
max_x = max(xs) + cell_radius + template_margin;
|
||||||
|
min_y = min(ys) - cell_radius - template_margin;
|
||||||
|
max_y = max(ys) + cell_radius + template_margin;
|
||||||
|
translate([min_x, min_y])
|
||||||
|
square([max_x - min_x, max_y - min_y]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flat 2D drawing (render with F6, then File -> Export -> Export as DXF / SVG)
|
||||||
|
module busbar_template_2d(centers)
|
||||||
|
{
|
||||||
|
difference()
|
||||||
|
{
|
||||||
|
template_plate_shape(centers);
|
||||||
|
for(p = centers)
|
||||||
|
translate([p.x,p.y])
|
||||||
|
circle(d = template_mark_dia);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Printable 3D plate
|
||||||
|
module busbar_template_3d(centers)
|
||||||
|
{
|
||||||
|
difference()
|
||||||
|
{
|
||||||
|
// Plate
|
||||||
|
linear_extrude(height = template_thickness)
|
||||||
|
template_plate_shape(centers);
|
||||||
|
|
||||||
|
// Per-cell markings
|
||||||
|
for(p = centers)
|
||||||
|
translate([p.x,p.y,0])
|
||||||
|
{
|
||||||
|
if(template_hole_style == "through")
|
||||||
|
translate([0,0,-extra])
|
||||||
|
cylinder(d = template_mark_dia, h = template_thickness + 2*extra);
|
||||||
|
else if(template_hole_style == "engrave")
|
||||||
|
translate([0,0,template_thickness - template_engrave_depth])
|
||||||
|
template_ring(template_mark_dia, template_line_width, template_engrave_depth + extra);
|
||||||
|
else if(template_hole_style == "center")
|
||||||
|
translate([0,0,-extra])
|
||||||
|
cylinder(d = template_center_mark_dia, h = template_thickness + 2*extra);
|
||||||
|
|
||||||
|
// Optional center mark (only useful when material remains, i.e. "engrave")
|
||||||
|
if(template_center_mark && template_hole_style == "engrave")
|
||||||
|
translate([0,0,-extra])
|
||||||
|
cylinder(d = template_center_mark_dia, h = template_thickness + 2*extra);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module busbar_template()
|
||||||
|
{
|
||||||
|
centers = get_all_hex_centers();
|
||||||
|
if(template_2d)
|
||||||
|
busbar_template_2d(centers);
|
||||||
|
else
|
||||||
|
busbar_template_3d(centers);
|
||||||
|
}
|
||||||
|
|||||||
@@ -31,6 +31,28 @@
|
|||||||
letter-spacing: 0.05em;
|
letter-spacing: 0.05em;
|
||||||
margin: 0 0 6px;
|
margin: 0 0 6px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
.param-group-title::before {
|
||||||
|
content: "\25BC"; /* ▼ */
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 8px;
|
||||||
|
transition: transform 0.12s ease;
|
||||||
|
}
|
||||||
|
.param-group.collapsed .param-group-title::before {
|
||||||
|
transform: rotate(-90deg); /* ▶ */
|
||||||
|
}
|
||||||
|
.param-group.collapsed .param-group-body {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.param-group-body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.param-row {
|
.param-row {
|
||||||
|
|||||||
+27
-2
@@ -53,13 +53,31 @@ function _whenViewerReady(cb) {
|
|||||||
|
|
||||||
// ----- form generation ------------------------------------------------------
|
// ----- form generation ------------------------------------------------------
|
||||||
|
|
||||||
const GROUP_ORDER = ["part", "cell", "size", "holder"];
|
const GROUP_ORDER = [
|
||||||
|
"part", "cell", "size", "holder",
|
||||||
|
"cap", "box", "insulator",
|
||||||
|
"bolts", "wires",
|
||||||
|
"stacking", "template", "advanced",
|
||||||
|
];
|
||||||
const GROUP_LABELS = {
|
const GROUP_LABELS = {
|
||||||
part: "Part / pack",
|
part: "Part / pack",
|
||||||
cell: "Cell",
|
cell: "Cell",
|
||||||
size: "Pack size",
|
size: "Pack size",
|
||||||
holder: "Holder",
|
holder: "Holder",
|
||||||
|
cap: "Cap",
|
||||||
|
box: "Box",
|
||||||
|
insulator: "Insulator",
|
||||||
|
bolts: "Bolts & zipties",
|
||||||
|
wires: "Wires & clamp",
|
||||||
|
stacking: "Vertical stacking",
|
||||||
|
template: "Busbar template",
|
||||||
|
advanced: "Advanced",
|
||||||
};
|
};
|
||||||
|
// Groups that start collapsed (user opens with a click). Basics stay open.
|
||||||
|
const GROUPS_COLLAPSED = new Set([
|
||||||
|
"cap", "box", "insulator", "bolts", "wires",
|
||||||
|
"stacking", "template", "advanced",
|
||||||
|
]);
|
||||||
|
|
||||||
function _renderForm(schema, defaults) {
|
function _renderForm(schema, defaults) {
|
||||||
const root = $("param-form");
|
const root = $("param-form");
|
||||||
@@ -77,11 +95,18 @@ function _renderForm(schema, defaults) {
|
|||||||
for (const g of groups) {
|
for (const g of groups) {
|
||||||
const wrap = document.createElement("div");
|
const wrap = document.createElement("div");
|
||||||
wrap.className = "param-group";
|
wrap.className = "param-group";
|
||||||
|
if (GROUPS_COLLAPSED.has(g)) wrap.classList.add("collapsed");
|
||||||
|
|
||||||
const title = document.createElement("h3");
|
const title = document.createElement("h3");
|
||||||
title.className = "param-group-title";
|
title.className = "param-group-title";
|
||||||
title.textContent = GROUP_LABELS[g] || g;
|
title.textContent = GROUP_LABELS[g] || g;
|
||||||
|
title.addEventListener("click", () => wrap.classList.toggle("collapsed"));
|
||||||
wrap.appendChild(title);
|
wrap.appendChild(title);
|
||||||
|
|
||||||
|
const body = document.createElement("div");
|
||||||
|
body.className = "param-group-body";
|
||||||
|
wrap.appendChild(body);
|
||||||
|
|
||||||
for (const p of byGroup.get(g)) {
|
for (const p of byGroup.get(g)) {
|
||||||
const row = document.createElement("div");
|
const row = document.createElement("div");
|
||||||
row.className = "param-row";
|
row.className = "param-row";
|
||||||
@@ -123,7 +148,7 @@ function _renderForm(schema, defaults) {
|
|||||||
h.textContent = p.help;
|
h.textContent = p.help;
|
||||||
row.appendChild(h);
|
row.appendChild(h);
|
||||||
}
|
}
|
||||||
wrap.appendChild(row);
|
body.appendChild(row);
|
||||||
}
|
}
|
||||||
root.appendChild(wrap);
|
root.appendChild(wrap);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user