import assert from "node:assert/strict"; import { readFile } from "node:fs/promises"; import { createMemorySessionStorage } from "../../app/src/runtime/five-axis-session.js"; import { createLinuxCncInterpreterRuntime } from "../../app/src/runtime/linuxcnc-interpreter-runtime.js"; import { parseLinuxCncIni } from "../../app/src/runtime/linuxcnc-ini-runtime.js"; import { getFiveAxisProfile } from "../../app/src/profiles/index.js"; import { createSimulationStore } from "../../app/src/state/store.js"; function sidebarEntry(state, id) { const entry = state.rightSidebarEntrances.find((candidate) => candidate.id === id); assert.ok(entry, `missing right sidebar entry ${id}`); return entry; } async function waitForValidatedProgram(store) { for (let attempt = 0; attempt < 30; attempt += 1) { const state = store.getState(); if (!state.interpreterExecutionPending && state.programValidation?.ready) { return state; } await new Promise((resolve) => setTimeout(resolve, 0)); } return store.getState(); } async function loadIniConfigFromVendoredSource(profile) { const text = await readFile( new URL(`../../../wasm-port/vendor/linuxcnc/${profile.iniPath}`, import.meta.url), "utf8", ); return parseLinuxCncIni(text, { path: profile.iniPath, profileId: profile.id, }); } const store = createSimulationStore(); let state = store.getState(); assert.equal(state.rightSidebarEntrances.length, 9); assert.deepEqual( state.rightSidebarEntrances.map((entry) => entry.label), ["E-STOP", "POWER", "RESET", "AUTO", "MANUAL", "JOG", "MDI", "IDENTITY", "TCP"], ); assert.equal(sidebarEntry(state, "power").allowed, true); assert.equal(sidebarEntry(state, "auto").allowed, false); assert.equal(sidebarEntry(state, "manual").allowed, false); assert.equal(sidebarEntry(state, "jog").allowed, false); assert.equal(sidebarEntry(state, "mdi").allowed, false); assert.equal(sidebarEntry(state, "identity").active, true); assert.equal(sidebarEntry(state, "identity").status, "active-blocked"); assert.equal(sidebarEntry(state, "tcp").allowed, false); store.dispatch({ type: "ESTOP" }); state = store.getState(); assert.equal(sidebarEntry(state, "estop").status, "emergency"); assert.equal(sidebarEntry(state, "power").allowed, false); assert.equal(sidebarEntry(state, "power").operatorMessage, "power on blocked: reset estop first"); store.dispatch({ type: "RESET" }); store.dispatch({ type: "TOGGLE_POWER" }); state = store.getState(); assert.equal(state.machine.taskState, "on"); assert.equal(sidebarEntry(state, "power").active, true); assert.equal(sidebarEntry(state, "manual").active, true); assert.equal(sidebarEntry(state, "jog").active, false); assert.equal(sidebarEntry(state, "auto").allowed, false); assert.equal(sidebarEntry(state, "auto").operatorMessage, "mode blocked: home machine before AUTO"); store.dispatch({ type: "SET_MODE", mode: "jog" }); state = store.getState(); assert.equal(state.machine.mode, "manual"); assert.equal(state.machine.manualPanel, "jog"); assert.equal(sidebarEntry(state, "manual").active, false); assert.equal(sidebarEntry(state, "jog").active, true); store.dispatch({ type: "HOME" }); store.dispatch({ type: "SET_MODE", mode: "auto" }); state = store.getState(); assert.equal(sidebarEntry(state, "auto").active, true); assert.equal(sidebarEntry(state, "mdi").allowed, true); assert.equal(sidebarEntry(state, "tcp").allowed, true); store.dispatch({ type: "SET_KINS_TYPE", kinsType: "tcp-xyzac" }); state = store.getState(); assert.equal(state.kinsType, "tcp-xyzac"); assert.equal(state.rtcpState, "on"); assert.equal(sidebarEntry(state, "tcp").active, true); store.dispatch({ type: "SET_KINS_TYPE", kinsType: "identity" }); assert.equal(store.getState().kinsType, "identity"); store.dispatch({ type: "LOAD_PROGRAM", filename: "sidebar-running-gate.ngc", content: [ "G0 X0 Y0 Z0", "G1 X1 F100", "G1 X2", "G1 X3", "G1 X4", "G1 X5", "G1 X6", "G1 X7", "G1 X8", "G1 X9", "M2", ].join("\n"), }); store.dispatch({ type: "RUN" }); state = store.getState(); assert.equal(state.runState, "running"); assert.equal(sidebarEntry(state, "manual").allowed, false); assert.equal(sidebarEntry(state, "tcp").allowed, false); assert.equal(sidebarEntry(state, "tcp").operatorMessage, "kinematics blocked: interpreter must be idle"); store.dispatch({ type: "STOP" }); store.dispatch({ type: "SET_MODE", mode: "manual" }); const profile = getFiveAxisProfile("xyzac-trt"); const iniConfig = await loadIniConfigFromVendoredSource(profile); store.dispatch({ type: "ATTACH_INI_CONFIG", profileId: profile.id, iniConfig }); store.dispatch({ type: "ATTACH_INTERPRETER_RUNTIME", runtime: await createLinuxCncInterpreterRuntime(), }); await store.stageMachineFiles({ storage: createMemorySessionStorage() }); state = store.getState(); assert.equal(state.machineProject.profileId, "xyzac-trt"); assert.equal(state.machineProject.projectRoot, "web-rtcp-5axis-sim-plan/machines/xyzac-trt"); assert.equal(state.machineProject.ini.filename, "xyzac-trt.ini"); assert.equal(state.machineProject.ini.sourceMatchesProfile, true); assert.equal(state.machineProject.ini.textMatchesLoadedIni, true); assert.equal(state.machineProject.configFileCount > 0, true); assert.equal(state.machineProject.gcodeFileCount, 16); assert.equal(state.machineProject.gcodeFiles.some((file) => file.filename === "impeller-7bl-xyzac.ngc"), true); store.dispatch({ type: "LOAD_LINUXCNC_GCODE_SOURCE", sourceRel: "configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/impeller-7bl-xyzac.ngc", }); state = await waitForValidatedProgram(store); assert.equal(state.programValidation.ready, true); assert.equal(state.programValidation.sourceGuard, "linuxcnc_vendored_5axis_gcode_source_file"); assert.equal(state.programValidation.previewSource, "linuxcnc_interpreter_canonical_motion"); assert.equal(state.programValidation.motionEventCount > 0, true); assert.equal(state.programValidation.plannerSampleCount > 0, true); assert.equal(state.programValidation.realtimeAxisValues.x, state.axisPose.x); assert.equal(state.machineProject.selectedProgram.filename, "impeller-7bl-xyzac.ngc"); const xyzbcStore = createSimulationStore(); xyzbcStore.dispatch({ type: "SET_PROFILE", profileId: "xyzbc-trt" }); const xyzbcProfile = getFiveAxisProfile("xyzbc-trt"); xyzbcStore.dispatch({ type: "ATTACH_INI_CONFIG", profileId: xyzbcProfile.id, iniConfig: await loadIniConfigFromVendoredSource(xyzbcProfile), }); await xyzbcStore.stageMachineFiles({ storage: createMemorySessionStorage() }); const xyzbcState = xyzbcStore.getState(); assert.equal(xyzbcState.machineProject.profileId, "xyzbc-trt"); assert.equal(xyzbcState.machineProject.ini.filename, "xyzbc-trt.ini"); assert.equal(xyzbcState.machineProject.gcodeFiles.some((file) => file.filename === "boat-xyzbc.ngc"), true); console.log("gmoccapy_trt_project_sidebar_smoke=ok");