From 7512393ef4f8982b82f38164ee5278f2f74edc94 Mon Sep 17 00:00:00 2001 From: wenil Date: Mon, 25 May 2026 14:04:19 +0300 Subject: [PATCH] busbars: FDM test-print export (extruded STL + STEP) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a way to print a busbar on a 3D printer to physically verify sizing before laser-cutting nickel/copper. The existing "Extrude solid + 0.2mm" path stays for thin-strip STEP — slicers can't print 0.2mm sheet — so the FDM path has its own thickness input (default 2mm, min 0.5mm). Backend: - busbar_export.py: new to_stl() writer. Forces extrude_flag = True (STL is inherently 3D) and bumps thickness up to 2mm if the incoming value is <0.5mm. Registers under WRITERS["stl"] so the existing /api/export/ route serves it for free. UI: - index.html: new "FDM test print" block under the Params panel with its own thickness input and two buttons (STL, STEP). The existing 'Extrude solid' checkbox + 0.2mm thickness keep driving plain "Export STEP". - styles.css: .fdm-block / .fdm-row / .fdm-buttons styles matching the existing panel typography. - app.js: _exportFdm(fmt) reuses Exporter.exportFormat with a shallow-merged params override ({extrude: true, thickness: fdmT}), so the on-the-fly request gets the FDM settings without mutating the live params state. Verified: STL render of a 3-cell strip @ 2mm => 73KB binary STL (opens cleanly in slicers); STEP @ 2mm => 160KB ISO-10303-21 solid; existing flat STEP path unchanged at 15KB. --- busbar_export.py | 22 ++++++++++++++++++++++ static/index.html | 18 +++++++++++++++++- static/js/app.js | 10 ++++++++++ static/styles.css | 30 ++++++++++++++++++++++++++++++ 4 files changed, 79 insertions(+), 1 deletion(-) diff --git a/busbar_export.py b/busbar_export.py index 602e4a7..d2d8a03 100644 --- a/busbar_export.py +++ b/busbar_export.py @@ -46,6 +46,7 @@ from build123d import ( Sketch, add, export_step, + export_stl, extrude, ) @@ -299,8 +300,29 @@ def to_svg(payload: dict) -> bytes: return path.read_bytes() +def to_stl(payload: dict) -> bytes: + """STL with the busbars extruded to a printable plate. + + STL is inherently 3D, so extrusion is forced regardless of the payload + flag. Thickness defaults to 2 mm when missing or <0.5 mm (slicers can't + do 0.2 mm sheet — that figure is for the 'thin nickel strip' STEP case). + """ + busbars, extrude_flag, thickness = parse_payload(payload) + if thickness < 0.5: + thickness = 2.0 + shapes = build_shapes(busbars, extrude_flag=True, thickness=thickness) + compound = _as_compound(shapes) + with TemporaryDirectory() as tmp: + path = Path(tmp) / "busbars.stl" + # build123d's export_stl takes a single shape; the Compound carries + # all busbars together so the slicer sees them as one job. + export_stl(compound, str(path)) + return path.read_bytes() + + WRITERS = { "step": (to_step, "application/step", "step"), "dxf": (to_dxf, "image/vnd.dxf", "dxf"), "svg": (to_svg, "image/svg+xml", "svg"), + "stl": (to_stl, "model/stl", "stl"), } diff --git a/static/index.html b/static/index.html index 1ccbd91..c8ee782 100644 --- a/static/index.html +++ b/static/index.html @@ -115,7 +115,23 @@ - + + +
+

+ FDM test print +

+
+ +
+ + +
+
diff --git a/static/js/app.js b/static/js/app.js index f03b378..4ea9aad 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -157,6 +157,16 @@ $("btn-export-dxf" ).addEventListener("click", () => Exporter.exportFormat("dxf", state, params)); $("btn-export-svg" ).addEventListener("click", () => Exporter.exportFormat("svg", state, params)); + // ---- FDM test print ----------------------------------------------------- + // Forces extrusion at the FDM thickness, regardless of the 'Extrude solid' + // checkbox above (which keeps owning the thin nickel-strip case). + function _exportFdm(fmt) { + const t = Math.max(0.5, +$("p-fdm-thickness").value || 2); + Exporter.exportFormat(fmt, state, { ...params, extrude: true, thickness: t }); + } + $("btn-export-fdm-stl") .addEventListener("click", () => _exportFdm("stl")); + $("btn-export-fdm-step").addEventListener("click", () => _exportFdm("step")); + // ---- viewport init ------------------------------------------------------ Viewport.init($("viewport"), state, params, { onCellClick: (cellId, mods) => { diff --git a/static/styles.css b/static/styles.css index 24e9c0b..7ad5de2 100644 --- a/static/styles.css +++ b/static/styles.css @@ -420,6 +420,36 @@ label.checkbox { color: var(--text); } +/* FDM test-print block inside the Params panel */ +.fdm-block { + margin-top: 10px; + padding-top: 10px; + border-top: 1px solid var(--border); +} +.fdm-title { + font-size: 11px; + font-weight: 600; + color: var(--muted); + text-transform: uppercase; + letter-spacing: 0.05em; + margin: 0 0 6px; +} +.fdm-row { + display: flex; + align-items: end; + gap: 10px; +} +.fdm-row label { + flex: 1; +} +.fdm-buttons { + display: flex; + gap: 4px; +} +.fdm-buttons button { + min-width: 50px; +} + input[type=number], input[type=text], select, textarea { background: var(--bg); color: var(--text);