import { createLinuxCncIniSdk, createLinuxCncInterpSdk, } from "../../sdk/src/index.js"; import { getOpfsRoot, loadTextFile, saveTextFile, } from "../../opfs/file-service.js"; import { loadGcodeProgram, loadMachineTextFiles, machineFilePaths, saveGcodeProgram, saveMachineTextFiles, } from "../../opfs/machine-file-store.js"; import { loadMachineSessionFromOpfs, } from "../../opfs/linuxcnc-machine-session-bridge.js"; const SAMPLE_PATH = "../../../linuxcnc/configs/sim/axis/vismach/5axis/table-dual-rotary/xyzab-tdr.ini"; const MACHINE_ID = "xyzab-tdr"; const MACHINE_PATHS = machineFilePaths(MACHINE_ID); const OPFS_FILE = MACHINE_PATHS.ini; const WASM_FILE = "/work/xyzab-tdr.ini"; const SESSION_WASM_FILES = { iniWasmPath: "/work/session-machine.ini", parameterWasmPath: "/work/session-linuxcnc.var", toolTableWasmPath: "/work/session-tool.tbl", }; const GCODE_FILENAME = "ui-session.ngc"; const GCODE_WASM_FILE = "/work/ui-session.ngc"; const DEFAULT_TOOL_TABLE = "T2 P7 Z3.125 D1.5 I12 J34 Q4 ;ui session tool\n"; const DEFAULT_PARAMETERS = [ "5161 0.0", "5162 0.0", "5220 1", "5221 0.0", "5399 0.0", "", ].join("\n"); const DEFAULT_GCODE = [ "G0 X1.0 Y2.0 (Comment)", "G1 X3.0 Y4.0 F120.0", "", ].join("\n"); const editor = document.getElementById("ini-editor"); const logNode = document.getElementById("log"); const eventNode = document.getElementById("canon-events"); const eventFilterNode = document.getElementById("event-filter"); const eventCountNode = document.getElementById("event-count"); const wasmBadge = document.getElementById("wasm-badge"); const interpBadge = document.getElementById("interp-badge"); const opfsBadge = document.getElementById("opfs-badge"); const fields = { machine: document.getElementById("field-machine"), display: document.getElementById("field-display"), kinematics: document.getElementById("field-kinematics"), joints: document.getElementById("field-joints"), coordinates: document.getElementById("field-coordinates"), parameterFile: document.getElementById("field-parameter-file"), toolTable: document.getElementById("field-tool-table"), sessionIni: document.getElementById("field-session-ini"), sessionParameters: document.getElementById("field-session-parameters"), sessionToolTable: document.getElementById("field-session-tool-table"), sessionGcode: document.getElementById("field-session-gcode"), runStatus: document.getElementById("field-run-status"), }; let iniSdk = null; let interpSdk = null; let loadedSession = null; let canonicalEventText = ""; function setBadge(node, text, className = "badge") { node.className = className; node.textContent = text; } function setLog(message, isError = false) { logNode.textContent = message; logNode.className = `log${isError ? " danger" : ""}`; } function setCanonicalEvents(text) { canonicalEventText = text || ""; renderCanonicalEvents(); } function renderCanonicalEvents() { const lines = canonicalEventText.split("\n").filter(Boolean); const filter = eventFilterNode.value.trim().toLowerCase(); const visibleLines = filter ? lines.filter((line) => line.toLowerCase().includes(filter)) : lines; eventNode.textContent = visibleLines.join("\n"); eventCountNode.textContent = `${visibleLines.length} / ${lines.length}`; } function setField(name, value) { fields[name].textContent = value ?? "-"; } function requireIniSdk() { if (!iniSdk) { throw new Error("INI WASM module is not ready yet."); } return iniSdk; } function requireInterpSdk() { if (!interpSdk) { throw new Error("Interpreter WASM module is not ready yet."); } return interpSdk; } function syncEditorToWasmFs() { requireIniSdk().writeTextFile(WASM_FILE, editor.value); } async function loadSample() { setLog(`Fetching ${SAMPLE_PATH}`); const response = await fetch(SAMPLE_PATH); if (!response.ok) { throw new Error(`Cannot fetch sample INI (${response.status})`); } editor.value = await response.text(); setLog("Loaded LinuxCNC sample INI into the standalone editor."); } async function boot() { try { iniSdk = await createLinuxCncIniSdk(); setBadge(wasmBadge, "INI WASM: ready"); } catch (error) { setBadge(wasmBadge, "INI WASM: failed", "badge danger"); setLog(`INI WASM init failed: ${error.message}`, true); throw error; } try { interpSdk = await createLinuxCncInterpSdk(); setBadge(interpBadge, "Interpreter WASM: ready"); } catch (error) { setBadge(interpBadge, "Interpreter WASM: failed", "badge danger"); setLog(`Interpreter WASM init failed: ${error.message}`, true); throw error; } try { await getOpfsRoot(); setBadge(opfsBadge, "OPFS: ready"); } catch (error) { setBadge(opfsBadge, "OPFS: unavailable", "badge danger"); setLog(`OPFS check failed: ${error.message}`, true); } } document.getElementById("load-sample").addEventListener("click", async () => { try { await loadSample(); } catch (error) { setLog(error.message, true); } }); document.getElementById("save-opfs").addEventListener("click", async () => { try { await saveTextFile(OPFS_FILE, editor.value); setLog(`Saved current INI text to OPFS at ${OPFS_FILE}`); } catch (error) { setLog(`OPFS save failed: ${error.message}`, true); } }); document.getElementById("save-machine-files").addEventListener("click", async () => { try { const paths = await saveMachineTextFiles(MACHINE_ID, { ini: editor.value, toolTable: DEFAULT_TOOL_TABLE, parameters: DEFAULT_PARAMETERS, }); const gcodePath = await saveGcodeProgram(GCODE_FILENAME, DEFAULT_GCODE); setLog( [ "Saved machine text files to OPFS.", `INI: ${paths.ini}`, `Tool table: ${paths.toolTable}`, `Parameters: ${paths.parameters}`, `G-code: ${gcodePath}`, ].join("\n"), ); } catch (error) { setLog(`Machine file save failed: ${error.message}`, true); } }); document.getElementById("load-session").addEventListener("click", async () => { try { loadedSession = await loadMachineSessionFromOpfs( requireInterpSdk(), MACHINE_ID, { ...SESSION_WASM_FILES, iniSdk: requireIniSdk(), }, ); setField("sessionIni", loadedSession.ini.wasmPath); setField("sessionParameters", loadedSession.parameters.wasmPath); setField("sessionToolTable", loadedSession.toolTable.wasmPath); setLog( [ "Loaded machine session into the interpreter WASM filesystem.", `INI: ${loadedSession.ini.opfsPath} -> ${loadedSession.ini.wasmPath}`, `Parameter file: ${loadedSession.parameters.opfsPath} -> ${loadedSession.parameters.wasmPath}`, `Parameters: ${loadedSession.parameters.result.trim()}`, `Tool table file: ${loadedSession.toolTable.opfsPath} -> ${loadedSession.toolTable.wasmPath}`, `Tool table: ${loadedSession.toolTable.result.trim()}`, ].join("\n"), ); } catch (error) { setLog(`Machine session load failed: ${error.message}`, true); } }); document.getElementById("run-gcode").addEventListener("click", async () => { try { if (!loadedSession) { throw new Error("Load a machine session before running G-code."); } const interp = requireInterpSdk(); const programText = await loadGcodeProgram(GCODE_FILENAME); interp.writeTextFile(GCODE_WASM_FILE, programText); const result = interp.runFileWithIni(GCODE_WASM_FILE, loadedSession.ini.wasmPath); setField("sessionGcode", GCODE_WASM_FILE); setField("runStatus", "ok"); setCanonicalEvents(result.trim()); setLog( [ "Ran G-code through LinuxCNC interpreter WASM.", `Program: ${GCODE_WASM_FILE}`, `INI: ${loadedSession.ini.wasmPath}`, result.trim(), ].join("\n"), ); } catch (error) { setField("runStatus", "failed"); setCanonicalEvents(""); setLog(`G-code run failed: ${error.message}`, true); } }); document.getElementById("load-opfs").addEventListener("click", async () => { try { editor.value = await loadTextFile(OPFS_FILE); setLog(`Loaded INI text from OPFS path ${OPFS_FILE}`); } catch (error) { setLog(`OPFS load failed: ${error.message}`, true); } }); document.getElementById("sync-wasm").addEventListener("click", () => { try { syncEditorToWasmFs(); setLog(`Synced editor contents to WASM filesystem at ${WASM_FILE}`); } catch (error) { setLog(`WASM sync failed: ${error.message}`, true); } }); document.getElementById("query").addEventListener("click", async () => { try { syncEditorToWasmFs(); const values = requireIniSdk().getFields(WASM_FILE, { machine: { section: "EMC", tag: "MACHINE" }, display: { section: "DISPLAY", tag: "DISPLAY" }, kinematics: { section: "KINS", tag: "KINEMATICS" }, joints: { section: "KINS", tag: "JOINTS" }, coordinates: { section: "TRAJ", tag: "COORDINATES" }, parameterFile: { section: "RS274NGC", tag: "PARAMETER_FILE" }, toolTable: { section: "EMCIO", tag: "TOOL_TABLE" }, }); for (const [name, value] of Object.entries(values)) { setField(name, value); } setLog("Queried LinuxCNC INI fields through the standalone WASM parser."); } catch (error) { setLog(`Query failed: ${error.message}`, true); } }); document.getElementById("load-machine-files").addEventListener("click", async () => { try { const files = await loadMachineTextFiles(MACHINE_ID); editor.value = files.ini; setLog( [ "Loaded machine text files from OPFS.", `INI bytes: ${files.ini.length}`, `Tool table bytes: ${files.toolTable.length}`, `Parameter bytes: ${files.parameters.length}`, ].join("\n"), ); } catch (error) { setLog(`Machine file load failed: ${error.message}`, true); } }); eventFilterNode.addEventListener("input", () => { renderCanonicalEvents(); }); boot().catch(() => {});