结论:浏览器 WASM smoke 已读取 LinuxCNC 生成的 64 个 switchkins 配置用例,覆盖 config/MACHINE/KINEMATICS/HALFILE 入口;native 与 source-link 验证通过。
175 lines
6.1 KiB
JavaScript
175 lines
6.1 KiB
JavaScript
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);
|
|
},
|
|
};
|
|
}
|