按规划继续工作
结论:补齐 LinuxCNC tooldata_common.cc 随机换刀器 load/save 验证边界,WASM/SDK/OPFS 只透传 random-toolchanger 配置;native、WASM、OPFS 和浏览器 smoke 验证已通过。
This commit is contained in:
@@ -243,17 +243,21 @@ restore/save behavior through the exported C ABI, including out-of-order file
|
||||
rejection, missing-file success, required numeric parameter writeback, removal
|
||||
of named-parameter-only lines from saved output, and the `.bak` backup produced
|
||||
by vendored `save_parameters()`. It also covers LinuxCNC tool-table
|
||||
load/save behavior through vendored `tooldata_common.cc`, with the SDK only
|
||||
copying text into the Emscripten filesystem and calling the exported C ABI.
|
||||
load/save behavior through vendored `tooldata_common.cc`, including the
|
||||
non-random and random-toolchanger `tooldata_init()` branches, with the SDK
|
||||
only copying text into the Emscripten filesystem and calling the exported C
|
||||
ABI.
|
||||
OPFS validation covers the JavaScript host-boundary adapter, the INI browser
|
||||
smoke harness, the INI panel UI's machine-session load and G-code run buttons,
|
||||
the raw canonical-event display fed directly by LinuxCNC interpreter WASM
|
||||
output, the Node parameter/tool-table bridges that copy OPFS text through the
|
||||
SDK into vendored LinuxCNC file APIs, the Node machine-session bridge that
|
||||
groups INI, parameter, and tool-table loading, and a browser interpreter smoke
|
||||
that uses the same session bridge before saving OPFS-backed parameter and
|
||||
tool-table text through vendored LinuxCNC file APIs. Full browser coverage,
|
||||
full SDK coverage, and richer machine-state validation remain future work.
|
||||
groups INI, parameter, and tool-table loading, the random-toolchanger flag as
|
||||
a host boundary passed through to the LinuxCNC-backed tool-table ABI, and a
|
||||
browser interpreter smoke that uses the same session bridge before saving
|
||||
OPFS-backed parameter and tool-table text through vendored LinuxCNC file APIs.
|
||||
Full browser coverage, full SDK coverage, and richer machine-state validation
|
||||
remain future work.
|
||||
|
||||
## WASM/Browser Fixture Matrix
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ semantic rewrites:
|
||||
| Canonical output | Canonical calls are captured as test events instead of driving hardware. |
|
||||
| Feed-rate state | `SET_FEED_RATE` updates standalone canonical runtime state so vendored `convert_length_units()` can read it back through `GET_EXTERNAL_FEED_RATE()` during G20/G21 changes. |
|
||||
| Tool-data reload | LinuxCNC `RELOAD_TOOLDATA` is captured as a canonical test event; browser/native tool-table reload plumbing remains a future host/runtime adapter. |
|
||||
| Tool table storage | Vendored LinuxCNC `tooldata_common.cc` owns tool-table parsing and formatting; the standalone boundary only supplies the in-memory `tooldata_get()`/`tooldata_put()` callbacks and exposes load/save through the WASM C ABI. |
|
||||
| Tool table storage | Vendored LinuxCNC `tooldata_common.cc` owns tool-table parsing and formatting; the standalone boundary only supplies the in-memory `tooldata_get()`/`tooldata_put()` callbacks, exposes load/save through the WASM C ABI, and forwards the host random-toolchanger flag to LinuxCNC `tooldata_init()`. |
|
||||
| Tool slot status | Standalone tool adapter state supplies `GET_EXTERNAL_TOOL_SLOT()` and `GET_EXTERNAL_SELECTED_TOOL_SLOT()` for vendored `Interp::synch()`; `linuxcnc_interp_init_harness` guards current/selected slot synchronization. |
|
||||
| Interpreter state tags | LinuxCNC `UPDATE_TAG` callbacks are captured from the state tags packed by vendored interpreter code; standalone code does not derive modal state. |
|
||||
| Rotary indexer lock state | LinuxCNC `UNLOCK_ROTARY` and `LOCK_ROTARY` callbacks are captured from the vendored single-axis indexer path; standalone code does not implement rotary-indexing semantics. |
|
||||
|
||||
@@ -47,7 +47,7 @@ Current validation is intentionally mechanical:
|
||||
| RS274 interpreter state and parser | `src/emc/rs274ngc/modal_state.*`, `interp_internal.*`, `interp_read.cc`, `interp_check.cc`, `interp_execute.cc`, `interp_find.cc`, `interp_array.cc`, `interp_queue.*`, `rs274ngc*`, `units.h` | Copy unchanged | Python/remap/runtime edges are isolated in standalone wrappers and shims; parser and execution logic remain LinuxCNC source | Vendor byte sync, per-file source probes, interpreter harness fixtures |
|
||||
| RS274 conversion semantics | `src/emc/rs274ngc/interp_convert.cc`, `interp_arc.cc`, `interp_inverse.cc`, `interp_cycles.cc`, `interp_g7x.cc`, `interp_o_word.cc`, `interp_write.cc` | Copy unchanged | Canonical calls are captured by standalone event sink functions; conversion behavior stays in vendored LinuxCNC files; feed-rate state is read back through the canonical runtime boundary during length-unit conversion | Vendor byte sync, per-file source probes, canonical fixture suite, no standalone `Interp::convert_g()` guard |
|
||||
| Named parameters and tool slot status | `src/emc/rs274ngc/interp_namedparams.cc`, related interpreter headers | Copy unchanged | `_ini[...]` and `_hal[...]` resolve through standalone INI/HAL adapters while lookup order stays LinuxCNC-derived; current/selected tool slot reads for vendored `Interp::synch()` come from the standalone tool adapter | Vendor byte sync, source probe, `linuxcnc_namedparam_harness`, `linuxcnc_interp_init_harness` |
|
||||
| Tool table parsing and formatting | `src/emc/tooldata/tooldata_common.cc` | Copy unchanged plus standalone storage callbacks | Native file parsing/formatting stays in vendored LinuxCNC source; the standalone tool adapter supplies `tooldata_get()`/`tooldata_put()` storage and index lookup, while OPFS remains a host-side persistence boundary | Vendor byte sync, `linuxcnc_tooldata_common_source_probe`, `tests/wasm/node/verify_interp_wasm.sh` load/save tool-table assertions |
|
||||
| Tool table parsing and formatting | `src/emc/tooldata/tooldata_common.cc` | Copy unchanged plus standalone storage callbacks | Native file parsing/formatting stays in vendored LinuxCNC source; the standalone tool adapter supplies `tooldata_get()`/`tooldata_put()` storage and index lookup, the WASM boundary only selects the LinuxCNC non-random or random-toolchanger branch via `tooldata_init()`, and OPFS remains a host-side persistence boundary | Vendor byte sync, `linuxcnc_tooldata_common_source_probe`, `tests/wasm/node/verify_interp_wasm.sh` non-random/random load/save tool-table assertions |
|
||||
| Dynamic interpreter base | `src/emc/rs274ngc/interp_base.*` | Copy unchanged | `interp_base.cc` source probe uses standalone `EMC2_HOME` compile-time path boundary for LinuxCNC dynamic interpreter lookup | Vendor byte sync, `linuxcnc_interp_base_source_probe` |
|
||||
|
||||
## Dependency Matrix
|
||||
@@ -75,9 +75,10 @@ Current validation is intentionally mechanical:
|
||||
the current interpreter-core smoke scope. The interpreter SDK is a thin
|
||||
allocation, filesystem, and C ABI wrapper over vendored LinuxCNC execution
|
||||
paths, including parameter-file restore/save through vendored
|
||||
`rs274ngc_pre.cc` and tool-table load/save through vendored
|
||||
`tooldata_common.cc`; it does not define G-code, parameter, or tool-table
|
||||
semantics. Full planner/session SDK coverage remains future work.
|
||||
`rs274ngc_pre.cc` and non-random/random tool-table load/save through
|
||||
vendored `tooldata_common.cc`; it does not define G-code, parameter, or
|
||||
tool-table semantics. Full planner/session SDK coverage remains future
|
||||
work.
|
||||
- OPFS persistence is connected to the INI panel through the host-side
|
||||
`runtime/opfs/file-service.js` adapter. `runtime/opfs/path-model.js` now
|
||||
defines paths for INI, tool table, parameter file, G-code program,
|
||||
|
||||
@@ -172,6 +172,13 @@ void append_tool_table_state(std::ostringstream &output)
|
||||
if (standalone::get_tool_entry(&tool, 2) == 0) {
|
||||
append_tool_entry(output, "tool_2", tool);
|
||||
}
|
||||
for (int idx = 0; idx < CANON_POCKETS_MAX; ++idx) {
|
||||
if (standalone::get_tool_entry(&tool, idx) == 0 && tool.toolno != -1) {
|
||||
std::ostringstream prefix;
|
||||
prefix << "tool_pocket_" << idx;
|
||||
append_tool_entry(output, prefix.str().c_str(), tool);
|
||||
}
|
||||
}
|
||||
output << "tool_index_for_tool_2=" << standalone::find_tool_index_for_tool(2) << "\n";
|
||||
}
|
||||
|
||||
@@ -196,6 +203,20 @@ void apply_parameter_assignments(Interp &interp, const char *assignments)
|
||||
}
|
||||
}
|
||||
|
||||
char *load_tool_table_with_mode(const char *path, bool random_tool_changer)
|
||||
{
|
||||
standalone::reset_tool_adapter();
|
||||
tooldata_init(random_tool_changer);
|
||||
tooldata_set_db(DB_NOTUSED);
|
||||
|
||||
std::ostringstream output;
|
||||
output << "tooldata_random_toolchanger=" << (random_tool_changer ? 1 : 0) << "\n";
|
||||
const int rc = tooldata_load(path);
|
||||
output << "tooldata_load=" << rc << "\n";
|
||||
append_tool_table_state(output);
|
||||
return copy_result(output.str());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C" {
|
||||
@@ -350,15 +371,13 @@ char *lcinterp_save_parameters(const char *path, const char *assignments)
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
char *lcinterp_load_tool_table(const char *path)
|
||||
{
|
||||
standalone::reset_tool_adapter();
|
||||
tooldata_init(false);
|
||||
tooldata_set_db(DB_NOTUSED);
|
||||
return load_tool_table_with_mode(path, false);
|
||||
}
|
||||
|
||||
std::ostringstream output;
|
||||
const int rc = tooldata_load(path);
|
||||
output << "tooldata_load=" << rc << "\n";
|
||||
append_tool_table_state(output);
|
||||
return copy_result(output.str());
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
char *lcinterp_load_tool_table_random(const char *path)
|
||||
{
|
||||
return load_tool_table_with_mode(path, true);
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
|
||||
@@ -45,6 +45,7 @@ export async function loadMachineSessionFromOpfs(interp, machineId, options = {}
|
||||
storage: options.storage,
|
||||
opfsPath: paths.toolTableOpfsPath,
|
||||
wasmPath: paths.toolTableWasmPath,
|
||||
randomToolChanger: options.randomToolChanger === true,
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
@@ -25,7 +25,9 @@ export async function loadMachineToolTableFromOpfs(interp, machineId, options =
|
||||
return {
|
||||
opfsPath,
|
||||
wasmPath,
|
||||
result: interp.loadToolTable(wasmPath),
|
||||
result: interp.loadToolTable(wasmPath, {
|
||||
randomToolChanger: options.randomToolChanger === true,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,11 @@ vendored LinuxCNC RS274NGC sources and exposes:
|
||||
- `runFileWithIni(path, iniPath)`
|
||||
- `restoreParameters(path)`
|
||||
- `saveParameters(path, values)`
|
||||
- `loadToolTable(path)`
|
||||
- `loadToolTable(path, options)`
|
||||
- `saveToolTable(path)`
|
||||
- `writeTextFile(path, text)`
|
||||
- `readTextFile(path)`
|
||||
|
||||
`loadToolTable()` accepts `{ randomToolChanger: true }` to select the
|
||||
LinuxCNC random-toolchanger branch before calling vendored
|
||||
`tooldata_load()`. The SDK only forwards that runtime boundary flag.
|
||||
|
||||
@@ -85,8 +85,11 @@ export async function createLinuxCncInterpSdk(moduleOptions = {}) {
|
||||
return callStringResult(mod, "lcinterp_save_parameters", path, assignments);
|
||||
},
|
||||
|
||||
loadToolTable(path) {
|
||||
return callStringResult(mod, "lcinterp_load_tool_table", path);
|
||||
loadToolTable(path, options = {}) {
|
||||
const functionName = options.randomToolChanger
|
||||
? "lcinterp_load_tool_table_random"
|
||||
: "lcinterp_load_tool_table";
|
||||
return callStringResult(mod, functionName, path);
|
||||
},
|
||||
|
||||
saveToolTable(path) {
|
||||
|
||||
@@ -235,6 +235,72 @@
|
||||
].join("\n"),
|
||||
);
|
||||
|
||||
await saveMachineTextFiles("browser-random-toolchanger", {
|
||||
ini: "[EMC]\nMACHINE = browser-random-toolchanger\n",
|
||||
toolTable: [
|
||||
"T2 P7 Z3.125 D1.5 I12 J34 Q4 ;browser random finish tool",
|
||||
"T5 P9 X1 Y2 Z3 ;browser random rough tool",
|
||||
"",
|
||||
].join("\n"),
|
||||
parameters: [
|
||||
"5161 10.5",
|
||||
"5162 20.25",
|
||||
"5220 1",
|
||||
"5221 2.25",
|
||||
"5399 44",
|
||||
"",
|
||||
].join("\n"),
|
||||
});
|
||||
const loadedRandomSession = await loadMachineSessionFromOpfs(
|
||||
interp,
|
||||
"browser-random-toolchanger",
|
||||
{
|
||||
iniWasmPath: "/work/browser-random-machine.ini",
|
||||
parameterWasmPath: "/work/browser-random-linuxcnc.var",
|
||||
toolTableWasmPath: "/work/browser-random-tool.tbl",
|
||||
randomToolChanger: true,
|
||||
},
|
||||
);
|
||||
verifyExpectedOutput(
|
||||
"opfs_load_random_tool_table",
|
||||
loadedRandomSession.toolTable.result,
|
||||
[
|
||||
"tooldata_random_toolchanger=1",
|
||||
"tooldata_load=0",
|
||||
"tool_pocket_7.toolno=2",
|
||||
"tool_pocket_7.pocketno=7",
|
||||
"tool_pocket_7.z=3.125",
|
||||
"tool_pocket_7.diameter=1.5",
|
||||
"tool_pocket_7.frontangle=12",
|
||||
"tool_pocket_7.backangle=34",
|
||||
"tool_pocket_7.orientation=4",
|
||||
"tool_pocket_7.comment=browser random finish tool",
|
||||
"tool_pocket_9.toolno=5",
|
||||
"tool_pocket_9.pocketno=9",
|
||||
"tool_index_for_tool_2=7",
|
||||
].join("\n"),
|
||||
);
|
||||
await saveMachineToolTableToOpfs(
|
||||
interp,
|
||||
"browser-random-toolchanger",
|
||||
{ wasmPath: "/work/browser-random-tool.tbl" },
|
||||
);
|
||||
const savedRandomMachineFiles = await loadMachineTextFiles(
|
||||
"browser-random-toolchanger",
|
||||
);
|
||||
verifyExpectedOutput(
|
||||
"opfs_saved_random_tool_table_text",
|
||||
savedRandomMachineFiles.toolTable,
|
||||
[
|
||||
"T2",
|
||||
"P7",
|
||||
";browser random finish tool",
|
||||
"T5",
|
||||
"P9",
|
||||
";browser random rough tool",
|
||||
].join("\n"),
|
||||
);
|
||||
|
||||
status.textContent = "browser_interp_smoke=ok";
|
||||
} catch (error) {
|
||||
status.textContent = `browser_interp_smoke=fail ${error.stack || error.message}`;
|
||||
|
||||
@@ -183,8 +183,13 @@ const bridgeInterp = {
|
||||
bridgeFiles.set(`${path}.bak`, "5161 0.0\n5162 0.0\n");
|
||||
return `save_parameters=0\nsave_path=${path}\nvalues=${Object.keys(values).join(",")}\n`;
|
||||
},
|
||||
loadToolTable(path) {
|
||||
return `tooldata_load=0\nload_tool_path=${path}\n`;
|
||||
loadToolTable(path, options = {}) {
|
||||
return [
|
||||
"tooldata_load=0",
|
||||
`load_tool_path=${path}`,
|
||||
`random_toolchanger=${options.randomToolChanger === true ? 1 : 0}`,
|
||||
"",
|
||||
].join("\n");
|
||||
},
|
||||
saveToolTable(path) {
|
||||
bridgeFiles.set(path, "T2 P2 Z+1.250000 D+0.250000 ;loaded by LinuxCNC\n");
|
||||
@@ -230,10 +235,22 @@ const loadedToolTable = await loadMachineToolTableFromOpfs(
|
||||
assert.deepEqual(loadedToolTable, {
|
||||
opfsPath: "linuxcnc/machines/xyzab-tdr/tool.tbl",
|
||||
wasmPath: "/work/bridge-tool.tbl",
|
||||
result: "tooldata_load=0\nload_tool_path=/work/bridge-tool.tbl\n",
|
||||
result: "tooldata_load=0\nload_tool_path=/work/bridge-tool.tbl\nrandom_toolchanger=0\n",
|
||||
});
|
||||
assert.equal(bridgeFiles.get("/work/bridge-tool.tbl"), "T0 P0 ; no tool\nT2 P2 Z1.25 D0.25\n");
|
||||
|
||||
const loadedRandomToolTable = await loadMachineToolTableFromOpfs(
|
||||
bridgeInterp,
|
||||
"xyzab-tdr",
|
||||
{ storage, wasmPath: "/work/random-bridge-tool.tbl", randomToolChanger: true },
|
||||
);
|
||||
assert.deepEqual(loadedRandomToolTable, {
|
||||
opfsPath: "linuxcnc/machines/xyzab-tdr/tool.tbl",
|
||||
wasmPath: "/work/random-bridge-tool.tbl",
|
||||
result:
|
||||
"tooldata_load=0\nload_tool_path=/work/random-bridge-tool.tbl\nrandom_toolchanger=1\n",
|
||||
});
|
||||
|
||||
const savedToolTable = await saveMachineToolTableToOpfs(
|
||||
bridgeInterp,
|
||||
"xyzab-tdr",
|
||||
@@ -271,7 +288,7 @@ assert.deepEqual(loadedSession, {
|
||||
toolTable: {
|
||||
opfsPath: "linuxcnc/machines/xyzab-tdr/tool.tbl",
|
||||
wasmPath: "/work/session-tool.tbl",
|
||||
result: "tooldata_load=0\nload_tool_path=/work/session-tool.tbl\n",
|
||||
result: "tooldata_load=0\nload_tool_path=/work/session-tool.tbl\nrandom_toolchanger=0\n",
|
||||
},
|
||||
});
|
||||
assert.equal(bridgeFiles.get("/work/session.ini"), "[EMC]\nMACHINE = xyzab-tdr\n");
|
||||
@@ -281,6 +298,22 @@ assert.equal(
|
||||
"T2 P2 Z+1.250000 D+0.250000 ;loaded by LinuxCNC\n",
|
||||
);
|
||||
|
||||
const loadedRandomSession = await loadMachineSessionFromOpfs(
|
||||
bridgeInterp,
|
||||
"xyzab-tdr",
|
||||
{
|
||||
storage,
|
||||
iniWasmPath: "/work/random-session.ini",
|
||||
parameterWasmPath: "/work/random-session.var",
|
||||
toolTableWasmPath: "/work/random-session-tool.tbl",
|
||||
randomToolChanger: true,
|
||||
},
|
||||
);
|
||||
assert.equal(
|
||||
loadedRandomSession.toolTable.result,
|
||||
"tooldata_load=0\nload_tool_path=/work/random-session-tool.tbl\nrandom_toolchanger=1\n",
|
||||
);
|
||||
|
||||
await assert.rejects(
|
||||
() => loadTextFile("linuxcnc/machines/missing.ini", storage),
|
||||
/missing file/,
|
||||
|
||||
@@ -256,4 +256,54 @@ assert.equal(savedToolTable.includes(";finish tool"), true);
|
||||
assert.equal(savedToolTable.includes("T5"), true);
|
||||
assert.equal(savedToolTable.includes("P9"), true);
|
||||
assert.equal(savedToolTable.includes(";rough tool"), true);
|
||||
|
||||
const randomToolTablePath = "/work/random-tool.tbl";
|
||||
interp.writeTextFile(
|
||||
randomToolTablePath,
|
||||
[
|
||||
"T2 P7 Z3.125 D1.5 I12 J34 Q4 ;random finish tool",
|
||||
"T5 P9 X1 Y2 Z3 ;random rough tool",
|
||||
"",
|
||||
].join("\n"),
|
||||
);
|
||||
verifyExpectedOutput(
|
||||
"load_tool_table_random",
|
||||
interp.loadToolTable(randomToolTablePath, { randomToolChanger: true }),
|
||||
[
|
||||
"tooldata_random_toolchanger=1",
|
||||
"tooldata_load=0",
|
||||
"tooldata_last_index=9",
|
||||
"tool_1.toolno=-1",
|
||||
"tool_2.toolno=-1",
|
||||
"tool_pocket_7.toolno=2",
|
||||
"tool_pocket_7.pocketno=7",
|
||||
"tool_pocket_7.z=3.125",
|
||||
"tool_pocket_7.diameter=1.5",
|
||||
"tool_pocket_7.frontangle=12",
|
||||
"tool_pocket_7.backangle=34",
|
||||
"tool_pocket_7.orientation=4",
|
||||
"tool_pocket_7.comment=random finish tool",
|
||||
"tool_pocket_9.toolno=5",
|
||||
"tool_pocket_9.pocketno=9",
|
||||
"tool_pocket_9.z=3",
|
||||
"tool_pocket_9.comment=random rough tool",
|
||||
"tool_index_for_tool_2=7",
|
||||
].join("\n"),
|
||||
);
|
||||
verifyExpectedOutput(
|
||||
"save_tool_table_random",
|
||||
interp.saveToolTable(randomToolTablePath),
|
||||
[
|
||||
"tooldata_save=0",
|
||||
"tool_pocket_7.toolno=2",
|
||||
"tool_pocket_9.toolno=5",
|
||||
].join("\n"),
|
||||
);
|
||||
const savedRandomToolTable = interp.readTextFile(randomToolTablePath);
|
||||
assert.equal(savedRandomToolTable.includes("T2"), true);
|
||||
assert.equal(savedRandomToolTable.includes("P7"), true);
|
||||
assert.equal(savedRandomToolTable.includes(";random finish tool"), true);
|
||||
assert.equal(savedRandomToolTable.includes("T5"), true);
|
||||
assert.equal(savedRandomToolTable.includes("P9"), true);
|
||||
assert.equal(savedRandomToolTable.includes(";random rough tool"), true);
|
||||
console.log("interp_wasm_node_smoke=ok");
|
||||
|
||||
@@ -96,5 +96,5 @@ link_wasm_module \
|
||||
-s STACK_SIZE=2MB \
|
||||
-s NO_EXIT_RUNTIME=1 \
|
||||
-s FORCE_FILESYSTEM=1 \
|
||||
-s EXPORTED_FUNCTIONS='["_malloc","_free","_lcinterp_run_program","_lcinterp_run_program_with_ini","_lcinterp_run_file","_lcinterp_run_file_with_ini","_lcinterp_restore_parameters","_lcinterp_save_parameters","_lcinterp_load_tool_table","_lcinterp_save_tool_table","_lcinterp_free_string"]' \
|
||||
-s EXPORTED_FUNCTIONS='["_malloc","_free","_lcinterp_run_program","_lcinterp_run_program_with_ini","_lcinterp_run_file","_lcinterp_run_file_with_ini","_lcinterp_restore_parameters","_lcinterp_save_parameters","_lcinterp_load_tool_table","_lcinterp_load_tool_table_random","_lcinterp_save_tool_table","_lcinterp_free_string"]' \
|
||||
-s EXPORTED_RUNTIME_METHODS='["FS","UTF8ToString","stringToUTF8","lengthBytesUTF8"]'
|
||||
|
||||
Reference in New Issue
Block a user