import { access, mkdir, readFile, writeFile } from "node:fs/promises"; import { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import { createMemorySessionStorage } from "../app/src/runtime/five-axis-session.js"; import { parseLinuxCncIni } from "../app/src/runtime/linuxcnc-ini-runtime.js"; import { selectMachineFileProgram, stageProfileMachineFiles, } from "../app/src/runtime/linuxcnc-machine-file-staging.js"; import { createSimulationStore } from "../app/src/state/store.js"; import { getFiveAxisProfile } from "../app/src/profiles/index.js"; const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../.."); const projectRoot = resolve(repoRoot, "web-rtcp-5axis-xyzbc-trt-sim-plan"); const outputPath = process.argv[2] || resolve(projectRoot, "working/evidence/web-xyzbc-trt-evidence.json"); const sourceRel = "configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/xyzbc_switchkins.ngc"; const profile = getFiveAxisProfile("xyzbc-trt"); const wasmArtifacts = await inspectWasmArtifacts(); const iniText = await readFile( resolve(repoRoot, "wasm-port/vendor/linuxcnc", profile.iniPath), "utf8", ); const ini = parseLinuxCncIni(iniText, { path: profile.iniPath, profileId: profile.id, }); const storage = createMemorySessionStorage(); const staged = await stageProfileMachineFiles(profile, { storage }); const selectedPlan = selectMachineFileProgram(staged.plan, staged.save, sourceRel); const store = createSimulationStore(); const storeStage = await store.stageMachineFiles({ storage: createMemorySessionStorage() }); store.dispatch({ type: "LOAD_LINUXCNC_GCODE_SOURCE", sourceRel }); const state = store.getState(); const evidence = { apiName: "xyzbc-trt-web-opfs-wasm-evidence", status: wasmArtifacts.ready ? "ready-for-wasm-runtime" : "blocked", collectedAt: new Date().toISOString(), profile: { id: profile.id, machineName: profile.machineName, iniPath: profile.iniPath, pyvcpXmlPath: profile.pyvcpXmlPath, toolTablePath: profile.toolTablePath, coordinates: profile.coordinates, kinematics: profile.kinematics, kinematicsModuleId: profile.kinematicsModuleId, defaultProgramFilename: profile.machineFileStaging?.defaultProgramFilename, switchkinsTypes: profile.kinematicsParameters?.switchkinsTypes, halPins: profile.halPins, }, ini: { ready: ini.validation.ready, machineName: ini.machineName, coordinates: ini.traj.coordinates, kinematicsName: ini.kinematics.name, kinematicsModuleId: ini.kinematicsModuleId, remaps: ini.rs274ngc.remaps, haluiMdiCommands: ini.halui.mdiCommands, toolTable: ini.emcio.toolTable, parameterFile: ini.rs274ngc.parameterFile, jointConfig: ini.jointConfig, }, opfsStaging: { storageMode: staged.save.storageMode, opfsRoot: staged.save.opfsRoot, fileCount: staged.save.fileCount, summary: staged.save.summary, files: staged.save.files.map((file) => ({ sourceRel: file.sourceRel, opfsPath: file.opfsPath, wasmPath: file.wasmPath, kind: file.kind, bytes: file.bytes, executable: file.executable, })), gcodeSources: staged.save.gcodeSources, selectedProgram: { sourceRel: selectedPlan.selectedProgramSourceRel, filename: selectedPlan.selectedProgramFilename, wasmProgramPath: selectedPlan.wasmProgramPath, }, }, store: { machineProfile: state.machineProfile, sessionName: state.sessionName, machineProjectRoot: state.machineProject?.projectRoot || storeStage.save.opfsRoot, activeProgram: state.activeProgram, programSource: state.programSource, programLineCount: state.programLines.length, selectedGcodeSourceRel: state.machineFileStaging.selectedGcodeSourceRel, }, wasm: wasmArtifacts, coverage: { profileDefaultXyzbc: profile.id === "xyzbc-trt", iniReady: ini.validation.ready, opfsStaged: staged.save.status === "saved" && staged.save.fileCount > 0, pyvcpXmlStaged: staged.save.files.some((file) => file.sourceRel.endsWith("xyzbc-trt.xml")), remapsStaged: ["428remap.ngc", "429remap.ngc", "430remap.ngc"].every((name) => ( staged.save.files.some((file) => file.sourceRel.endsWith(`/remap_subs/${name}`)) )), toolTableStaged: staged.save.files.some((file) => file.sourceRel.endsWith("xyzbc-trt.tbl")), parameterFileStaged: staged.save.files.some((file) => file.sourceRel.endsWith("xyzbc.var")), defaultProgramStaged: staged.save.gcodeSources.some((source) => source.filename === "xyzbc_switchkins.ngc"), wasmArtifactsReady: wasmArtifacts.ready, }, blockers: [ ...(wasmArtifacts.ready ? [] : [{ id: "missing-wasm-artifacts", detail: "wasm-port/build/wasm does not contain all required kinematics/core/tp/task-hal artifacts.", required: wasmArtifacts.required, missing: wasmArtifacts.missing, }]), ...(staged.save.files.some((file) => file.sourceRel.endsWith("xyzbc.var")) ? [] : [{ id: "missing-parameter-file-staging", detail: "xyzbc.var is referenced by INI but absent from wasm-port/vendor manifest in this workspace.", }]), ], semanticBoundary: "web_opfs_wasm_runtime_readiness_for_linuxcnc_xyzbc_trt", }; await mkdir(dirname(outputPath), { recursive: true }); await writeFile(outputPath, JSON.stringify(evidence, null, 2) + "\n", "utf8"); console.log(`web_xyzbc_trt_evidence=${outputPath}`); async function inspectWasmArtifacts() { const required = [ "wasm-port/build/wasm/kinematics/linuxcnc_xyzbc_trt_kinematics.js", "wasm-port/build/wasm/kinematics/linuxcnc_xyzbc_trt_kinematics.wasm", "wasm-port/build/wasm/core/linuxcnc_interp.js", "wasm-port/build/wasm/core/linuxcnc_interp.wasm", "wasm-port/build/wasm/tp/linuxcnc_tp.js", "wasm-port/build/wasm/tp/linuxcnc_tp.wasm", "wasm-port/build/wasm/task-hal/linuxcnc_task_hal.js", "wasm-port/build/wasm/task-hal/linuxcnc_task_hal.wasm", ]; const files = []; const missing = []; for (const rel of required) { const abs = resolve(repoRoot, rel); try { await access(abs); files.push(rel); } catch { missing.push(rel); } } return { required, files, missing, ready: missing.length === 0, emscriptenAvailable: Boolean(await commandExists("emcc")), }; } async function commandExists(command) { const { spawn } = await import("node:child_process"); return new Promise((resolveCommand) => { const child = spawn("bash", ["-lc", `command -v ${command}`], { stdio: "ignore" }); child.on("exit", (code) => resolveCommand(code === 0)); }); }