Corrected call: the earlier "leave kerf to Kiri's own tool-diameter
field" choice wasn't wrong about double-compensation risk (doing both
at once genuinely would double-offset the cut), but replicating the
already-proven controller-side approach exactly is the lower-risk
starting point - it doesn't depend on whoever runs a job correctly
dialing in a matching diameter in Kiri every time, and it's known-good.
No code change needed - G41 goes in gcodeSpindle right before M07, G40
in gcodeSpindleOff right after M08, using the per-cut hook already
built. Matches the proven file's exact bracket order per cut.
CAM mode's exporter treats the tool as continuously on from the first
cut to the last (correct for a spinning router bit, just lifted via Z
between cuts) - it has no way to turn a tool off between separate
disconnected cut regions. A real plasma torch may need to be genuinely
off during rapid travel between cuts, not just raised, matching
CMMS/ATG's own SteelTailor table's proven-working G-code (M07/M08
bracketing every individual feature, reverse-engineered from a real
working file, not a manual's spec).
Added a device.gcodeSpindleOff hook: when a profile sets it to a
non-empty array, every disconnected toolpath group re-triggers the
spindle-on command (forcing newOp regardless of operation-type changes)
and fires gcodeSpindleOff right after each group finishes - mirrors the
existing laser on/off mechanism, generalized. Every device profile that
doesn't set gcodeSpindleOff (i.e. every profile that existed before
this) behaves exactly as before - this is a pure opt-in addition, not a
behavior change to anything else.
New profile: SteelTailor.Plasma.json - G90/absolute (controller's own
touchscreen sets the origin, so gcodePre doesn't redefine it), M07/M08
via the new per-cut mechanism, no G41/G40 cutter comp (Kiri already
offsets toolpaths in software via the operation's own tool-diameter
setting - stacking controller-side comp on top would double-compensate).
Bed size/max height are placeholders pending real numbers.
restoreOrCreate() revives whatever document this browser last had open in
IndexedDB - the opposite of what a job-scoped CMMS launch wants (load
this exact file, not whatever was last open in this browser, per the
comment already above in init()). It only looked fixed on a browser with
no prior Mesh:Tool history (falls through to a blank "Untitled" doc in
that case) - any browser with real history would have shown a stale
name instead of the real filename.
document_new({name}) is what actually creates a fresh document with a
given name (mirrors what File > New does), so this uses that instead,
and drops the now-redundant separate set_doc_name() call since
document_new() already does that itself.
restore_space() (the normal boot path, restoring a previous IndexedDB
session) is intentionally skipped for a CMMS-scoped launch - but its
finalization tail (hide the loading curtain via boot_done(), enable
handles, publish app_ready) only ever ran inside restore_space() itself.
Skipping it meant the curtain never went away even after cmms_load_from_url
fetched and loaded the file successfully underneath it. app_ready
subscribers never fired either - mesh/history.js's undo-tracking enable,
mesh/build.js's doc-name label - a second, less visible bug from the same
cause.
Wrapped in try/finally so this always runs, even on a failed fetch -
a file that can't load shouldn't leave the user stuck behind the
curtain either. Also calls api.document.restoreOrCreate() first, since
restore_space() always does that before any file-load in normal use.
A parent iframe cannot inject window.MESH_TOOL_CONFIG into this app's
window before init() runs - by the time the iframe's load event fires
(the only cross-frame signal available for a same-origin page this app's
own server generates, not the CMMS), init() has already executed and
already decided to call restore_space() instead. Query params are read
synchronously during init() itself, before the existing 'hide url params'
cleanup strips them - self.MESH_TOOL_CONFIG is still set from them so
util.download() (src/mesh/build.js) has one stable place to read from.
Additive only, gated entirely on window.MESH_TOOL_CONFIG being present (set
by the CMMS's public/mesh-edit.php host page, absent everywhere else -
standalone use is unaffected).
- src/main/mesh.js: cmms_load_from_url() fetches bytes from the
host-provided loadUrl and feeds them through the existing load_files()
path (same one drag-and-drop and the Import button use) - skips
restore_space() when embedded, since job-scoped launch means "load this
exact file", not whatever was last open in this browser's IndexedDB.
- src/mesh/build.js: util.download() calls cmms_save_to_host() instead of
triggering a browser download when embedded - POSTs to the same
job/manufacturing_project-scoped save endpoint pattern as the CAD editor
fork's post_bytes_to_host().
- Update base image from node:18-slim to node:22-slim (matching
package.json engines requirement)
- Add git to apt-get install (needed by npm dependencies)
- Add webpack-ext step (builds external bundles)
- Add mkdir -p src/pack (prevents race condition)
- Add pack-prod step (builds application bundles)
- Reorder: build steps before gs-app-server install
Tested on ARM64 (Raspberry Pi 4, Docker 29.4.0) and x86_64 (Ubuntu
24.04, Docker 28.0.4). Without these changes, the container starts
but serves an incomplete app that never finishes loading.