import assert from "node:assert/strict"; import { readFile } from "node:fs/promises"; import { createMemorySessionStorage } from "../../app/src/runtime/five-axis-session.js"; import { parseLinuxCncIni } from "../../app/src/runtime/linuxcnc-ini-runtime.js"; import { createMachineFileStagingPlan, selectMachineFileProgram, stageProfileMachineFiles, } from "../../app/src/runtime/linuxcnc-machine-file-staging.js"; import { createLinuxCncInterpreterRuntime } from "../../app/src/runtime/linuxcnc-interpreter-runtime.js"; import { buildTaskHalProgramMotionPlan, buildTaskHalSessionFromMachineFiles, wrapTaskHalSdk, } from "../../app/src/runtime/linuxcnc-task-hal-runtime.js"; import { getFiveAxisProfile } from "../../app/src/profiles/index.js"; import { createSimulationStore } from "../../app/src/state/store.js"; import { createLinuxCncTaskHalSdk } from "../../../wasm-port/runtime/sdk/src/linuxcnc-task-hal.js"; const profile = getFiveAxisProfile(); assert.equal(profile.id, "xyzbc-trt"); assert.equal(profile.machineName, "sim-xyzbc-trt-kins (switchkins)"); assert.equal(profile.traj.coordinates, "XYZBC"); assert.deepEqual(profile.coordinates, ["X", "Y", "Z", "B", "C"]); assert.equal(profile.kinematics, "xyzbc-trt-kins"); assert.equal(profile.kinematicsModuleId, "xyzbc-trt"); assert.equal(profile.machineFileStaging.defaultProgramFilename, "xyzbc_switchkins.ngc"); assert.equal(profile.panelSchema.id, "xyzbc-trt-switchkins-pyvcp"); assert.ok(profile.halPins.includes("xyzbc-trt-kins.x-offset")); assert.ok(profile.halPins.includes("motion.switchkins-type")); assert.deepEqual(profile.samplePrograms, [ "configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/xyzbc_switchkins.ngc", "configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/boat-xyzbc.ngc", ]); assert.ok(profile.remaps.some((remap) => remap.code === "M428" && remap.switchkinsType === 1)); assert.ok(profile.remaps.some((remap) => remap.code === "M429" && remap.switchkinsType === 0)); assert.ok(profile.remaps.some((remap) => remap.code === "M430" && remap.switchkinsType === 2)); const iniText = await readFile( new URL("../../../wasm-port/vendor/linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.ini", import.meta.url), "utf8", ); const ini = parseLinuxCncIni(iniText, { path: profile.iniPath, profileId: profile.id, }); assert.equal(ini.validation.ready, true); assert.equal(ini.machineName, "sim-xyzbc-trt-kins (switchkins)"); assert.equal(ini.kinematics.name, "xyzbc-trt-kins"); assert.equal(ini.kinematicsModuleId, "xyzbc-trt"); assert.equal(ini.traj.coordinates, "XYZBC"); assert.equal(ini.rs274ngc.halPinVars, true); assert.equal(ini.rs274ngc.parameterFile, "xyzbc.var"); assert.equal(ini.emcio.toolTable, "xyzbc-trt.tbl"); assert.equal(ini.jointConfig[3].axis, "B"); assert.equal(ini.jointConfig[4].axis, "C"); assert.deepEqual(ini.halui.mdiCommands, ["M429", "M428", "M430"]); const storage = createMemorySessionStorage(); const staged = await stageProfileMachineFiles(profile, { storage }); assert.equal(staged.plan.profileId, "xyzbc-trt"); assert.equal(staged.plan.wasmDir, "/work/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt"); assert.equal(staged.save.status, "saved"); assert.equal(staged.save.storageMode, "memory"); assert.ok(staged.save.opfsRoot.endsWith("/xyzbc-trt")); assert.ok(staged.save.files.every((file) => file.opfsPath.startsWith("web-rtcp-5axis-xyzbc-trt-sim-plan/machines/xyzbc-trt/"))); assert.ok(staged.save.files.some((file) => file.sourceRel.endsWith("xyzbc-trt.ini") && file.kind === "ini")); assert.ok(staged.save.files.some((file) => file.sourceRel.endsWith("xyzbc-trt.xml") && file.kind === "pyvcp")); assert.ok(staged.save.files.some((file) => file.sourceRel.endsWith("xyzbc-trt.tbl") && file.kind === "toolTable")); assert.ok(staged.save.files.some((file) => file.sourceRel.endsWith("remap_subs/428remap.ngc") && file.kind === "remap")); assert.ok(staged.save.files.some((file) => file.sourceRel.endsWith("remap_subs/429remap.ngc") && file.kind === "remap")); assert.ok(staged.save.files.some((file) => file.sourceRel.endsWith("remap_subs/430remap.ngc") && file.kind === "remap")); assert.ok(staged.save.files.some((file) => file.sourceRel.endsWith("remap_subs/xyzbc_switchkins_sub.ngc") && file.kind === "remap")); assert.ok(staged.save.files.some((file) => file.sourceRel.endsWith("remap_subs/centering.ngc") && file.kind === "remap")); assert.ok(staged.save.files.some((file) => file.sourceRel.endsWith("remap_subs/helix_bc.ngc") && file.kind === "remap")); assert.ok(staged.save.gcodeSources.some((source) => source.filename === "xyzbc_switchkins.ngc")); assert.ok(staged.save.gcodeSources.some((source) => source.filename === "boat-xyzbc.ngc")); assert.equal(staged.save.files.some((file) => file.sourceRel.endsWith("xyzbc.var") && file.kind === "parameters"), true); const selectedPlan = selectMachineFileProgram( staged.plan, staged.save, "configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/xyzbc_switchkins.ngc", ); assert.equal(selectedPlan.selectedProgramFilename, "xyzbc_switchkins.ngc"); assert.ok(selectedPlan.wasmProgramPath.endsWith("/demos/xyzbc_switchkins.ngc")); const interpreter = await createLinuxCncInterpreterRuntime(); const execution = interpreter.runMachineFileProgram({ plan: selectedPlan, files: staged.save.files, executionMode: "fiveAxisRemap", }); assert.equal(execution.summary.machineFileExecutionReady, true); assert.equal(execution.plannerTiming.plannerRuntimeReady, true); assert.equal(execution.plannerTiming.samples.length > 0, true); const taskHalWasm = await readFile( new URL("../../../wasm-port/build/wasm/task-hal/linuxcnc_task_hal.wasm", import.meta.url), ); const taskHal = wrapTaskHalSdk(await createLinuxCncTaskHalSdk({ wasmBinary: taskHalWasm, print() {}, printErr() {}, })); const taskHalSession = buildTaskHalSessionFromMachineFiles({ profile, plan: selectedPlan, save: staged.save, selectedProgramRel: selectedPlan.selectedProgramSourceRel, }); taskHal.initSession(taskHalSession); taskHal.stageFiles(taskHalSession.files); taskHal.openProgram(taskHalSession.programPath); taskHal.loadProgramMotionPlan(buildTaskHalProgramMotionPlan({ programPath: taskHalSession.programPath, motion: execution.motion, timing: execution.plannerTiming, linearUnits: execution.plannerTiming.linearUnits || "mm", programLines: staged.save.files .find((file) => (file.wasmPath || file.path) === taskHalSession.programPath) ?.text.split(/\r?\n/) || [], })); taskHal.sendCommand({ type: "EMC_TASK_SET_STATE", state: "ON" }); taskHal.sendCommand({ type: "EMC_TASK_SET_MODE", mode: "AUTO" }); taskHal.sendCommand({ type: "EMC_TASK_PLAN_RUN", line: 0 }); taskHal.runCycles({ taskPeriodNs: 20000000, servoPeriodNs: 1000000, taskCycles: 3 }); const taskHalStatus = taskHal.readStatus(); assert.equal(taskHalStatus.summary.taskRuntimeReady, true); assert.equal(taskHalStatus.summary.halSyncReady, true); assert.equal(taskHalStatus.ui.activeLine >= 1, true); assert.equal(Number.isFinite(taskHalStatus.motionStatus.axis.x), true); assert.equal(Number.isFinite(taskHalStatus.motionStatus.axis.y), true); assert.equal(Number.isFinite(taskHalStatus.motionStatus.axis.z), true); const store = createSimulationStore(); const state = store.getState(); assert.equal(state.machineProfile, "xyzbc-trt"); assert.equal(state.profile.id, "xyzbc-trt"); assert.equal(state.sessionName, "xyzbc-trt-web-session"); assert.equal(state.kinematicsRuntimeReadiness, null); const storeStage = await store.stageMachineFiles({ storage: createMemorySessionStorage() }); const stagedState = store.getState(); assert.equal(storeStage.save.profileId, "xyzbc-trt"); assert.equal(stagedState.machineFileStaging.profileId, "xyzbc-trt"); assert.ok(stagedState.machineProject.projectRoot.startsWith("web-rtcp-5axis-xyzbc-trt-sim-plan/machines/xyzbc-trt")); assert.ok(stagedState.machineFileStaging.gcodeSources.some((source) => source.filename === "xyzbc_switchkins.ngc")); const stagingPlan = await createMachineFileStagingPlan({ profile }); assert.equal(stagingPlan.summary.remapFileCount >= 3, true); assert.equal(stagingPlan.files.some((file) => file.sourceRel.endsWith("xyzbc.var")), true); console.log("xyzbc_trt_web_app_smoke=ok");