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 { createLinuxCncKinematicsRuntime } from "../../app/src/runtime/linuxcnc-kinematics-runtime.js"; import { parseLinuxCncIni } from "../../app/src/runtime/linuxcnc-ini-runtime.js"; import { selectMachineFileProgram, stageProfileMachineFiles, } from "../../app/src/runtime/linuxcnc-machine-file-staging.js"; import { buildRtcpFrame } from "../../app/src/runtime/rtcp-frame.js"; import { createProfileSourceReferenceSummary } from "../../app/src/profiles/source-reference-map.js"; import { getFiveAxisProfile } from "../../app/src/profiles/index.js"; import { createSimulationStore, validateRunPreconditions } from "../../app/src/state/store.js"; const GMOCAPY_TRT_EXAMPLE_SOURCE_PREFIX = "configs/sim/gmoccapy/non_trivial_kinematics/table-rotary-tilting/examples/"; const GMOCAPY_TRT_EXAMPLE_FILES = [ "boat-xyzac.ngc", "boat-xyzbc.ngc", "impeller-7bl-xyzac.ngc", "test-xyzac.ngc", "test-xyzbc.ngc", ]; const DIRECT_PROGRAM_EXAMPLES = [ "impeller-7bl-xyzac.ngc", "boat-xyzac.ngc", "boat-xyzbc.ngc", ]; const NGC_GUI_SUBROUTINE_EXAMPLES = [ { filename: "test-xyzac.ngc", subroutine: "test-xyzac", rotaryAxis: "A" }, { filename: "test-xyzbc.ngc", subroutine: "test-xyzbc", rotaryAxis: "B" }, ]; const profile = getFiveAxisProfile("gmoccapy-xyzac-trt"); const iniText = await readFile( new URL("../../../wasm-port/vendor/linuxcnc/configs/sim/gmoccapy/non_trivial_kinematics/table-rotary-tilting/xyzac-trt.ini", import.meta.url), "utf8", ); const postguiText = await readFile( new URL("../../../wasm-port/vendor/linuxcnc/configs/sim/gmoccapy/non_trivial_kinematics/table-rotary-tilting/postgui.hal", import.meta.url), "utf8", ); assert.equal(profile.display.display, "gmoccapy"); assert.equal(profile.display.openFile, "./examples/impeller-7bl-xyzac.ngc"); assert.equal(profile.kinematics, "xyzac-trt-kins"); assert.equal(profile.kinematicsModuleId, "xyzac-trt"); assert.equal(profile.kinematicsParameters.fixedTrtDefault, true); assert.equal(profile.kinematicsParameters.switchkinsTypes[0].webKinsType, "tcp-xyzac"); assert.equal(profile.rtcpProof, true); assert.equal(profile.machineFileStaging.machineRel, "gmoccapy/non_trivial_kinematics/table-rotary-tilting"); assert.equal(profile.machineFileStaging.demoDirectory, "examples"); assert.equal(profile.hal.halcmd.offsetNets.some((net) => net.target === "gmoccapy.tooloffset-z"), true); assert.equal(profile.hal.halcmd.toolChange.manualGmoccapyPinsConnected, true); const sourceSummary = createProfileSourceReferenceSummary(profile.id); assert.equal(sourceSummary.referenceCount >= 10, true); assert.equal(sourceSummary.kinds.includes("ini"), true); assert.equal(sourceSummary.kinds.includes("gcode_demo"), true); assert.equal(sourceSummary.kinds.includes("gcode_ngcgui_subroutine"), true); assert.equal(sourceSummary.kinds.includes("remap_subroutine"), true); assert.equal(sourceSummary.sourceRequiredCount >= 2, true); for (const filename of GMOCAPY_TRT_EXAMPLE_FILES) { assert.equal( sourceSummary.references.some((reference) => reference.path === `${GMOCAPY_TRT_EXAMPLE_SOURCE_PREFIX}${filename}`), true, `${filename} source reference`, ); } const iniConfig = parseLinuxCncIni(iniText, { path: profile.iniPath, profileId: profile.id, }); assert.equal(iniConfig.validation.ready, true); assert.equal(iniConfig.machineName, "sim-xyzac-trt"); assert.equal(iniConfig.display.display, "gmoccapy"); assert.equal(iniConfig.traj.coordinates, "XYZAC"); assert.equal(iniConfig.kinematics.name, "xyzac-trt-kins"); assert.equal(iniConfig.kinematicsParameters.sparm, null); assert.deepEqual(iniConfig.rs274ngc.remaps.map((remap) => remap.code), ["M6", "M61"]); assert.equal(iniConfig.rs274ngc.subroutinePath, "./examples:../../macros"); assert.equal(iniConfig.hal.postguiHalFiles.includes("postgui.hal"), true); assert.equal(iniConfig.hal.halcmd.some((line) => line.includes("xyzac-trt-gui")), true); assert.equal(iniConfig.hal.halcmd.some((line) => line.includes("xyzac-trt-kins.tool-offset")), true); assert.equal(iniConfig.hal.halcmd.some((line) => line.includes("sets :y-offset 20")), true); assert.equal(iniConfig.emcmot.servoPeriodNs, 1000000); assert.equal(iniConfig.task.cycleTimeSeconds, 0.010); assert.equal(postguiText.includes("gmoccapy.spindle_feedback_bar"), true); assert.equal(postguiText.includes("gmoccapy.toolchange-change"), true); assert.equal(postguiText.includes("gmoccapy.tooloffset-z"), true); const staged = await stageProfileMachineFiles(profile, { storage: createMemorySessionStorage(), }); assert.equal(staged.plan.profileId, profile.id); assert.equal(staged.plan.machineRel, "gmoccapy/non_trivial_kinematics/table-rotary-tilting"); assert.equal(staged.plan.demoDirectory, "examples"); assert.equal(staged.save.files.some((file) => file.sourceRel === profile.iniPath), true); assert.equal(staged.save.files.some((file) => file.sourceRel === profile.toolTablePath), true); assert.equal(staged.save.files.some((file) => file.sourceRel.endsWith("macros/change_g43.ngc")), true); assert.equal(staged.save.files.some((file) => file.sourceRel.endsWith("macros/settool_g43.ngc")), true); assert.deepEqual(staged.save.gcodeSources.map((source) => source.filename), GMOCAPY_TRT_EXAMPLE_FILES); assert.equal(staged.save.gcodeSources.every((source) => source.sourceRel.startsWith(GMOCAPY_TRT_EXAMPLE_SOURCE_PREFIX)), true); assert.equal(staged.save.gcodeSources.every((source) => source.semanticBoundary === "linuxcnc_vendored_5axis_gcode_source_file"), true); assert.equal(staged.save.gcodeFiles.some((file) => file.filename === "test-xyzac.ngc"), true); assert.equal(staged.save.gcodeFiles.some((file) => file.filename === "test-xyzbc.ngc"), true); const selectedPlan = selectMachineFileProgram( staged.plan, staged.save, "configs/sim/gmoccapy/non_trivial_kinematics/table-rotary-tilting/examples/impeller-7bl-xyzac.ngc", ); assert.equal(selectedPlan.selectedProgramFilename, "impeller-7bl-xyzac.ngc"); assert.equal(selectedPlan.wasmProgramPath.endsWith("/examples/impeller-7bl-xyzac.ngc"), true); const runtime = await createLinuxCncInterpreterRuntime(); for (const filename of DIRECT_PROGRAM_EXAMPLES) { const source = staged.save.files.find((file) => file.sourceRel.endsWith(`/examples/${filename}`)); assert.ok(source, `${filename} staged`); const execution = runtime.runProgram(source.text); assert.equal(execution.sourceMode, "linuxcnc-interpreter-wasm", `${filename} source mode`); assert.equal(execution.summary.ready, true, `${filename} ready`); assert.equal(execution.summary.motionEventCount > 0, true, `${filename} motion`); assert.equal(execution.plannerTiming.samples.length > 0, true, `${filename} planner samples`); } for (const { filename, subroutine, rotaryAxis } of NGC_GUI_SUBROUTINE_EXAMPLES) { const source = staged.save.files.find((file) => file.sourceRel.endsWith(`/examples/${filename}`)); assert.ok(source, `${filename} staged`); assert.equal(source.text.includes(`o<${subroutine}> sub`), true, `${filename} subroutine open`); assert.equal(source.text.includes(`o<${subroutine}> endsub`), true, `${filename} subroutine close`); assert.equal(source.text.includes("T# M6"), true, `${filename} tool-change semantics`); assert.equal(source.text.includes("G43 H# Z0"), true, `${filename} tool-offset semantics`); assert.equal(new RegExp(`G00 ${rotaryAxis}#<${rotaryAxis.toLowerCase()}_angle> C#`).test(source.text), true, `${filename} rotary-axis semantics`); const execution = runtime.runProgram(source.text); assert.equal(execution.sourceMode, "linuxcnc-interpreter-wasm", `${filename} source mode`); assert.equal(execution.summary.ready, false, `${filename} ngcgui subroutine is not a standalone main program`); assert.equal(execution.summary.motionEventCount, 0, `${filename} direct subroutine execution has no motion`); } const kinematics = await createLinuxCncKinematicsRuntime({ moduleId: "xyzac-trt", switchkinsType: 0 }); const frame = buildRtcpFrame({ axisPose: { x: 16.339, y: -25.409, z: 33.353, a: -71.841, c: -35.93 }, activeLine: 5, kinsType: "tcp-xyzac", rtcpEnabled: true, profile, linuxCncKinematicsResult: kinematics.frameForJoints([16.339, -25.409, 33.353, -71.841, -35.93]), }); assert.equal(frame.sourceMode, "source-derived-kinematics-wasm"); assert.equal(frame.readiness.linuxCncKinematicsReady, true); assert.equal(frame.rtcpState, "on"); assert.equal(frame.profileId, "gmoccapy-xyzac-trt"); const store = createSimulationStore(); store.dispatch({ type: "SET_PROFILE", profileId: profile.id }); store.dispatch({ type: "ATTACH_INI_CONFIG", profileId: profile.id, iniConfig }); store.dispatch({ type: "ATTACH_KINEMATICS_RUNTIME", runtime: kinematics }); await store.refreshKinematicsFrame(); store.dispatch({ type: "ATTACH_INTERPRETER_RUNTIME", runtime }); await store.stageMachineFiles({ storage: createMemorySessionStorage() }); store.dispatch({ type: "LOAD_LINUXCNC_GCODE_SOURCE", sourceRel: "configs/sim/gmoccapy/non_trivial_kinematics/table-rotary-tilting/examples/impeller-7bl-xyzac.ngc", }); await new Promise((resolve) => setTimeout(resolve, 0)); const loadedState = store.getState(); assert.equal(loadedState.machineProfile, profile.id); assert.equal(loadedState.display?.display, undefined); assert.equal(loadedState.kinsType, "tcp-xyzac"); assert.equal(loadedState.rtcpState, "on"); assert.equal(loadedState.machineProject.gcodeDirectory.endsWith("/configs/sim/gmoccapy/non_trivial_kinematics/table-rotary-tilting/examples"), true); assert.equal(loadedState.machineProject.selectedProgram.filename, "impeller-7bl-xyzac.ngc"); assert.equal(loadedState.programValidation.sourceGuard, "linuxcnc_vendored_5axis_gcode_source_file"); assert.equal(loadedState.programExecution?.summary?.motionEventCount > 0, true); assert.equal(loadedState.toolDbReadiness.toolCount, 10); const preconditions = validateRunPreconditions(loadedState, { requireTaskHalRuntime: false, requireTaskHalSession: false, }); assert.equal(preconditions.ok, true); assert.equal(preconditions.profileId, "gmoccapy-xyzac-trt"); assert.equal(preconditions.selectedGcodeSourceRel.endsWith("/examples/impeller-7bl-xyzac.ngc"), true); console.log("gmoccapy_xyzac_trt_parity_smoke=ok");