const DIALECT = { linuxcnc: 0, fanuc: 1, siemens: 2, }; const EVENT_TYPE = { 1: "error", 2: "comment", 3: "set-units", 4: "set-plane", 5: "set-feed", 6: "set-spindle", 7: "tool-change", 8: "dwell", 9: "rapid", 10: "linear-feed", 11: "arc-feed", 12: "probe", 13: "program-end", 14: "rtcp-pivot", 15: "kinematics-switch", 16: "rtcp-state", 17: "set-g5x-offset", 18: "set-g92-offset", 19: "set-xy-rotation", }; const EVENT_OFFSETS = { type: 4, line: 8, plane: 12, tool: 16, feed: 24, spindle: 32, dwellSeconds: 40, start: 48, end: 120, center: 192, arcTurns: 264, reserved: 268, }; function readPose(view, offset) { return { x: view.getFloat64(offset + 0, true), y: view.getFloat64(offset + 8, true), z: view.getFloat64(offset + 16, true), a: view.getFloat64(offset + 24, true), b: view.getFloat64(offset + 32, true), c: view.getFloat64(offset + 40, true), u: view.getFloat64(offset + 48, true), v: view.getFloat64(offset + 56, true), w: view.getFloat64(offset + 64, true), }; } function readEvent(module, ptr) { const view = new DataView(module.HEAPU8.buffer, ptr, 272); return { type: EVENT_TYPE[view.getInt32(EVENT_OFFSETS.type, true)] ?? "unknown", line: view.getInt32(EVENT_OFFSETS.line, true), plane: view.getInt32(EVENT_OFFSETS.plane, true), tool: view.getInt32(EVENT_OFFSETS.tool, true), feed: view.getFloat64(EVENT_OFFSETS.feed, true), spindle: view.getFloat64(EVENT_OFFSETS.spindle, true), dwellSeconds: view.getFloat64(EVENT_OFFSETS.dwellSeconds, true), start: readPose(view, EVENT_OFFSETS.start), end: readPose(view, EVENT_OFFSETS.end), center: readPose(view, EVENT_OFFSETS.center), arcTurns: view.getInt32(EVENT_OFFSETS.arcTurns, true), reserved: view.getInt32(EVENT_OFFSETS.reserved, true), }; } async function loadModuleFactory() { if (globalThis.createCncSimModule) { return globalThis.createCncSimModule; } const module = await import("/cnc_sim.js"); return module.default ?? module.createCncSimModule ?? globalThis.createCncSimModule; } export async function createWasmSimulator(moduleOptions = {}) { const createModule = await loadModuleFactory(); if (!createModule) { throw new Error("cnc_sim.js did not export createCncSimModule"); } const module = await createModule(moduleOptions); const create = module.cwrap("cnc_sim_create", "number", []); const destroy = module.cwrap("cnc_sim_destroy", null, ["number"]); const reset = module.cwrap("cnc_sim_reset", null, ["number"]); const setDialect = module.cwrap("cnc_sim_set_dialect", "number", ["number", "number"]); const setCallback = module.cwrap("cnc_sim_set_event_callback", "number", ["number", "number", "number"]); const loadConfig = module.cwrap("cnc_sim_load_config_json", "number", ["number", "number", "number"]); const parseProgram = module.cwrap("cnc_sim_parse_program", "number", ["number", "number", "number"]); const lastError = module.cwrap("cnc_sim_last_error", "number", ["number"]); const handle = create(); let callbackPtr = 0; return { parse(program, dialect = "linuxcnc", options = {}) { const events = []; reset(handle); setDialect(handle, DIALECT[dialect] ?? DIALECT.linuxcnc); const config = JSON.stringify({ backend: options.backend ?? "linuxcnc-rs274", ...(options.rtcp ? { rtcp: options.rtcp } : {}), ...(options.blockDelete !== undefined ? { blockDelete: options.blockDelete } : {}), ...(options.block_delete !== undefined ? { block_delete: options.block_delete } : {}), ...(options.switchkins ? { switchkins: options.switchkins } : {}), ...(options.remap ? { remap: options.remap } : {}), ...(options.machine ? { machine: options.machine } : {}), ...(options.config ? { config: options.config } : {}), ...(options.configPath ? { configPath: options.configPath } : {}), ...(options.config_path ? { config_path: options.config_path } : {}), ...(options.halfile ? { halfile: options.halfile } : {}), ...(options.hal_file ? { hal_file: options.hal_file } : {}), ...(options.kinematics ? { kinematics: options.kinematics } : {}), ...(options.trt ? { trt: options.trt } : {}), ...(options.pivotLength !== undefined ? { pivotLength: options.pivotLength } : {}), ...(options.pivot_length !== undefined ? { pivot_length: options.pivot_length } : {}), ...(options.kinematicsType !== undefined ? { kinematicsType: options.kinematicsType } : {}), ...(options.kinematics_type !== undefined ? { kinematics_type: options.kinematics_type } : {}), ...(options.fiveaxisKinematicsType !== undefined ? { fiveaxisKinematicsType: options.fiveaxisKinematicsType } : {}), ...(options.fiveaxis_kinematics_type !== undefined ? { fiveaxis_kinematics_type: options.fiveaxis_kinematics_type } : {}), ...(options.xyzbcTrt ? { xyzbcTrt: options.xyzbcTrt } : {}), }); const configBytes = module.lengthBytesUTF8(config) + 1; const configPtr = module._malloc(configBytes); module.stringToUTF8(config, configPtr, configBytes); const configRc = loadConfig(handle, configPtr, configBytes - 1); module._free(configPtr); if (configRc !== 0) { throw new Error(module.UTF8ToString(lastError(handle))); } if (callbackPtr) { module.removeFunction(callbackPtr); } callbackPtr = module.addFunction((eventPtr) => { events.push(readEvent(module, eventPtr)); return 0; }, "iii"); setCallback(handle, callbackPtr, 0); const bytes = module.lengthBytesUTF8(program) + 1; const ptr = module._malloc(bytes); module.stringToUTF8(program, ptr, bytes); const rc = parseProgram(handle, ptr, bytes - 1); module._free(ptr); if (rc !== 0) { throw new Error(module.UTF8ToString(lastError(handle))); } return events; }, dispose() { if (callbackPtr) { module.removeFunction(callbackPtr); callbackPtr = 0; } destroy(handle); }, }; }