Files
wenil d8cb0dc06d Initial commit: Busbar Designer
Web tool for designing nickel/copper busbars over cylindrical-cell battery
packs (21700, 18650) in hex holders. Flask + build123d backend exports
STEP/DXF/SVG; vanilla JS frontend with live preview, multi-project SQLite
persistence, snapshot history.

Deploy scripts in deploy/ (proxmox-lxc.sh, install.sh, update.sh).
2026-05-24 18:59:50 +03:00

168 lines
5.1 KiB
Python
Raw Permalink 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.
"""Sanity tests for busbar_export — exercise STEP/DXF/SVG writers end-to-end."""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from busbar_export import to_step, to_dxf, to_svg
def _payload():
return {
"extrude": False,
"busbars": [
{
"name": "P1",
"color": "#ff8800",
"strip_width": 8.0,
"pad_radius": 7.0,
"hole_radius": 5.0,
"cells": [
{"id": 1, "x": 0.0, "y": 0.0},
{"id": 2, "x": 22.8, "y": 0.0},
{"id": 3, "x": 45.6, "y": 0.0},
],
}
],
}
def test_step_export_is_iso_10303():
data = to_step(_payload())
text = data[:200].decode("ascii", errors="ignore")
assert "ISO-10303-21" in text, f"STEP header missing, got: {text!r}"
assert len(data) > 500
def test_dxf_export_is_nonempty():
data = to_dxf(_payload())
assert b"SECTION" in data[:2000]
def test_svg_export_is_svg():
data = to_svg(_payload())
assert b"<svg" in data[:2000]
def test_extruded_step_includes_solid():
payload = _payload()
payload["extrude"] = True
payload["thickness"] = 0.2
data = to_step(payload)
assert b"MANIFOLD_SOLID_BREP" in data or b"CLOSED_SHELL" in data
def test_panel_5p_row_is_stadium():
"""5 cells in a row → collinear hull → stadium-shaped panel."""
payload = {
"busbars": [{
"name": "5P",
"shape": "panel",
"pad_radius": 10.0,
"hole_radius": 5.0,
"cells": [{"id": i + 1, "x": i * 22.8, "y": 0.0} for i in range(5)],
}]
}
data = to_step(payload)
assert b"ISO-10303-21" in data[:200]
assert len(data) > 500
def test_panel_grid_uses_convex_hull():
"""4-cell square → 4-vertex hull → rounded rectangle."""
payload = {
"busbars": [{
"name": "4P",
"shape": "panel",
"pad_radius": 10.0,
"hole_radius": 5.0,
"cells": [
{"id": 1, "x": 0.0, "y": 0.0},
{"id": 2, "x": 22.8, "y": 0.0},
{"id": 3, "x": 22.8, "y": 19.746},
{"id": 4, "x": 0.0, "y": 19.746},
],
}]
}
data = to_step(payload)
assert b"ISO-10303-21" in data[:200]
def test_panel_single_cell_is_disc_with_hole():
payload = {
"busbars": [{
"name": "1P",
"shape": "panel",
"pad_radius": 10.0,
"hole_radius": 5.0,
"cells": [{"id": 1, "x": 0.0, "y": 0.0}],
}]
}
data = to_step(payload)
assert b"ISO-10303-21" in data[:200]
def test_wire_shape_still_works():
payload = {
"busbars": [{
"name": "wire",
"shape": "wire",
"strip_width": 6.0,
"pad_radius": 7.0,
"hole_radius": 5.0,
"cells": [{"x": 0, "y": 0}, {"x": 22.8, "y": 0}],
}]
}
data = to_step(payload)
assert b"ISO-10303-21" in data[:200]
def test_panel_L_shape_has_no_diagonal_bridge():
"""L-shape (7 cells in column + 2 cells across top) must not bridge across
non-selected cells. The convex-hull approach would have, but neighbor-edge
must not — verify by checking that the area of the resulting face is close
to the sum of stadium-chain segments, not the area of the L's bounding box.
"""
from busbar_export import parse_payload, busbar_sketch
cells = [{"x": 0, "y": i * 19.75} for i in range(7)]
cells += [{"x": 22.8, "y": 6 * 19.75}, {"x": 45.6, "y": 6 * 19.75}]
payload = {"busbars": [{
"name": "L", "shape": "panel",
"pad_radius": 10.0, "hole_radius": 6.0,
"hole_shape": "cross", "slit_width": 1.8,
"cells": cells,
}]}
busbars, *_ = parse_payload(payload)
face = busbar_sketch(busbars[0])
area = face.area
# Pad disc area = π·10² ≈ 314 per cell, 9 cells → 2826 max if all disjoint.
# Bridges add some, holes subtract some. The L's bounding box is
# 65.6 × 138.25 ≈ 9070 — convex hull would be at least ~4500. Neighbor
# chain should be well under 3500.
assert 2000 < area < 3500, f"area {area} suggests convex-hull bridging"
def test_cross_slit_punches_through():
"""Cross slit must remove area from the panel (vs. no holes baseline)."""
from busbar_export import parse_payload, busbar_sketch
base = {
"busbars": [{
"name": "P", "shape": "panel", "pad_radius": 10.0,
"hole_radius": 6.0, "hole_shape": "cross", "slit_width": 1.8,
"cells": [{"x": 0, "y": 0}, {"x": 22.8, "y": 0}],
}]
}
with_cross, *_ = parse_payload(base)
area_cross = busbar_sketch(with_cross[0]).area
# Same payload, tiny hole — should be nearly the same area as un-punched.
base["busbars"][0]["hole_radius"] = 0.1
base["busbars"][0]["slit_width"] = 0.1
tiny, *_ = parse_payload(base)
area_tiny = busbar_sketch(tiny[0]).area
assert area_tiny > area_cross + 50, \
f"cross didn't punch much: tiny={area_tiny:.1f}, cross={area_cross:.1f}"