Compare commits
40 Commits
2bff22e0d9
...
27d2316f78
| Author | SHA1 | Date | |
|---|---|---|---|
| 27d2316f78 | |||
| 74617cc3fa | |||
| 4133d202f0 | |||
| e6f55d775f | |||
| f537a8fa42 | |||
| a1c34e454e | |||
| 25268404cb | |||
| 404cb1344c | |||
| 5b45491e55 | |||
| e80ecd4b7a | |||
| 7d57bce5de | |||
| 75818c1379 | |||
| 8e8a319d78 | |||
| ddc9a4e0f3 | |||
| 4e4a9c667f | |||
| 48a0f61b4a | |||
| b85444b858 | |||
| 405cc0b011 | |||
| a60826bfce | |||
| 083b117c21 | |||
| 3ca213a889 | |||
| aeb290b885 | |||
| b2b8bd6b4f | |||
| b989de23ae | |||
| 56322115f9 | |||
| 4ac2f6fbf6 | |||
| 8137cce488 | |||
| ccd6b67e35 | |||
| f96329d67f | |||
| 6242b415ff | |||
| 0c0da13681 | |||
| 5cd5cb6869 | |||
| 69de3305c9 | |||
| d40a6422e8 | |||
| a158f66071 | |||
| f204b7f113 | |||
| 633b8fd0ed | |||
| 558960b2f2 | |||
| 37daf58796 | |||
| a829984300 |
1634
text10.txt
1634
text10.txt
File diff suppressed because it is too large
Load Diff
1145
text11.txt
Normal file
1145
text11.txt
Normal file
File diff suppressed because it is too large
Load Diff
@@ -60,6 +60,19 @@ The current OPFS host-boundary validation command is:
|
||||
wasm-port/tests/opfs/node/verify_file_service.sh
|
||||
```
|
||||
|
||||
The current UI helper Node smoke validation command is:
|
||||
|
||||
```bash
|
||||
wasm-port/tests/ui/node/verify_ui_node_smokes.sh
|
||||
```
|
||||
|
||||
UI helper docs:
|
||||
|
||||
- `wasm-port/docs/ui-run-summary-helpers.md`
|
||||
- `wasm-port/docs/ui-session-summary-helpers.md`
|
||||
- `wasm-port/docs/ui-machine-file-helpers.md`
|
||||
- `wasm-port/docs/ui-panel-state-summary.md`
|
||||
|
||||
The current browser smoke validation command is:
|
||||
|
||||
```bash
|
||||
@@ -102,6 +115,7 @@ wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.sh
|
||||
wasm-port/tests/wasm/node/verify_nc_files_wasm.sh
|
||||
wasm-port/tests/wasm/node/verify_tp_wasm.sh
|
||||
wasm-port/tests/opfs/node/verify_file_service.sh
|
||||
wasm-port/tests/ui/node/verify_ui_node_smokes.sh
|
||||
wasm-port/tests/browser/verify_ini_panel_browser.sh
|
||||
wasm-port/tests/browser/verify_interp_browser.sh
|
||||
wasm-port/tests/host/verify_host_smokes.sh
|
||||
|
||||
68
wasm-port/docs/ui-machine-file-helpers.md
Normal file
68
wasm-port/docs/ui-machine-file-helpers.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# UI Machine File Helpers
|
||||
|
||||
## Purpose
|
||||
|
||||
`runtime/ui/ini-panel/machine-file-summary.js` contains small host-boundary
|
||||
helpers for summarizing machine text file save/load actions. These helpers are
|
||||
pure so browser UI panels, SDK-facing wrappers, and Node smoke tests can share
|
||||
the same data shape without parsing log text.
|
||||
|
||||
The helpers do not implement CNC behavior. They only describe OPFS path mapping
|
||||
and captured text lengths for machine files staged by the UI.
|
||||
|
||||
## `createMachineFileSaveSummary(paths, gcodePath)`
|
||||
|
||||
`paths` is the object returned by `saveMachineTextFiles(...)`.
|
||||
`gcodePath` is the OPFS path returned by `saveGcodeProgram(...)`.
|
||||
|
||||
The returned summary has this shape:
|
||||
|
||||
```js
|
||||
{
|
||||
iniOpfsPath: "linuxcnc/machines/xyzab-tdr/machine.ini",
|
||||
toolTableOpfsPath: "linuxcnc/machines/xyzab-tdr/tool.tbl",
|
||||
parameterOpfsPath: "linuxcnc/machines/xyzab-tdr/linuxcnc.var",
|
||||
gcodeOpfsPath: "linuxcnc/gcode/ui-session.ngc"
|
||||
}
|
||||
```
|
||||
|
||||
## `createMachineFileLoadSummary(files)`
|
||||
|
||||
`files` is the object returned by `loadMachineTextFiles(...)`.
|
||||
|
||||
The returned summary has this shape:
|
||||
|
||||
```js
|
||||
{
|
||||
iniBytes: 26,
|
||||
toolTableBytes: 13,
|
||||
parameterBytes: 7
|
||||
}
|
||||
```
|
||||
|
||||
## Log Helpers
|
||||
|
||||
- `machineFileSaveLogLines(summary)` renders the staged OPFS paths.
|
||||
- `machineFileLoadLogLines(summary)` renders the loaded byte lengths.
|
||||
|
||||
## Boundary Rules
|
||||
|
||||
- Do not parse G-code text in these helpers.
|
||||
- Do not infer tool, parameter, kinematics, planner, or modal semantics.
|
||||
- Do not derive new CNC behavior from the staged file names or lengths.
|
||||
- Keep new fields limited to host/runtime boundary facts already produced by
|
||||
the LinuxCNC-owned runtime or UI staging layer.
|
||||
|
||||
## Validation
|
||||
|
||||
Run the focused Node smoke after changing these helpers:
|
||||
|
||||
```bash
|
||||
wasm-port/tests/ui/node/verify_ini_panel_machine_file_summary.sh
|
||||
```
|
||||
|
||||
The aggregate UI Node smoke is:
|
||||
|
||||
```bash
|
||||
wasm-port/tests/ui/node/verify_ui_node_smokes.sh
|
||||
```
|
||||
201
wasm-port/docs/ui-panel-state-summary.md
Normal file
201
wasm-port/docs/ui-panel-state-summary.md
Normal file
@@ -0,0 +1,201 @@
|
||||
# UI Panel State Summary
|
||||
|
||||
## Purpose
|
||||
|
||||
`runtime/ui/ini-panel/panel-state-summary.js` contains a pure helper for
|
||||
bundling the already-observed host/runtime boundary state of the INI panel into
|
||||
a single machine-readable snapshot.
|
||||
|
||||
The helper does not implement CNC behavior. It only packages the summary
|
||||
objects produced by the run/session/machine-file helpers and the visible badge
|
||||
state so browser tests or future UI consumers can compare panel state without
|
||||
reading ad hoc DOM text.
|
||||
|
||||
Upper-level UI/API consumers should read `createMachineSessionStateReport(...)`
|
||||
or `window.linuxCncIniPanelApi.getMachineSessionStateReport()` rather than
|
||||
scraping DOM nodes or depending on `window.linuxCncIniPanelMachineSessionState`
|
||||
directly. The legacy `window.getMachineSessionStateReport()` and
|
||||
`window.getMachineSessionStateReportExport()` globals remain as compatibility
|
||||
shims.
|
||||
|
||||
For control-page polling, use
|
||||
`window.linuxCncIniPanelApi.getMachineSessionWorkflowStatus()`. It returns a
|
||||
smaller workflow-facing object derived from the same report data.
|
||||
For a single read call, use `window.linuxCncIniPanelApi.getMachineSessionStateBundle()`.
|
||||
For direct rendering, use `window.linuxCncIniPanelApi.getMachineSessionControlView()`.
|
||||
The panel also exposes
|
||||
`window.linuxCncIniPanelApi.renderMachineSessionControlView(container)`, which
|
||||
renders the current view into a supplied DOM container.
|
||||
|
||||
## `createIniPanelStateSummary(input)`
|
||||
|
||||
`input` may contain:
|
||||
|
||||
```js
|
||||
{
|
||||
badges: { ini: "INI WASM: ready", interp: "Interpreter WASM: ready", opfs: "OPFS: ready" },
|
||||
machine: { machine: "xyzab-tdr", kinematics: "trivkins" },
|
||||
machineFiles: { iniOpfsPath: "...", toolTableOpfsPath: "...", parameterOpfsPath: "...", gcodeOpfsPath: "..." },
|
||||
sessionSnapshot: { snapshotLabel: "...", workflow: "save-session-snapshot" },
|
||||
sessionLoad: { iniOpfsPath: "...", iniWasmPath: "..." },
|
||||
run: { runStatus: "ok", programSource: "snapshot" },
|
||||
fiveaxisRemap: "ok"
|
||||
}
|
||||
```
|
||||
|
||||
The returned snapshot preserves those nested objects and normalizes missing
|
||||
badge entries to `null`.
|
||||
|
||||
## `createMachineSessionStateSnapshot(input)`
|
||||
|
||||
This helper builds the richer state snapshot used by the INI panel and exposed
|
||||
as `window.linuxCncIniPanelMachineSessionState`.
|
||||
|
||||
It preserves the base panel state and adds:
|
||||
|
||||
```js
|
||||
{
|
||||
selectedProgram: { source: "snapshot", opfsPath: "...", wasmPath: "..." },
|
||||
fields: {
|
||||
sessionIni: "/work/session-machine.ini",
|
||||
sessionParameters: "/work/session-linuxcnc.var",
|
||||
sessionToolTable: "/work/session-tool.tbl",
|
||||
sessionGcode: "linuxcnc/gcode/ui-session.ngc",
|
||||
sessionSnapshot: "ui-machine-session/ui-machine-session.json",
|
||||
runStatus: "ok",
|
||||
runSource: "snapshot",
|
||||
runSnapshot: "ui-machine-session/ui-machine-session.json",
|
||||
runWorkflow: "save-session-snapshot",
|
||||
runOpfsPath: "linuxcnc/gcode/ui-session.ngc",
|
||||
runWasmPath: "/work/ui-session.ngc"
|
||||
},
|
||||
lastAction: "run-gcode",
|
||||
error: null
|
||||
}
|
||||
```
|
||||
|
||||
The INI panel updates this state after save, restore, load, run, and error
|
||||
paths so browser tests and future API consumers can read one machine-readable
|
||||
state object instead of collecting DOM fields and log text.
|
||||
|
||||
## `createMachineSessionStateReport(state)`
|
||||
|
||||
This helper converts the richer state snapshot into a stable consumer report
|
||||
with four sections:
|
||||
|
||||
```js
|
||||
{
|
||||
readiness: { ini: "...", interp: "...", opfs: "..." },
|
||||
session: { snapshotLabel: "...", workflow: "...", ini: "...", parameters: "...", toolTable: "...", gcode: "..." },
|
||||
loaded: { iniWasmPath: "...", parameterWasmPath: "...", toolTableWasmPath: "..." },
|
||||
program: { source: "snapshot", opfsPath: "...", wasmPath: "..." },
|
||||
run: { status: "ok", canonicalEventCount: 2, motionSnapshotCount: 2, iniWasmPath: "..." },
|
||||
status: { lastAction: "run-gcode", error: null, fiveaxisRemap: "ok" }
|
||||
}
|
||||
```
|
||||
|
||||
The report is intended for upper-level UI consumers or export paths that want a
|
||||
stable summary shape without touching DOM nodes.
|
||||
|
||||
## `createMachineSessionStateReportExport(state, options)`
|
||||
|
||||
This helper wraps the report in a stable export payload:
|
||||
|
||||
```js
|
||||
{
|
||||
filename: "machine-session-state-report.json",
|
||||
mediaType: "application/json",
|
||||
report: { ... },
|
||||
text: "{\n ...\n}\n"
|
||||
}
|
||||
```
|
||||
|
||||
It is a small export-layer helper for future copy/download/UI SDK wrappers.
|
||||
|
||||
## `createMachineSessionWorkflowStatus(state)`
|
||||
|
||||
This helper derives a lightweight status object for upper-level control pages:
|
||||
|
||||
```js
|
||||
{
|
||||
readiness: { ini: "...", interp: "...", opfs: "..." },
|
||||
lastAction: "run-gcode",
|
||||
error: null,
|
||||
session: { snapshotLabel: "...", workflow: "...", gcode: "..." },
|
||||
run: { status: "ok", canonicalEventCount: 2, motionSnapshotCount: 2, iniWasmPath: "..." }
|
||||
}
|
||||
```
|
||||
|
||||
It is intended for polling or status panels that need current UI workflow
|
||||
state without parsing logs, DOM nodes, or transient window state.
|
||||
|
||||
## `createMachineSessionStateBundle(state, options)`
|
||||
|
||||
This helper packages the current report, export payload, and workflow status
|
||||
into one read-only object:
|
||||
|
||||
```js
|
||||
{
|
||||
report: { ... },
|
||||
export: { filename: "...", mediaType: "application/json", report: { ... }, text: "{...}\n" },
|
||||
workflowStatus: { readiness: { ... }, lastAction: "...", error: null, session: { ... }, run: { ... } }
|
||||
}
|
||||
```
|
||||
|
||||
It is the preferred single-entry helper for control-page polling and other
|
||||
upper-level UI consumers that want one stable read instead of stitching the
|
||||
three views together themselves.
|
||||
|
||||
## `createMachineSessionControlView(bundle)`
|
||||
|
||||
This helper maps a bundle into UI-ready status and sections:
|
||||
|
||||
```js
|
||||
{
|
||||
status: { lastAction: "run-gcode", error: null, runStatus: "ok" },
|
||||
sections: [
|
||||
{ id: "readiness", title: "Readiness", rows: [{ label: "INI", value: "..." }] },
|
||||
{ id: "session", title: "Session", rows: [{ label: "Workflow", value: "..." }] },
|
||||
{ id: "run", title: "Run", rows: [{ label: "Canonical events", value: 2 }] }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
It is a presentation mapping over the bundle only. It does not parse G-code,
|
||||
canonical events, or LinuxCNC-owned runtime content.
|
||||
|
||||
A minimal read-only browser consumer can render the sections directly:
|
||||
|
||||
```js
|
||||
window.linuxCncIniPanelApi.renderMachineSessionControlView(
|
||||
document.getElementById("machine-session-control-view"),
|
||||
);
|
||||
```
|
||||
|
||||
Consumers should treat `rows` as display data and keep actions wired through
|
||||
explicit workflow APIs instead of deriving behavior from labels.
|
||||
|
||||
For external pages that only have a view object, import the pure renderer:
|
||||
|
||||
```js
|
||||
import { renderMachineSessionControlView } from "./control-view-renderer.js";
|
||||
|
||||
renderMachineSessionControlView(container, view);
|
||||
```
|
||||
|
||||
## Boundary Rules
|
||||
|
||||
- Do not parse G-code text in this helper.
|
||||
- Do not infer CNC semantics from the nested summaries.
|
||||
- Keep the helper as a bundling layer over already-observed UI boundary state.
|
||||
- Keep workflow result objects as host/runtime boundary facts; this helper must
|
||||
not promote, reinterpret, or synthesize CNC behavior.
|
||||
|
||||
## Validation
|
||||
|
||||
The helper is covered by:
|
||||
|
||||
```bash
|
||||
wasm-port/tests/ui/node/verify_ini_panel_state_summary.sh
|
||||
wasm-port/tests/ui/node/verify_ui_node_smokes.sh
|
||||
```
|
||||
149
wasm-port/docs/ui-run-summary-helpers.md
Normal file
149
wasm-port/docs/ui-run-summary-helpers.md
Normal file
@@ -0,0 +1,149 @@
|
||||
# UI Run Summary Helpers
|
||||
|
||||
## Purpose
|
||||
|
||||
`runtime/ui/ini-panel/run-summary.js` contains small host-boundary helpers for
|
||||
summarizing an INI panel G-code run.
|
||||
`runtime/ui/ini-panel/run-workflow.js` contains the reusable UI workflow for
|
||||
selecting the session/default G-code program, loading it from OPFS, staging it
|
||||
into the interpreter WASM filesystem, invoking the LinuxCNC interpreter, and
|
||||
returning run field/log state. These helpers are intentionally pure so browser
|
||||
UI panels, SDK-facing wrappers, and Node smoke tests can share the same summary
|
||||
shape without reading DOM text or parsing log output.
|
||||
|
||||
The helpers do not implement CNC behavior. G-code execution, canonical events,
|
||||
tool handling, parameters, kinematics, and planner behavior remain owned by the
|
||||
vendored LinuxCNC runtime.
|
||||
|
||||
## `createRunSummary(program, runtime, snapshot)`
|
||||
|
||||
`program` describes the host-selected program path mapping:
|
||||
|
||||
```js
|
||||
{
|
||||
source: "default" | "snapshot",
|
||||
opfsPath: "linuxcnc/gcode/ui-session.ngc",
|
||||
wasmPath: "/work/ui-session.ngc"
|
||||
}
|
||||
```
|
||||
|
||||
`runtime` describes already-observed runtime boundary results:
|
||||
|
||||
```js
|
||||
{
|
||||
runStatus: "ok",
|
||||
snapshotLabel: "ui-machine-session/ui-machine-session.json",
|
||||
iniWasmPath: "/work/session-machine.ini",
|
||||
canonicalEventCount: 2,
|
||||
motionSnapshotCount: 2
|
||||
}
|
||||
```
|
||||
|
||||
`snapshot` is the optional machine session snapshot loaded by the UI. The helper
|
||||
reads only snapshot metadata such as `workflow` and `snapshotLabel`.
|
||||
When `runtime.snapshotLabel` is present, it is used as the returned
|
||||
`snapshotLabel`; otherwise the helper falls back to `snapshot.metadata.snapshotLabel`
|
||||
and then `null`.
|
||||
|
||||
The returned summary has this shape:
|
||||
|
||||
```js
|
||||
{
|
||||
runStatus: "ok",
|
||||
programSource: "snapshot",
|
||||
snapshotLabel: "ui-machine-session/ui-machine-session.json",
|
||||
workflow: "save-session-snapshot",
|
||||
programOpfsPath: "linuxcnc/gcode/ui-session.ngc",
|
||||
programWasmPath: "/work/ui-session.ngc",
|
||||
iniWasmPath: "/work/session-machine.ini",
|
||||
canonicalEventCount: 2,
|
||||
motionSnapshotCount: 2
|
||||
}
|
||||
```
|
||||
|
||||
## `summarizeRunResult(resultText, snapshots)`
|
||||
|
||||
`resultText` is the raw text returned by the LinuxCNC interpreter WASM call.
|
||||
`snapshots` is the UI's already-parsed motion snapshot list.
|
||||
|
||||
The helper returns:
|
||||
|
||||
```js
|
||||
{
|
||||
canonicalEventCount: 2,
|
||||
motionSnapshotCount: 2
|
||||
}
|
||||
```
|
||||
|
||||
`canonicalEventCount` only counts result lines beginning with `canon_event=`.
|
||||
`motionSnapshotCount` is only `snapshots.length`.
|
||||
|
||||
## `runGcodeSessionWorkflow(input)`
|
||||
|
||||
The workflow requires a loaded machine session and injected host/runtime
|
||||
functions:
|
||||
|
||||
```js
|
||||
{
|
||||
interp,
|
||||
loadedSession,
|
||||
snapshot,
|
||||
loadGcodeProgram,
|
||||
gcodeFilenameFromProgramPath,
|
||||
summarizeRun
|
||||
}
|
||||
```
|
||||
|
||||
It returns the selected program mapping, raw interpreter result text, run
|
||||
summary, UI field state, log lines, and optional UI motion snapshots produced
|
||||
by the injected `summarizeRun` callback:
|
||||
|
||||
```js
|
||||
{
|
||||
program: {
|
||||
source: "snapshot",
|
||||
opfsPath: "linuxcnc/gcode/ui-session.ngc",
|
||||
wasmPath: "/work/ui-session.ngc",
|
||||
text: "G0 X1\n"
|
||||
},
|
||||
resultText: "canon_event=...",
|
||||
summary: { runStatus: "ok", programSource: "snapshot" },
|
||||
fields: { runStatus: "ok", sessionGcode: "linuxcnc/gcode/ui-session.ngc" },
|
||||
motionSnapshots: [{ line: 1 }],
|
||||
logLines: ["Program: linuxcnc/gcode/ui-session.ngc -> /work/ui-session.ngc"]
|
||||
}
|
||||
```
|
||||
|
||||
`selectSessionGcodeProgram(input)` is the pure program-selection helper used by
|
||||
the workflow. Invalid snapshot G-code paths fall back to the UI default program.
|
||||
|
||||
## Boundary Rules
|
||||
|
||||
- Do not parse G-code text in these helpers.
|
||||
- Do not infer tool, parameter, kinematics, planner, or modal semantics.
|
||||
- Do not derive new canonical events in JavaScript.
|
||||
- Keep canonical-event and motion parsing in the existing UI display layer or
|
||||
inject it as already-observed UI state; the workflow must not own CNC
|
||||
semantics.
|
||||
- Do not make UI helper output a promotion signal for runtime families blocked
|
||||
by host dependencies.
|
||||
- Keep new fields limited to host/runtime boundary facts already produced by
|
||||
the LinuxCNC-owned runtime or UI staging layer.
|
||||
|
||||
## Validation
|
||||
|
||||
Run the focused Node smoke after changing these helpers:
|
||||
|
||||
```bash
|
||||
wasm-port/tests/ui/node/verify_ini_panel_run_summary.sh
|
||||
wasm-port/tests/ui/node/verify_ini_panel_run_workflow.sh
|
||||
```
|
||||
|
||||
The aggregate UI Node smoke is:
|
||||
|
||||
```bash
|
||||
wasm-port/tests/ui/node/verify_ui_node_smokes.sh
|
||||
```
|
||||
|
||||
`wasm-port/tests/host/verify_host_smokes.sh` includes the UI Node smoke so these
|
||||
helpers are also covered by the regular host/WASM/browser gate.
|
||||
163
wasm-port/docs/ui-session-summary-helpers.md
Normal file
163
wasm-port/docs/ui-session-summary-helpers.md
Normal file
@@ -0,0 +1,163 @@
|
||||
# UI Session Summary Helpers
|
||||
|
||||
## Purpose
|
||||
|
||||
`runtime/ui/ini-panel/session-summary.js` contains small host-boundary helpers
|
||||
for summarizing machine session snapshot state.
|
||||
`runtime/ui/ini-panel/session-load-summary.js` contains the matching helpers
|
||||
for machine-session load state. These helpers are pure so browser UI panels,
|
||||
SDK-facing wrappers, and Node smoke tests can share the same data shape without
|
||||
parsing log text.
|
||||
`runtime/ui/ini-panel/session-workflow.js` contains the reusable UI workflows
|
||||
for saving, restoring, and restore-loading a machine session snapshot as
|
||||
host-boundary operations.
|
||||
|
||||
The helpers do not implement CNC behavior. G-code execution, canonical events,
|
||||
tool handling, parameters, kinematics, and planner behavior remain owned by the
|
||||
vendored LinuxCNC runtime.
|
||||
|
||||
## `createSessionSnapshotSummary(snapshot, fallbackLabel)`
|
||||
|
||||
`snapshot` is the saved machine session snapshot loaded by the UI.
|
||||
`fallbackLabel` is the UI-selected label to prefer when present.
|
||||
|
||||
The returned summary has this shape:
|
||||
|
||||
```js
|
||||
{
|
||||
snapshotLabel: "ui-machine-session/ui-machine-session.json",
|
||||
workflow: "save-session-snapshot",
|
||||
defaultGcodeOpfsPath: "linuxcnc/gcode/ui-session.ngc",
|
||||
iniOpfsPath: "linuxcnc/machines/xyzab-tdr/machine.ini",
|
||||
parameterOpfsPath: "linuxcnc/machines/xyzab-tdr/linuxcnc.var",
|
||||
toolTableOpfsPath: "linuxcnc/machines/xyzab-tdr/tool.tbl",
|
||||
gcodeOpfsPath: "linuxcnc/gcode/ui-session.ngc"
|
||||
}
|
||||
```
|
||||
|
||||
## `createMachineSessionLoadSummary(session)`
|
||||
|
||||
`session` is the loaded interpreter/session bridge result. The helper extracts
|
||||
the OPFS and WASM path mapping plus the captured parameter/tool-table result
|
||||
texts.
|
||||
|
||||
The returned summary has this shape:
|
||||
|
||||
```js
|
||||
{
|
||||
iniOpfsPath: "linuxcnc/machines/xyzab-tdr/machine.ini",
|
||||
iniWasmPath: "/work/session-machine.ini",
|
||||
parameterOpfsPath: "linuxcnc/machines/xyzab-tdr/linuxcnc.var",
|
||||
parameterWasmPath: "/work/session-linuxcnc.var",
|
||||
parameterResult: "restore_parameters=0",
|
||||
toolTableOpfsPath: "linuxcnc/machines/xyzab-tdr/tool.tbl",
|
||||
toolTableWasmPath: "/work/session-tool.tbl",
|
||||
toolTableResult: "tooldata_load=0"
|
||||
}
|
||||
```
|
||||
|
||||
## `saveMachineSessionSnapshotWorkflow(input)`
|
||||
|
||||
The workflow writes the current INI/tool/parameter text files, writes the
|
||||
default G-code program, saves the machine session snapshot, and returns the
|
||||
snapshot summary, field state, and log lines for UI consumers.
|
||||
|
||||
The persistence functions are injected by the caller:
|
||||
|
||||
```js
|
||||
{
|
||||
saveMachineTextFiles,
|
||||
saveGcodeProgram,
|
||||
saveMachineSessionSnapshot
|
||||
}
|
||||
```
|
||||
|
||||
## `restoreMachineSessionSnapshotWorkflow(input)`
|
||||
|
||||
The workflow loads the machine session snapshot, loads the persisted machine
|
||||
text files so the editor can be restored, and optionally loads the machine
|
||||
session into the interpreter WASM filesystem.
|
||||
|
||||
When `loadSession` is omitted, the returned `fields` contain the OPFS snapshot
|
||||
paths. When `loadSession` is supplied, the returned `fields` contain the loaded
|
||||
WASM paths for INI/parameter/tool-table files and the snapshot G-code OPFS
|
||||
path.
|
||||
|
||||
The returned object has this shape:
|
||||
|
||||
```js
|
||||
{
|
||||
snapshot: { ... },
|
||||
editorIniText: "[EMC]\n...",
|
||||
summary: { snapshotLabel: "...", workflow: "save-session-snapshot" },
|
||||
fields: {
|
||||
sessionIni: "/work/session-machine.ini",
|
||||
sessionParameters: "/work/session-linuxcnc.var",
|
||||
sessionToolTable: "/work/session-tool.tbl",
|
||||
sessionGcode: "linuxcnc/gcode/ui-session.ngc",
|
||||
sessionSnapshot: "ui-machine-session/ui-machine-session.json"
|
||||
},
|
||||
loadSummary: { iniWasmPath: "/work/session-machine.ini" },
|
||||
loadedSession: { ... },
|
||||
logLines: [
|
||||
"Snapshot: ui-machine-session/ui-machine-session.json",
|
||||
"Workflow: save-session-snapshot"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The returned object has this shape:
|
||||
|
||||
```js
|
||||
{
|
||||
snapshot: { ... },
|
||||
summary: { snapshotLabel: "...", workflow: "save-session-snapshot" },
|
||||
fields: {
|
||||
sessionIni: "linuxcnc/machines/xyzab-tdr/machine.ini",
|
||||
sessionParameters: "linuxcnc/machines/xyzab-tdr/linuxcnc.var",
|
||||
sessionToolTable: "linuxcnc/machines/xyzab-tdr/tool.tbl",
|
||||
sessionGcode: "linuxcnc/gcode/ui-session.ngc",
|
||||
sessionSnapshot: "ui-machine-session/ui-machine-session.json"
|
||||
},
|
||||
logLines: [
|
||||
"Snapshot: ui-machine-session/ui-machine-session.json",
|
||||
"Workflow: save-session-snapshot"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Log Helpers
|
||||
|
||||
- `sessionSnapshotSummaryLogLines(summary)` renders snapshot label, workflow,
|
||||
and default G-code path.
|
||||
- `sessionSnapshotMetadataLogLines(summary)` renders the metadata subset used in
|
||||
save/restore log summaries.
|
||||
- `machineSessionLoadLogLines(summary)` renders the interpreter/session
|
||||
OPFS-to-WASM mapping and captured result lines.
|
||||
- `sessionSnapshotSaveLogLines(summary)` renders the save-workflow snapshot
|
||||
paths used by the INI panel.
|
||||
- `sessionSnapshotRestoreLogLines(summary)` renders the restore-workflow
|
||||
snapshot paths used by the INI panel.
|
||||
|
||||
## Boundary Rules
|
||||
|
||||
- Do not parse G-code text in these helpers.
|
||||
- Do not infer tool, parameter, kinematics, planner, or modal semantics.
|
||||
- Do not derive new CNC behavior from the load result strings.
|
||||
- Keep new fields limited to host/runtime boundary facts already produced by the
|
||||
LinuxCNC-owned runtime or UI staging layer.
|
||||
|
||||
## Validation
|
||||
|
||||
Run the focused Node smoke after changing these helpers:
|
||||
|
||||
```bash
|
||||
wasm-port/tests/ui/node/verify_ini_panel_session_summary.sh
|
||||
wasm-port/tests/ui/node/verify_ini_panel_session_workflow.sh
|
||||
```
|
||||
|
||||
The aggregate UI Node smoke is:
|
||||
|
||||
```bash
|
||||
wasm-port/tests/ui/node/verify_ui_node_smokes.sh
|
||||
```
|
||||
@@ -3,9 +3,12 @@ import {
|
||||
gcodeProgramPath,
|
||||
machineIniPath,
|
||||
parameterFilePath,
|
||||
splitOpfsPath,
|
||||
toolTablePath,
|
||||
} from "./path-model.js";
|
||||
|
||||
const GCODE_PROGRAM_ROOT = ["linuxcnc", "gcode"];
|
||||
|
||||
const MACHINE_FILE_PATHS = {
|
||||
ini: machineIniPath,
|
||||
toolTable: toolTablePath,
|
||||
@@ -43,6 +46,18 @@ export async function loadMachineTextFiles(machineId, options = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
export function gcodeFilenameFromProgramPath(path) {
|
||||
const parts = splitOpfsPath(path);
|
||||
if (
|
||||
parts.length !== 3 ||
|
||||
parts[0] !== GCODE_PROGRAM_ROOT[0] ||
|
||||
parts[1] !== GCODE_PROGRAM_ROOT[1]
|
||||
) {
|
||||
throw new Error(`Invalid G-code program OPFS path: ${path}`);
|
||||
}
|
||||
return parts[2];
|
||||
}
|
||||
|
||||
export async function saveGcodeProgram(filename, text, options = {}) {
|
||||
const path = gcodeProgramPath(filename);
|
||||
await saveTextFile(path, text, options.storage);
|
||||
|
||||
@@ -27,6 +27,27 @@ function optionalOpfsPath(value, label) {
|
||||
return normalizeOpfsPath(value);
|
||||
}
|
||||
|
||||
function requiredSnapshotFilePath(files, key, label) {
|
||||
const value = files[key];
|
||||
if (typeof value !== "string") {
|
||||
throw new Error(`${label} must be a string.`);
|
||||
}
|
||||
return normalizeOpfsPath(value);
|
||||
}
|
||||
|
||||
function validateMachineSessionSnapshotPayload(payload, machineId) {
|
||||
assertPlainObject(payload, "machine session snapshot payload");
|
||||
if (payload.machineId !== machineId) {
|
||||
throw new Error(`Machine session snapshot id mismatch: ${payload.machineId}`);
|
||||
}
|
||||
assertPlainObject(payload.files, "machine session snapshot files");
|
||||
requiredSnapshotFilePath(payload.files, "ini", "machine session INI OPFS path");
|
||||
requiredSnapshotFilePath(payload.files, "parameters", "machine session parameter OPFS path");
|
||||
requiredSnapshotFilePath(payload.files, "toolTable", "machine session tool table OPFS path");
|
||||
optionalOpfsPath(payload.files.gcode, "machine session G-code OPFS path");
|
||||
return payload;
|
||||
}
|
||||
|
||||
export function createMachineSessionSnapshotPayload(machineId, options = {}) {
|
||||
assertPlainObject(options, "machine session snapshot options");
|
||||
const ini = optionalOpfsPath(options.iniOpfsPath, "INI OPFS path") ??
|
||||
@@ -89,6 +110,18 @@ export async function saveSessionSnapshot(sessionId, payload, options = {}) {
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
export async function saveMachineSessionSnapshot(sessionId, machineId, options = {}) {
|
||||
assertPlainObject(options, "machine session snapshot save options");
|
||||
const payload = createMachineSessionSnapshotPayload(machineId, options);
|
||||
return saveSessionSnapshot(sessionId, payload, options);
|
||||
}
|
||||
|
||||
export async function loadMachineSessionSnapshot(sessionId, machineId, options = {}) {
|
||||
const snapshot = await loadSessionSnapshot(sessionId, options);
|
||||
validateMachineSessionSnapshotPayload(snapshot.payload, machineId);
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
export async function loadSessionSnapshot(sessionId, options = {}) {
|
||||
const path = sessionSnapshotPath(sessionId, options.filename);
|
||||
const text = await loadTextFile(path, options.storage);
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
saveTextFile,
|
||||
} from "../../opfs/file-service.js";
|
||||
import {
|
||||
gcodeFilenameFromProgramPath,
|
||||
loadGcodeProgram,
|
||||
loadMachineTextFiles,
|
||||
machineFilePaths,
|
||||
@@ -17,6 +18,44 @@ import {
|
||||
import {
|
||||
loadMachineSessionFromOpfs,
|
||||
} from "../../opfs/linuxcnc-machine-session-bridge.js";
|
||||
import {
|
||||
loadMachineSessionSnapshot,
|
||||
saveMachineSessionSnapshot,
|
||||
} from "../../opfs/snapshot-store.js";
|
||||
import {
|
||||
createMachineFileLoadSummary,
|
||||
createMachineFileSaveSummary,
|
||||
machineFileLoadLogLines,
|
||||
machineFileSaveLogLines,
|
||||
} from "./machine-file-summary.js";
|
||||
import {
|
||||
summarizeRunResult,
|
||||
} from "./run-summary.js";
|
||||
import {
|
||||
runGcodeSessionWorkflow,
|
||||
} from "./run-workflow.js";
|
||||
import {
|
||||
createMachineSessionLoadSummary,
|
||||
machineSessionLoadLogLines,
|
||||
} from "./session-load-summary.js";
|
||||
import {
|
||||
sessionSnapshotSummaryLogLines,
|
||||
} from "./session-summary.js";
|
||||
import {
|
||||
restoreMachineSessionSnapshotWorkflow,
|
||||
saveMachineSessionSnapshotWorkflow,
|
||||
} from "./session-workflow.js";
|
||||
import {
|
||||
createMachineSessionControlView,
|
||||
createMachineSessionStateSnapshot,
|
||||
createMachineSessionStateBundle,
|
||||
createMachineSessionStateReport,
|
||||
createMachineSessionStateReportExport,
|
||||
createMachineSessionWorkflowStatus,
|
||||
} from "./panel-state-summary.js";
|
||||
import {
|
||||
renderMachineSessionControlView,
|
||||
} from "./control-view-renderer.js";
|
||||
|
||||
const SAMPLE_PATH =
|
||||
"../../../vendor/linuxcnc/configs/sim/axis/vismach/5axis/table-dual-rotary/xyzab-tdr.ini";
|
||||
@@ -44,6 +83,12 @@ const SESSION_WASM_FILES = {
|
||||
};
|
||||
const GCODE_FILENAME = "ui-session.ngc";
|
||||
const GCODE_WASM_FILE = "/work/ui-session.ngc";
|
||||
const UI_SESSION = {
|
||||
snapshotId: "ui-machine-session",
|
||||
snapshotFilename: "ui-machine-session.json",
|
||||
defaultGcodeFilename: GCODE_FILENAME,
|
||||
gcodeWasmPath: GCODE_WASM_FILE,
|
||||
};
|
||||
const DEFAULT_TOOL_TABLE = "T2 P7 Z3.125 D1.5 I12 J34 Q4 ;ui session tool\n";
|
||||
const DEFAULT_PARAMETERS = [
|
||||
"5161 0.0",
|
||||
@@ -72,6 +117,7 @@ const runProgressLabelNode = document.getElementById("run-progress-label");
|
||||
const runMotionNode = document.getElementById("run-motion");
|
||||
const runLineNode = document.getElementById("run-line");
|
||||
const runStatementNode = document.getElementById("run-statement");
|
||||
const controlViewNode = document.getElementById("machine-session-control-view");
|
||||
const axisNodes = {
|
||||
x: document.getElementById("axis-x"),
|
||||
y: document.getElementById("axis-y"),
|
||||
@@ -96,15 +142,78 @@ const fields = {
|
||||
sessionParameters: document.getElementById("field-session-parameters"),
|
||||
sessionToolTable: document.getElementById("field-session-tool-table"),
|
||||
sessionGcode: document.getElementById("field-session-gcode"),
|
||||
sessionSnapshot: document.getElementById("field-session-snapshot"),
|
||||
runStatus: document.getElementById("field-run-status"),
|
||||
runSource: document.getElementById("field-run-source"),
|
||||
runSnapshot: document.getElementById("field-run-snapshot"),
|
||||
runWorkflow: document.getElementById("field-run-workflow"),
|
||||
runOpfsPath: document.getElementById("field-run-opfs-path"),
|
||||
runWasmPath: document.getElementById("field-run-wasm-path"),
|
||||
fiveaxisRemap: document.getElementById("field-fiveaxis-remap"),
|
||||
};
|
||||
|
||||
let iniSdk = null;
|
||||
let interpSdk = null;
|
||||
let loadedSession = null;
|
||||
let restoredSessionSnapshot = null;
|
||||
let canonicalEventText = "";
|
||||
let runPlaybackTimer = null;
|
||||
let panelMachineSessionState = createMachineSessionStateSnapshot();
|
||||
|
||||
function getMachineSessionStateReport() {
|
||||
return createMachineSessionStateReport(window.linuxCncIniPanelMachineSessionState);
|
||||
}
|
||||
|
||||
function getMachineSessionStateReportExport() {
|
||||
return createMachineSessionStateReportExport(window.linuxCncIniPanelMachineSessionState);
|
||||
}
|
||||
|
||||
function getMachineSessionWorkflowStatus() {
|
||||
return createMachineSessionWorkflowStatus(window.linuxCncIniPanelMachineSessionState);
|
||||
}
|
||||
|
||||
function getMachineSessionStateBundle() {
|
||||
return createMachineSessionStateBundle(window.linuxCncIniPanelMachineSessionState);
|
||||
}
|
||||
|
||||
function getMachineSessionControlView() {
|
||||
return createMachineSessionControlView(getMachineSessionStateBundle());
|
||||
}
|
||||
|
||||
function renderCurrentMachineSessionControlView(container = controlViewNode) {
|
||||
return renderMachineSessionControlView(container, getMachineSessionControlView());
|
||||
}
|
||||
|
||||
function updatePanelMachineSessionState(patch = {}) {
|
||||
panelMachineSessionState = createMachineSessionStateSnapshot({
|
||||
...panelMachineSessionState,
|
||||
...patch,
|
||||
fields: {
|
||||
...panelMachineSessionState.fields,
|
||||
...(patch.fields ?? {}),
|
||||
},
|
||||
});
|
||||
window.linuxCncIniPanelMachineSessionState = panelMachineSessionState;
|
||||
renderCurrentMachineSessionControlView();
|
||||
return panelMachineSessionState;
|
||||
}
|
||||
|
||||
window.linuxCncIniPanelApi = {
|
||||
getMachineSessionControlView,
|
||||
getMachineSessionStateBundle,
|
||||
getMachineSessionStateReport,
|
||||
getMachineSessionStateReportExport,
|
||||
getMachineSessionWorkflowStatus,
|
||||
renderMachineSessionControlView: renderCurrentMachineSessionControlView,
|
||||
};
|
||||
window.getMachineSessionControlView = getMachineSessionControlView;
|
||||
window.getMachineSessionStateBundle = getMachineSessionStateBundle;
|
||||
window.getMachineSessionStateReport = getMachineSessionStateReport;
|
||||
window.getMachineSessionStateReportExport = getMachineSessionStateReportExport;
|
||||
window.getMachineSessionWorkflowStatus = getMachineSessionWorkflowStatus;
|
||||
window.renderMachineSessionControlView = renderCurrentMachineSessionControlView;
|
||||
window.linuxCncIniPanelMachineSessionState = panelMachineSessionState;
|
||||
renderCurrentMachineSessionControlView();
|
||||
|
||||
function setBadge(node, text, className = "badge") {
|
||||
node.className = className;
|
||||
@@ -264,9 +373,8 @@ function showRunSnapshot(snapshot, index, total) {
|
||||
);
|
||||
}
|
||||
|
||||
function playRunMotion(resultText, programText) {
|
||||
function playRunMotion(resultText, programText, snapshots = parseRunMotion(resultText, programText)) {
|
||||
clearRunPlayback();
|
||||
const snapshots = parseRunMotion(resultText, programText);
|
||||
if (snapshots.length === 0) {
|
||||
setRunMonitor(100, "100%", {}, "complete; no axis motion");
|
||||
return;
|
||||
@@ -303,6 +411,86 @@ function syncEditorToWasmFs() {
|
||||
requireIniSdk().writeTextFile(WASM_FILE, editor.value);
|
||||
}
|
||||
|
||||
function sessionSnapshotLabel(snapshot = UI_SESSION) {
|
||||
const sessionId = typeof snapshot === "string" ? snapshot : snapshot.snapshotId ?? snapshot.sessionId;
|
||||
return `${sessionId}/${UI_SESSION.snapshotFilename}`;
|
||||
}
|
||||
|
||||
function defaultGcodeOpfsPath() {
|
||||
return `linuxcnc/gcode/${UI_SESSION.defaultGcodeFilename}`;
|
||||
}
|
||||
|
||||
function sessionSnapshotMetadata() {
|
||||
return {
|
||||
source: "ini-panel",
|
||||
workflow: "save-session-snapshot",
|
||||
snapshotLabel: sessionSnapshotLabel(UI_SESSION),
|
||||
defaultGcodeOpfsPath: defaultGcodeOpfsPath(),
|
||||
};
|
||||
}
|
||||
|
||||
function clearLastRunSummary() {
|
||||
window.linuxCncIniPanelLastRunSummary = null;
|
||||
setField("runSource", "-");
|
||||
setField("runSnapshot", "-");
|
||||
setField("runWorkflow", "-");
|
||||
setField("runOpfsPath", "-");
|
||||
setField("runWasmPath", "-");
|
||||
}
|
||||
|
||||
function setLastRunSummary(summary) {
|
||||
window.linuxCncIniPanelLastRunSummary = summary;
|
||||
setField("runSource", summary.programSource);
|
||||
setField("runSnapshot", summary.snapshotLabel ?? "-");
|
||||
setField("runWorkflow", summary.workflow ?? "-");
|
||||
setField("runOpfsPath", summary.programOpfsPath);
|
||||
setField("runWasmPath", summary.programWasmPath);
|
||||
updatePanelMachineSessionState({
|
||||
run: summary,
|
||||
fields: {
|
||||
runStatus: summary.runStatus,
|
||||
runSource: summary.programSource,
|
||||
runSnapshot: summary.snapshotLabel ?? "-",
|
||||
runWorkflow: summary.workflow ?? "-",
|
||||
runOpfsPath: summary.programOpfsPath,
|
||||
runWasmPath: summary.programWasmPath,
|
||||
},
|
||||
});
|
||||
return summary;
|
||||
}
|
||||
|
||||
async function restoreSessionSnapshotToEditor() {
|
||||
const snapshot = await loadMachineSessionSnapshot(
|
||||
UI_SESSION.snapshotId,
|
||||
MACHINE_ID,
|
||||
{ filename: UI_SESSION.snapshotFilename },
|
||||
);
|
||||
const files = await loadMachineTextFiles(MACHINE_ID);
|
||||
editor.value = files.ini;
|
||||
setField("sessionIni", snapshot.payload.files.ini);
|
||||
setField("sessionParameters", snapshot.payload.files.parameters);
|
||||
setField("sessionToolTable", snapshot.payload.files.toolTable);
|
||||
setField("sessionGcode", snapshot.payload.files.gcode);
|
||||
setField("sessionSnapshot", sessionSnapshotLabel(snapshot.sessionId));
|
||||
restoredSessionSnapshot = snapshot;
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
async function loadMachineSessionIntoWasm() {
|
||||
loadedSession = await loadMachineSessionFromOpfs(
|
||||
requireInterpSdk(),
|
||||
MACHINE_ID,
|
||||
{
|
||||
...SESSION_WASM_FILES,
|
||||
iniSdk: requireIniSdk(),
|
||||
},
|
||||
);
|
||||
setField("sessionIni", loadedSession.ini.wasmPath);
|
||||
setField("sessionParameters", loadedSession.parameters.wasmPath);
|
||||
setField("sessionToolTable", loadedSession.toolTable.wasmPath);
|
||||
return loadedSession;
|
||||
}
|
||||
|
||||
async function loadSample() {
|
||||
setLog(`Fetching ${SAMPLE_PATH}`);
|
||||
const response = await fetch(SAMPLE_PATH);
|
||||
@@ -354,27 +542,45 @@ async function boot() {
|
||||
try {
|
||||
iniSdk = await createLinuxCncIniSdk();
|
||||
setBadge(wasmBadge, "INI WASM: ready");
|
||||
updatePanelMachineSessionState({ badges: { ...panelMachineSessionState.badges, ini: "INI WASM: ready" } });
|
||||
} catch (error) {
|
||||
setBadge(wasmBadge, "INI WASM: failed", "badge danger");
|
||||
setLog(`INI WASM init failed: ${error.message}`, true);
|
||||
updatePanelMachineSessionState({
|
||||
badges: { ...panelMachineSessionState.badges, ini: "INI WASM: failed" },
|
||||
lastAction: "boot",
|
||||
error: error.message,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
|
||||
try {
|
||||
interpSdk = await createLinuxCncInterpSdk();
|
||||
setBadge(interpBadge, "Interpreter WASM: ready");
|
||||
updatePanelMachineSessionState({ badges: { ...panelMachineSessionState.badges, interp: "Interpreter WASM: ready" } });
|
||||
} catch (error) {
|
||||
setBadge(interpBadge, "Interpreter WASM: failed", "badge danger");
|
||||
setLog(`Interpreter WASM init failed: ${error.message}`, true);
|
||||
updatePanelMachineSessionState({
|
||||
badges: { ...panelMachineSessionState.badges, interp: "Interpreter WASM: failed" },
|
||||
lastAction: "boot",
|
||||
error: error.message,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
|
||||
try {
|
||||
await getOpfsRoot();
|
||||
setBadge(opfsBadge, "OPFS: ready");
|
||||
updatePanelMachineSessionState({ badges: { ...panelMachineSessionState.badges, opfs: "OPFS: ready" } });
|
||||
} catch (error) {
|
||||
setBadge(opfsBadge, "OPFS: unavailable", "badge danger");
|
||||
setLog(`OPFS check failed: ${error.message}`, true);
|
||||
updatePanelMachineSessionState({
|
||||
badges: { ...panelMachineSessionState.badges, opfs: "OPFS: unavailable" },
|
||||
lastAction: "boot",
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -402,14 +608,17 @@ document.getElementById("save-machine-files").addEventListener("click", async ()
|
||||
toolTable: DEFAULT_TOOL_TABLE,
|
||||
parameters: DEFAULT_PARAMETERS,
|
||||
});
|
||||
const gcodePath = await saveGcodeProgram(GCODE_FILENAME, DEFAULT_GCODE);
|
||||
const gcodePath = await saveGcodeProgram(UI_SESSION.defaultGcodeFilename, DEFAULT_GCODE);
|
||||
const summary = createMachineFileSaveSummary(paths, gcodePath);
|
||||
updatePanelMachineSessionState({
|
||||
machineFiles: summary,
|
||||
lastAction: "save-machine-files",
|
||||
error: null,
|
||||
});
|
||||
setLog(
|
||||
[
|
||||
"Saved machine text files to OPFS.",
|
||||
`INI: ${paths.ini}`,
|
||||
`Tool table: ${paths.toolTable}`,
|
||||
`Parameters: ${paths.parameters}`,
|
||||
`G-code: ${gcodePath}`,
|
||||
...machineFileSaveLogLines(summary),
|
||||
].join("\n"),
|
||||
);
|
||||
} catch (error) {
|
||||
@@ -417,64 +626,227 @@ document.getElementById("save-machine-files").addEventListener("click", async ()
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("load-session").addEventListener("click", async () => {
|
||||
document.getElementById("save-session-snapshot").addEventListener("click", async () => {
|
||||
try {
|
||||
loadedSession = await loadMachineSessionFromOpfs(
|
||||
requireInterpSdk(),
|
||||
MACHINE_ID,
|
||||
{
|
||||
...SESSION_WASM_FILES,
|
||||
iniSdk: requireIniSdk(),
|
||||
},
|
||||
);
|
||||
setField("sessionIni", loadedSession.ini.wasmPath);
|
||||
setField("sessionParameters", loadedSession.parameters.wasmPath);
|
||||
setField("sessionToolTable", loadedSession.toolTable.wasmPath);
|
||||
const workflow = await saveMachineSessionSnapshotWorkflow({
|
||||
machineId: MACHINE_ID,
|
||||
snapshotId: UI_SESSION.snapshotId,
|
||||
snapshotFilename: UI_SESSION.snapshotFilename,
|
||||
snapshotLabel: sessionSnapshotLabel(UI_SESSION),
|
||||
iniText: editor.value,
|
||||
toolTableText: DEFAULT_TOOL_TABLE,
|
||||
parameterText: DEFAULT_PARAMETERS,
|
||||
gcodeFilename: UI_SESSION.defaultGcodeFilename,
|
||||
gcodeText: DEFAULT_GCODE,
|
||||
metadata: sessionSnapshotMetadata(),
|
||||
saveMachineTextFiles,
|
||||
saveGcodeProgram,
|
||||
saveMachineSessionSnapshot,
|
||||
});
|
||||
for (const [name, value] of Object.entries(workflow.fields)) {
|
||||
setField(name, value);
|
||||
}
|
||||
restoredSessionSnapshot = workflow.snapshot;
|
||||
updatePanelMachineSessionState({
|
||||
sessionSnapshot: workflow.summary,
|
||||
sessionLoad: null,
|
||||
selectedProgram: null,
|
||||
fields: workflow.fields,
|
||||
lastAction: "save-session-snapshot",
|
||||
error: null,
|
||||
});
|
||||
setLog(
|
||||
[
|
||||
"Loaded machine session into the interpreter WASM filesystem.",
|
||||
`INI: ${loadedSession.ini.opfsPath} -> ${loadedSession.ini.wasmPath}`,
|
||||
`Parameter file: ${loadedSession.parameters.opfsPath} -> ${loadedSession.parameters.wasmPath}`,
|
||||
`Parameters: ${loadedSession.parameters.result.trim()}`,
|
||||
`Tool table file: ${loadedSession.toolTable.opfsPath} -> ${loadedSession.toolTable.wasmPath}`,
|
||||
`Tool table: ${loadedSession.toolTable.result.trim()}`,
|
||||
"Saved machine session snapshot to OPFS.",
|
||||
...workflow.logLines,
|
||||
].join("\n"),
|
||||
);
|
||||
} catch (error) {
|
||||
updatePanelMachineSessionState({
|
||||
lastAction: "save-session-snapshot",
|
||||
error: error.message,
|
||||
});
|
||||
setLog(`Machine session snapshot save failed: ${error.message}`, true);
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("restore-session-snapshot").addEventListener("click", async () => {
|
||||
try {
|
||||
const workflow = await restoreMachineSessionSnapshotWorkflow({
|
||||
machineId: MACHINE_ID,
|
||||
snapshotId: UI_SESSION.snapshotId,
|
||||
snapshotFilename: UI_SESSION.snapshotFilename,
|
||||
snapshotLabel: sessionSnapshotLabel(UI_SESSION),
|
||||
loadMachineSessionSnapshot,
|
||||
loadMachineTextFiles,
|
||||
});
|
||||
editor.value = workflow.editorIniText;
|
||||
setField("sessionIni", workflow.fields.sessionIni);
|
||||
setField("sessionParameters", workflow.fields.sessionParameters);
|
||||
setField("sessionToolTable", workflow.fields.sessionToolTable);
|
||||
setField("sessionGcode", workflow.fields.sessionGcode);
|
||||
setField("sessionSnapshot", workflow.fields.sessionSnapshot);
|
||||
restoredSessionSnapshot = workflow.snapshot;
|
||||
updatePanelMachineSessionState({
|
||||
sessionSnapshot: workflow.summary,
|
||||
sessionLoad: null,
|
||||
selectedProgram: null,
|
||||
fields: workflow.fields,
|
||||
lastAction: "restore-session-snapshot",
|
||||
error: null,
|
||||
});
|
||||
setLog(
|
||||
[
|
||||
"Restored machine session snapshot from OPFS.",
|
||||
...workflow.logLines,
|
||||
].join("\n"),
|
||||
);
|
||||
} catch (error) {
|
||||
updatePanelMachineSessionState({
|
||||
lastAction: "restore-session-snapshot",
|
||||
error: error.message,
|
||||
});
|
||||
setLog(`Machine session snapshot restore failed: ${error.message}`, true);
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("load-session").addEventListener("click", async () => {
|
||||
try {
|
||||
await loadMachineSessionIntoWasm();
|
||||
const loadSummary = createMachineSessionLoadSummary(loadedSession);
|
||||
updatePanelMachineSessionState({
|
||||
sessionLoad: loadSummary,
|
||||
fields: {
|
||||
sessionIni: loadSummary.iniWasmPath,
|
||||
sessionParameters: loadSummary.parameterWasmPath,
|
||||
sessionToolTable: loadSummary.toolTableWasmPath,
|
||||
},
|
||||
lastAction: "load-session",
|
||||
error: null,
|
||||
});
|
||||
setLog(
|
||||
[
|
||||
"Loaded machine session into the interpreter WASM filesystem.",
|
||||
...machineSessionLoadLogLines(loadSummary),
|
||||
].join("\n"),
|
||||
);
|
||||
} catch (error) {
|
||||
updatePanelMachineSessionState({
|
||||
lastAction: "load-session",
|
||||
error: error.message,
|
||||
});
|
||||
setLog(`Machine session load failed: ${error.message}`, true);
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("restore-load-session").addEventListener("click", async () => {
|
||||
try {
|
||||
const workflow = await restoreMachineSessionSnapshotWorkflow({
|
||||
machineId: MACHINE_ID,
|
||||
snapshotId: UI_SESSION.snapshotId,
|
||||
snapshotFilename: UI_SESSION.snapshotFilename,
|
||||
snapshotLabel: sessionSnapshotLabel(UI_SESSION),
|
||||
loadMachineSessionSnapshot,
|
||||
loadMachineTextFiles,
|
||||
loadSession: () => loadMachineSessionIntoWasm(),
|
||||
});
|
||||
editor.value = workflow.editorIniText;
|
||||
setField("sessionIni", workflow.fields.sessionIni);
|
||||
setField("sessionParameters", workflow.fields.sessionParameters);
|
||||
setField("sessionToolTable", workflow.fields.sessionToolTable);
|
||||
setField("sessionGcode", workflow.fields.sessionGcode);
|
||||
setField("sessionSnapshot", workflow.fields.sessionSnapshot);
|
||||
restoredSessionSnapshot = workflow.snapshot;
|
||||
if (workflow.loadedSession) {
|
||||
loadedSession = workflow.loadedSession;
|
||||
}
|
||||
updatePanelMachineSessionState({
|
||||
sessionSnapshot: workflow.summary,
|
||||
sessionLoad: workflow.loadSummary,
|
||||
selectedProgram: null,
|
||||
fields: workflow.fields,
|
||||
lastAction: "restore-load-session",
|
||||
error: null,
|
||||
});
|
||||
setLog(
|
||||
[
|
||||
"Restored and loaded machine session snapshot.",
|
||||
...workflow.logLines,
|
||||
].join("\n"),
|
||||
);
|
||||
} catch (error) {
|
||||
updatePanelMachineSessionState({
|
||||
lastAction: "restore-load-session",
|
||||
error: error.message,
|
||||
});
|
||||
setLog(`Machine session snapshot restore/load failed: ${error.message}`, true);
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("run-gcode").addEventListener("click", async () => {
|
||||
try {
|
||||
if (!loadedSession) {
|
||||
throw new Error("Load a machine session before running G-code.");
|
||||
}
|
||||
const interp = requireInterpSdk();
|
||||
const programText = await loadGcodeProgram(GCODE_FILENAME);
|
||||
interp.writeTextFile(GCODE_WASM_FILE, programText);
|
||||
clearRunPlayback();
|
||||
setField("runStatus", "running");
|
||||
clearLastRunSummary();
|
||||
setRunMonitor(5, "running", {}, "running");
|
||||
await nextFrame();
|
||||
const result = interp.runFileWithIni(GCODE_WASM_FILE, loadedSession.ini.wasmPath);
|
||||
setField("sessionGcode", GCODE_WASM_FILE);
|
||||
setField("runStatus", "ok");
|
||||
setCanonicalEvents(result.trim());
|
||||
playRunMotion(result.trim(), programText);
|
||||
const workflow = await runGcodeSessionWorkflow({
|
||||
interp,
|
||||
loadedSession,
|
||||
snapshot: restoredSessionSnapshot,
|
||||
defaultFilename: UI_SESSION.defaultGcodeFilename,
|
||||
defaultOpfsPath: defaultGcodeOpfsPath(),
|
||||
wasmPath: UI_SESSION.gcodeWasmPath,
|
||||
gcodeFilenameFromProgramPath,
|
||||
loadGcodeProgram,
|
||||
snapshotLabel: restoredSessionSnapshot ? sessionSnapshotLabel(restoredSessionSnapshot.sessionId) : null,
|
||||
summarizeRun: (resultText, programText) => {
|
||||
const motionSnapshots = parseRunMotion(resultText, programText);
|
||||
return {
|
||||
...summarizeRunResult(resultText, motionSnapshots),
|
||||
motionSnapshots,
|
||||
};
|
||||
},
|
||||
});
|
||||
setField("sessionGcode", workflow.fields.sessionGcode);
|
||||
setField("runStatus", workflow.fields.runStatus);
|
||||
setLastRunSummary(workflow.summary);
|
||||
updatePanelMachineSessionState({
|
||||
selectedProgram: {
|
||||
source: workflow.program.source,
|
||||
opfsPath: workflow.program.opfsPath,
|
||||
wasmPath: workflow.program.wasmPath,
|
||||
},
|
||||
run: workflow.summary,
|
||||
fields: workflow.fields,
|
||||
lastAction: "run-gcode",
|
||||
error: null,
|
||||
});
|
||||
setCanonicalEvents(workflow.resultText);
|
||||
playRunMotion(workflow.resultText, workflow.program.text, workflow.motionSnapshots);
|
||||
setLog(
|
||||
[
|
||||
"Ran G-code through LinuxCNC interpreter WASM.",
|
||||
`Program: ${GCODE_WASM_FILE}`,
|
||||
`INI: ${loadedSession.ini.wasmPath}`,
|
||||
result.trim(),
|
||||
...workflow.logLines,
|
||||
].join("\n"),
|
||||
);
|
||||
} catch (error) {
|
||||
setField("runStatus", "failed");
|
||||
setCanonicalEvents("");
|
||||
clearRunPlayback();
|
||||
clearLastRunSummary();
|
||||
setRunMonitor(0, "failed", {}, "failed");
|
||||
updatePanelMachineSessionState({
|
||||
fields: {
|
||||
runStatus: "failed",
|
||||
},
|
||||
lastAction: "run-gcode",
|
||||
error: error.message,
|
||||
});
|
||||
setLog(`G-code run failed: ${error.message}`, true);
|
||||
}
|
||||
});
|
||||
@@ -548,12 +920,11 @@ document.getElementById("load-machine-files").addEventListener("click", async ()
|
||||
try {
|
||||
const files = await loadMachineTextFiles(MACHINE_ID);
|
||||
editor.value = files.ini;
|
||||
const summary = createMachineFileLoadSummary(files);
|
||||
setLog(
|
||||
[
|
||||
"Loaded machine text files from OPFS.",
|
||||
`INI bytes: ${files.ini.length}`,
|
||||
`Tool table bytes: ${files.toolTable.length}`,
|
||||
`Parameter bytes: ${files.parameters.length}`,
|
||||
...machineFileLoadLogLines(summary),
|
||||
].join("\n"),
|
||||
);
|
||||
} catch (error) {
|
||||
|
||||
60
wasm-port/runtime/ui/ini-panel/control-view-renderer.js
Normal file
60
wasm-port/runtime/ui/ini-panel/control-view-renderer.js
Normal file
@@ -0,0 +1,60 @@
|
||||
function displayValue(value) {
|
||||
return String(value ?? "-");
|
||||
}
|
||||
|
||||
function statusText(status = {}) {
|
||||
return [
|
||||
status.lastAction ?? "-",
|
||||
status.runStatus ?? "-",
|
||||
status.error ?? "-",
|
||||
].join(" | ");
|
||||
}
|
||||
|
||||
export function renderMachineSessionControlView(container, view = {}) {
|
||||
if (!container) {
|
||||
throw new Error("A control view container is required.");
|
||||
}
|
||||
|
||||
const document = container.ownerDocument;
|
||||
container.textContent = "";
|
||||
|
||||
const statusNode = document.createElement("div");
|
||||
statusNode.dataset.controlViewStatus = "";
|
||||
statusNode.textContent = statusText(view.status);
|
||||
container.appendChild(statusNode);
|
||||
|
||||
const sectionsNode = document.createElement("div");
|
||||
sectionsNode.dataset.controlViewSections = "";
|
||||
container.appendChild(sectionsNode);
|
||||
|
||||
const sectionNodes = [];
|
||||
for (const section of view.sections ?? []) {
|
||||
const sectionNode = document.createElement("section");
|
||||
sectionNode.dataset.sectionId = section.id;
|
||||
sectionNode.dataset.controlViewSectionId = section.id;
|
||||
|
||||
const heading = document.createElement("h3");
|
||||
heading.textContent = section.title;
|
||||
sectionNode.appendChild(heading);
|
||||
|
||||
const list = document.createElement("dl");
|
||||
for (const row of section.rows ?? []) {
|
||||
const term = document.createElement("dt");
|
||||
term.textContent = row.label;
|
||||
const def = document.createElement("dd");
|
||||
def.dataset.controlViewRow = row.label;
|
||||
def.textContent = displayValue(row.value);
|
||||
list.append(term, def);
|
||||
}
|
||||
|
||||
sectionNode.appendChild(list);
|
||||
sectionsNode.appendChild(sectionNode);
|
||||
sectionNodes.push(sectionNode);
|
||||
}
|
||||
|
||||
return {
|
||||
statusNode,
|
||||
sectionsNode,
|
||||
sectionNodes,
|
||||
};
|
||||
}
|
||||
@@ -85,6 +85,47 @@
|
||||
background: #fbfdff;
|
||||
}
|
||||
.status { display: grid; gap: 8px; }
|
||||
.control-view {
|
||||
border-top: 1px solid #edf2f6;
|
||||
padding-top: 12px;
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
.control-view [data-control-view-status] {
|
||||
color: var(--muted);
|
||||
font: 13px/1.4 "SFMono-Regular", "Consolas", monospace;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.control-view [data-control-view-sections] {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
.control-view section {
|
||||
border-top: 1px solid #edf2f6;
|
||||
padding-top: 10px;
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
.control-view h3 {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.control-view dl {
|
||||
display: grid;
|
||||
grid-template-columns: 132px 1fr;
|
||||
gap: 6px 10px;
|
||||
margin: 0;
|
||||
}
|
||||
.control-view dt {
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
.control-view dd {
|
||||
margin: 0;
|
||||
font: 12px/1.4 "SFMono-Regular", "Consolas", monospace;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: 4px 8px;
|
||||
@@ -228,6 +269,9 @@
|
||||
<button id="load-opfs">Load From OPFS</button>
|
||||
<button id="save-machine-files">Save Machine Files</button>
|
||||
<button id="load-machine-files">Load Machine Files</button>
|
||||
<button id="save-session-snapshot">Save Session Snapshot</button>
|
||||
<button id="restore-session-snapshot">Restore Session Snapshot</button>
|
||||
<button id="restore-load-session" class="secondary">Restore And Load Session</button>
|
||||
<button id="sync-wasm">Sync To WASM FS</button>
|
||||
<button id="query" class="secondary">Query LinuxCNC Fields</button>
|
||||
<button id="load-session" class="secondary">Load Machine Session</button>
|
||||
@@ -244,6 +288,7 @@
|
||||
<div><span id="interp-badge" class="badge">Interpreter WASM: loading</span></div>
|
||||
<div><span id="opfs-badge" class="badge">OPFS: checking</span></div>
|
||||
</div>
|
||||
<div id="machine-session-control-view" class="control-view"></div>
|
||||
|
||||
<dl class="kv">
|
||||
<dt>Machine</dt>
|
||||
@@ -272,8 +317,20 @@
|
||||
<dd id="field-session-tool-table">-</dd>
|
||||
<dt>Session G-code</dt>
|
||||
<dd id="field-session-gcode">-</dd>
|
||||
<dt>Session Snapshot</dt>
|
||||
<dd id="field-session-snapshot">-</dd>
|
||||
<dt>Run Status</dt>
|
||||
<dd id="field-run-status">-</dd>
|
||||
<dt>Run Source</dt>
|
||||
<dd id="field-run-source">-</dd>
|
||||
<dt>Run Snapshot</dt>
|
||||
<dd id="field-run-snapshot">-</dd>
|
||||
<dt>Run Workflow</dt>
|
||||
<dd id="field-run-workflow">-</dd>
|
||||
<dt>Run OPFS Path</dt>
|
||||
<dd id="field-run-opfs-path">-</dd>
|
||||
<dt>Run WASM Path</dt>
|
||||
<dd id="field-run-wasm-path">-</dd>
|
||||
<dt>5-Axis Remap</dt>
|
||||
<dd id="field-fiveaxis-remap">-</dd>
|
||||
</dl>
|
||||
|
||||
33
wasm-port/runtime/ui/ini-panel/machine-file-summary.js
Normal file
33
wasm-port/runtime/ui/ini-panel/machine-file-summary.js
Normal file
@@ -0,0 +1,33 @@
|
||||
export function createMachineFileSaveSummary(paths, gcodePath = null) {
|
||||
return {
|
||||
iniOpfsPath: paths?.ini ?? null,
|
||||
toolTableOpfsPath: paths?.toolTable ?? null,
|
||||
parameterOpfsPath: paths?.parameters ?? null,
|
||||
gcodeOpfsPath: gcodePath ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
export function machineFileSaveLogLines(summary) {
|
||||
return [
|
||||
`INI: ${summary.iniOpfsPath ?? "-"}`,
|
||||
`Tool table: ${summary.toolTableOpfsPath ?? "-"}`,
|
||||
`Parameters: ${summary.parameterOpfsPath ?? "-"}`,
|
||||
`G-code: ${summary.gcodeOpfsPath ?? "-"}`,
|
||||
];
|
||||
}
|
||||
|
||||
export function createMachineFileLoadSummary(files) {
|
||||
return {
|
||||
iniBytes: files?.ini?.length ?? 0,
|
||||
toolTableBytes: files?.toolTable?.length ?? 0,
|
||||
parameterBytes: files?.parameters?.length ?? 0,
|
||||
};
|
||||
}
|
||||
|
||||
export function machineFileLoadLogLines(summary) {
|
||||
return [
|
||||
`INI bytes: ${summary.iniBytes}`,
|
||||
`Tool table bytes: ${summary.toolTableBytes}`,
|
||||
`Parameter bytes: ${summary.parameterBytes}`,
|
||||
];
|
||||
}
|
||||
187
wasm-port/runtime/ui/ini-panel/panel-state-summary.js
Normal file
187
wasm-port/runtime/ui/ini-panel/panel-state-summary.js
Normal file
@@ -0,0 +1,187 @@
|
||||
export function createIniPanelStateSummary({
|
||||
badges = {},
|
||||
machine = null,
|
||||
machineFiles = null,
|
||||
sessionSnapshot = null,
|
||||
sessionLoad = null,
|
||||
run = null,
|
||||
fiveaxisRemap = null,
|
||||
} = {}) {
|
||||
return {
|
||||
badges: {
|
||||
ini: badges.ini ?? null,
|
||||
interp: badges.interp ?? null,
|
||||
opfs: badges.opfs ?? null,
|
||||
},
|
||||
machine,
|
||||
machineFiles,
|
||||
sessionSnapshot,
|
||||
sessionLoad,
|
||||
run,
|
||||
fiveaxisRemap: fiveaxisRemap ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
export function createMachineSessionStateSnapshot({
|
||||
badges = {},
|
||||
machine = null,
|
||||
machineFiles = null,
|
||||
sessionSnapshot = null,
|
||||
sessionLoad = null,
|
||||
selectedProgram = null,
|
||||
run = null,
|
||||
fields = {},
|
||||
lastAction = null,
|
||||
error = null,
|
||||
fiveaxisRemap = null,
|
||||
} = {}) {
|
||||
return {
|
||||
...createIniPanelStateSummary({
|
||||
badges,
|
||||
machine,
|
||||
machineFiles,
|
||||
sessionSnapshot,
|
||||
sessionLoad,
|
||||
run,
|
||||
fiveaxisRemap,
|
||||
}),
|
||||
selectedProgram,
|
||||
fields: {
|
||||
sessionIni: fields.sessionIni ?? null,
|
||||
sessionParameters: fields.sessionParameters ?? null,
|
||||
sessionToolTable: fields.sessionToolTable ?? null,
|
||||
sessionGcode: fields.sessionGcode ?? null,
|
||||
sessionSnapshot: fields.sessionSnapshot ?? null,
|
||||
runStatus: fields.runStatus ?? null,
|
||||
runSource: fields.runSource ?? null,
|
||||
runSnapshot: fields.runSnapshot ?? null,
|
||||
runWorkflow: fields.runWorkflow ?? null,
|
||||
runOpfsPath: fields.runOpfsPath ?? null,
|
||||
runWasmPath: fields.runWasmPath ?? null,
|
||||
},
|
||||
lastAction: lastAction ?? null,
|
||||
error: error ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
export function createMachineSessionStateReport(state = {}) {
|
||||
const badges = state.badges ?? {};
|
||||
const fields = state.fields ?? {};
|
||||
const sessionSnapshot = state.sessionSnapshot ?? {};
|
||||
const sessionLoad = state.sessionLoad ?? {};
|
||||
const selectedProgram = state.selectedProgram ?? {};
|
||||
const run = state.run ?? {};
|
||||
return {
|
||||
readiness: {
|
||||
ini: badges.ini ?? null,
|
||||
interp: badges.interp ?? null,
|
||||
opfs: badges.opfs ?? null,
|
||||
},
|
||||
session: {
|
||||
snapshotLabel: sessionSnapshot.snapshotLabel ?? fields.sessionSnapshot ?? null,
|
||||
workflow: sessionSnapshot.workflow ?? fields.runWorkflow ?? null,
|
||||
ini: fields.sessionIni ?? sessionSnapshot.iniOpfsPath ?? sessionLoad.iniWasmPath ?? null,
|
||||
parameters: fields.sessionParameters ?? sessionSnapshot.parameterOpfsPath ?? sessionLoad.parameterWasmPath ?? null,
|
||||
toolTable: fields.sessionToolTable ?? sessionSnapshot.toolTableOpfsPath ?? sessionLoad.toolTableWasmPath ?? null,
|
||||
gcode: fields.sessionGcode ?? sessionSnapshot.gcodeOpfsPath ?? null,
|
||||
},
|
||||
loaded: {
|
||||
iniWasmPath: sessionLoad.iniWasmPath ?? null,
|
||||
parameterWasmPath: sessionLoad.parameterWasmPath ?? null,
|
||||
toolTableWasmPath: sessionLoad.toolTableWasmPath ?? null,
|
||||
},
|
||||
program: {
|
||||
source: selectedProgram.source ?? run.programSource ?? fields.runSource ?? null,
|
||||
opfsPath: selectedProgram.opfsPath ?? run.programOpfsPath ?? fields.runOpfsPath ?? null,
|
||||
wasmPath: selectedProgram.wasmPath ?? run.programWasmPath ?? fields.runWasmPath ?? null,
|
||||
},
|
||||
run: {
|
||||
status: run.runStatus ?? fields.runStatus ?? null,
|
||||
canonicalEventCount: run.canonicalEventCount ?? 0,
|
||||
motionSnapshotCount: run.motionSnapshotCount ?? 0,
|
||||
iniWasmPath: run.iniWasmPath ?? null,
|
||||
},
|
||||
status: {
|
||||
lastAction: state.lastAction ?? null,
|
||||
error: state.error ?? null,
|
||||
fiveaxisRemap: state.fiveaxisRemap ?? null,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function createMachineSessionStateReportExport(state = {}, options = {}) {
|
||||
const report = createMachineSessionStateReport(state);
|
||||
return {
|
||||
filename: options.filename ?? "machine-session-state-report.json",
|
||||
mediaType: "application/json",
|
||||
report,
|
||||
text: `${JSON.stringify(report, null, 2)}\n`,
|
||||
};
|
||||
}
|
||||
|
||||
export function createMachineSessionWorkflowStatus(state = {}) {
|
||||
const report = createMachineSessionStateReport(state);
|
||||
return {
|
||||
readiness: report.readiness,
|
||||
lastAction: report.status.lastAction,
|
||||
error: report.status.error,
|
||||
session: report.session,
|
||||
run: report.run,
|
||||
};
|
||||
}
|
||||
|
||||
export function createMachineSessionStateBundle(state = {}, options = {}) {
|
||||
const report = createMachineSessionStateReport(state);
|
||||
const exportPayload = createMachineSessionStateReportExport(state, options);
|
||||
const workflowStatus = createMachineSessionWorkflowStatus(state);
|
||||
return {
|
||||
report,
|
||||
export: exportPayload,
|
||||
workflowStatus,
|
||||
};
|
||||
}
|
||||
|
||||
export function createMachineSessionControlView(bundle = {}) {
|
||||
const report = bundle.report ?? {};
|
||||
const workflowStatus = bundle.workflowStatus ?? {};
|
||||
const session = workflowStatus.session ?? report.session ?? {};
|
||||
const run = workflowStatus.run ?? report.run ?? {};
|
||||
return {
|
||||
status: {
|
||||
lastAction: workflowStatus.lastAction ?? report.status?.lastAction ?? null,
|
||||
error: workflowStatus.error ?? report.status?.error ?? null,
|
||||
runStatus: run.status ?? null,
|
||||
},
|
||||
sections: [
|
||||
{
|
||||
id: "readiness",
|
||||
title: "Readiness",
|
||||
rows: [
|
||||
{ label: "INI", value: workflowStatus.readiness?.ini ?? report.readiness?.ini ?? null },
|
||||
{ label: "Interpreter", value: workflowStatus.readiness?.interp ?? report.readiness?.interp ?? null },
|
||||
{ label: "OPFS", value: workflowStatus.readiness?.opfs ?? report.readiness?.opfs ?? null },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "session",
|
||||
title: "Session",
|
||||
rows: [
|
||||
{ label: "Snapshot", value: session.snapshotLabel ?? null },
|
||||
{ label: "Workflow", value: session.workflow ?? null },
|
||||
{ label: "INI", value: session.ini ?? null },
|
||||
{ label: "G-code", value: session.gcode ?? null },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "run",
|
||||
title: "Run",
|
||||
rows: [
|
||||
{ label: "Status", value: run.status ?? null },
|
||||
{ label: "Canonical events", value: run.canonicalEventCount ?? 0 },
|
||||
{ label: "Motion snapshots", value: run.motionSnapshotCount ?? 0 },
|
||||
{ label: "INI WASM", value: run.iniWasmPath ?? null },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
21
wasm-port/runtime/ui/ini-panel/run-summary.js
Normal file
21
wasm-port/runtime/ui/ini-panel/run-summary.js
Normal file
@@ -0,0 +1,21 @@
|
||||
export function summarizeRunResult(resultText, snapshots = []) {
|
||||
return {
|
||||
canonicalEventCount: resultText.split("\n").filter((line) => line.startsWith("canon_event=")).length,
|
||||
motionSnapshotCount: snapshots.length,
|
||||
};
|
||||
}
|
||||
|
||||
export function createRunSummary(program, runtime = {}, snapshot = null) {
|
||||
const metadata = snapshot?.metadata ?? {};
|
||||
return {
|
||||
runStatus: runtime.runStatus ?? "ok",
|
||||
programSource: program.source,
|
||||
snapshotLabel: runtime.snapshotLabel ?? metadata.snapshotLabel ?? null,
|
||||
workflow: metadata.workflow ?? null,
|
||||
programOpfsPath: program.opfsPath,
|
||||
programWasmPath: program.wasmPath,
|
||||
iniWasmPath: runtime.iniWasmPath ?? null,
|
||||
canonicalEventCount: runtime.canonicalEventCount ?? 0,
|
||||
motionSnapshotCount: runtime.motionSnapshotCount ?? 0,
|
||||
};
|
||||
}
|
||||
109
wasm-port/runtime/ui/ini-panel/run-workflow.js
Normal file
109
wasm-port/runtime/ui/ini-panel/run-workflow.js
Normal file
@@ -0,0 +1,109 @@
|
||||
import {
|
||||
createRunSummary,
|
||||
} from "./run-summary.js";
|
||||
import {
|
||||
createSessionSnapshotSummary,
|
||||
sessionSnapshotMetadataLogLines,
|
||||
} from "./session-summary.js";
|
||||
|
||||
export function selectSessionGcodeProgram({
|
||||
snapshot = null,
|
||||
defaultFilename,
|
||||
defaultOpfsPath,
|
||||
wasmPath,
|
||||
gcodeFilenameFromProgramPath,
|
||||
}) {
|
||||
const snapshotOpfsPath = snapshot?.payload?.files?.gcode;
|
||||
if (typeof snapshotOpfsPath === "string") {
|
||||
try {
|
||||
return {
|
||||
filename: gcodeFilenameFromProgramPath(snapshotOpfsPath),
|
||||
opfsPath: snapshotOpfsPath,
|
||||
source: "snapshot",
|
||||
wasmPath,
|
||||
};
|
||||
} catch {
|
||||
// Invalid snapshot program paths fall back to the UI default program.
|
||||
}
|
||||
}
|
||||
return {
|
||||
filename: defaultFilename,
|
||||
opfsPath: defaultOpfsPath,
|
||||
source: "default",
|
||||
wasmPath,
|
||||
};
|
||||
}
|
||||
|
||||
export function runSessionContextLogLines(program, snapshot = null, snapshotLabel = null) {
|
||||
const summary = createSessionSnapshotSummary({
|
||||
metadata: snapshot?.metadata ?? {},
|
||||
});
|
||||
return [
|
||||
`Program: ${program.opfsPath} -> ${program.wasmPath}`,
|
||||
`Program source: ${program.source}`,
|
||||
`Snapshot: ${snapshot ? (snapshotLabel ?? snapshot?.metadata?.snapshotLabel ?? snapshot?.sessionId) : "-"}`,
|
||||
...sessionSnapshotMetadataLogLines(summary),
|
||||
];
|
||||
}
|
||||
|
||||
export async function runGcodeSessionWorkflow({
|
||||
interp,
|
||||
loadedSession,
|
||||
snapshot = null,
|
||||
defaultFilename,
|
||||
defaultOpfsPath,
|
||||
wasmPath,
|
||||
gcodeFilenameFromProgramPath,
|
||||
loadGcodeProgram,
|
||||
summarizeRun = () => ({}),
|
||||
snapshotLabel = null,
|
||||
}) {
|
||||
if (!loadedSession) {
|
||||
throw new Error("Load a machine session before running G-code.");
|
||||
}
|
||||
const program = selectSessionGcodeProgram({
|
||||
snapshot,
|
||||
defaultFilename,
|
||||
defaultOpfsPath,
|
||||
wasmPath,
|
||||
gcodeFilenameFromProgramPath,
|
||||
});
|
||||
const programText = await loadGcodeProgram(program.filename);
|
||||
interp.writeTextFile(program.wasmPath, programText);
|
||||
const resultText = interp.runFileWithIni(program.wasmPath, loadedSession.ini.wasmPath).trim();
|
||||
const runResultSummary = summarizeRun(resultText, programText);
|
||||
const { motionSnapshots = [], ...runCounts } = runResultSummary;
|
||||
const summary = createRunSummary(
|
||||
program,
|
||||
{
|
||||
runStatus: "ok",
|
||||
iniWasmPath: loadedSession.ini.wasmPath,
|
||||
snapshotLabel: snapshot ? snapshotLabel : null,
|
||||
...runCounts,
|
||||
},
|
||||
snapshot,
|
||||
);
|
||||
return {
|
||||
program: {
|
||||
...program,
|
||||
text: programText,
|
||||
},
|
||||
resultText,
|
||||
motionSnapshots,
|
||||
summary,
|
||||
fields: {
|
||||
sessionGcode: program.opfsPath,
|
||||
runStatus: summary.runStatus,
|
||||
runSource: summary.programSource,
|
||||
runSnapshot: summary.snapshotLabel ?? "-",
|
||||
runWorkflow: summary.workflow ?? "-",
|
||||
runOpfsPath: summary.programOpfsPath,
|
||||
runWasmPath: summary.programWasmPath,
|
||||
},
|
||||
logLines: [
|
||||
...runSessionContextLogLines(program, snapshot, summary.snapshotLabel),
|
||||
`INI: ${loadedSession.ini.wasmPath}`,
|
||||
resultText,
|
||||
],
|
||||
};
|
||||
}
|
||||
22
wasm-port/runtime/ui/ini-panel/session-load-summary.js
Normal file
22
wasm-port/runtime/ui/ini-panel/session-load-summary.js
Normal file
@@ -0,0 +1,22 @@
|
||||
export function createMachineSessionLoadSummary(session) {
|
||||
return {
|
||||
iniOpfsPath: session?.ini?.opfsPath ?? null,
|
||||
iniWasmPath: session?.ini?.wasmPath ?? null,
|
||||
parameterOpfsPath: session?.parameters?.opfsPath ?? null,
|
||||
parameterWasmPath: session?.parameters?.wasmPath ?? null,
|
||||
parameterResult: session?.parameters?.result ?? "",
|
||||
toolTableOpfsPath: session?.toolTable?.opfsPath ?? null,
|
||||
toolTableWasmPath: session?.toolTable?.wasmPath ?? null,
|
||||
toolTableResult: session?.toolTable?.result ?? "",
|
||||
};
|
||||
}
|
||||
|
||||
export function machineSessionLoadLogLines(summary) {
|
||||
return [
|
||||
`INI: ${summary.iniOpfsPath ?? "-"} -> ${summary.iniWasmPath ?? "-"}`,
|
||||
`Parameter file: ${summary.parameterOpfsPath ?? "-"} -> ${summary.parameterWasmPath ?? "-"}`,
|
||||
`Parameters: ${summary.parameterResult.trim()}`,
|
||||
`Tool table file: ${summary.toolTableOpfsPath ?? "-"} -> ${summary.toolTableWasmPath ?? "-"}`,
|
||||
`Tool table: ${summary.toolTableResult.trim()}`,
|
||||
];
|
||||
}
|
||||
28
wasm-port/runtime/ui/ini-panel/session-summary.js
Normal file
28
wasm-port/runtime/ui/ini-panel/session-summary.js
Normal file
@@ -0,0 +1,28 @@
|
||||
export function createSessionSnapshotSummary(snapshot, fallbackLabel = null) {
|
||||
const metadata = snapshot?.metadata ?? {};
|
||||
const files = snapshot?.payload?.files ?? {};
|
||||
return {
|
||||
snapshotLabel: fallbackLabel ?? metadata.snapshotLabel ?? null,
|
||||
workflow: metadata.workflow ?? null,
|
||||
defaultGcodeOpfsPath: metadata.defaultGcodeOpfsPath ?? null,
|
||||
iniOpfsPath: files.ini ?? null,
|
||||
parameterOpfsPath: files.parameters ?? null,
|
||||
toolTableOpfsPath: files.toolTable ?? null,
|
||||
gcodeOpfsPath: files.gcode ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
export function sessionSnapshotSummaryLogLines(summary) {
|
||||
return [
|
||||
`Snapshot: ${summary.snapshotLabel ?? "-"}`,
|
||||
`Workflow: ${summary.workflow ?? "-"}`,
|
||||
`Default G-code: ${summary.defaultGcodeOpfsPath ?? "-"}`,
|
||||
];
|
||||
}
|
||||
|
||||
export function sessionSnapshotMetadataLogLines(summary) {
|
||||
return [
|
||||
`Workflow: ${summary.workflow ?? "-"}`,
|
||||
`Default G-code: ${summary.defaultGcodeOpfsPath ?? "-"}`,
|
||||
];
|
||||
}
|
||||
130
wasm-port/runtime/ui/ini-panel/session-workflow.js
Normal file
130
wasm-port/runtime/ui/ini-panel/session-workflow.js
Normal file
@@ -0,0 +1,130 @@
|
||||
import {
|
||||
createMachineSessionLoadSummary,
|
||||
machineSessionLoadLogLines,
|
||||
} from "./session-load-summary.js";
|
||||
import {
|
||||
createSessionSnapshotSummary,
|
||||
sessionSnapshotSummaryLogLines,
|
||||
} from "./session-summary.js";
|
||||
|
||||
export function createSessionSnapshotFieldState(summary) {
|
||||
return {
|
||||
sessionIni: summary.iniOpfsPath,
|
||||
sessionParameters: summary.parameterOpfsPath,
|
||||
sessionToolTable: summary.toolTableOpfsPath,
|
||||
sessionGcode: summary.gcodeOpfsPath,
|
||||
sessionSnapshot: summary.snapshotLabel,
|
||||
};
|
||||
}
|
||||
|
||||
export function sessionSnapshotSaveLogLines(summary) {
|
||||
return [
|
||||
...sessionSnapshotSummaryLogLines(summary),
|
||||
`INI: ${summary.iniOpfsPath}`,
|
||||
`Parameter file: ${summary.parameterOpfsPath}`,
|
||||
`Tool table file: ${summary.toolTableOpfsPath}`,
|
||||
`G-code: ${summary.gcodeOpfsPath}`,
|
||||
];
|
||||
}
|
||||
|
||||
export function sessionSnapshotRestoreLogLines(summary) {
|
||||
return [
|
||||
...sessionSnapshotSummaryLogLines(summary),
|
||||
`INI: ${summary.iniOpfsPath}`,
|
||||
`Parameter file: ${summary.parameterOpfsPath}`,
|
||||
`Tool table file: ${summary.toolTableOpfsPath}`,
|
||||
`G-code: ${summary.gcodeOpfsPath ?? "-"}`,
|
||||
];
|
||||
}
|
||||
|
||||
export async function saveMachineSessionSnapshotWorkflow({
|
||||
machineId,
|
||||
snapshotId,
|
||||
snapshotFilename,
|
||||
snapshotLabel,
|
||||
iniText,
|
||||
toolTableText,
|
||||
parameterText,
|
||||
gcodeFilename,
|
||||
gcodeText,
|
||||
metadata,
|
||||
saveMachineTextFiles,
|
||||
saveGcodeProgram,
|
||||
saveMachineSessionSnapshot,
|
||||
}) {
|
||||
await Promise.all([
|
||||
saveMachineTextFiles(machineId, {
|
||||
ini: iniText,
|
||||
toolTable: toolTableText,
|
||||
parameters: parameterText,
|
||||
}),
|
||||
saveGcodeProgram(gcodeFilename, gcodeText),
|
||||
]);
|
||||
const snapshot = await saveMachineSessionSnapshot(
|
||||
snapshotId,
|
||||
machineId,
|
||||
{
|
||||
filename: snapshotFilename,
|
||||
gcodeFilename,
|
||||
metadata,
|
||||
},
|
||||
);
|
||||
const summary = createSessionSnapshotSummary(snapshot, snapshotLabel);
|
||||
return {
|
||||
snapshot,
|
||||
summary,
|
||||
fields: createSessionSnapshotFieldState(summary),
|
||||
logLines: sessionSnapshotSaveLogLines(summary),
|
||||
};
|
||||
}
|
||||
|
||||
export async function restoreMachineSessionSnapshotWorkflow({
|
||||
machineId,
|
||||
snapshotId,
|
||||
snapshotFilename,
|
||||
snapshotLabel,
|
||||
loadMachineSessionSnapshot,
|
||||
loadMachineTextFiles,
|
||||
loadSession = null,
|
||||
}) {
|
||||
const snapshot = await loadMachineSessionSnapshot(
|
||||
snapshotId,
|
||||
machineId,
|
||||
{ filename: snapshotFilename },
|
||||
);
|
||||
const files = await loadMachineTextFiles(machineId);
|
||||
const summary = createSessionSnapshotSummary(snapshot, snapshotLabel ?? snapshotLabelFromSession(snapshot));
|
||||
const fields = createSessionSnapshotFieldState(summary);
|
||||
const result = {
|
||||
snapshot,
|
||||
editorIniText: files.ini,
|
||||
summary,
|
||||
fields,
|
||||
loadSummary: null,
|
||||
loadedSession: null,
|
||||
logLines: sessionSnapshotRestoreLogLines(summary),
|
||||
};
|
||||
if (loadSession) {
|
||||
const loadedSession = await loadSession();
|
||||
const loadSummary = createMachineSessionLoadSummary(loadedSession);
|
||||
result.loadedSession = loadedSession;
|
||||
result.loadSummary = loadSummary;
|
||||
result.fields = {
|
||||
...fields,
|
||||
sessionIni: loadSummary.iniWasmPath,
|
||||
sessionParameters: loadSummary.parameterWasmPath,
|
||||
sessionToolTable: loadSummary.toolTableWasmPath,
|
||||
sessionGcode: summary.gcodeOpfsPath,
|
||||
};
|
||||
result.logLines = [
|
||||
...sessionSnapshotSummaryLogLines(summary),
|
||||
...machineSessionLoadLogLines(loadSummary),
|
||||
`G-code: ${summary.gcodeOpfsPath ?? "-"}`,
|
||||
];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function snapshotLabelFromSession(snapshot) {
|
||||
return snapshot?.metadata?.snapshotLabel ?? snapshot?.sessionId ?? null;
|
||||
}
|
||||
@@ -6,8 +6,10 @@
|
||||
</head>
|
||||
<body>
|
||||
<pre id="status">running</pre>
|
||||
<div id="control-view"></div>
|
||||
<script type="module">
|
||||
import { createLinuxCncIniSdk } from "../../runtime/sdk/src/index.js";
|
||||
import { renderMachineSessionControlView } from "../../runtime/ui/ini-panel/control-view-renderer.js";
|
||||
import { loadTextFile, saveTextFile } from "../../runtime/opfs/file-service.js";
|
||||
import {
|
||||
gcodeProgramPath,
|
||||
@@ -16,10 +18,13 @@
|
||||
} from "../../runtime/opfs/path-model.js";
|
||||
import {
|
||||
createMachineSessionSnapshotPayload,
|
||||
loadMachineSessionSnapshot,
|
||||
loadSessionSnapshot,
|
||||
saveMachineSessionSnapshot,
|
||||
saveSessionSnapshot,
|
||||
} from "../../runtime/opfs/snapshot-store.js";
|
||||
import {
|
||||
gcodeFilenameFromProgramPath,
|
||||
loadGcodeProgram,
|
||||
loadMachineTextFiles,
|
||||
saveGcodeProgram,
|
||||
@@ -204,6 +209,61 @@ TOOL_TABLE = browser-tool.tbl
|
||||
customSnapshot.metadata.source,
|
||||
"custom snapshot metadata",
|
||||
);
|
||||
const machineSessionSnapshot = await saveMachineSessionSnapshot(
|
||||
"browser-machine-session",
|
||||
"browser-smoke",
|
||||
{
|
||||
filename: "browser-machine-snapshot.json",
|
||||
parameterFilename: "browser-linuxcnc.var",
|
||||
toolTableFilename: "browser-tool.tbl",
|
||||
gcodeFilename: "browser-smoke.ngc",
|
||||
createdAt: "2026-06-08T00:00:00.000Z",
|
||||
metadata: { source: "browser-machine-session" },
|
||||
},
|
||||
);
|
||||
const loadedMachineSessionSnapshot = await loadSessionSnapshot(
|
||||
"browser-machine-session",
|
||||
{ filename: "browser-machine-snapshot.json" },
|
||||
);
|
||||
const loadedCheckedMachineSessionSnapshot = await loadMachineSessionSnapshot(
|
||||
"browser-machine-session",
|
||||
"browser-smoke",
|
||||
{ filename: "browser-machine-snapshot.json" },
|
||||
);
|
||||
assertEqual(
|
||||
machineSessionSnapshot.payload.files.parameters,
|
||||
"linuxcnc/machines/browser-smoke/browser-linuxcnc.var",
|
||||
"machine session snapshot parameter file",
|
||||
);
|
||||
assertEqual(
|
||||
machineSessionSnapshot.payload.files.toolTable,
|
||||
"linuxcnc/machines/browser-smoke/browser-tool.tbl",
|
||||
"machine session snapshot tool table",
|
||||
);
|
||||
assertEqual(
|
||||
loadedMachineSessionSnapshot.payload.files.gcode,
|
||||
"linuxcnc/gcode/browser-smoke.ngc",
|
||||
"machine session snapshot G-code",
|
||||
);
|
||||
assertEqual(
|
||||
loadedMachineSessionSnapshot.metadata.source,
|
||||
"browser-machine-session",
|
||||
"machine session snapshot metadata",
|
||||
);
|
||||
assertEqual(
|
||||
loadedCheckedMachineSessionSnapshot.payload.machineId,
|
||||
"browser-smoke",
|
||||
"checked machine session snapshot id",
|
||||
);
|
||||
await assertRejects(
|
||||
"browser machine snapshot wrong machine",
|
||||
() => loadMachineSessionSnapshot(
|
||||
"browser-machine-session",
|
||||
"other-browser-machine",
|
||||
{ filename: "browser-machine-snapshot.json" },
|
||||
),
|
||||
/Machine session snapshot id mismatch/,
|
||||
);
|
||||
await assertRejects(
|
||||
"browser snapshot invalid filename save",
|
||||
() => saveSessionSnapshot("browser-session", {}, {
|
||||
@@ -271,6 +331,16 @@ TOOL_TABLE = browser-tool.tbl
|
||||
"linuxcnc/gcode/browser-custom.ngc",
|
||||
"custom G-code path",
|
||||
);
|
||||
assertEqual(
|
||||
gcodeFilenameFromProgramPath("linuxcnc/gcode/browser-custom.ngc"),
|
||||
"browser-custom.ngc",
|
||||
"custom G-code filename from OPFS path",
|
||||
);
|
||||
await assertRejects(
|
||||
"browser G-code invalid program OPFS path",
|
||||
() => Promise.resolve(gcodeFilenameFromProgramPath("linuxcnc/machines/browser-custom.ngc")),
|
||||
/Invalid G-code program OPFS path/,
|
||||
);
|
||||
await saveGcodeProgram("browser-smoke.ngc", "G0 X0 Y0\nM2\n");
|
||||
assertEqual(await loadGcodeProgram("browser-smoke.ngc"), "G0 X0 Y0\nM2\n", "gcode text");
|
||||
await saveGcodeProgram("browser-custom.ngc", "G1 X1 F10\nM2\n");
|
||||
@@ -299,6 +369,17 @@ TOOL_TABLE = browser-tool.tbl
|
||||
() => uiDocument.getElementById("opfs-badge")?.textContent.includes("ready"),
|
||||
"INI panel OPFS readiness",
|
||||
);
|
||||
const uiApi = uiDocument.defaultView.linuxCncIniPanelApi;
|
||||
if (
|
||||
!uiApi?.getMachineSessionControlView ||
|
||||
!uiApi?.getMachineSessionStateBundle ||
|
||||
!uiApi?.getMachineSessionStateReport ||
|
||||
!uiApi?.getMachineSessionStateReportExport ||
|
||||
!uiApi?.getMachineSessionWorkflowStatus ||
|
||||
!uiApi?.renderMachineSessionControlView
|
||||
) {
|
||||
throw new Error("INI panel API namespace did not expose state getters");
|
||||
}
|
||||
|
||||
uiDocument.getElementById("ini-editor").value = iniText;
|
||||
uiDocument.getElementById("query").click();
|
||||
@@ -325,7 +406,168 @@ TOOL_TABLE = browser-tool.tbl
|
||||
uiDocument.getElementById("load-session").click();
|
||||
await waitFor(
|
||||
() => uiDocument.getElementById("log")?.textContent.includes("Loaded machine session"),
|
||||
"machine session load",
|
||||
"fallback machine session load",
|
||||
);
|
||||
uiDocument.getElementById("run-gcode").click();
|
||||
await waitFor(
|
||||
() => uiDocument.getElementById("log")?.textContent.includes("Ran G-code"),
|
||||
"fallback G-code run",
|
||||
);
|
||||
const fallbackRunLog = uiDocument.getElementById("log").textContent;
|
||||
if (
|
||||
!fallbackRunLog.includes("Program: linuxcnc/gcode/ui-session.ngc -> /work/ui-session.ngc") ||
|
||||
!fallbackRunLog.includes("Program source: default") ||
|
||||
!fallbackRunLog.includes("Snapshot: -") ||
|
||||
!fallbackRunLog.includes("Workflow: -") ||
|
||||
!fallbackRunLog.includes("Default G-code: -") ||
|
||||
!fallbackRunLog.includes("canon_event=STRAIGHT_FEED")
|
||||
) {
|
||||
throw new Error(`UI fallback G-code run did not use default OPFS program path: ${fallbackRunLog}`);
|
||||
}
|
||||
assertEqual(
|
||||
uiDocument.getElementById("field-run-source").textContent,
|
||||
"default",
|
||||
"UI fallback run source field",
|
||||
);
|
||||
assertEqual(
|
||||
uiDocument.getElementById("field-run-snapshot").textContent,
|
||||
"-",
|
||||
"UI fallback run snapshot field",
|
||||
);
|
||||
assertEqual(
|
||||
uiDocument.getElementById("field-run-workflow").textContent,
|
||||
"-",
|
||||
"UI fallback run workflow field",
|
||||
);
|
||||
assertEqual(
|
||||
uiDocument.getElementById("field-run-opfs-path").textContent,
|
||||
"linuxcnc/gcode/ui-session.ngc",
|
||||
"UI fallback run OPFS field",
|
||||
);
|
||||
assertEqual(
|
||||
uiDocument.getElementById("field-run-wasm-path").textContent,
|
||||
"/work/ui-session.ngc",
|
||||
"UI fallback run WASM field",
|
||||
);
|
||||
const fallbackRunSummary = uiDocument.defaultView.linuxCncIniPanelLastRunSummary;
|
||||
assertEqual(fallbackRunSummary.runStatus, "ok", "UI fallback run summary status");
|
||||
assertEqual(fallbackRunSummary.programSource, "default", "UI fallback run summary source");
|
||||
assertEqual(fallbackRunSummary.snapshotLabel, null, "UI fallback run summary snapshot");
|
||||
assertEqual(fallbackRunSummary.workflow, null, "UI fallback run summary workflow");
|
||||
assertEqual(
|
||||
fallbackRunSummary.programOpfsPath,
|
||||
"linuxcnc/gcode/ui-session.ngc",
|
||||
"UI fallback run summary OPFS path",
|
||||
);
|
||||
assertEqual(
|
||||
fallbackRunSummary.programWasmPath,
|
||||
"/work/ui-session.ngc",
|
||||
"UI fallback run summary WASM path",
|
||||
);
|
||||
assertEqual(
|
||||
fallbackRunSummary.iniWasmPath,
|
||||
"/work/session-machine.ini",
|
||||
"UI fallback run summary INI WASM path",
|
||||
);
|
||||
assertEqual(
|
||||
fallbackRunSummary.canonicalEventCount,
|
||||
2,
|
||||
"UI fallback run summary canonical event count",
|
||||
);
|
||||
assertEqual(
|
||||
fallbackRunSummary.motionSnapshotCount,
|
||||
2,
|
||||
"UI fallback run summary motion snapshot count",
|
||||
);
|
||||
uiDocument.getElementById("save-session-snapshot").click();
|
||||
await waitFor(
|
||||
() => uiDocument.getElementById("log")?.textContent.includes("Saved machine session snapshot"),
|
||||
"machine session snapshot save",
|
||||
);
|
||||
assertEqual(
|
||||
uiDocument.getElementById("field-session-snapshot").textContent,
|
||||
"ui-machine-session/ui-machine-session.json",
|
||||
"UI session snapshot path",
|
||||
);
|
||||
assertEqual(
|
||||
uiDocument.getElementById("field-session-ini").textContent,
|
||||
"linuxcnc/machines/xyzab-tdr/machine.ini",
|
||||
"UI snapshot INI OPFS path",
|
||||
);
|
||||
assertEqual(
|
||||
uiDocument.getElementById("field-session-gcode").textContent,
|
||||
"linuxcnc/gcode/ui-session.ngc",
|
||||
"UI snapshot G-code OPFS path",
|
||||
);
|
||||
const savedSnapshotLog = uiDocument.getElementById("log").textContent;
|
||||
if (
|
||||
!savedSnapshotLog.includes("Workflow: save-session-snapshot") ||
|
||||
!savedSnapshotLog.includes("Default G-code: linuxcnc/gcode/ui-session.ngc")
|
||||
) {
|
||||
throw new Error(`UI saved snapshot log missing metadata summary: ${savedSnapshotLog}`);
|
||||
}
|
||||
const savedMachineSessionStateReport = uiApi.getMachineSessionStateReport();
|
||||
assertEqual(
|
||||
savedMachineSessionStateReport.session.snapshotLabel,
|
||||
"ui-machine-session/ui-machine-session.json",
|
||||
"UI saved machine session state report snapshot label",
|
||||
);
|
||||
assertEqual(
|
||||
savedMachineSessionStateReport.session.gcode,
|
||||
"linuxcnc/gcode/ui-session.ngc",
|
||||
"UI saved machine session state report G-code path",
|
||||
);
|
||||
assertEqual(
|
||||
savedMachineSessionStateReport.status.lastAction,
|
||||
"save-session-snapshot",
|
||||
"UI saved machine session state report last action",
|
||||
);
|
||||
const savedUiSnapshot = await loadMachineSessionSnapshot(
|
||||
"ui-machine-session",
|
||||
"xyzab-tdr",
|
||||
{ filename: "ui-machine-session.json" },
|
||||
);
|
||||
assertEqual(savedUiSnapshot.metadata.source, "ini-panel", "UI snapshot metadata source");
|
||||
assertEqual(
|
||||
savedUiSnapshot.metadata.workflow,
|
||||
"save-session-snapshot",
|
||||
"UI snapshot metadata workflow",
|
||||
);
|
||||
assertEqual(
|
||||
savedUiSnapshot.metadata.snapshotLabel,
|
||||
"ui-machine-session/ui-machine-session.json",
|
||||
"UI snapshot metadata label",
|
||||
);
|
||||
assertEqual(
|
||||
savedUiSnapshot.metadata.defaultGcodeOpfsPath,
|
||||
"linuxcnc/gcode/ui-session.ngc",
|
||||
"UI snapshot metadata default G-code path",
|
||||
);
|
||||
uiDocument.getElementById("ini-editor").value = "";
|
||||
uiDocument.getElementById("restore-session-snapshot").click();
|
||||
await waitFor(
|
||||
() => uiDocument.getElementById("log")?.textContent.includes("Restored machine session snapshot"),
|
||||
"machine session snapshot restore",
|
||||
);
|
||||
assertEqual(
|
||||
uiDocument.getElementById("field-session-snapshot").textContent,
|
||||
"ui-machine-session/ui-machine-session.json",
|
||||
"UI restored session snapshot path",
|
||||
);
|
||||
const restoredSnapshotLog = uiDocument.getElementById("log").textContent;
|
||||
if (
|
||||
!restoredSnapshotLog.includes("Workflow: save-session-snapshot") ||
|
||||
!restoredSnapshotLog.includes("Default G-code: linuxcnc/gcode/ui-session.ngc")
|
||||
) {
|
||||
throw new Error(`UI restored snapshot log missing metadata summary: ${restoredSnapshotLog}`);
|
||||
}
|
||||
if (!uiDocument.getElementById("ini-editor").value.includes("MACHINE = browser-smoke")) {
|
||||
throw new Error("UI restored session snapshot did not reload INI text");
|
||||
}
|
||||
uiDocument.getElementById("restore-load-session").click();
|
||||
await waitFor(
|
||||
() => uiDocument.getElementById("log")?.textContent.includes("Restored and loaded machine session"),
|
||||
"restore and load machine session",
|
||||
);
|
||||
assertEqual(
|
||||
uiDocument.getElementById("field-session-ini").textContent,
|
||||
@@ -344,9 +586,12 @@ TOOL_TABLE = browser-tool.tbl
|
||||
);
|
||||
const uiLog = uiDocument.getElementById("log").textContent;
|
||||
if (!uiLog.includes("restore_parameters=0") || !uiLog.includes("tooldata_load=0")) {
|
||||
throw new Error(`UI session log missing LinuxCNC load result: ${uiLog}`);
|
||||
throw new Error(`UI restore/load session log missing LinuxCNC load result: ${uiLog}`);
|
||||
}
|
||||
if (
|
||||
!uiLog.includes("Snapshot: ui-machine-session/ui-machine-session.json") ||
|
||||
!uiLog.includes("Workflow: save-session-snapshot") ||
|
||||
!uiLog.includes("Default G-code: linuxcnc/gcode/ui-session.ngc") ||
|
||||
!uiLog.includes(
|
||||
"Parameter file: linuxcnc/machines/xyzab-tdr/linuxcnc.var -> /work/session-linuxcnc.var",
|
||||
) ||
|
||||
@@ -354,8 +599,29 @@ TOOL_TABLE = browser-tool.tbl
|
||||
"Tool table file: linuxcnc/machines/xyzab-tdr/tool.tbl -> /work/session-tool.tbl",
|
||||
)
|
||||
) {
|
||||
throw new Error(`UI session log missing OPFS default file mapping: ${uiLog}`);
|
||||
throw new Error(`UI restore/load session log missing OPFS default file mapping: ${uiLog}`);
|
||||
}
|
||||
const restoredLoadedMachineSessionStateReport = uiApi.getMachineSessionStateReport();
|
||||
assertEqual(
|
||||
restoredLoadedMachineSessionStateReport.session.snapshotLabel,
|
||||
"ui-machine-session/ui-machine-session.json",
|
||||
"UI restored loaded machine session state report snapshot label",
|
||||
);
|
||||
assertEqual(
|
||||
restoredLoadedMachineSessionStateReport.loaded.iniWasmPath,
|
||||
"/work/session-machine.ini",
|
||||
"UI restored loaded machine session state report loaded INI WASM path",
|
||||
);
|
||||
assertEqual(
|
||||
restoredLoadedMachineSessionStateReport.loaded.parameterWasmPath,
|
||||
"/work/session-linuxcnc.var",
|
||||
"UI restored loaded machine session state report loaded parameter WASM path",
|
||||
);
|
||||
assertEqual(
|
||||
restoredLoadedMachineSessionStateReport.status.lastAction,
|
||||
"restore-load-session",
|
||||
"UI restored loaded machine session state report last action",
|
||||
);
|
||||
uiDocument.getElementById("run-gcode").click();
|
||||
await waitFor(
|
||||
() => uiDocument.getElementById("log")?.textContent.includes("Ran G-code"),
|
||||
@@ -363,8 +629,8 @@ TOOL_TABLE = browser-tool.tbl
|
||||
);
|
||||
assertEqual(
|
||||
uiDocument.getElementById("field-session-gcode").textContent,
|
||||
"/work/ui-session.ngc",
|
||||
"UI session G-code path",
|
||||
"linuxcnc/gcode/ui-session.ngc",
|
||||
"UI session snapshot G-code path",
|
||||
);
|
||||
assertEqual(
|
||||
uiDocument.getElementById("field-run-status").textContent,
|
||||
@@ -372,6 +638,270 @@ TOOL_TABLE = browser-tool.tbl
|
||||
"UI G-code run status",
|
||||
);
|
||||
const runLog = uiDocument.getElementById("log").textContent;
|
||||
if (
|
||||
!runLog.includes("Program: linuxcnc/gcode/ui-session.ngc -> /work/ui-session.ngc") ||
|
||||
!runLog.includes("Program source: snapshot") ||
|
||||
!runLog.includes("Snapshot: ui-machine-session/ui-machine-session.json") ||
|
||||
!runLog.includes("Workflow: save-session-snapshot") ||
|
||||
!runLog.includes("Default G-code: linuxcnc/gcode/ui-session.ngc")
|
||||
) {
|
||||
throw new Error(`UI G-code run missing snapshot session context: ${runLog}`);
|
||||
}
|
||||
assertEqual(
|
||||
uiDocument.getElementById("field-run-source").textContent,
|
||||
"snapshot",
|
||||
"UI snapshot run source field",
|
||||
);
|
||||
assertEqual(
|
||||
uiDocument.getElementById("field-run-snapshot").textContent,
|
||||
"ui-machine-session/ui-machine-session.json",
|
||||
"UI snapshot run snapshot field",
|
||||
);
|
||||
assertEqual(
|
||||
uiDocument.getElementById("field-run-workflow").textContent,
|
||||
"save-session-snapshot",
|
||||
"UI snapshot run workflow field",
|
||||
);
|
||||
assertEqual(
|
||||
uiDocument.getElementById("field-run-opfs-path").textContent,
|
||||
"linuxcnc/gcode/ui-session.ngc",
|
||||
"UI snapshot run OPFS field",
|
||||
);
|
||||
assertEqual(
|
||||
uiDocument.getElementById("field-run-wasm-path").textContent,
|
||||
"/work/ui-session.ngc",
|
||||
"UI snapshot run WASM field",
|
||||
);
|
||||
const snapshotRunSummary = uiDocument.defaultView.linuxCncIniPanelLastRunSummary;
|
||||
assertEqual(snapshotRunSummary.runStatus, "ok", "UI snapshot run summary status");
|
||||
assertEqual(snapshotRunSummary.programSource, "snapshot", "UI snapshot run summary source");
|
||||
assertEqual(
|
||||
snapshotRunSummary.snapshotLabel,
|
||||
"ui-machine-session/ui-machine-session.json",
|
||||
"UI snapshot run summary snapshot",
|
||||
);
|
||||
assertEqual(
|
||||
snapshotRunSummary.workflow,
|
||||
"save-session-snapshot",
|
||||
"UI snapshot run summary workflow",
|
||||
);
|
||||
assertEqual(
|
||||
snapshotRunSummary.programOpfsPath,
|
||||
"linuxcnc/gcode/ui-session.ngc",
|
||||
"UI snapshot run summary OPFS path",
|
||||
);
|
||||
assertEqual(
|
||||
snapshotRunSummary.programWasmPath,
|
||||
"/work/ui-session.ngc",
|
||||
"UI snapshot run summary WASM path",
|
||||
);
|
||||
assertEqual(
|
||||
snapshotRunSummary.iniWasmPath,
|
||||
"/work/session-machine.ini",
|
||||
"UI snapshot run summary INI WASM path",
|
||||
);
|
||||
assertEqual(
|
||||
snapshotRunSummary.canonicalEventCount,
|
||||
2,
|
||||
"UI snapshot run summary canonical event count",
|
||||
);
|
||||
assertEqual(
|
||||
snapshotRunSummary.motionSnapshotCount,
|
||||
2,
|
||||
"UI snapshot run summary motion snapshot count",
|
||||
);
|
||||
const machineSessionStateReport = uiApi.getMachineSessionStateReport();
|
||||
assertEqual(
|
||||
machineSessionStateReport.status.lastAction,
|
||||
"run-gcode",
|
||||
"UI machine session state report last action",
|
||||
);
|
||||
assertEqual(
|
||||
machineSessionStateReport.program.opfsPath,
|
||||
"linuxcnc/gcode/ui-session.ngc",
|
||||
"UI machine session state report selected program OPFS path",
|
||||
);
|
||||
assertEqual(
|
||||
machineSessionStateReport.loaded.iniWasmPath,
|
||||
"/work/session-machine.ini",
|
||||
"UI machine session state report loaded INI WASM path",
|
||||
);
|
||||
assertEqual(
|
||||
machineSessionStateReport.program.source,
|
||||
"snapshot",
|
||||
"UI machine session state report run source",
|
||||
);
|
||||
assertEqual(
|
||||
machineSessionStateReport.session.workflow,
|
||||
"save-session-snapshot",
|
||||
"UI machine session state report workflow",
|
||||
);
|
||||
assertEqual(
|
||||
machineSessionStateReport.status.error,
|
||||
null,
|
||||
"UI machine session state report error",
|
||||
);
|
||||
assertEqual(
|
||||
machineSessionStateReport.run.status,
|
||||
"ok",
|
||||
"UI machine session state report run status",
|
||||
);
|
||||
assertEqual(
|
||||
machineSessionStateReport.run.canonicalEventCount,
|
||||
2,
|
||||
"UI machine session state report canonical event count",
|
||||
);
|
||||
assertEqual(
|
||||
machineSessionStateReport.run.motionSnapshotCount,
|
||||
2,
|
||||
"UI machine session state report motion snapshot count",
|
||||
);
|
||||
const machineSessionStateReportExport = uiApi.getMachineSessionStateReportExport();
|
||||
assertEqual(
|
||||
machineSessionStateReportExport.filename,
|
||||
"machine-session-state-report.json",
|
||||
"UI machine session state report export filename",
|
||||
);
|
||||
assertEqual(
|
||||
machineSessionStateReportExport.mediaType,
|
||||
"application/json",
|
||||
"UI machine session state report export media type",
|
||||
);
|
||||
assertEqual(
|
||||
machineSessionStateReportExport.text.endsWith("\n"),
|
||||
true,
|
||||
"UI machine session state report export newline",
|
||||
);
|
||||
assertEqual(
|
||||
JSON.parse(machineSessionStateReportExport.text).run.canonicalEventCount,
|
||||
2,
|
||||
"UI machine session state report export canonical event count",
|
||||
);
|
||||
assertEqual(
|
||||
JSON.parse(machineSessionStateReportExport.text).program.source,
|
||||
"snapshot",
|
||||
"UI machine session state report export program source",
|
||||
);
|
||||
const machineSessionWorkflowStatus = uiApi.getMachineSessionWorkflowStatus();
|
||||
assertEqual(
|
||||
machineSessionWorkflowStatus.lastAction,
|
||||
"run-gcode",
|
||||
"UI machine session workflow status last action",
|
||||
);
|
||||
assertEqual(
|
||||
machineSessionWorkflowStatus.error,
|
||||
null,
|
||||
"UI machine session workflow status error",
|
||||
);
|
||||
assertEqual(
|
||||
machineSessionWorkflowStatus.readiness.opfs,
|
||||
"OPFS: ready",
|
||||
"UI machine session workflow status OPFS readiness",
|
||||
);
|
||||
assertEqual(
|
||||
machineSessionWorkflowStatus.session.workflow,
|
||||
"save-session-snapshot",
|
||||
"UI machine session workflow status session workflow",
|
||||
);
|
||||
assertEqual(
|
||||
machineSessionWorkflowStatus.run.status,
|
||||
"ok",
|
||||
"UI machine session workflow status run status",
|
||||
);
|
||||
assertEqual(
|
||||
machineSessionWorkflowStatus.run.canonicalEventCount,
|
||||
2,
|
||||
"UI machine session workflow status canonical event count",
|
||||
);
|
||||
const machineSessionStateBundle = uiApi.getMachineSessionStateBundle();
|
||||
assertEqual(
|
||||
machineSessionStateBundle.report.status.lastAction,
|
||||
machineSessionStateReport.status.lastAction,
|
||||
"UI machine session state bundle report last action",
|
||||
);
|
||||
assertEqual(
|
||||
machineSessionStateBundle.export.text,
|
||||
machineSessionStateReportExport.text,
|
||||
"UI machine session state bundle export text",
|
||||
);
|
||||
assertEqual(
|
||||
machineSessionStateBundle.workflowStatus.run.canonicalEventCount,
|
||||
machineSessionWorkflowStatus.run.canonicalEventCount,
|
||||
"UI machine session state bundle workflow status canonical event count",
|
||||
);
|
||||
assertEqual(
|
||||
uiDocument.defaultView.getMachineSessionStateBundle().workflowStatus.lastAction,
|
||||
machineSessionStateBundle.workflowStatus.lastAction,
|
||||
"UI legacy machine session state bundle getter matches API namespace",
|
||||
);
|
||||
const machineSessionControlView = uiApi.getMachineSessionControlView();
|
||||
const renderedControlView = renderMachineSessionControlView(
|
||||
document.getElementById("control-view"),
|
||||
machineSessionControlView,
|
||||
);
|
||||
assertEqual(
|
||||
machineSessionControlView.status.runStatus,
|
||||
"ok",
|
||||
"UI machine session control view run status",
|
||||
);
|
||||
assertEqual(
|
||||
machineSessionControlView.sections[0].id,
|
||||
"readiness",
|
||||
"UI machine session control view readiness section",
|
||||
);
|
||||
assertEqual(
|
||||
machineSessionControlView.sections[1].rows[1].value,
|
||||
"save-session-snapshot",
|
||||
"UI machine session control view session workflow row",
|
||||
);
|
||||
assertEqual(
|
||||
machineSessionControlView.sections[2].rows[1].value,
|
||||
2,
|
||||
"UI machine session control view canonical event row",
|
||||
);
|
||||
assertEqual(
|
||||
renderedControlView.statusNode.textContent,
|
||||
"run-gcode | ok | -",
|
||||
"UI machine session control view DOM status",
|
||||
);
|
||||
assertEqual(
|
||||
renderedControlView.sectionsNode.querySelectorAll("section").length,
|
||||
3,
|
||||
"UI machine session control view DOM section count",
|
||||
);
|
||||
assertEqual(
|
||||
renderedControlView.sectionsNode.querySelector('section[data-section-id=\"run\"] dd:nth-of-type(2)').textContent,
|
||||
"2",
|
||||
"UI machine session control view DOM canonical event row",
|
||||
);
|
||||
const uiRenderedControlView = uiApi.renderMachineSessionControlView(
|
||||
uiDocument.getElementById("machine-session-control-view"),
|
||||
);
|
||||
assertEqual(
|
||||
uiRenderedControlView.statusNode.textContent,
|
||||
"run-gcode | ok | -",
|
||||
"UI machine session built-in control view DOM status",
|
||||
);
|
||||
assertEqual(
|
||||
uiRenderedControlView.sectionsNode.querySelector('section[data-section-id=\"run\"] dd:nth-of-type(2)').textContent,
|
||||
"2",
|
||||
"UI machine session built-in control view canonical event row",
|
||||
);
|
||||
assertEqual(
|
||||
uiDocument.defaultView.getMachineSessionControlView().status.lastAction,
|
||||
machineSessionControlView.status.lastAction,
|
||||
"UI legacy machine session control view getter matches API namespace",
|
||||
);
|
||||
assertEqual(
|
||||
uiDocument.defaultView.getMachineSessionStateReport().status.lastAction,
|
||||
machineSessionStateReport.status.lastAction,
|
||||
"UI legacy machine session state report getter matches API namespace",
|
||||
);
|
||||
assertEqual(
|
||||
uiDocument.defaultView.getMachineSessionStateReportExport().text,
|
||||
machineSessionStateReportExport.text,
|
||||
"UI legacy machine session state report export getter matches API namespace",
|
||||
);
|
||||
if (
|
||||
!runLog.includes("canon_event=STRAIGHT_TRAVERSE") ||
|
||||
!runLog.includes("canon_event=STRAIGHT_FEED")
|
||||
|
||||
@@ -4839,6 +4839,14 @@
|
||||
await verifyOpfsSavedText(label, () => loadTextFile(opfsPath), expectedLines);
|
||||
}
|
||||
|
||||
function opfsSessionSavedReadbackPaths(session, savedParameters) {
|
||||
return {
|
||||
parameters: session.parameters.opfsPath,
|
||||
parameterBackup: savedParameters.backupOpfsPath,
|
||||
toolTable: session.toolTable.opfsPath,
|
||||
};
|
||||
}
|
||||
|
||||
async function verifyOpfsMachineFileReadback(label, machineId, fileKey, expectedLines) {
|
||||
await verifyOpfsSavedText(label, async () => {
|
||||
const machineFiles = await loadMachineTextFiles(machineId);
|
||||
@@ -9881,20 +9889,22 @@
|
||||
"\t",
|
||||
iniNamedParameterReadbackValues,
|
||||
);
|
||||
const iniNamedSavedParameterTextPath = loadedIniFileSession.parameters.opfsPath;
|
||||
const iniNamedSavedReadbackPaths = opfsSessionSavedReadbackPaths(
|
||||
loadedIniFileSession,
|
||||
savedIniNamedParameters,
|
||||
);
|
||||
await verifyOpfsReadback(
|
||||
"opfs_saved_ini_named_parameter_text",
|
||||
iniNamedSavedParameterTextPath,
|
||||
iniNamedSavedReadbackPaths.parameters,
|
||||
iniNamedSavedParameterTextLines,
|
||||
);
|
||||
const iniNamedSavedParameterBackupTextLines = opfsSavedParameterPlainTextLinesForValues(
|
||||
" ",
|
||||
iniNamedInitialParameterValues,
|
||||
);
|
||||
const iniNamedSavedParameterBackupPath = savedIniNamedParameters.backupOpfsPath;
|
||||
await verifyOpfsReadback(
|
||||
"opfs_saved_ini_named_parameter_backup_text",
|
||||
iniNamedSavedParameterBackupPath,
|
||||
iniNamedSavedReadbackPaths.parameterBackup,
|
||||
iniNamedSavedParameterBackupTextLines,
|
||||
);
|
||||
const iniNamedLoadToolTableResultLines = opfsToolTableResultLinesForTool(
|
||||
@@ -9937,10 +9947,9 @@
|
||||
iniNamedSaveToolTableResultLines,
|
||||
);
|
||||
const iniNamedSavedToolTableTextLines = opfsToolTableTextLinesForTool(iniNamedToolTable);
|
||||
const iniNamedSavedToolTableTextPath = loadedIniFileSession.toolTable.opfsPath;
|
||||
await verifyOpfsReadback(
|
||||
"opfs_saved_ini_named_tool_table_text",
|
||||
iniNamedSavedToolTableTextPath,
|
||||
iniNamedSavedReadbackPaths.toolTable,
|
||||
iniNamedSavedToolTableTextLines,
|
||||
);
|
||||
|
||||
@@ -10073,20 +10082,22 @@
|
||||
"\t",
|
||||
explicitParameterReadbackValues,
|
||||
);
|
||||
const explicitSavedParameterTextPath = loadedIniOverrideSession.parameters.opfsPath;
|
||||
const explicitSavedReadbackPaths = opfsSessionSavedReadbackPaths(
|
||||
loadedIniOverrideSession,
|
||||
savedExplicitParameters,
|
||||
);
|
||||
await verifyOpfsReadback(
|
||||
"opfs_saved_explicit_parameter_text",
|
||||
explicitSavedParameterTextPath,
|
||||
explicitSavedReadbackPaths.parameters,
|
||||
explicitSavedParameterTextLines,
|
||||
);
|
||||
const explicitSavedParameterBackupTextLines = opfsSavedParameterPlainTextLinesForValues(
|
||||
" ",
|
||||
explicitInitialParameterValues,
|
||||
);
|
||||
const explicitSavedParameterBackupPath = savedExplicitParameters.backupOpfsPath;
|
||||
await verifyOpfsReadback(
|
||||
"opfs_saved_explicit_parameter_backup_text",
|
||||
explicitSavedParameterBackupPath,
|
||||
explicitSavedReadbackPaths.parameterBackup,
|
||||
explicitSavedParameterBackupTextLines,
|
||||
);
|
||||
const savedExplicitToolTable = await saveMachineToolTableToOpfs(
|
||||
@@ -10115,10 +10126,9 @@
|
||||
explicitSaveToolTableResultLines,
|
||||
);
|
||||
const explicitSavedToolTableTextLines = opfsToolTableTextLinesForTool(explicitToolTable);
|
||||
const explicitSavedToolTableTextPath = loadedIniOverrideSession.toolTable.opfsPath;
|
||||
await verifyOpfsReadback(
|
||||
"opfs_saved_explicit_tool_table_text",
|
||||
explicitSavedToolTableTextPath,
|
||||
explicitSavedReadbackPaths.toolTable,
|
||||
explicitSavedToolTableTextLines,
|
||||
);
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ SKIP_INTERP_BUILD=1 "$ROOT_DIR/tests/wasm/node/verify_sim_configs_inventory_wasm
|
||||
SKIP_INTERP_BUILD=1 "$ROOT_DIR/tests/wasm/node/verify_nc_files_wasm.sh"
|
||||
SKIP_TP_BUILD=1 "$ROOT_DIR/tests/wasm/node/verify_tp_wasm.sh"
|
||||
"$ROOT_DIR/tests/opfs/node/verify_file_service.sh"
|
||||
"$ROOT_DIR/tests/ui/node/verify_ui_node_smokes.sh"
|
||||
SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 "$ROOT_DIR/tests/browser/verify_ini_panel_browser.sh"
|
||||
SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 "$ROOT_DIR/tests/browser/verify_interp_browser.sh"
|
||||
|
||||
|
||||
@@ -18,11 +18,14 @@ import {
|
||||
import {
|
||||
createMachineSessionSnapshotPayload,
|
||||
createSessionSnapshot,
|
||||
loadMachineSessionSnapshot,
|
||||
loadSessionSnapshot,
|
||||
saveMachineSessionSnapshot,
|
||||
saveSessionSnapshot,
|
||||
validateSessionSnapshot,
|
||||
} from "../../../runtime/opfs/snapshot-store.js";
|
||||
import {
|
||||
gcodeFilenameFromProgramPath,
|
||||
loadGcodeProgram,
|
||||
loadMachineTextFiles,
|
||||
machineFilePaths,
|
||||
@@ -199,6 +202,18 @@ assertGcodeProgramStoragePath(
|
||||
"custom-fixture.ngc",
|
||||
gcodeProgramPath("custom-fixture.ngc"),
|
||||
);
|
||||
assert.equal(
|
||||
gcodeFilenameFromProgramPath("linuxcnc/gcode/fixture.ngc"),
|
||||
"fixture.ngc",
|
||||
);
|
||||
assert.throws(
|
||||
() => gcodeFilenameFromProgramPath("linuxcnc/machines/xyzab-tdr/fixture.ngc"),
|
||||
/Invalid G-code program OPFS path/,
|
||||
);
|
||||
assert.throws(
|
||||
() => gcodeFilenameFromProgramPath("linuxcnc/gcode/nested/fixture.ngc"),
|
||||
/Invalid G-code program OPFS path/,
|
||||
);
|
||||
assertPreviewCacheStoragePath("fixture", previewCachePath("fixture"));
|
||||
assertPreviewCacheStoragePath(
|
||||
"fixture",
|
||||
@@ -325,6 +340,64 @@ assert.deepEqual(
|
||||
}),
|
||||
customSnapshot,
|
||||
);
|
||||
const machineSessionSnapshot = await saveMachineSessionSnapshot(
|
||||
"machine-session-1",
|
||||
"xyzab-tdr",
|
||||
{
|
||||
storage,
|
||||
filename: "machine-session-snapshot.json",
|
||||
gcodeFilename: "fixture.ngc",
|
||||
createdAt: "2026-06-08T00:00:00.000Z",
|
||||
metadata: { source: "node-machine-session" },
|
||||
},
|
||||
);
|
||||
assertSessionSnapshotStoragePath(
|
||||
"machine-session-1",
|
||||
sessionSnapshotPath("machine-session-1", "machine-session-snapshot.json"),
|
||||
"machine-session-snapshot.json",
|
||||
);
|
||||
assert.deepEqual(machineSessionSnapshot.payload, snapshotPayload);
|
||||
assert.deepEqual(
|
||||
await loadSessionSnapshot("machine-session-1", {
|
||||
storage,
|
||||
filename: "machine-session-snapshot.json",
|
||||
}),
|
||||
machineSessionSnapshot,
|
||||
);
|
||||
assert.deepEqual(
|
||||
await loadMachineSessionSnapshot("machine-session-1", "xyzab-tdr", {
|
||||
storage,
|
||||
filename: "machine-session-snapshot.json",
|
||||
}),
|
||||
machineSessionSnapshot,
|
||||
);
|
||||
await assert.rejects(
|
||||
() => loadMachineSessionSnapshot("machine-session-1", "other-machine", {
|
||||
storage,
|
||||
filename: "machine-session-snapshot.json",
|
||||
}),
|
||||
/Machine session snapshot id mismatch/,
|
||||
);
|
||||
await saveTextFile(
|
||||
sessionSnapshotPath("machine-session-bad-files"),
|
||||
JSON.stringify({
|
||||
...machineSessionSnapshot,
|
||||
sessionId: "machine-session-bad-files",
|
||||
payload: {
|
||||
machineId: "xyzab-tdr",
|
||||
files: {
|
||||
ini: "linuxcnc/machines/xyzab-tdr/machine.ini",
|
||||
parameters: "../linuxcnc.var",
|
||||
toolTable: "linuxcnc/machines/xyzab-tdr/tool.tbl",
|
||||
},
|
||||
},
|
||||
}),
|
||||
storage,
|
||||
);
|
||||
await assert.rejects(
|
||||
() => loadMachineSessionSnapshot("machine-session-bad-files", "xyzab-tdr", { storage }),
|
||||
/Invalid OPFS path/,
|
||||
);
|
||||
|
||||
await saveMachineTextFiles("xyzab-tdr", {
|
||||
ini: "[EMC]\nMACHINE = xyzab-tdr\n",
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import {
|
||||
createMachineFileLoadSummary,
|
||||
createMachineFileSaveSummary,
|
||||
machineFileLoadLogLines,
|
||||
machineFileSaveLogLines,
|
||||
} from "../../../runtime/ui/ini-panel/machine-file-summary.js";
|
||||
|
||||
const saveSummary = createMachineFileSaveSummary({
|
||||
ini: "linuxcnc/machines/xyzab-tdr/machine.ini",
|
||||
toolTable: "linuxcnc/machines/xyzab-tdr/tool.tbl",
|
||||
parameters: "linuxcnc/machines/xyzab-tdr/linuxcnc.var",
|
||||
}, "linuxcnc/gcode/ui-session.ngc");
|
||||
|
||||
assert.deepEqual(saveSummary, {
|
||||
iniOpfsPath: "linuxcnc/machines/xyzab-tdr/machine.ini",
|
||||
toolTableOpfsPath: "linuxcnc/machines/xyzab-tdr/tool.tbl",
|
||||
parameterOpfsPath: "linuxcnc/machines/xyzab-tdr/linuxcnc.var",
|
||||
gcodeOpfsPath: "linuxcnc/gcode/ui-session.ngc",
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
machineFileSaveLogLines(saveSummary),
|
||||
[
|
||||
"INI: linuxcnc/machines/xyzab-tdr/machine.ini",
|
||||
"Tool table: linuxcnc/machines/xyzab-tdr/tool.tbl",
|
||||
"Parameters: linuxcnc/machines/xyzab-tdr/linuxcnc.var",
|
||||
"G-code: linuxcnc/gcode/ui-session.ngc",
|
||||
],
|
||||
);
|
||||
|
||||
const loadSummary = createMachineFileLoadSummary({
|
||||
ini: "[EMC]\nMACHINE = xyzab-tdr\n",
|
||||
toolTable: "T2 P7 Z3.125\n",
|
||||
parameters: "5220 1\n",
|
||||
});
|
||||
|
||||
assert.deepEqual(loadSummary, {
|
||||
iniBytes: 26,
|
||||
toolTableBytes: 13,
|
||||
parameterBytes: 7,
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
machineFileLoadLogLines(loadSummary),
|
||||
[
|
||||
"INI bytes: 26",
|
||||
"Tool table bytes: 13",
|
||||
"Parameter bytes: 7",
|
||||
],
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
createMachineFileSaveSummary(null),
|
||||
{
|
||||
iniOpfsPath: null,
|
||||
toolTableOpfsPath: null,
|
||||
parameterOpfsPath: null,
|
||||
gcodeOpfsPath: null,
|
||||
},
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
createMachineFileLoadSummary(null),
|
||||
{
|
||||
iniBytes: 0,
|
||||
toolTableBytes: 0,
|
||||
parameterBytes: 0,
|
||||
},
|
||||
);
|
||||
|
||||
console.log("ini_panel_machine_file_summary_node_smoke=ok");
|
||||
6
wasm-port/tests/ui/node/verify_ini_panel_machine_file_summary.sh
Executable file
6
wasm-port/tests/ui/node/verify_ini_panel_machine_file_summary.sh
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/../../.." && pwd)"
|
||||
|
||||
node "$ROOT_DIR/tests/ui/node/verify_ini_panel_machine_file_summary.mjs"
|
||||
99
wasm-port/tests/ui/node/verify_ini_panel_run_summary.mjs
Normal file
99
wasm-port/tests/ui/node/verify_ini_panel_run_summary.mjs
Normal file
@@ -0,0 +1,99 @@
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import {
|
||||
createRunSummary,
|
||||
summarizeRunResult,
|
||||
} from "../../../runtime/ui/ini-panel/run-summary.js";
|
||||
|
||||
const program = {
|
||||
source: "snapshot",
|
||||
opfsPath: "linuxcnc/gcode/ui-session.ngc",
|
||||
wasmPath: "/work/ui-session.ngc",
|
||||
};
|
||||
const snapshot = {
|
||||
sessionId: "ui-machine-session",
|
||||
metadata: {
|
||||
workflow: "save-session-snapshot",
|
||||
snapshotLabel: "ui-machine-session/ui-machine-session.json",
|
||||
},
|
||||
};
|
||||
|
||||
assert.deepEqual(
|
||||
createRunSummary(program, {
|
||||
runStatus: "ok",
|
||||
iniWasmPath: "/work/session-machine.ini",
|
||||
canonicalEventCount: 2,
|
||||
motionSnapshotCount: 2,
|
||||
}, snapshot),
|
||||
{
|
||||
runStatus: "ok",
|
||||
programSource: "snapshot",
|
||||
snapshotLabel: "ui-machine-session/ui-machine-session.json",
|
||||
workflow: "save-session-snapshot",
|
||||
programOpfsPath: "linuxcnc/gcode/ui-session.ngc",
|
||||
programWasmPath: "/work/ui-session.ngc",
|
||||
iniWasmPath: "/work/session-machine.ini",
|
||||
canonicalEventCount: 2,
|
||||
motionSnapshotCount: 2,
|
||||
},
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
createRunSummary({
|
||||
source: "default",
|
||||
opfsPath: "linuxcnc/gcode/ui-session.ngc",
|
||||
wasmPath: "/work/ui-session.ngc",
|
||||
}),
|
||||
{
|
||||
runStatus: "ok",
|
||||
programSource: "default",
|
||||
snapshotLabel: null,
|
||||
workflow: null,
|
||||
programOpfsPath: "linuxcnc/gcode/ui-session.ngc",
|
||||
programWasmPath: "/work/ui-session.ngc",
|
||||
iniWasmPath: null,
|
||||
canonicalEventCount: 0,
|
||||
motionSnapshotCount: 0,
|
||||
},
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
createRunSummary(program, {
|
||||
snapshotLabel: "override-session/override.json",
|
||||
}, snapshot),
|
||||
{
|
||||
runStatus: "ok",
|
||||
programSource: "snapshot",
|
||||
snapshotLabel: "override-session/override.json",
|
||||
workflow: "save-session-snapshot",
|
||||
programOpfsPath: "linuxcnc/gcode/ui-session.ngc",
|
||||
programWasmPath: "/work/ui-session.ngc",
|
||||
iniWasmPath: null,
|
||||
canonicalEventCount: 0,
|
||||
motionSnapshotCount: 0,
|
||||
},
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
summarizeRunResult([
|
||||
"canon_event=STRAIGHT_TRAVERSE line=1 x=1 y=2",
|
||||
"run_step phase=execute line=1",
|
||||
"canon_event=STRAIGHT_FEED line=2 x=3 y=4",
|
||||
"message=canon_event=not-a-canonical-prefix",
|
||||
"",
|
||||
].join("\n"), [{}, {}]),
|
||||
{
|
||||
canonicalEventCount: 2,
|
||||
motionSnapshotCount: 2,
|
||||
},
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
summarizeRunResult("", []),
|
||||
{
|
||||
canonicalEventCount: 0,
|
||||
motionSnapshotCount: 0,
|
||||
},
|
||||
);
|
||||
|
||||
console.log("ini_panel_run_summary_node_smoke=ok");
|
||||
6
wasm-port/tests/ui/node/verify_ini_panel_run_summary.sh
Executable file
6
wasm-port/tests/ui/node/verify_ini_panel_run_summary.sh
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/../../.." && pwd)"
|
||||
|
||||
node "$ROOT_DIR/tests/ui/node/verify_ini_panel_run_summary.mjs"
|
||||
149
wasm-port/tests/ui/node/verify_ini_panel_run_workflow.mjs
Normal file
149
wasm-port/tests/ui/node/verify_ini_panel_run_workflow.mjs
Normal file
@@ -0,0 +1,149 @@
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import {
|
||||
runGcodeSessionWorkflow,
|
||||
selectSessionGcodeProgram,
|
||||
} from "../../../runtime/ui/ini-panel/run-workflow.js";
|
||||
|
||||
const defaultProgram = selectSessionGcodeProgram({
|
||||
defaultFilename: "ui-session.ngc",
|
||||
defaultOpfsPath: "linuxcnc/gcode/ui-session.ngc",
|
||||
wasmPath: "/work/ui-session.ngc",
|
||||
gcodeFilenameFromProgramPath: () => {
|
||||
throw new Error("not used");
|
||||
},
|
||||
});
|
||||
|
||||
assert.deepEqual(defaultProgram, {
|
||||
filename: "ui-session.ngc",
|
||||
opfsPath: "linuxcnc/gcode/ui-session.ngc",
|
||||
source: "default",
|
||||
wasmPath: "/work/ui-session.ngc",
|
||||
});
|
||||
|
||||
const snapshotProgram = selectSessionGcodeProgram({
|
||||
snapshot: {
|
||||
payload: {
|
||||
files: {
|
||||
gcode: "linuxcnc/gcode/snapshot-session.ngc",
|
||||
},
|
||||
},
|
||||
},
|
||||
defaultFilename: "ui-session.ngc",
|
||||
defaultOpfsPath: "linuxcnc/gcode/ui-session.ngc",
|
||||
wasmPath: "/work/ui-session.ngc",
|
||||
gcodeFilenameFromProgramPath: (path) => path.split("/").at(-1),
|
||||
});
|
||||
|
||||
assert.deepEqual(snapshotProgram, {
|
||||
filename: "snapshot-session.ngc",
|
||||
opfsPath: "linuxcnc/gcode/snapshot-session.ngc",
|
||||
source: "snapshot",
|
||||
wasmPath: "/work/ui-session.ngc",
|
||||
});
|
||||
|
||||
const calls = [];
|
||||
const workflow = await runGcodeSessionWorkflow({
|
||||
interp: {
|
||||
writeTextFile: (wasmPath, text) => {
|
||||
calls.push(["writeTextFile", wasmPath, text]);
|
||||
},
|
||||
runFileWithIni: (programWasmPath, iniWasmPath) => {
|
||||
calls.push(["runFileWithIni", programWasmPath, iniWasmPath]);
|
||||
return [
|
||||
"canon_event=STRAIGHT_FEED line=1 x=1 y=2",
|
||||
"run_step phase=execute line=1 x=1 y=2 statement_uri=G1%20X1%20Y2",
|
||||
"",
|
||||
].join("\n");
|
||||
},
|
||||
},
|
||||
loadedSession: {
|
||||
ini: {
|
||||
wasmPath: "/work/session-machine.ini",
|
||||
},
|
||||
},
|
||||
snapshot: {
|
||||
sessionId: "ui-machine-session",
|
||||
metadata: {
|
||||
workflow: "save-session-snapshot",
|
||||
snapshotLabel: "ui-machine-session/ui-machine-session.json",
|
||||
defaultGcodeOpfsPath: "linuxcnc/gcode/ui-session.ngc",
|
||||
},
|
||||
payload: {
|
||||
files: {
|
||||
gcode: "linuxcnc/gcode/snapshot-session.ngc",
|
||||
},
|
||||
},
|
||||
},
|
||||
defaultFilename: "ui-session.ngc",
|
||||
defaultOpfsPath: "linuxcnc/gcode/ui-session.ngc",
|
||||
wasmPath: "/work/ui-session.ngc",
|
||||
gcodeFilenameFromProgramPath: (path) => path.split("/").at(-1),
|
||||
loadGcodeProgram: async (filename) => {
|
||||
calls.push(["loadGcodeProgram", filename]);
|
||||
return "G1 X1 Y2\n";
|
||||
},
|
||||
snapshotLabel: "ui-machine-session/ui-machine-session.json",
|
||||
summarizeRun: (resultText, programText) => {
|
||||
calls.push(["summarizeRun", resultText, programText]);
|
||||
return {
|
||||
canonicalEventCount: 1,
|
||||
motionSnapshotCount: 1,
|
||||
motionSnapshots: [{ line: 1 }],
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
assert.deepEqual(calls, [
|
||||
["loadGcodeProgram", "snapshot-session.ngc"],
|
||||
["writeTextFile", "/work/ui-session.ngc", "G1 X1 Y2\n"],
|
||||
["runFileWithIni", "/work/ui-session.ngc", "/work/session-machine.ini"],
|
||||
[
|
||||
"summarizeRun",
|
||||
"canon_event=STRAIGHT_FEED line=1 x=1 y=2\nrun_step phase=execute line=1 x=1 y=2 statement_uri=G1%20X1%20Y2",
|
||||
"G1 X1 Y2\n",
|
||||
],
|
||||
]);
|
||||
|
||||
assert.deepEqual(workflow.summary, {
|
||||
runStatus: "ok",
|
||||
programSource: "snapshot",
|
||||
snapshotLabel: "ui-machine-session/ui-machine-session.json",
|
||||
workflow: "save-session-snapshot",
|
||||
programOpfsPath: "linuxcnc/gcode/snapshot-session.ngc",
|
||||
programWasmPath: "/work/ui-session.ngc",
|
||||
iniWasmPath: "/work/session-machine.ini",
|
||||
canonicalEventCount: 1,
|
||||
motionSnapshotCount: 1,
|
||||
});
|
||||
|
||||
assert.deepEqual(workflow.fields, {
|
||||
sessionGcode: "linuxcnc/gcode/snapshot-session.ngc",
|
||||
runStatus: "ok",
|
||||
runSource: "snapshot",
|
||||
runSnapshot: "ui-machine-session/ui-machine-session.json",
|
||||
runWorkflow: "save-session-snapshot",
|
||||
runOpfsPath: "linuxcnc/gcode/snapshot-session.ngc",
|
||||
runWasmPath: "/work/ui-session.ngc",
|
||||
});
|
||||
|
||||
assert.deepEqual(workflow.motionSnapshots, [{ line: 1 }]);
|
||||
|
||||
assert.deepEqual(workflow.logLines, [
|
||||
"Program: linuxcnc/gcode/snapshot-session.ngc -> /work/ui-session.ngc",
|
||||
"Program source: snapshot",
|
||||
"Snapshot: ui-machine-session/ui-machine-session.json",
|
||||
"Workflow: save-session-snapshot",
|
||||
"Default G-code: linuxcnc/gcode/ui-session.ngc",
|
||||
"INI: /work/session-machine.ini",
|
||||
"canon_event=STRAIGHT_FEED line=1 x=1 y=2\nrun_step phase=execute line=1 x=1 y=2 statement_uri=G1%20X1%20Y2",
|
||||
]);
|
||||
|
||||
await assert.rejects(
|
||||
() => runGcodeSessionWorkflow({
|
||||
loadedSession: null,
|
||||
}),
|
||||
/Load a machine session before running G-code/,
|
||||
);
|
||||
|
||||
console.log("ini_panel_run_workflow_node_smoke=ok");
|
||||
6
wasm-port/tests/ui/node/verify_ini_panel_run_workflow.sh
Executable file
6
wasm-port/tests/ui/node/verify_ini_panel_run_workflow.sh
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/../../.." && pwd)"
|
||||
|
||||
node "$ROOT_DIR/tests/ui/node/verify_ini_panel_run_workflow.mjs"
|
||||
130
wasm-port/tests/ui/node/verify_ini_panel_session_summary.mjs
Normal file
130
wasm-port/tests/ui/node/verify_ini_panel_session_summary.mjs
Normal file
@@ -0,0 +1,130 @@
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import {
|
||||
createMachineSessionLoadSummary,
|
||||
machineSessionLoadLogLines,
|
||||
} from "../../../runtime/ui/ini-panel/session-load-summary.js";
|
||||
import {
|
||||
createSessionSnapshotSummary,
|
||||
sessionSnapshotMetadataLogLines,
|
||||
sessionSnapshotSummaryLogLines,
|
||||
} from "../../../runtime/ui/ini-panel/session-summary.js";
|
||||
|
||||
const snapshot = {
|
||||
metadata: {
|
||||
workflow: "save-session-snapshot",
|
||||
snapshotLabel: "ui-machine-session/ui-machine-session.json",
|
||||
defaultGcodeOpfsPath: "linuxcnc/gcode/ui-session.ngc",
|
||||
},
|
||||
payload: {
|
||||
files: {
|
||||
ini: "linuxcnc/machines/xyzab-tdr/machine.ini",
|
||||
parameters: "linuxcnc/machines/xyzab-tdr/linuxcnc.var",
|
||||
toolTable: "linuxcnc/machines/xyzab-tdr/tool.tbl",
|
||||
gcode: "linuxcnc/gcode/ui-session.ngc",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const summary = createSessionSnapshotSummary(snapshot);
|
||||
assert.deepEqual(summary, {
|
||||
snapshotLabel: "ui-machine-session/ui-machine-session.json",
|
||||
workflow: "save-session-snapshot",
|
||||
defaultGcodeOpfsPath: "linuxcnc/gcode/ui-session.ngc",
|
||||
iniOpfsPath: "linuxcnc/machines/xyzab-tdr/machine.ini",
|
||||
parameterOpfsPath: "linuxcnc/machines/xyzab-tdr/linuxcnc.var",
|
||||
toolTableOpfsPath: "linuxcnc/machines/xyzab-tdr/tool.tbl",
|
||||
gcodeOpfsPath: "linuxcnc/gcode/ui-session.ngc",
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
createSessionSnapshotSummary(snapshot, "override-session/override.json"),
|
||||
{
|
||||
...summary,
|
||||
snapshotLabel: "override-session/override.json",
|
||||
},
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
sessionSnapshotSummaryLogLines(summary),
|
||||
[
|
||||
"Snapshot: ui-machine-session/ui-machine-session.json",
|
||||
"Workflow: save-session-snapshot",
|
||||
"Default G-code: linuxcnc/gcode/ui-session.ngc",
|
||||
],
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
sessionSnapshotMetadataLogLines(summary),
|
||||
[
|
||||
"Workflow: save-session-snapshot",
|
||||
"Default G-code: linuxcnc/gcode/ui-session.ngc",
|
||||
],
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
createSessionSnapshotSummary(null),
|
||||
{
|
||||
snapshotLabel: null,
|
||||
workflow: null,
|
||||
defaultGcodeOpfsPath: null,
|
||||
iniOpfsPath: null,
|
||||
parameterOpfsPath: null,
|
||||
toolTableOpfsPath: null,
|
||||
gcodeOpfsPath: null,
|
||||
},
|
||||
);
|
||||
|
||||
const loadSummary = createMachineSessionLoadSummary({
|
||||
ini: {
|
||||
opfsPath: "linuxcnc/machines/xyzab-tdr/machine.ini",
|
||||
wasmPath: "/work/session-machine.ini",
|
||||
},
|
||||
parameters: {
|
||||
opfsPath: "linuxcnc/machines/xyzab-tdr/linuxcnc.var",
|
||||
wasmPath: "/work/session-linuxcnc.var",
|
||||
result: "restore_parameters=0",
|
||||
},
|
||||
toolTable: {
|
||||
opfsPath: "linuxcnc/machines/xyzab-tdr/tool.tbl",
|
||||
wasmPath: "/work/session-tool.tbl",
|
||||
result: "tooldata_load=0",
|
||||
},
|
||||
});
|
||||
assert.deepEqual(loadSummary, {
|
||||
iniOpfsPath: "linuxcnc/machines/xyzab-tdr/machine.ini",
|
||||
iniWasmPath: "/work/session-machine.ini",
|
||||
parameterOpfsPath: "linuxcnc/machines/xyzab-tdr/linuxcnc.var",
|
||||
parameterWasmPath: "/work/session-linuxcnc.var",
|
||||
parameterResult: "restore_parameters=0",
|
||||
toolTableOpfsPath: "linuxcnc/machines/xyzab-tdr/tool.tbl",
|
||||
toolTableWasmPath: "/work/session-tool.tbl",
|
||||
toolTableResult: "tooldata_load=0",
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
machineSessionLoadLogLines(loadSummary),
|
||||
[
|
||||
"INI: linuxcnc/machines/xyzab-tdr/machine.ini -> /work/session-machine.ini",
|
||||
"Parameter file: linuxcnc/machines/xyzab-tdr/linuxcnc.var -> /work/session-linuxcnc.var",
|
||||
"Parameters: restore_parameters=0",
|
||||
"Tool table file: linuxcnc/machines/xyzab-tdr/tool.tbl -> /work/session-tool.tbl",
|
||||
"Tool table: tooldata_load=0",
|
||||
],
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
createMachineSessionLoadSummary(null),
|
||||
{
|
||||
iniOpfsPath: null,
|
||||
iniWasmPath: null,
|
||||
parameterOpfsPath: null,
|
||||
parameterWasmPath: null,
|
||||
parameterResult: "",
|
||||
toolTableOpfsPath: null,
|
||||
toolTableWasmPath: null,
|
||||
toolTableResult: "",
|
||||
},
|
||||
);
|
||||
|
||||
console.log("ini_panel_session_summary_node_smoke=ok");
|
||||
6
wasm-port/tests/ui/node/verify_ini_panel_session_summary.sh
Executable file
6
wasm-port/tests/ui/node/verify_ini_panel_session_summary.sh
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/../../.." && pwd)"
|
||||
|
||||
node "$ROOT_DIR/tests/ui/node/verify_ini_panel_session_summary.mjs"
|
||||
252
wasm-port/tests/ui/node/verify_ini_panel_session_workflow.mjs
Normal file
252
wasm-port/tests/ui/node/verify_ini_panel_session_workflow.mjs
Normal file
@@ -0,0 +1,252 @@
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import {
|
||||
restoreMachineSessionSnapshotWorkflow,
|
||||
saveMachineSessionSnapshotWorkflow,
|
||||
} from "../../../runtime/ui/ini-panel/session-workflow.js";
|
||||
|
||||
const calls = [];
|
||||
const saveWorkflow = await saveMachineSessionSnapshotWorkflow({
|
||||
machineId: "xyzab-tdr",
|
||||
snapshotId: "ui-machine-session",
|
||||
snapshotFilename: "ui-machine-session.json",
|
||||
snapshotLabel: "ui-machine-session/ui-machine-session.json",
|
||||
iniText: "[EMC]\nMACHINE = xyzab-tdr\n",
|
||||
toolTableText: "T2 P7 Z3.125 D1.5 I12 J34 Q4 ;ui session tool\n",
|
||||
parameterText: "5161 0.0\n",
|
||||
gcodeFilename: "ui-session.ngc",
|
||||
gcodeText: "G0 X1.0 Y2.0 (Comment)\nG1 X3.0 Y4.0 F120.0\n",
|
||||
metadata: {
|
||||
source: "ini-panel",
|
||||
workflow: "save-session-snapshot",
|
||||
snapshotLabel: "ui-machine-session/ui-machine-session.json",
|
||||
defaultGcodeOpfsPath: "linuxcnc/gcode/ui-session.ngc",
|
||||
},
|
||||
saveMachineTextFiles: async (machineId, files) => {
|
||||
calls.push(["saveMachineTextFiles", machineId, files]);
|
||||
},
|
||||
saveGcodeProgram: async (filename, text) => {
|
||||
calls.push(["saveGcodeProgram", filename, text]);
|
||||
},
|
||||
saveMachineSessionSnapshot: async (snapshotId, machineId, options) => {
|
||||
calls.push(["saveMachineSessionSnapshot", snapshotId, machineId, options]);
|
||||
return {
|
||||
sessionId: snapshotId,
|
||||
metadata: options.metadata,
|
||||
payload: {
|
||||
files: {
|
||||
ini: "linuxcnc/machines/xyzab-tdr/machine.ini",
|
||||
parameters: "linuxcnc/machines/xyzab-tdr/linuxcnc.var",
|
||||
toolTable: "linuxcnc/machines/xyzab-tdr/tool.tbl",
|
||||
gcode: "linuxcnc/gcode/ui-session.ngc",
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
assert.deepEqual(calls, [
|
||||
[
|
||||
"saveMachineTextFiles",
|
||||
"xyzab-tdr",
|
||||
{
|
||||
ini: "[EMC]\nMACHINE = xyzab-tdr\n",
|
||||
toolTable: "T2 P7 Z3.125 D1.5 I12 J34 Q4 ;ui session tool\n",
|
||||
parameters: "5161 0.0\n",
|
||||
},
|
||||
],
|
||||
[
|
||||
"saveGcodeProgram",
|
||||
"ui-session.ngc",
|
||||
"G0 X1.0 Y2.0 (Comment)\nG1 X3.0 Y4.0 F120.0\n",
|
||||
],
|
||||
[
|
||||
"saveMachineSessionSnapshot",
|
||||
"ui-machine-session",
|
||||
"xyzab-tdr",
|
||||
{
|
||||
filename: "ui-machine-session.json",
|
||||
gcodeFilename: "ui-session.ngc",
|
||||
metadata: {
|
||||
source: "ini-panel",
|
||||
workflow: "save-session-snapshot",
|
||||
snapshotLabel: "ui-machine-session/ui-machine-session.json",
|
||||
defaultGcodeOpfsPath: "linuxcnc/gcode/ui-session.ngc",
|
||||
},
|
||||
},
|
||||
],
|
||||
]);
|
||||
|
||||
assert.deepEqual(saveWorkflow.summary, {
|
||||
snapshotLabel: "ui-machine-session/ui-machine-session.json",
|
||||
workflow: "save-session-snapshot",
|
||||
defaultGcodeOpfsPath: "linuxcnc/gcode/ui-session.ngc",
|
||||
iniOpfsPath: "linuxcnc/machines/xyzab-tdr/machine.ini",
|
||||
parameterOpfsPath: "linuxcnc/machines/xyzab-tdr/linuxcnc.var",
|
||||
toolTableOpfsPath: "linuxcnc/machines/xyzab-tdr/tool.tbl",
|
||||
gcodeOpfsPath: "linuxcnc/gcode/ui-session.ngc",
|
||||
});
|
||||
|
||||
assert.deepEqual(saveWorkflow.fields, {
|
||||
sessionIni: "linuxcnc/machines/xyzab-tdr/machine.ini",
|
||||
sessionParameters: "linuxcnc/machines/xyzab-tdr/linuxcnc.var",
|
||||
sessionToolTable: "linuxcnc/machines/xyzab-tdr/tool.tbl",
|
||||
sessionGcode: "linuxcnc/gcode/ui-session.ngc",
|
||||
sessionSnapshot: "ui-machine-session/ui-machine-session.json",
|
||||
});
|
||||
|
||||
assert.deepEqual(saveWorkflow.logLines, [
|
||||
"Snapshot: ui-machine-session/ui-machine-session.json",
|
||||
"Workflow: save-session-snapshot",
|
||||
"Default G-code: linuxcnc/gcode/ui-session.ngc",
|
||||
"INI: linuxcnc/machines/xyzab-tdr/machine.ini",
|
||||
"Parameter file: linuxcnc/machines/xyzab-tdr/linuxcnc.var",
|
||||
"Tool table file: linuxcnc/machines/xyzab-tdr/tool.tbl",
|
||||
"G-code: linuxcnc/gcode/ui-session.ngc",
|
||||
]);
|
||||
|
||||
const restoreCalls = [];
|
||||
const restoreWorkflow = await restoreMachineSessionSnapshotWorkflow({
|
||||
machineId: "xyzab-tdr",
|
||||
snapshotId: "ui-machine-session",
|
||||
snapshotFilename: "ui-machine-session.json",
|
||||
snapshotLabel: "ui-machine-session/ui-machine-session.json",
|
||||
loadMachineSessionSnapshot: async (snapshotId, machineId, options) => {
|
||||
restoreCalls.push(["loadMachineSessionSnapshot", snapshotId, machineId, options]);
|
||||
return {
|
||||
sessionId: snapshotId,
|
||||
metadata: {
|
||||
workflow: "save-session-snapshot",
|
||||
snapshotLabel: "ui-machine-session/ui-machine-session.json",
|
||||
defaultGcodeOpfsPath: "linuxcnc/gcode/ui-session.ngc",
|
||||
},
|
||||
payload: {
|
||||
files: {
|
||||
ini: "linuxcnc/machines/xyzab-tdr/machine.ini",
|
||||
parameters: "linuxcnc/machines/xyzab-tdr/linuxcnc.var",
|
||||
toolTable: "linuxcnc/machines/xyzab-tdr/tool.tbl",
|
||||
gcode: "linuxcnc/gcode/ui-session.ngc",
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
loadMachineTextFiles: async (machineId) => {
|
||||
restoreCalls.push(["loadMachineTextFiles", machineId]);
|
||||
return {
|
||||
ini: "[EMC]\nMACHINE = xyzab-tdr\n",
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
assert.deepEqual(restoreCalls, [
|
||||
[
|
||||
"loadMachineSessionSnapshot",
|
||||
"ui-machine-session",
|
||||
"xyzab-tdr",
|
||||
{ filename: "ui-machine-session.json" },
|
||||
],
|
||||
["loadMachineTextFiles", "xyzab-tdr"],
|
||||
]);
|
||||
|
||||
assert.deepEqual(restoreWorkflow.fields, {
|
||||
sessionIni: "linuxcnc/machines/xyzab-tdr/machine.ini",
|
||||
sessionParameters: "linuxcnc/machines/xyzab-tdr/linuxcnc.var",
|
||||
sessionToolTable: "linuxcnc/machines/xyzab-tdr/tool.tbl",
|
||||
sessionGcode: "linuxcnc/gcode/ui-session.ngc",
|
||||
sessionSnapshot: "ui-machine-session/ui-machine-session.json",
|
||||
});
|
||||
|
||||
assert.deepEqual(restoreWorkflow.logLines, [
|
||||
"Snapshot: ui-machine-session/ui-machine-session.json",
|
||||
"Workflow: save-session-snapshot",
|
||||
"Default G-code: linuxcnc/gcode/ui-session.ngc",
|
||||
"INI: linuxcnc/machines/xyzab-tdr/machine.ini",
|
||||
"Parameter file: linuxcnc/machines/xyzab-tdr/linuxcnc.var",
|
||||
"Tool table file: linuxcnc/machines/xyzab-tdr/tool.tbl",
|
||||
"G-code: linuxcnc/gcode/ui-session.ngc",
|
||||
]);
|
||||
|
||||
const restoreLoadCalls = [];
|
||||
const restoreLoadWorkflow = await restoreMachineSessionSnapshotWorkflow({
|
||||
machineId: "xyzab-tdr",
|
||||
snapshotId: "ui-machine-session",
|
||||
snapshotFilename: "ui-machine-session.json",
|
||||
snapshotLabel: "ui-machine-session/ui-machine-session.json",
|
||||
loadMachineSessionSnapshot: async (snapshotId, machineId, options) => {
|
||||
restoreLoadCalls.push(["loadMachineSessionSnapshot", snapshotId, machineId, options]);
|
||||
return {
|
||||
sessionId: snapshotId,
|
||||
metadata: {
|
||||
workflow: "save-session-snapshot",
|
||||
snapshotLabel: "ui-machine-session/ui-machine-session.json",
|
||||
defaultGcodeOpfsPath: "linuxcnc/gcode/ui-session.ngc",
|
||||
},
|
||||
payload: {
|
||||
files: {
|
||||
ini: "linuxcnc/machines/xyzab-tdr/machine.ini",
|
||||
parameters: "linuxcnc/machines/xyzab-tdr/linuxcnc.var",
|
||||
toolTable: "linuxcnc/machines/xyzab-tdr/tool.tbl",
|
||||
gcode: "linuxcnc/gcode/ui-session.ngc",
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
loadMachineTextFiles: async (machineId) => {
|
||||
restoreLoadCalls.push(["loadMachineTextFiles", machineId]);
|
||||
return {
|
||||
ini: "[EMC]\nMACHINE = xyzab-tdr\n",
|
||||
};
|
||||
},
|
||||
loadSession: async () => {
|
||||
restoreLoadCalls.push(["loadSession"]);
|
||||
return {
|
||||
ini: {
|
||||
opfsPath: "linuxcnc/machines/xyzab-tdr/machine.ini",
|
||||
wasmPath: "/work/session-machine.ini",
|
||||
},
|
||||
parameters: {
|
||||
opfsPath: "linuxcnc/machines/xyzab-tdr/linuxcnc.var",
|
||||
wasmPath: "/work/session-linuxcnc.var",
|
||||
result: "restore_parameters=0",
|
||||
},
|
||||
toolTable: {
|
||||
opfsPath: "linuxcnc/machines/xyzab-tdr/tool.tbl",
|
||||
wasmPath: "/work/session-tool.tbl",
|
||||
result: "tooldata_load=0",
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
assert.deepEqual(restoreLoadCalls, [
|
||||
[
|
||||
"loadMachineSessionSnapshot",
|
||||
"ui-machine-session",
|
||||
"xyzab-tdr",
|
||||
{ filename: "ui-machine-session.json" },
|
||||
],
|
||||
["loadMachineTextFiles", "xyzab-tdr"],
|
||||
["loadSession"],
|
||||
]);
|
||||
|
||||
assert.deepEqual(restoreLoadWorkflow.fields, {
|
||||
sessionIni: "/work/session-machine.ini",
|
||||
sessionParameters: "/work/session-linuxcnc.var",
|
||||
sessionToolTable: "/work/session-tool.tbl",
|
||||
sessionGcode: "linuxcnc/gcode/ui-session.ngc",
|
||||
sessionSnapshot: "ui-machine-session/ui-machine-session.json",
|
||||
});
|
||||
|
||||
assert.deepEqual(restoreLoadWorkflow.logLines, [
|
||||
"Snapshot: ui-machine-session/ui-machine-session.json",
|
||||
"Workflow: save-session-snapshot",
|
||||
"Default G-code: linuxcnc/gcode/ui-session.ngc",
|
||||
"INI: linuxcnc/machines/xyzab-tdr/machine.ini -> /work/session-machine.ini",
|
||||
"Parameter file: linuxcnc/machines/xyzab-tdr/linuxcnc.var -> /work/session-linuxcnc.var",
|
||||
"Parameters: restore_parameters=0",
|
||||
"Tool table file: linuxcnc/machines/xyzab-tdr/tool.tbl -> /work/session-tool.tbl",
|
||||
"Tool table: tooldata_load=0",
|
||||
"G-code: linuxcnc/gcode/ui-session.ngc",
|
||||
]);
|
||||
|
||||
console.log("ini_panel_session_workflow_node_smoke=ok");
|
||||
6
wasm-port/tests/ui/node/verify_ini_panel_session_workflow.sh
Executable file
6
wasm-port/tests/ui/node/verify_ini_panel_session_workflow.sh
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/../../.." && pwd)"
|
||||
|
||||
node "$ROOT_DIR/tests/ui/node/verify_ini_panel_session_workflow.mjs"
|
||||
613
wasm-port/tests/ui/node/verify_ini_panel_state_summary.mjs
Normal file
613
wasm-port/tests/ui/node/verify_ini_panel_state_summary.mjs
Normal file
@@ -0,0 +1,613 @@
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import {
|
||||
createIniPanelStateSummary,
|
||||
createMachineSessionControlView,
|
||||
createMachineSessionStateSnapshot,
|
||||
createMachineSessionStateBundle,
|
||||
createMachineSessionStateReport,
|
||||
createMachineSessionStateReportExport,
|
||||
createMachineSessionWorkflowStatus,
|
||||
} from "../../../runtime/ui/ini-panel/panel-state-summary.js";
|
||||
import {
|
||||
renderMachineSessionControlView,
|
||||
} from "../../../runtime/ui/ini-panel/control-view-renderer.js";
|
||||
|
||||
const summary = createIniPanelStateSummary({
|
||||
badges: {
|
||||
ini: "INI WASM: ready",
|
||||
interp: "Interpreter WASM: ready",
|
||||
opfs: "OPFS: ready",
|
||||
},
|
||||
machine: {
|
||||
machine: "xyzab-tdr",
|
||||
kinematics: "trivkins",
|
||||
},
|
||||
machineFiles: {
|
||||
iniOpfsPath: "linuxcnc/machines/xyzab-tdr/machine.ini",
|
||||
toolTableOpfsPath: "linuxcnc/machines/xyzab-tdr/tool.tbl",
|
||||
parameterOpfsPath: "linuxcnc/machines/xyzab-tdr/linuxcnc.var",
|
||||
gcodeOpfsPath: "linuxcnc/gcode/ui-session.ngc",
|
||||
},
|
||||
sessionSnapshot: {
|
||||
snapshotLabel: "ui-machine-session/ui-machine-session.json",
|
||||
workflow: "save-session-snapshot",
|
||||
},
|
||||
sessionLoad: {
|
||||
iniOpfsPath: "linuxcnc/machines/xyzab-tdr/machine.ini",
|
||||
iniWasmPath: "/work/session-machine.ini",
|
||||
},
|
||||
run: {
|
||||
runStatus: "ok",
|
||||
programSource: "snapshot",
|
||||
},
|
||||
fiveaxisRemap: "ok",
|
||||
});
|
||||
|
||||
assert.deepEqual(summary, {
|
||||
badges: {
|
||||
ini: "INI WASM: ready",
|
||||
interp: "Interpreter WASM: ready",
|
||||
opfs: "OPFS: ready",
|
||||
},
|
||||
machine: {
|
||||
machine: "xyzab-tdr",
|
||||
kinematics: "trivkins",
|
||||
},
|
||||
machineFiles: {
|
||||
iniOpfsPath: "linuxcnc/machines/xyzab-tdr/machine.ini",
|
||||
toolTableOpfsPath: "linuxcnc/machines/xyzab-tdr/tool.tbl",
|
||||
parameterOpfsPath: "linuxcnc/machines/xyzab-tdr/linuxcnc.var",
|
||||
gcodeOpfsPath: "linuxcnc/gcode/ui-session.ngc",
|
||||
},
|
||||
sessionSnapshot: {
|
||||
snapshotLabel: "ui-machine-session/ui-machine-session.json",
|
||||
workflow: "save-session-snapshot",
|
||||
},
|
||||
sessionLoad: {
|
||||
iniOpfsPath: "linuxcnc/machines/xyzab-tdr/machine.ini",
|
||||
iniWasmPath: "/work/session-machine.ini",
|
||||
},
|
||||
run: {
|
||||
runStatus: "ok",
|
||||
programSource: "snapshot",
|
||||
},
|
||||
fiveaxisRemap: "ok",
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
createIniPanelStateSummary(),
|
||||
{
|
||||
badges: {
|
||||
ini: null,
|
||||
interp: null,
|
||||
opfs: null,
|
||||
},
|
||||
machine: null,
|
||||
machineFiles: null,
|
||||
sessionSnapshot: null,
|
||||
sessionLoad: null,
|
||||
run: null,
|
||||
fiveaxisRemap: null,
|
||||
},
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
createMachineSessionStateSnapshot({
|
||||
badges: {
|
||||
ini: "INI WASM: ready",
|
||||
interp: "Interpreter WASM: ready",
|
||||
opfs: "OPFS: ready",
|
||||
},
|
||||
machine: {
|
||||
machine: "xyzab-tdr",
|
||||
},
|
||||
sessionSnapshot: {
|
||||
snapshotLabel: "ui-machine-session/ui-machine-session.json",
|
||||
},
|
||||
sessionLoad: {
|
||||
iniWasmPath: "/work/session-machine.ini",
|
||||
},
|
||||
selectedProgram: {
|
||||
source: "snapshot",
|
||||
opfsPath: "linuxcnc/gcode/ui-session.ngc",
|
||||
wasmPath: "/work/ui-session.ngc",
|
||||
},
|
||||
run: {
|
||||
runStatus: "ok",
|
||||
programSource: "snapshot",
|
||||
},
|
||||
fields: {
|
||||
sessionIni: "/work/session-machine.ini",
|
||||
sessionParameters: "/work/session-linuxcnc.var",
|
||||
sessionToolTable: "/work/session-tool.tbl",
|
||||
sessionGcode: "linuxcnc/gcode/ui-session.ngc",
|
||||
sessionSnapshot: "ui-machine-session/ui-machine-session.json",
|
||||
runStatus: "ok",
|
||||
runSource: "snapshot",
|
||||
runSnapshot: "ui-machine-session/ui-machine-session.json",
|
||||
runWorkflow: "save-session-snapshot",
|
||||
runOpfsPath: "linuxcnc/gcode/ui-session.ngc",
|
||||
runWasmPath: "/work/ui-session.ngc",
|
||||
},
|
||||
lastAction: "run-gcode",
|
||||
fiveaxisRemap: "ok",
|
||||
}),
|
||||
{
|
||||
badges: {
|
||||
ini: "INI WASM: ready",
|
||||
interp: "Interpreter WASM: ready",
|
||||
opfs: "OPFS: ready",
|
||||
},
|
||||
machine: {
|
||||
machine: "xyzab-tdr",
|
||||
},
|
||||
machineFiles: null,
|
||||
sessionSnapshot: {
|
||||
snapshotLabel: "ui-machine-session/ui-machine-session.json",
|
||||
},
|
||||
sessionLoad: {
|
||||
iniWasmPath: "/work/session-machine.ini",
|
||||
},
|
||||
run: {
|
||||
runStatus: "ok",
|
||||
programSource: "snapshot",
|
||||
},
|
||||
fiveaxisRemap: "ok",
|
||||
selectedProgram: {
|
||||
source: "snapshot",
|
||||
opfsPath: "linuxcnc/gcode/ui-session.ngc",
|
||||
wasmPath: "/work/ui-session.ngc",
|
||||
},
|
||||
fields: {
|
||||
sessionIni: "/work/session-machine.ini",
|
||||
sessionParameters: "/work/session-linuxcnc.var",
|
||||
sessionToolTable: "/work/session-tool.tbl",
|
||||
sessionGcode: "linuxcnc/gcode/ui-session.ngc",
|
||||
sessionSnapshot: "ui-machine-session/ui-machine-session.json",
|
||||
runStatus: "ok",
|
||||
runSource: "snapshot",
|
||||
runSnapshot: "ui-machine-session/ui-machine-session.json",
|
||||
runWorkflow: "save-session-snapshot",
|
||||
runOpfsPath: "linuxcnc/gcode/ui-session.ngc",
|
||||
runWasmPath: "/work/ui-session.ngc",
|
||||
},
|
||||
lastAction: "run-gcode",
|
||||
error: null,
|
||||
},
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
createMachineSessionStateSnapshot().fields,
|
||||
{
|
||||
sessionIni: null,
|
||||
sessionParameters: null,
|
||||
sessionToolTable: null,
|
||||
sessionGcode: null,
|
||||
sessionSnapshot: null,
|
||||
runStatus: null,
|
||||
runSource: null,
|
||||
runSnapshot: null,
|
||||
runWorkflow: null,
|
||||
runOpfsPath: null,
|
||||
runWasmPath: null,
|
||||
},
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
createMachineSessionStateReport({
|
||||
badges: {
|
||||
ini: "INI WASM: ready",
|
||||
interp: "Interpreter WASM: ready",
|
||||
opfs: "OPFS: ready",
|
||||
},
|
||||
fields: {
|
||||
sessionSnapshot: "ui-machine-session/ui-machine-session.json",
|
||||
sessionIni: "/work/session-machine.ini",
|
||||
sessionParameters: "/work/session-linuxcnc.var",
|
||||
sessionToolTable: "/work/session-tool.tbl",
|
||||
sessionGcode: "linuxcnc/gcode/ui-session.ngc",
|
||||
runStatus: "ok",
|
||||
runSource: "snapshot",
|
||||
runSnapshot: "ui-machine-session/ui-machine-session.json",
|
||||
runWorkflow: "save-session-snapshot",
|
||||
runOpfsPath: "linuxcnc/gcode/ui-session.ngc",
|
||||
runWasmPath: "/work/ui-session.ngc",
|
||||
},
|
||||
sessionLoad: {
|
||||
iniWasmPath: "/work/session-machine.ini",
|
||||
parameterWasmPath: "/work/session-linuxcnc.var",
|
||||
toolTableWasmPath: "/work/session-tool.tbl",
|
||||
},
|
||||
selectedProgram: {
|
||||
source: "snapshot",
|
||||
opfsPath: "linuxcnc/gcode/ui-session.ngc",
|
||||
wasmPath: "/work/ui-session.ngc",
|
||||
},
|
||||
run: {
|
||||
runStatus: "ok",
|
||||
programSource: "snapshot",
|
||||
programOpfsPath: "linuxcnc/gcode/ui-session.ngc",
|
||||
programWasmPath: "/work/ui-session.ngc",
|
||||
iniWasmPath: "/work/session-machine.ini",
|
||||
canonicalEventCount: 2,
|
||||
motionSnapshotCount: 2,
|
||||
},
|
||||
lastAction: "run-gcode",
|
||||
error: null,
|
||||
fiveaxisRemap: "ok",
|
||||
}),
|
||||
{
|
||||
readiness: {
|
||||
ini: "INI WASM: ready",
|
||||
interp: "Interpreter WASM: ready",
|
||||
opfs: "OPFS: ready",
|
||||
},
|
||||
session: {
|
||||
snapshotLabel: "ui-machine-session/ui-machine-session.json",
|
||||
workflow: "save-session-snapshot",
|
||||
ini: "/work/session-machine.ini",
|
||||
parameters: "/work/session-linuxcnc.var",
|
||||
toolTable: "/work/session-tool.tbl",
|
||||
gcode: "linuxcnc/gcode/ui-session.ngc",
|
||||
},
|
||||
loaded: {
|
||||
iniWasmPath: "/work/session-machine.ini",
|
||||
parameterWasmPath: "/work/session-linuxcnc.var",
|
||||
toolTableWasmPath: "/work/session-tool.tbl",
|
||||
},
|
||||
program: {
|
||||
source: "snapshot",
|
||||
opfsPath: "linuxcnc/gcode/ui-session.ngc",
|
||||
wasmPath: "/work/ui-session.ngc",
|
||||
},
|
||||
run: {
|
||||
status: "ok",
|
||||
canonicalEventCount: 2,
|
||||
motionSnapshotCount: 2,
|
||||
iniWasmPath: "/work/session-machine.ini",
|
||||
},
|
||||
status: {
|
||||
lastAction: "run-gcode",
|
||||
error: null,
|
||||
fiveaxisRemap: "ok",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const expectedExportReport = {
|
||||
readiness: {
|
||||
ini: "INI WASM: ready",
|
||||
interp: "Interpreter WASM: ready",
|
||||
opfs: "OPFS: ready",
|
||||
},
|
||||
session: {
|
||||
snapshotLabel: "ui-machine-session/ui-machine-session.json",
|
||||
workflow: null,
|
||||
ini: "/work/session-machine.ini",
|
||||
parameters: null,
|
||||
toolTable: null,
|
||||
gcode: null,
|
||||
},
|
||||
loaded: {
|
||||
iniWasmPath: "/work/session-machine.ini",
|
||||
parameterWasmPath: null,
|
||||
toolTableWasmPath: null,
|
||||
},
|
||||
program: {
|
||||
source: null,
|
||||
opfsPath: null,
|
||||
wasmPath: null,
|
||||
},
|
||||
run: {
|
||||
status: null,
|
||||
canonicalEventCount: 0,
|
||||
motionSnapshotCount: 0,
|
||||
iniWasmPath: null,
|
||||
},
|
||||
status: {
|
||||
lastAction: "save-session-snapshot",
|
||||
error: null,
|
||||
fiveaxisRemap: null,
|
||||
},
|
||||
};
|
||||
|
||||
assert.deepEqual(
|
||||
createMachineSessionStateReportExport({
|
||||
badges: {
|
||||
ini: "INI WASM: ready",
|
||||
interp: "Interpreter WASM: ready",
|
||||
opfs: "OPFS: ready",
|
||||
},
|
||||
fields: {
|
||||
sessionSnapshot: "ui-machine-session/ui-machine-session.json",
|
||||
sessionIni: "/work/session-machine.ini",
|
||||
},
|
||||
sessionLoad: {
|
||||
iniWasmPath: "/work/session-machine.ini",
|
||||
},
|
||||
lastAction: "save-session-snapshot",
|
||||
}),
|
||||
{
|
||||
filename: "machine-session-state-report.json",
|
||||
mediaType: "application/json",
|
||||
report: expectedExportReport,
|
||||
text: `${JSON.stringify(expectedExportReport, null, 2)}\n`,
|
||||
},
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
createMachineSessionWorkflowStatus({
|
||||
badges: {
|
||||
ini: "INI WASM: ready",
|
||||
interp: "Interpreter WASM: ready",
|
||||
opfs: "OPFS: ready",
|
||||
},
|
||||
fields: {
|
||||
sessionSnapshot: "ui-machine-session/ui-machine-session.json",
|
||||
sessionIni: "/work/session-machine.ini",
|
||||
sessionGcode: "linuxcnc/gcode/ui-session.ngc",
|
||||
runStatus: "ok",
|
||||
runWorkflow: "save-session-snapshot",
|
||||
},
|
||||
run: {
|
||||
runStatus: "ok",
|
||||
canonicalEventCount: 2,
|
||||
motionSnapshotCount: 2,
|
||||
},
|
||||
lastAction: "run-gcode",
|
||||
error: null,
|
||||
}),
|
||||
{
|
||||
readiness: {
|
||||
ini: "INI WASM: ready",
|
||||
interp: "Interpreter WASM: ready",
|
||||
opfs: "OPFS: ready",
|
||||
},
|
||||
lastAction: "run-gcode",
|
||||
error: null,
|
||||
session: {
|
||||
snapshotLabel: "ui-machine-session/ui-machine-session.json",
|
||||
workflow: "save-session-snapshot",
|
||||
ini: "/work/session-machine.ini",
|
||||
parameters: null,
|
||||
toolTable: null,
|
||||
gcode: "linuxcnc/gcode/ui-session.ngc",
|
||||
},
|
||||
run: {
|
||||
status: "ok",
|
||||
canonicalEventCount: 2,
|
||||
motionSnapshotCount: 2,
|
||||
iniWasmPath: null,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const expectedBundleReport = {
|
||||
readiness: {
|
||||
ini: "INI WASM: ready",
|
||||
interp: "Interpreter WASM: ready",
|
||||
opfs: "OPFS: ready",
|
||||
},
|
||||
session: {
|
||||
snapshotLabel: "ui-machine-session/ui-machine-session.json",
|
||||
workflow: "save-session-snapshot",
|
||||
ini: "/work/session-machine.ini",
|
||||
parameters: null,
|
||||
toolTable: null,
|
||||
gcode: "linuxcnc/gcode/ui-session.ngc",
|
||||
},
|
||||
loaded: {
|
||||
iniWasmPath: null,
|
||||
parameterWasmPath: null,
|
||||
toolTableWasmPath: null,
|
||||
},
|
||||
program: {
|
||||
source: null,
|
||||
opfsPath: null,
|
||||
wasmPath: null,
|
||||
},
|
||||
run: {
|
||||
status: "ok",
|
||||
canonicalEventCount: 2,
|
||||
motionSnapshotCount: 2,
|
||||
iniWasmPath: null,
|
||||
},
|
||||
status: {
|
||||
lastAction: "run-gcode",
|
||||
error: null,
|
||||
fiveaxisRemap: null,
|
||||
},
|
||||
};
|
||||
|
||||
assert.deepEqual(
|
||||
createMachineSessionStateBundle({
|
||||
badges: {
|
||||
ini: "INI WASM: ready",
|
||||
interp: "Interpreter WASM: ready",
|
||||
opfs: "OPFS: ready",
|
||||
},
|
||||
fields: {
|
||||
sessionSnapshot: "ui-machine-session/ui-machine-session.json",
|
||||
sessionIni: "/work/session-machine.ini",
|
||||
sessionGcode: "linuxcnc/gcode/ui-session.ngc",
|
||||
runStatus: "ok",
|
||||
runWorkflow: "save-session-snapshot",
|
||||
},
|
||||
run: {
|
||||
runStatus: "ok",
|
||||
canonicalEventCount: 2,
|
||||
motionSnapshotCount: 2,
|
||||
},
|
||||
lastAction: "run-gcode",
|
||||
error: null,
|
||||
}),
|
||||
{
|
||||
report: expectedBundleReport,
|
||||
export: {
|
||||
filename: "machine-session-state-report.json",
|
||||
mediaType: "application/json",
|
||||
report: expectedBundleReport,
|
||||
text: `${JSON.stringify(expectedBundleReport, null, 2)}\n`,
|
||||
},
|
||||
workflowStatus: {
|
||||
readiness: {
|
||||
ini: "INI WASM: ready",
|
||||
interp: "Interpreter WASM: ready",
|
||||
opfs: "OPFS: ready",
|
||||
},
|
||||
lastAction: "run-gcode",
|
||||
error: null,
|
||||
session: {
|
||||
snapshotLabel: "ui-machine-session/ui-machine-session.json",
|
||||
workflow: "save-session-snapshot",
|
||||
ini: "/work/session-machine.ini",
|
||||
parameters: null,
|
||||
toolTable: null,
|
||||
gcode: "linuxcnc/gcode/ui-session.ngc",
|
||||
},
|
||||
run: {
|
||||
status: "ok",
|
||||
canonicalEventCount: 2,
|
||||
motionSnapshotCount: 2,
|
||||
iniWasmPath: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
createMachineSessionControlView({
|
||||
report: expectedBundleReport,
|
||||
export: {
|
||||
filename: "machine-session-state-report.json",
|
||||
mediaType: "application/json",
|
||||
report: expectedBundleReport,
|
||||
text: `${JSON.stringify(expectedBundleReport, null, 2)}\n`,
|
||||
},
|
||||
workflowStatus: {
|
||||
readiness: {
|
||||
ini: "INI WASM: ready",
|
||||
interp: "Interpreter WASM: ready",
|
||||
opfs: "OPFS: ready",
|
||||
},
|
||||
lastAction: "run-gcode",
|
||||
error: null,
|
||||
session: {
|
||||
snapshotLabel: "ui-machine-session/ui-machine-session.json",
|
||||
workflow: "save-session-snapshot",
|
||||
ini: "/work/session-machine.ini",
|
||||
parameters: null,
|
||||
toolTable: null,
|
||||
gcode: "linuxcnc/gcode/ui-session.ngc",
|
||||
},
|
||||
run: {
|
||||
status: "ok",
|
||||
canonicalEventCount: 2,
|
||||
motionSnapshotCount: 2,
|
||||
iniWasmPath: null,
|
||||
},
|
||||
},
|
||||
}),
|
||||
{
|
||||
status: {
|
||||
lastAction: "run-gcode",
|
||||
error: null,
|
||||
runStatus: "ok",
|
||||
},
|
||||
sections: [
|
||||
{
|
||||
id: "readiness",
|
||||
title: "Readiness",
|
||||
rows: [
|
||||
{ label: "INI", value: "INI WASM: ready" },
|
||||
{ label: "Interpreter", value: "Interpreter WASM: ready" },
|
||||
{ label: "OPFS", value: "OPFS: ready" },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "session",
|
||||
title: "Session",
|
||||
rows: [
|
||||
{ label: "Snapshot", value: "ui-machine-session/ui-machine-session.json" },
|
||||
{ label: "Workflow", value: "save-session-snapshot" },
|
||||
{ label: "INI", value: "/work/session-machine.ini" },
|
||||
{ label: "G-code", value: "linuxcnc/gcode/ui-session.ngc" },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "run",
|
||||
title: "Run",
|
||||
rows: [
|
||||
{ label: "Status", value: "ok" },
|
||||
{ label: "Canonical events", value: 2 },
|
||||
{ label: "Motion snapshots", value: 2 },
|
||||
{ label: "INI WASM", value: null },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
|
||||
function createElement(tagName) {
|
||||
const node = {
|
||||
tagName,
|
||||
children: [],
|
||||
dataset: {},
|
||||
textContent: "",
|
||||
ownerDocument: null,
|
||||
append(...children) {
|
||||
this.children.push(...children);
|
||||
},
|
||||
appendChild(child) {
|
||||
this.children.push(child);
|
||||
return child;
|
||||
},
|
||||
};
|
||||
node.ownerDocument = { createElement };
|
||||
return node;
|
||||
}
|
||||
|
||||
const controlViewContainer = createElement("div");
|
||||
const renderedControlView = renderMachineSessionControlView(
|
||||
controlViewContainer,
|
||||
createMachineSessionControlView({
|
||||
report: expectedBundleReport,
|
||||
workflowStatus: {
|
||||
readiness: expectedBundleReport.readiness,
|
||||
lastAction: "run-gcode",
|
||||
error: null,
|
||||
session: expectedBundleReport.session,
|
||||
run: expectedBundleReport.run,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
assert.equal(renderedControlView.statusNode.textContent, "run-gcode | ok | -");
|
||||
assert.equal(renderedControlView.sectionNodes.length, 3);
|
||||
assert.equal(renderedControlView.sectionNodes[2].dataset.sectionId, "run");
|
||||
assert.equal(
|
||||
renderedControlView.sectionNodes[2].children[1].children[3].textContent,
|
||||
"2",
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
{
|
||||
getMachineSessionControlView: typeof globalThis.window?.linuxCncIniPanelApi?.getMachineSessionControlView,
|
||||
renderMachineSessionControlView: typeof globalThis.window?.linuxCncIniPanelApi?.renderMachineSessionControlView,
|
||||
getMachineSessionStateBundle: typeof globalThis.window?.linuxCncIniPanelApi?.getMachineSessionStateBundle,
|
||||
getMachineSessionStateReport: typeof globalThis.window?.linuxCncIniPanelApi?.getMachineSessionStateReport,
|
||||
getMachineSessionStateReportExport: typeof globalThis.window?.linuxCncIniPanelApi?.getMachineSessionStateReportExport,
|
||||
getMachineSessionWorkflowStatus: typeof globalThis.window?.linuxCncIniPanelApi?.getMachineSessionWorkflowStatus,
|
||||
},
|
||||
{
|
||||
getMachineSessionControlView: "undefined",
|
||||
renderMachineSessionControlView: "undefined",
|
||||
getMachineSessionStateBundle: "undefined",
|
||||
getMachineSessionStateReport: "undefined",
|
||||
getMachineSessionStateReportExport: "undefined",
|
||||
getMachineSessionWorkflowStatus: "undefined",
|
||||
},
|
||||
);
|
||||
|
||||
console.log("ini_panel_state_summary_node_smoke=ok");
|
||||
6
wasm-port/tests/ui/node/verify_ini_panel_state_summary.sh
Executable file
6
wasm-port/tests/ui/node/verify_ini_panel_state_summary.sh
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/../../.." && pwd)"
|
||||
|
||||
node "$ROOT_DIR/tests/ui/node/verify_ini_panel_state_summary.mjs"
|
||||
13
wasm-port/tests/ui/node/verify_ui_node_smokes.sh
Executable file
13
wasm-port/tests/ui/node/verify_ui_node_smokes.sh
Executable file
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/../../.." && pwd)"
|
||||
|
||||
"$ROOT_DIR/tests/ui/node/verify_ini_panel_run_summary.sh"
|
||||
"$ROOT_DIR/tests/ui/node/verify_ini_panel_run_workflow.sh"
|
||||
"$ROOT_DIR/tests/ui/node/verify_ini_panel_session_summary.sh"
|
||||
"$ROOT_DIR/tests/ui/node/verify_ini_panel_session_workflow.sh"
|
||||
"$ROOT_DIR/tests/ui/node/verify_ini_panel_machine_file_summary.sh"
|
||||
"$ROOT_DIR/tests/ui/node/verify_ini_panel_state_summary.sh"
|
||||
|
||||
echo "ui_node_smokes=ok"
|
||||
Reference in New Issue
Block a user