Show line-by-line live G-code execution trace
This commit is contained in:
@@ -74,7 +74,7 @@
|
||||
},
|
||||
{
|
||||
"path": "web/worker/sim-session.mjs",
|
||||
"sha256": "6ffad5f415dac511b85670475c61c821c5b1cecea2003d812123a7bc3a0bb16f"
|
||||
"sha256": "4d6bce53441013bccd656eec4d365267f5547767e96c1de6d6e6946ef3a65e18"
|
||||
},
|
||||
{
|
||||
"path": "web/worker/test_xyzac_exact_preview.mjs",
|
||||
@@ -102,11 +102,11 @@
|
||||
},
|
||||
{
|
||||
"path": "web/react/src/App.tsx",
|
||||
"sha256": "d97cbe5f9d57e3071a43f0669bc9babd652a3ac347c4db226f749ee2d77fed10"
|
||||
"sha256": "e8d8beee65031cdcb70e8c5ab5b59db90e6b93ee2ac229fc4894d6da07caaeed"
|
||||
},
|
||||
{
|
||||
"path": "web/react/src/styles.css",
|
||||
"sha256": "9f1bd2fe7e365bebfc88ccc382a1ca7268be4e3b4bc5a07a10714be491be0d39"
|
||||
"sha256": "0c523d532688228e29bcad6b828733a2f6b542cdb544584264c2f8d20ac9fad4"
|
||||
},
|
||||
{
|
||||
"path": "wasm-core/src/sim_task.c",
|
||||
@@ -383,7 +383,7 @@
|
||||
}
|
||||
},
|
||||
"release_status": "STOP_RELEASE",
|
||||
"report_sha256": "b7b9b7f02688d1f3fe1f1dab5165cc3426f0cb99aa04f951f2a1af1a89840e71",
|
||||
"report_sha256": "8fceb9e6f6af1089b405fd7b57aec455fc11b34ae5dc81b59448ae1469472a73",
|
||||
"schema": "xyzac-trt-tool-preview-report.v1",
|
||||
"status": "verified",
|
||||
"task_id": "RTCP-10-TOOL-PREVIEW",
|
||||
|
||||
@@ -128,10 +128,10 @@
|
||||
"source_slice": "AXIS-XYZAC-PROGRAM-MDI",
|
||||
"sources": [
|
||||
{
|
||||
"lines": "122-1003",
|
||||
"lines": "122-1004",
|
||||
"path": "web/worker/sim-session.mjs",
|
||||
"role": "session-authority-routing",
|
||||
"sha256": "6ffad5f415dac511b85670475c61c821c5b1cecea2003d812123a7bc3a0bb16f",
|
||||
"sha256": "4d6bce53441013bccd656eec4d365267f5547767e96c1de6d6e6946ef3a65e18",
|
||||
"status": "resolved",
|
||||
"symbol": "new ProgramRunner"
|
||||
},
|
||||
@@ -161,10 +161,10 @@
|
||||
"symbol": "export class MotionState"
|
||||
},
|
||||
{
|
||||
"lines": "122-1003",
|
||||
"lines": "122-1004",
|
||||
"path": "web/worker/sim-session.mjs",
|
||||
"role": "session-authority-routing",
|
||||
"sha256": "6ffad5f415dac511b85670475c61c821c5b1cecea2003d812123a7bc3a0bb16f",
|
||||
"sha256": "4d6bce53441013bccd656eec4d365267f5547767e96c1de6d6e6946ef3a65e18",
|
||||
"status": "resolved",
|
||||
"symbol": "new ProgramRunner"
|
||||
}
|
||||
@@ -186,10 +186,10 @@
|
||||
"symbol": "export class AuthoritativeRuntime"
|
||||
},
|
||||
{
|
||||
"lines": "122-1003",
|
||||
"lines": "122-1004",
|
||||
"path": "web/worker/sim-session.mjs",
|
||||
"role": "session-authority-routing",
|
||||
"sha256": "6ffad5f415dac511b85670475c61c821c5b1cecea2003d812123a7bc3a0bb16f",
|
||||
"sha256": "4d6bce53441013bccd656eec4d365267f5547767e96c1de6d6e6946ef3a65e18",
|
||||
"status": "resolved",
|
||||
"symbol": "new ProgramRunner"
|
||||
}
|
||||
@@ -205,7 +205,7 @@
|
||||
"recovery": "Chromium SIGKILL/profile-reopen, corruption detection and Firefox OPFS round-trip are verified"
|
||||
},
|
||||
"release_status": "STOP_RELEASE",
|
||||
"report_sha256": "2b7f0ee5b74ba0b961f9679a5bfa156e361c9268cc14a89bf42d12776a22085a",
|
||||
"report_sha256": "960537fe609868696cecacac63eb693adf11cc37406c7b098e653a914d0f5365",
|
||||
"scans": {
|
||||
"js_gcode_matches": [
|
||||
{
|
||||
@@ -274,12 +274,12 @@
|
||||
"text": "const result = call(this.core, \"sim_interpreter_execute\", \"number\", [\"number\", \"number\", \"number\"], [this.interpreter, this.blockBuffer, bytes.length]);"
|
||||
},
|
||||
{
|
||||
"line": 1736,
|
||||
"line": 1744,
|
||||
"path": "web/worker/sim-session.mjs",
|
||||
"text": "const result = this.xyzacProgram.executeBlock(block);"
|
||||
},
|
||||
{
|
||||
"line": 1755,
|
||||
"line": 1763,
|
||||
"path": "web/worker/sim-session.mjs",
|
||||
"text": "const result = this.program.executeBlock(block);"
|
||||
},
|
||||
@@ -292,7 +292,7 @@
|
||||
"opfs_writer_matches": [
|
||||
{
|
||||
"line": 1,
|
||||
"path": "web/react/dist/assets/sim-worker-entry-CP8X4Y-6.js",
|
||||
"path": "web/react/dist/assets/sim-worker-entry-BnpVdnWv.js",
|
||||
"text": "(function(){var e=(()=>{var e=self.location.href;return(function(t){t||={};var t=t===void 0?{}:t,n,r;t.ready=new Promise(function(e,t){n=e,r=t});var i=Object.assign({},t),a=[],o=typeof window==`object`,s=typeof importScripts==`function`;typ"
|
||||
},
|
||||
{
|
||||
@@ -1496,7 +1496,7 @@
|
||||
"files": [
|
||||
{
|
||||
"path": "web/react/src/App.tsx",
|
||||
"sha256": "d97cbe5f9d57e3071a43f0669bc9babd652a3ac347c4db226f749ee2d77fed10"
|
||||
"sha256": "e8d8beee65031cdcb70e8c5ab5b59db90e6b93ee2ac229fc4894d6da07caaeed"
|
||||
},
|
||||
{
|
||||
"path": "web/react/src/ThreeMachineView.tsx",
|
||||
@@ -1548,7 +1548,7 @@
|
||||
},
|
||||
{
|
||||
"path": "web/react/src/store.ts",
|
||||
"sha256": "899f8c4e7ce512b91375ed47289d4a9f2b5146504acde4e23d0587018d7ec766"
|
||||
"sha256": "98a2634b0658d55bd1874c13196b7ac4ffffcdc9ff4246882b81147dad65523d"
|
||||
},
|
||||
{
|
||||
"path": "web/react/src/test_xyzac_threejs.mjs",
|
||||
@@ -1656,7 +1656,7 @@
|
||||
},
|
||||
{
|
||||
"path": "web/worker/sim-session.mjs",
|
||||
"sha256": "6ffad5f415dac511b85670475c61c821c5b1cecea2003d812123a7bc3a0bb16f"
|
||||
"sha256": "4d6bce53441013bccd656eec4d365267f5547767e96c1de6d6e6946ef3a65e18"
|
||||
},
|
||||
{
|
||||
"path": "web/worker/sim-task.mjs",
|
||||
@@ -2094,10 +2094,10 @@
|
||||
"scope": "selected_xyzac_simulation_only",
|
||||
"sources": [
|
||||
{
|
||||
"lines": "122-1003",
|
||||
"lines": "122-1004",
|
||||
"path": "web/worker/sim-session.mjs",
|
||||
"role": "session-authority-routing",
|
||||
"sha256": "6ffad5f415dac511b85670475c61c821c5b1cecea2003d812123a7bc3a0bb16f",
|
||||
"sha256": "4d6bce53441013bccd656eec4d365267f5547767e96c1de6d6e6946ef3a65e18",
|
||||
"status": "resolved",
|
||||
"symbol": "new ProgramRunner"
|
||||
},
|
||||
|
||||
@@ -94,19 +94,19 @@
|
||||
"coverage/xyzac-trt-gate-matrix.report.json": "3a0dd402abd95e79243e0f61e62819d595110ac6d1c3ea2a3b2251424718ba86",
|
||||
"coverage/xyzac-trt-hal-tool-alarm.report.json": "666fb1273c2471cd5869aea83e847a657309ee8cb4915713e25f4a99c94b3943",
|
||||
"coverage/xyzac-trt-manual-control.report.json": "bd38d4f20322ae3e246438740b3e35827ab2ac6b7b16b8cee22920d2c24c1847",
|
||||
"coverage/xyzac-trt-program-preview.report.json": "c4db73f5ebe63e26921425d44c9e914ca453300a54f51b9da05d709a9e68cf15",
|
||||
"coverage/xyzac-trt-protocol-react.report.json": "af78f0fd639fca95c5406b8afdcf5305225d97aba448637745bd5a941aa19689",
|
||||
"coverage/xyzac-trt-program-preview.report.json": "b3352590c5e1e48c938808ae452a63dbcadc1a1f1e1fbd4f14f56305ae447e6e",
|
||||
"coverage/xyzac-trt-protocol-react.report.json": "cd6e765f44d42c11c30b94e3a75b32448b95c82ebd4c79f47bb321f5ba3e08dd",
|
||||
"coverage/xyzac-trt-recovery.report.json": "96df470c39326eab1979bd86669fb894335185806015479d586711f67c19ee05",
|
||||
"coverage/xyzac-trt-rtcp.report.json": "b1562d6d2c9cd5144c7c8c9b334bd8bf67919ea7297f103ec67cb32773608d08",
|
||||
"coverage/xyzac-trt-source-slice-inventory.report.json": "bf5f3fd36e1943e0dc42bafb2ed195fad147bcb3e56a8d208d7f01a3641829de",
|
||||
"coverage/xyzac-trt-startup.report.json": "56e040b645bcefc3d6c57244cb2d3aba16f421ff5137089d9c3336970dfd968b",
|
||||
"coverage/xyzac-trt-threejs.report.json": "5172d7118c555fca5f92bcb443b9c259ea0225346482558d4c33ef8a4f8da5cb",
|
||||
"coverage/xyzac-trt-tool-preview.report.json": "c1643894bf1f2f1e49f43052731bef461ceaaf307d170e4877ac251d16e37013",
|
||||
"coverage/xyzac-trt-tool-preview.report.json": "1980f3dfad0471bdef5025aec230af5c5ba477ebd5ecccb9ada732d435465efd",
|
||||
"coverage/xyzac-trt-trace-parity.report.json": "6c6b3500bc1189ff68513b74f0e5144879c3af7641349cb61737191303b76950",
|
||||
"coverage/xyzac-trt-wasm-authority-audit.report.json": "f0d1de5dc69141fc85756b37d4c2a69ed3a8071b43962a5bd549f6465d94f3de",
|
||||
"coverage/xyzac-trt-wasm-authority-audit.report.json": "57af1a779dc5d48b40b4a8576b7949aecaab2fd2eeaae7dfbbab35c06a254102",
|
||||
"coverage/xyzac-trt-workflow.report.json": "9d0ed087b17ec14c906f4268d2d616dbc3800da03f731376b2ef9ae44ca5c6e7"
|
||||
},
|
||||
"report_sha256": "59b7792f766f45b0dfc2f59829d3932d8dfa75097c890ef58c41030583c11178",
|
||||
"report_sha256": "03d305c986a03aaaf6e04833b8e0d4095d840f89741582cd50be307f1aeed73c",
|
||||
"schema": "xyzac-trt-web-wasm-parity-report.v1",
|
||||
"source_coverage": {
|
||||
"axis_keyboard": 10,
|
||||
|
||||
@@ -87,10 +87,15 @@ function DroView({ axes, state, coordType, displayType, metric, setCoordType, se
|
||||
return <section className="dro-view" data-axis-feature="AXIS-VIEW-04" role="tabpanel" aria-label="DRO"><div className="dro-mode-row"><strong>Position</strong><label>Coordinate<select data-axis-feature="AXIS-VIEW-18" value={coordType} onChange={(event) => setCoordType(event.target.value as "machine" | "relative")}><option value="machine">Machine</option><option value="relative">Relative</option></select></label><label>Display<select data-axis-feature="AXIS-VIEW-17" value={displayType} onChange={(event) => setDisplayType(event.target.value as "actual" | "commanded")}><option value="actual">Actual</option><option value="commanded">Commanded</option></select></label><label>Units<select data-axis-feature="AXIS-VIEW-02" value={metric ? "mm" : "inch"} onChange={(event) => setMetric(event.target.value === "mm")}><option value="mm">mm</option><option value="inch">inch</option></select></label></div><div className="dro-column-headings"><span /><span>Displayed</span><span>Actual</span><span>Relative</span></div><div className="dro-table">{labels.map((label, index) => { const actual = Number(axes[index] ?? 0); const target = Number(commanded[index] ?? actual); const relativeValue = Number(relative[index] ?? actual); const displayed = displayType === "commanded" ? target : actual; return <div className="dro-row" key={label}><strong>{label}</strong><output>{format(coordType === "relative" ? relativeValue : displayed, index)}</output><output>{format(actual, index)}</output><output>{format(relativeValue, index)}</output></div>; })}</div><div className="dro-footer"><span>Units: <b>{metric ? "mm" : "inch"}</b> / deg</span><span>{coordType} / {displayType}</span><span>Tick: {state.tick}</span><span>Hash: {state.snapshot?.state_hash?.slice(0, 12) || "-"}</span></div></section>;
|
||||
}
|
||||
|
||||
function ProgramPane({ text, setText, activeGcodes, onLoad, onRun, onStep, onOpenFile, canLoad, canRun, canStep, currentLine, executionLine, executionLineText, diagnostic }: { text: string; setText: (text: string) => void; activeGcodes: string; onLoad: () => void; onRun: () => void; onStep: () => void; onOpenFile: () => void; canLoad: boolean; canRun: boolean; canStep: boolean; currentLine: number | null; executionLine: number | null; executionLineText: string; diagnostic: string | null }) {
|
||||
function ProgramPane({ text, setText, activeGcodes, executionTrace, onLoad, onRun, onStep, onOpenFile, canLoad, canRun, canStep, currentLine, executionLine, executionLineText, diagnostic }: { text: string; setText: (text: string) => void; activeGcodes: string; executionTrace: Array<{ line: number; text: string; program_status?: string | null }>; onLoad: () => void; onRun: () => void; onStep: () => void; onOpenFile: () => void; canLoad: boolean; canRun: boolean; canStep: boolean; currentLine: number | null; executionLine: number | null; executionLineText: string; diagnostic: string | null }) {
|
||||
const lines = text.split("\n");
|
||||
const mainLine = currentLine && currentLine > 0 && currentLine <= lines.length ? currentLine : null;
|
||||
return <section className="axis-program-pane" data-axis-feature="AXIS-UI-07" aria-label="G-code program"><div className="program-editor-wrap"><div className="program-gutter" aria-label="G-code line numbers">{lines.map((_, index) => <span key={index} className={index + 1 === mainLine ? "current-line" : ""} aria-current={index + 1 === mainLine ? "true" : undefined}>{index + 1}</span>)}</div><textarea id="axis-program" data-axis-feature="AXIS-EDIT-01" value={text} onChange={(event) => setText(event.target.value)} spellCheck={false} aria-invalid={Boolean(diagnostic)} aria-label="G-code program" /></div><div className="program-execution-line" role="status" aria-label="Currently executing G-code"><span>{executionLine ? `Executing source line ${executionLine}` : "Execution idle"}</span><code>{executionLineText || "-"}</code></div><aside className="program-side"><div className="program-actions"><TextButton feature="AXIS-EDIT-01" onClick={onOpenFile}>Open file</TextButton><TextButton feature="AXIS-EDIT-01" onClick={onLoad} disabled={!canLoad}>Load</TextButton><TextButton feature="AXIS-CTRL-02" onClick={onRun} disabled={!canRun}>Run</TextButton><TextButton feature="AXIS-CTRL-02" onClick={onStep} disabled={!canStep}>Step</TextButton></div><label className="active-gcodes-label">Active G-Codes:<textarea data-axis-feature="AXIS-UI-07" readOnly value={activeGcodes} aria-label="Active G-Codes" /></label>{diagnostic && <output className="program-diagnostic" role="alert" data-axis-feature="AXIS-EDIT-01">{diagnostic}</output>}</aside></section>;
|
||||
const traceRef = useRef<HTMLOListElement>(null);
|
||||
useEffect(() => {
|
||||
const node = traceRef.current;
|
||||
if (node) node.scrollTop = node.scrollHeight;
|
||||
}, [executionTrace]);
|
||||
return <section className="axis-program-pane" data-axis-feature="AXIS-UI-07" aria-label="G-code program"><div className="program-editor-wrap"><div className="program-gutter" aria-label="G-code line numbers">{lines.map((_, index) => <span key={index} className={index + 1 === mainLine ? "current-line" : ""} aria-current={index + 1 === mainLine ? "true" : undefined}>{index + 1}</span>)}</div><textarea id="axis-program" data-axis-feature="AXIS-EDIT-01" value={text} onChange={(event) => setText(event.target.value)} spellCheck={false} aria-invalid={Boolean(diagnostic)} aria-label="G-code program" /></div><div className="program-execution-line" role="status" aria-label="Currently executing G-code"><span>{executionLine ? `Executing source line ${executionLine}` : "Execution idle"}</span><code>{executionLineText || "-"}</code></div><div className="program-execution-trace" role="log" aria-label="G-code execution trace" aria-live="polite"><strong>Execution trace</strong><ol ref={traceRef}>{executionTrace.slice(-8).map((entry, index) => <li key={`${entry.line}-${index}`}><span>L{entry.line}</span><code>{entry.text || "-"}</code></li>)}</ol></div><aside className="program-side"><div className="program-actions"><TextButton feature="AXIS-EDIT-01" onClick={onOpenFile}>Open file</TextButton><TextButton feature="AXIS-EDIT-01" onClick={onLoad} disabled={!canLoad}>Load</TextButton><TextButton feature="AXIS-CTRL-02" onClick={onRun} disabled={!canRun}>Run</TextButton><TextButton feature="AXIS-CTRL-02" onClick={onStep} disabled={!canStep}>Step</TextButton></div><label className="active-gcodes-label">Active G-Codes:<textarea data-axis-feature="AXIS-UI-07" readOnly value={activeGcodes} aria-label="Active G-Codes" /></label>{diagnostic && <output className="program-diagnostic" role="alert" data-axis-feature="AXIS-EDIT-01">{diagnostic}</output>}</aside></section>;
|
||||
}
|
||||
|
||||
function DiagnosticsPanel({ state, gates }: { state: typeof INITIAL_STATE; gates: Record<string, { enabled: boolean; reason: string }> }) {
|
||||
@@ -235,7 +240,7 @@ export function App() {
|
||||
<XyzacTrtPanel view={xyzacView} command={xyzacCommand} />
|
||||
<section className="axis-top-pane" data-axis-feature="AXIS-UI-06"><div className="axis-notebook left-notebook" data-axis-feature="AXIS-UI-06"><div className="notebook-tabs" role="tablist" aria-label="Manual, MDI and diagnostics controls"><button id="axis-tab-manual" type="button" role="tab" aria-controls="axis-panel-manual" aria-selected={leftTab === "manual"} className={leftTab === "manual" ? "active" : ""} onClick={enterManualMode}>Manual Control [F3]</button><button id="axis-tab-mdi" type="button" role="tab" aria-controls="axis-panel-mdi" aria-selected={leftTab === "mdi"} className={leftTab === "mdi" ? "active" : ""} onClick={() => setLeftTab("mdi")}>MDI [F5]</button><button id="axis-tab-diagnostics" type="button" role="tab" aria-controls="axis-panel-diagnostics" aria-selected={leftTab === "diagnostics"} className={leftTab === "diagnostics" ? "active" : ""} onClick={() => setLeftTab("diagnostics")}>Diagnostics</button></div><div id="axis-panel-manual" role="tabpanel" aria-labelledby="axis-tab-manual" hidden={leftTab !== "manual"}><ManualControl action={execute} ready={gates.jog.enabled} canHome={gates.home.enabled} onHomeAll={homeAll} selectedAxis={selectedAxis} setSelectedAxis={setSelectedAxis} spindleOn={spindleOn} coolant={coolant} onSpindle={onSpindle} onCoolant={onCoolant} devices={state.devices ?? {}} /></div><div id="axis-panel-mdi" role="tabpanel" aria-labelledby="axis-tab-mdi" hidden={leftTab !== "mdi"}><MdiControl action={execute} ready={gates.mdi.enabled} /></div><div id="axis-panel-diagnostics" role="tabpanel" aria-labelledby="axis-tab-diagnostics" hidden={leftTab !== "diagnostics"}><DiagnosticsPanel state={state} gates={gates} /><XyzacDiagnostics view={xyzacView} operations={operationTrace} alarms={state.alarms} error={state.error} /></div></div><div className="axis-notebook right-notebook" data-axis-feature="AXIS-UI-06"><div className="notebook-tabs" role="tablist" aria-label="Preview and DRO"><button id="axis-tab-preview" type="button" role="tab" aria-controls="axis-panel-preview" aria-selected={rightTab === "preview"} className={rightTab === "preview" ? "active" : ""} onClick={() => setRightTab("preview")}>Preview</button><button id="axis-tab-dro" type="button" role="tab" aria-controls="axis-panel-dro" aria-selected={rightTab === "dro"} className={rightTab === "dro" ? "active" : ""} onClick={() => setRightTab("dro")}>DRO</button></div><div id="axis-panel-preview" role="tabpanel" aria-labelledby="axis-tab-preview" className="axis-preview" hidden={rightTab !== "preview"}><XyzacToolPreviewFromSnapshot snapshot={state.snapshot} view={view} zoom={zoom} rotateMode={rotateMode} showPath={layers.path} showLive={layers.live} showLimits={layers.limits} showVelocity={layers.velocity} showOffsets={layers.offsets} showExtents={layers.extents} /></div><div id="axis-panel-dro" role="tabpanel" aria-labelledby="axis-tab-dro" hidden={rightTab !== "dro"}><DroView axes={axes} state={state} coordType={coordType} displayType={displayType} metric={metric} setCoordType={setCoordType} setDisplayType={setDisplayType} setMetric={setMetric} /></div></div></section>
|
||||
<section className="axis-metrics" data-axis-feature="AXIS-VIEW-03"><div className="axis-slider"><label>Jog Speed:<output>50%</output><input type="range" min="6" max="100" defaultValue="50" /></label><label>Feed Override:<output>{Math.round((state.snapshot?.overrides?.feed ?? 1) * 100)}%</output><input type="range" min="0" max="120" defaultValue="100" onChange={(event) => execute("SET_OVERRIDE", { feed: Number(event.target.value) / 100 })} /></label><label>Spindle Override:<output>{Math.round((state.snapshot?.overrides?.spindle?.[0] ?? 1) * 100)}%</output><input type="range" min="0" max="120" defaultValue="100" /></label></div><div className="axis-layer-controls" aria-label="Preview layers">{(["path", "live", "limits", "velocity", "offsets", "extents"] as const).map((layer) => <label key={layer}><input type="checkbox" data-axis-feature="AXIS-VIEW-03" checked={layers[layer]} onChange={(event) => setLayers((current) => ({ ...current, [layer]: event.target.checked }))} /> {layer}</label>)}</div></section>
|
||||
<XyzacProgramPane status={String(state.program?.program_status ?? "idle")} currentLine={executionLine} sourceHash={String(state.program?.source_hash ?? "")}><ProgramPane text={programText} setText={setProgramText} activeGcodes={activeGcodes} onOpenFile={openProgramFile} onLoad={() => { setPlotCleared(false); execute("PROGRAM_LOAD", { source: programText, path: PROGRAM_PATH }); }} onRun={runProgram} onStep={() => execute("PROGRAM_STEP")} canLoad={gates.load.enabled} canRun={gates.run.enabled} canStep={gates.step.enabled} currentLine={currentLine} executionLine={executionLine} executionLineText={executionLineText} diagnostic={state.error?.code ?? null} /></XyzacProgramPane>
|
||||
<XyzacProgramPane status={String(state.program?.program_status ?? "idle")} currentLine={executionLine} sourceHash={String(state.program?.source_hash ?? "")}><ProgramPane text={programText} setText={setProgramText} activeGcodes={activeGcodes} executionTrace={state.executionTrace} onOpenFile={openProgramFile} onLoad={() => { setPlotCleared(false); execute("PROGRAM_LOAD", { source: programText, path: PROGRAM_PATH }); }} onRun={runProgram} onStep={() => execute("PROGRAM_STEP")} canLoad={gates.load.enabled} canRun={gates.run.enabled} canStep={gates.step.enabled} currentLine={currentLine} executionLine={executionLine} executionLineText={executionLineText} diagnostic={state.error?.code ?? null} /></XyzacProgramPane>
|
||||
<input ref={programFileInput} className="visually-hidden-file-input" type="file" accept=".ngc,.nc,.gcode,.tap,text/plain" onChange={onProgramFile} aria-label="Open G-code file" />
|
||||
<footer className="axis-info-bar" data-axis-feature="AXIS-UI-07"><span className={`task-state ${state.status}`} data-axis-feature="AXIS-SHELL-01">{taskState}</span><span className="machine-state" data-axis-state-machine={machineState}>Machine {machineState}</span><span className="operation-state" data-axis-state-operation={operationState}>Operation {operationState}</span><span className="tool-state" data-axis-feature="AXIS-UI-07">Tool {toolNumber}</span><span className="position-state" data-axis-feature="AXIS-VIEW-17">{position}</span><span className="footer-status" role="status">{state.error ? `Error: ${state.error.code}` : commandStatus}</span></footer>
|
||||
</main>;
|
||||
|
||||
@@ -24,6 +24,7 @@ export type FrontendState = {
|
||||
axisState: AxisStateView | null;
|
||||
gates: Record<string, { action: string; enabled: boolean; reason: string }>;
|
||||
toolpath: Array<{ id: number; source_line?: number | null; start: Record<string, number>; end: Record<string, number>; active?: boolean }>;
|
||||
executionTrace: Array<{ line: number; text: string; program_status?: string | null }>;
|
||||
};
|
||||
|
||||
export const INITIAL_STATE: FrontendState = {
|
||||
@@ -48,6 +49,7 @@ export const INITIAL_STATE: FrontendState = {
|
||||
axisState: null,
|
||||
gates: {},
|
||||
toolpath: [],
|
||||
executionTrace: [],
|
||||
};
|
||||
|
||||
function payload<T>(message: MessageEnvelope): T {
|
||||
@@ -56,6 +58,12 @@ function payload<T>(message: MessageEnvelope): T {
|
||||
|
||||
function projection(result: Record<string, unknown>, message: MessageEnvelope, previous: FrontendState) {
|
||||
const panel = result.panel_snapshot as PanelSnapshotV1 | undefined;
|
||||
const trace = Array.isArray(result.execution_trace)
|
||||
? (result.execution_trace as Array<Record<string, unknown>>)
|
||||
.map((entry) => ({ line: Number(entry.line), text: String(entry.text ?? ""), program_status: entry.program_status ? String(entry.program_status) : null }))
|
||||
.filter((entry) => Number.isSafeInteger(entry.line) && entry.line > 0)
|
||||
: [];
|
||||
const executionTrace = trace.length ? [...previous.executionTrace, ...trace].slice(-32) : previous.executionTrace;
|
||||
const delta = result.motion_sample as { tick?: number; pose?: Record<string, number>; joints?: number[]; active_segment_id?: number; active_progress?: number } | undefined;
|
||||
if (!panel && delta && previous.snapshot) {
|
||||
const pose = delta.pose ?? {};
|
||||
@@ -89,6 +97,7 @@ function projection(result: Record<string, unknown>, message: MessageEnvelope, p
|
||||
program: (result.program as Record<string, unknown>) ?? previous.program,
|
||||
axisState: (result.axis_state as AxisStateView) ?? previous.axisState,
|
||||
gates: (result.gates as FrontendState["gates"]) ?? previous.gates,
|
||||
executionTrace,
|
||||
};
|
||||
}
|
||||
if (panel?.schema === "axis-panel-snapshot.v1") {
|
||||
@@ -145,6 +154,7 @@ function projection(result: Record<string, unknown>, message: MessageEnvelope, p
|
||||
hal: panel.hal ?? previous.hal,
|
||||
axisState: panel.axis_state,
|
||||
gates: { ...panel.gates, jog: panel.gates.jogStart, load: panel.gates.load, run: panel.gates.run, step: panel.gates.step, pause: panel.gates.pause, resume: panel.gates.resume, abort: panel.gates.abort, mdi: panel.gates.mdi, home: panel.gates.home },
|
||||
executionTrace,
|
||||
};
|
||||
}
|
||||
const wasmMotion = (result.wasm_motion as { state?: Record<string, unknown> } | undefined)?.state;
|
||||
@@ -186,7 +196,7 @@ function projection(result: Record<string, unknown>, message: MessageEnvelope, p
|
||||
for (const segment of nextToolpath) segment.active = false;
|
||||
}
|
||||
const snapshot = { simulation_tick: message.simulation_tick ?? previous.tick, state_hash: String(result.state_hash ?? previous.snapshot?.state_hash ?? ""), task: result.task as SimSnapshotV1["task"], motion: { axes, joints, velocity: Array.isArray(motionState.velocity) ? motionState.velocity as number[] : [], homed: typeof motionState.homed === "boolean" ? motionState.homed : undefined, homed_axes: Array.isArray(motionState.homed_axes) ? motionState.homed_axes as boolean[] : undefined }, authoritative } satisfies SimSnapshotV1;
|
||||
return { snapshot, toolpath: nextToolpath, task: (result.task as Record<string, unknown>) ?? previous.task, program: programResult as Record<string, unknown> ?? previous.program, devices: (result.devices as Record<string, unknown>) ?? previous.devices, deviceIo: (result.device_io as Record<string, unknown>) ?? previous.deviceIo, hal: (result.hal_runtime as Record<string, unknown>) ?? previous.hal, plc: (result.plc as Record<string, unknown>) ?? previous.plc, python: (result.python as Record<string, unknown>) ?? previous.python, ioAck: (result.io_ack as Record<string, unknown>) ?? previous.ioAck, axisState: (result.axis_state as AxisStateView) ?? previous.axisState, gates: (result.gates as FrontendState["gates"]) ?? previous.gates };
|
||||
return { snapshot, toolpath: nextToolpath, task: (result.task as Record<string, unknown>) ?? previous.task, program: programResult as Record<string, unknown> ?? previous.program, devices: (result.devices as Record<string, unknown>) ?? previous.devices, deviceIo: (result.device_io as Record<string, unknown>) ?? previous.deviceIo, hal: (result.hal_runtime as Record<string, unknown>) ?? previous.hal, plc: (result.plc as Record<string, unknown>) ?? previous.plc, python: (result.python as Record<string, unknown>) ?? previous.python, ioAck: (result.io_ack as Record<string, unknown>) ?? previous.ioAck, axisState: (result.axis_state as AxisStateView) ?? previous.axisState, gates: (result.gates as FrontendState["gates"]) ?? previous.gates, executionTrace };
|
||||
}
|
||||
|
||||
export function reduceFrontendState(state: FrontendState, message: MessageEnvelope): FrontendState {
|
||||
|
||||
@@ -170,7 +170,7 @@ output { font: 12px ui-monospace, monospace; text-align: right; }
|
||||
.axis-slider { display: grid; grid-template-columns: repeat(3, minmax(170px, 1fr)); gap: 12px; }
|
||||
.axis-slider label { grid-template-columns: auto 48px; }
|
||||
|
||||
.axis-program-pane { display: grid; grid-template-columns: minmax(0, 1fr) 230px; flex: 0 0 205px; height: 205px; min-height: 205px; margin: 0 5px 5px; border: 2px solid #777; background: #f7f7f7; }
|
||||
.axis-program-pane { display: grid; grid-template-columns: minmax(0, 1fr) 230px; grid-template-rows: minmax(100px, 1fr) auto auto; flex: 0 0 285px; height: 285px; min-height: 285px; margin: 0 5px 5px; border: 2px solid #777; background: #f7f7f7; }
|
||||
.axis-diagnostics { display: grid; gap: 8px; padding: 10px; min-height: 190px; overflow: auto; background: #f7f7f7; font-size: 12px; }
|
||||
.diagnostic-summary, .diagnostic-error, .diagnostic-row { display: flex; gap: 10px; align-items: baseline; flex-wrap: wrap; }
|
||||
.diagnostic-summary { padding-bottom: 6px; border-bottom: 1px solid #b3b3b3; }
|
||||
@@ -186,7 +186,13 @@ output { font: 12px ui-monospace, monospace; text-align: right; }
|
||||
.program-execution-line { display: grid; grid-column: 1; grid-row: 2; grid-template-columns: max-content minmax(0, 1fr); align-items: baseline; gap: 10px; min-width: 0; padding: 5px 8px; border-top: 1px solid #9aa8b4; border-bottom: 1px solid #9aa8b4; background: #e9f1f8; color: #173b5f; font: 12px/1.35 ui-monospace, monospace; }
|
||||
.program-execution-line span { font-weight: 700; white-space: nowrap; }
|
||||
.program-execution-line code { min-width: 0; overflow: hidden; color: #111; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.program-side { display: flex; grid-column: 2; grid-row: 1 / span 2; flex-direction: column; gap: 12px; padding: 8px; border-left: 1px solid #aaa; background: #d9d9d9; }
|
||||
.program-execution-trace { grid-column: 1; grid-row: 3; min-width: 0; padding: 4px 8px; overflow: auto; border-bottom: 1px solid #9aa8b4; background: #f4f7fa; color: #253746; font: 11px/1.3 ui-monospace, monospace; }
|
||||
.program-execution-trace strong { display: block; margin-bottom: 2px; font-size: 11px; }
|
||||
.program-execution-trace ol { display: grid; gap: 1px; max-height: 64px; margin: 0; padding: 0; overflow: auto; list-style: none; }
|
||||
.program-execution-trace li { display: grid; grid-template-columns: 42px minmax(0, 1fr); gap: 8px; min-width: 0; }
|
||||
.program-execution-trace li span { color: #174d84; font-weight: 700; }
|
||||
.program-execution-trace li code { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.program-side { display: flex; grid-column: 2; grid-row: 1 / span 3; flex-direction: column; gap: 12px; padding: 8px; border-left: 1px solid #aaa; background: #d9d9d9; }
|
||||
.program-actions { display: flex; flex-wrap: wrap; gap: 5px; }
|
||||
.active-gcodes-label { display: grid; gap: 5px; color: #333; }
|
||||
.active-gcodes-label textarea { width: 100%; min-height: 84px; resize: vertical; border: 1px solid #999; background: white; color: #222; font: 11px/1.35 ui-monospace, monospace; }
|
||||
@@ -208,9 +214,10 @@ output { font: 12px ui-monospace, monospace; text-align: right; }
|
||||
.axis-top-pane { grid-template-columns: 1fr; height: auto; min-height: 0; flex: 0 0 auto; }
|
||||
.axis-notebook { min-height: 330px; }
|
||||
.axis-preview { min-height: 320px; }
|
||||
.axis-program-pane { flex: 0 0 auto; height: auto; min-height: 205px; grid-template-columns: 1fr; }
|
||||
.axis-program-pane { flex: 0 0 auto; height: auto; min-height: 300px; grid-template-columns: 1fr; grid-template-rows: minmax(100px, 1fr) auto auto auto; }
|
||||
.program-execution-line { grid-column: 1; grid-row: 2; }
|
||||
.program-side { grid-column: 1; grid-row: 3; border-top: 1px solid #aaa; border-left: 0; }
|
||||
.program-execution-trace { grid-column: 1; grid-row: 3; }
|
||||
.program-side { grid-column: 1; grid-row: 4; border-top: 1px solid #aaa; border-left: 0; }
|
||||
.active-gcodes-label textarea { min-height: 58px; }
|
||||
}
|
||||
@media (max-width: 620px) {
|
||||
|
||||
@@ -180,6 +180,7 @@ export class SimSession {
|
||||
status: statePayload.status,
|
||||
cycles: statePayload.cycles,
|
||||
motion_trace: trace,
|
||||
execution_trace: Array.isArray(statePayload.execution_trace) ? statePayload.execution_trace : [],
|
||||
motion_sample: motionSample,
|
||||
snapshot_mode: "motion_delta",
|
||||
motion_authority: statePayload.motion_authority ?? this.motionAuthority,
|
||||
@@ -1534,6 +1535,13 @@ export class SimSession {
|
||||
return finish(this.error(input, "scheduler", String(error?.message ?? error)), "failed");
|
||||
}
|
||||
const program = this.xyzacProgram ? this.xyzacProgram.snapshot() : this.program.loaded ? this.program.snapshot() : null;
|
||||
const execution_trace = program_steps
|
||||
.map((step) => ({
|
||||
line: step.line ?? step.program?.execution_line ?? null,
|
||||
text: step.program?.execution_line_text ?? "",
|
||||
program_status: step.program_status ?? step.program?.program_status ?? null,
|
||||
}))
|
||||
.filter((entry) => Number.isSafeInteger(entry.line) && entry.line > 0);
|
||||
if (program) {
|
||||
const status = this.xyzacProgram ? (program.status === 4 ? "completed" : program.status === 2 ? "running" : program.status === 3 ? "paused" : "loaded") : (program.program_status === "completed" ? "completed" : program.program_status === "running" ? "running" : program.program_status);
|
||||
this.task.setProgram(status, this.xyzacProgram ? program.current_line : program.line);
|
||||
@@ -1544,7 +1552,7 @@ export class SimSession {
|
||||
const snapshot = this.timeline.checkpoint(input.seq, this.tick, snapshotState, this.timeline.snapshots.latest()?.snapshot_id ?? null);
|
||||
const lightweightXyzac = Boolean(this.xyzacProgram);
|
||||
return finish(this.envelope("COMMAND_RESULT", input, {
|
||||
status: "verified", cycles, program_steps,
|
||||
status: "verified", cycles, program_steps, execution_trace,
|
||||
...(lightweightXyzac && cycles <= 32 && motion_trace.length ? { motion_trace } : {}),
|
||||
motion_authority: this.motionAuthority,
|
||||
task: this.task.export(), scheduler: this.scheduler.export(), authoritative: snapshotState.authoritative,
|
||||
|
||||
Reference in New Issue
Block a user