按规划持续工作

结论:新增 vendored LinuxCNC trajectory planner WASM 构建和 Node smoke,覆盖线段、圆弧、队列规划探针,并纳入 host 聚合验证。
This commit is contained in:
2026-06-08 14:16:53 +08:00
parent bbd58e61a1
commit 19687c3268
7 changed files with 671 additions and 12 deletions

View File

@@ -24,6 +24,12 @@ The current WASM interpreter-core smoke validation command is:
wasm-port/tests/wasm/node/verify_interp_wasm.sh
```
The current WASM trajectory-planner smoke validation command is:
```bash
wasm-port/tests/wasm/node/verify_tp_wasm.sh
```
The current OPFS host-boundary validation command is:
```bash
@@ -110,6 +116,13 @@ negative G-code fixtures into the Emscripten filesystem and verifies their
LinuxCNC-produced error text through the `Interp::open()`/`read()`/`execute()`
file path.
The WASM trajectory-planner smoke script builds
`build/wasm/tp/linuxcnc_tp.js` and `linuxcnc_tp.wasm` from vendored LinuxCNC
TP, TC, TC queue, spherical-arc, blendmath, S-curve, Ruckig-wrapper, C Ruckig
support, emcpose, and posemath source files. It loads the module in Node and
calls the exported TP probe C ABI to validate the same LinuxCNC linear, arc,
and queued-line planner calls covered by the native `linuxcnc_tp_api_probe`.
The OPFS host-boundary script validates the JavaScript file-service adapter
with a Node mock of the browser File System Access handles. It covers nested
directory creation, text save/load, missing file behavior, invalid relative
@@ -172,9 +185,9 @@ override INI-derived names before OPFS text is copied into the LinuxCNC-backed
WASM filesystem, and that absent INI file-name values use the default OPFS
parameter/tool-table paths in a real browser session.
The aggregate host smoke script builds the INI and interpreter-core WASM
artifacts once, then runs the Node WASM smokes, the Node OPFS mock smoke, and
the Chromium browser smokes.
The aggregate host smoke script builds the INI, interpreter-core, and
trajectory-planner WASM artifacts once, then runs the Node WASM smokes, the
Node OPFS mock smoke, and the Chromium browser smokes.
## Source Coverage
@@ -224,10 +237,11 @@ The validation fails if:
| --- | --- |
| `tests/wasm/node/verify_ini_wasm.sh` | Validates the browser-facing INI WASM module can be built from vendored LinuxCNC `inifile.cc`, loaded through the JS SDK in Node, and queried through the exported C ABI, including LinuxCNC-backed boolean conversion and machine-session file-name string lookup. |
| `tests/wasm/node/verify_interp_wasm.sh` | Validates the initial interpreter-core WASM module can be built from vendored LinuxCNC interpreter source, loaded through the interpreter JS SDK, run the first fixture group through `Interp::execute()` and selected file fixtures through `Interp::open()`/`read()`/`execute()`, match the native canonical event plus required state readback fixtures, and run parameter-file restore/save through vendored LinuxCNC `Interp::restore_parameters()` and `Interp::save_parameters()`. |
| `tests/wasm/node/verify_tp_wasm.sh` | Validates a standalone trajectory-planner WASM module can be built from vendored LinuxCNC TP/TC/Ruckig support source, loaded in Node, and run the same linear, arc, and queued-line planner probe paths covered by the native TP harness. |
| `tests/opfs/node/verify_file_service.sh` | Validates the host-owned OPFS text-file adapter, path model, session snapshot store including custom filenames and envelope/path rejection paths, machine file store, G-code text store including filename rejection paths, OPFS-to-WASM parameter/tool-table bridges, and grouped machine-session loading without moving file persistence, parameter semantics, or tool-table semantics into the WASM core. |
| `tests/browser/verify_ini_panel_browser.sh` | Validates the INI SDK, INI/interpreter WASM module loading, LinuxCNC-backed INI machine-session file-name string lookup, OPFS text-file round trip, generic session snapshot round trip plus custom filename and envelope/path rejection paths, machine file text round trip, G-code text round trip plus filename rejection paths, and the INI panel UI's machine-session load with default OPFS parameter/tool-table file mapping, G-code run, and canonical-event display paths in a real browser runtime. |
| `tests/browser/verify_interp_browser.sh` | Validates the interpreter-core WASM module loads through the interpreter JS SDK in a real browser runtime and runs selected positive and negative canonical fixtures through vendored LinuxCNC `Interp::execute()` plus `Interp::open()`/`read()`/`execute()` via the exported C ABI, including direct browser SDK and OPFS-backed parameter-file restore/save plus non-random/random tool-table load/save through vendored LinuxCNC source. |
| `tests/host/verify_host_smokes.sh` | Runs the current host-side Node, WASM interpreter-core, OPFS, and browser smoke validation with shared WASM builds. |
| `tests/host/verify_host_smokes.sh` | Runs the current host-side Node, WASM interpreter-core, WASM trajectory-planner, OPFS, and browser smoke validation with shared WASM builds. |
## Fixture Coverage

View File

@@ -36,14 +36,14 @@ Current validation is intentionally mechanical:
| INI parsing | `src/emc/ini/inifile.cc`, `inifile.h`, `inifile.hh` | Copy unchanged | Native file IO remains LinuxCNC-style in the vendored parser; browser OPFS integration remains outside this layer; `runtime/sdk/src/index.js` exports the INI SDK wrapper around the generated WASM C ABI, including LinuxCNC `iniFindBool()` for machine-session flags and LinuxCNC string lookup for INI-declared `[RS274NGC]PARAMETER_FILE` and `[EMCIO]TOOL_TABLE` machine file names | Vendor byte sync, `linuxcnc_ini_probe`, `linuxcnc_inifile_source_probe`, `tests/wasm/node/verify_ini_wasm.sh` boolean plus file-name lookup assertions, `tests/browser/verify_ini_panel_browser.sh`, `tests/host/verify_host_smokes.sh` |
| RTAPI compatibility headers | `src/rtapi/rtapi_*.h` in the manifest | Copy unchanged plus standalone shim include path | `runtime/core/shims/rtapi.h` supplies the minimal standalone RTAPI surface needed by vendored code | Vendor byte sync, compile coverage through dependent source probes |
| Canon/NML-facing interpreter types | `src/emc/nml_intf/canon*.hh`, `emctool.h`, `interp_return.hh`, `motion_types.h`, `emcpose.*`, `emcpos.h`, `debugflags.h`, `src/emc/linuxcnc.h` | Copy unchanged plus narrow standalone status shim | NML transport is not ported; `runtime/core/shims/nml_intf/emc.hh` exposes only the `emcStatus` machine-units status edge currently needed by vendored interpreter conversion and initialization code | Vendor byte sync, dependent source probes, `linuxcnc_emc_status_probe`, `linuxcnc_tp_api_probe`, interpreter harnesses, `tests/wasm/node/verify_interp_wasm.sh` and `tests/browser/verify_interp_browser.sh` `Interp::init()` machine-unit assertions |
| Motion state headers | `src/emc/motion/state_tag.h`, `emcmotcfg.h`, `simple_tp.h`, `motion.h`, `mot_priv.h`, `axis.h` | Copy unchanged | Realtime motion process is not ported; standalone probes seed the small motion status/config state required by TP calls | Vendor byte sync, `linuxcnc_tp_api_probe` |
| Motion state headers | `src/emc/motion/state_tag.h`, `emcmotcfg.h`, `simple_tp.h`, `motion.h`, `mot_priv.h`, `axis.h` | Copy unchanged | Realtime motion process is not ported; standalone probes seed the small motion status/config state required by TP calls | Vendor byte sync, `linuxcnc_tp_api_probe`, `tests/wasm/node/verify_tp_wasm.sh` |
| Identity/trivial kinematics | `src/emc/kinematics/kinematics.h`, `cubic.h`, `kins_util.c`, `trivkins.c` | Copy unchanged | HAL component lifecycle and RTAPI module metadata are replaced by standalone shims; forward/inverse mapping behavior remains LinuxCNC source | Vendor byte sync, per-file source probes, `linuxcnc_kinematics_probe` |
| Switchable 5-axis bridge kinematics | `src/emc/kinematics/5axiskins.c`, `switchkins.c`, `switchkins.h`, `userkfuncs.c`, plus `src/rtapi/rtapi_ctype.h` | Copy unchanged | HAL pin allocation, HAL component lifecycle, and RTAPI module metadata are standalone runtime edges; switchable 5-axis forward/inverse behavior remains LinuxCNC source | Vendor byte sync, per-file source probes, `linuxcnc_5axis_kinematics_probe` |
| TRT table-rotary kinematics | `src/emc/kinematics/trtfuncs.c`, `xyzac-trt-kins.c`, `xyzbc-trt-kins.c` | Copy unchanged | HAL pin allocation and switchkins lifecycle stay runtime boundaries; XYZAC/XYZBC TRT forward/inverse behavior remains LinuxCNC source | Vendor byte sync, per-file source probes, `linuxcnc_xyzac_trt_kinematics_probe`, `linuxcnc_xyzbc_trt_kinematics_probe` |
| Additional non-switchable kinematics | `src/emc/kinematics/corexykins.c`, `rotatekins.c`, `rosekins.c`, `maxkins.c`, `lineardeltakins.c`, `lineardeltakins-common.h`, `rotarydeltakins.c`, `rotarydeltakins-common.h`, `scorbot-kins.c`, `tripodkins.c`, `scarakins.c`, `pumakins.c`, `pumakins.h`, `genhexkins.c`, `genhexkins.h`, `genserfuncs.c`, `genserkins.c`, `genserkins.h`, `ugenserkins.c`, `pentakins.c`, `pentakins.h`, `cubic.c` | Copy unchanged | HAL pin allocation, HAL parameter allocation, HAL component lifecycle, RTAPI module metadata, Go math C/C++ linkage, switchkins iterative-forward warmup, and userspace test-program process entry remain standalone runtime edges; forward/inverse behavior remains LinuxCNC source where the module exposes it | Vendor byte sync, per-file source probes, `linuxcnc_corexy_kinematics_probe`, `linuxcnc_rotate_kinematics_probe`, `linuxcnc_rose_kinematics_probe`, `linuxcnc_max_kinematics_probe`, `linuxcnc_lineardelta_kinematics_probe`, `linuxcnc_rotarydelta_kinematics_probe`, `linuxcnc_scorbot_kinematics_probe`, `linuxcnc_tripod_kinematics_probe`, `linuxcnc_scara_kinematics_probe`, `linuxcnc_puma_kinematics_probe`, `linuxcnc_genser_kinematics_probe`, `linuxcnc_genhex_kinematics_probe`, `linuxcnc_pentakins_kinematics_probe` |
| Trajectory planner | `src/emc/tp/tp.c`, `tc.c`, `tcq.c`, `spherical_arc.c`, `blendmath.c`, `sp_scurve.c`, `ruckig_wrapper.c`, plus matching `*.h` files | Copy unchanged | Native realtime scheduling and motion process state are replaced by standalone probe setup | Vendor byte sync, per-file source probes, `linuxcnc_tp_api_probe` |
| Ruckig C planner support | Selected `src/emc/tp/cruckig/*.c` and `*.h` files in the manifest | Copy unchanged | Used as LinuxCNC planner support code through vendored TP sources | Vendor byte sync, per-file source probes |
| Posemath | `src/libnml/posemath/posemath.cc`, `_posemath.c`, `gomath.c`, `sincos.c`, and matching headers | Copy unchanged | `gomath.c` is compiled as C; `rtapi.h` shim is C/C++ compatible for this boundary | Vendor byte sync, per-file source probes, TP probe |
| Trajectory planner | `src/emc/tp/tp.c`, `tc.c`, `tcq.c`, `spherical_arc.c`, `blendmath.c`, `sp_scurve.c`, `ruckig_wrapper.c`, plus matching `*.h` files | Copy unchanged | Native realtime scheduling and motion process state are replaced by standalone probe setup; the WASM TP probe uses the same deterministic status/config boundary and calls vendored TP APIs through a narrow C ABI | Vendor byte sync, per-file source probes, `linuxcnc_tp_api_probe`, `tests/wasm/node/verify_tp_wasm.sh` |
| Ruckig C planner support | Selected `src/emc/tp/cruckig/*.c` and `*.h` files in the manifest | Copy unchanged | Used as LinuxCNC planner support code through vendored TP sources | Vendor byte sync, per-file source probes, `tests/wasm/node/verify_tp_wasm.sh` |
| Posemath | `src/libnml/posemath/posemath.cc`, `_posemath.c`, `gomath.c`, `sincos.c`, and matching headers | Copy unchanged | `gomath.c` is compiled as C; `rtapi.h` shim is C/C++ compatible for this boundary | Vendor byte sync, per-file source probes, TP native and WASM probes |
| 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; single-axis rotary indexer lock/unlock dispatch remains vendored LinuxCNC `issue_straight_index()` behavior | Vendor byte sync, per-file source probes, canonical fixture suite, no standalone `Interp::convert_g()` guard, `linuxcnc_indexer_harness`, `tests/wasm/node/verify_interp_wasm.sh` and `tests/browser/verify_interp_browser.sh` rotary-indexer assertions |
| 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`, `tests/wasm/node/verify_interp_wasm.sh` and `tests/browser/verify_interp_browser.sh` `Interp::synch()` tool-slot assertions plus `Interp::init_named_parameters()`/`find_named_param()` named-parameter lookup assertions |
@@ -71,14 +71,15 @@ Current validation is intentionally mechanical:
userspace genser flows are not yet established.
- Cutter compensation positive motion and negative interpreter paths are
fixture-covered through vendored `interp_convert.cc` and `interp_queue.cc`.
- Browser/WASM C ABI and JS SDK layers are now present for the INI parser and
the current interpreter-core smoke scope. The interpreter SDK is a thin
- Browser/WASM C ABI and JS SDK layers are now present for the INI parser, the
current interpreter-core smoke scope, and a trajectory-planner TP probe. 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 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.
tool-table semantics. The TP WASM probe calls vendored LinuxCNC planner APIs
directly and does not expose a browser planner SDK yet; 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,

View File

@@ -0,0 +1,442 @@
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <emscripten/emscripten.h>
#include "emc/motion/motion.h"
#include "emc/nml_intf/motion_types.h"
#include "emc/tp/tp.h"
static emcmot_status_t status;
static emcmot_config_t config;
static void dio_write(int index, char value)
{
(void)index;
(void)value;
}
static void aio_write(int index, double value)
{
(void)index;
(void)value;
}
static void set_rotary_unlock(int joint, int unlock)
{
(void)joint;
(void)unlock;
}
static int get_rotary_unlock(int joint)
{
(void)joint;
return 1;
}
static double axis_vel_limit(int axis)
{
return axis >= 0 && axis < EMCMOT_MAX_AXIS ? 10.0 : 0.0;
}
static double axis_acc_limit(int axis)
{
return axis >= 0 && axis < EMCMOT_MAX_AXIS ? 20.0 : 0.0;
}
static void init_motion_state(void)
{
memset(&status, 0, sizeof(status));
memset(&config, 0, sizeof(config));
config.numJoints = 3;
config.numSpindles = 1;
config.numDIO = 4;
config.numAIO = 4;
config.arcBlendOptDepth = 0;
config.arcBlendEnable = 0;
config.arcBlendFallbackEnable = 0;
config.arcBlendGapCycles = 4;
config.arcBlendRampFreq = 20.0;
config.arcBlendTangentKinkRatio = 0.1;
config.maxFeedScale = 1.0;
status.net_feed_scale = 1.0;
status.feed_scale = 1.0;
status.rapid_scale = 1.0;
status.enables_new = FS_ENABLED | SS_ENABLED;
status.enables_queued = status.enables_new;
status.spindle_status[0].at_speed = 1;
status.spindle_status[0].direction = 1;
status.spindleSync = 0;
status.jerk = 0.0;
status.planner_type = 0;
tpMotData(&status, &config);
tpMotFunctions(
dio_write,
aio_write,
set_rotary_unlock,
get_rotary_unlock,
axis_vel_limit,
axis_acc_limit);
}
static int pose_near(const EmcPose *actual, const EmcPose *expected)
{
return fabs(actual->tran.x - expected->tran.x) < 1e-9 &&
fabs(actual->tran.y - expected->tran.y) < 1e-9 &&
fabs(actual->tran.z - expected->tran.z) < 1e-9;
}
static void append(char *out, size_t size, size_t *offset, const char *text)
{
if (*offset >= size) {
return;
}
const int written = snprintf(out + *offset, size - *offset, "%s", text);
if (written > 0) {
*offset += (size_t)written;
}
}
static void append_linear_probe(char *out, size_t size, size_t *offset)
{
init_motion_state();
TP_STRUCT tp;
EmcPose start;
EmcPose end;
struct state_tag_t tag;
memset(&tp, 0, sizeof(tp));
memset(&start, 0, sizeof(start));
memset(&end, 0, sizeof(end));
memset(&tag, 0, sizeof(tag));
end.tran.x = 1.0;
const int create_rc = tpCreate(&tp, TP_DEFAULT_QUEUE_SIZE, 1);
const int set_cycle_rc = tpSetCycleTime(&tp, 0.001);
const int set_pos_rc = tpSetPos(&tp, &start);
const int set_vmax_rc = tpSetVmax(&tp, 1.0, 1.0);
const int set_vlimit_rc = tpSetVlimit(&tp, 1.0);
const int set_amax_rc = tpSetAmax(&tp, 10.0);
const int set_term_rc = tpSetTermCond(&tp, TC_TERM_COND_STOP, 0.0);
const int add_line_rc = tpAddLine(
&tp,
end,
EMC_MOTION_TYPE_FEED,
1.0,
1.0,
10.0,
100.0,
status.enables_new,
0,
-1,
tag);
int cycle_rc = 0;
int cycles = 0;
for (; cycles < 10000 && !tpIsDone(&tp); ++cycles) {
cycle_rc = tpRunCycle(&tp, 1000000);
if (cycle_rc < 0) {
break;
}
}
EmcPose final_pos;
memset(&final_pos, 0, sizeof(final_pos));
const int get_pos_rc = tpGetPos(&tp, &final_pos);
char line[1024];
snprintf(line, sizeof(line),
"tp_create=%d\n"
"tp_set_cycle_time=%d\n"
"tp_set_pos=%d\n"
"tp_set_vmax=%d\n"
"tp_set_vlimit=%d\n"
"tp_set_amax=%d\n"
"tp_set_term_cond=%d\n"
"tp_add_line=%d\n"
"tp_cycle_rc=%d\n"
"tp_cycles=%d\n"
"tp_get_pos=%d\n"
"tp_done_after_line=%d\n"
"tp_queue_depth=%d\n"
"tp_final_pos=%.12g,%.12g,%.12g\n",
create_rc,
set_cycle_rc,
set_pos_rc,
set_vmax_rc,
set_vlimit_rc,
set_amax_rc,
set_term_rc,
add_line_rc,
cycle_rc,
cycles,
get_pos_rc,
tpIsDone(&tp),
tpQueueDepth(&tp),
final_pos.tran.x,
final_pos.tran.y,
final_pos.tran.z);
append(out, size, offset, line);
}
static void append_arc_probe(char *out, size_t size, size_t *offset)
{
init_motion_state();
TP_STRUCT tp;
EmcPose start;
EmcPose end;
PmCartesian center;
PmCartesian normal;
struct state_tag_t tag;
memset(&tp, 0, sizeof(tp));
memset(&start, 0, sizeof(start));
memset(&end, 0, sizeof(end));
memset(&center, 0, sizeof(center));
memset(&normal, 0, sizeof(normal));
memset(&tag, 0, sizeof(tag));
start.tran.x = 1.0;
end.tran.y = 1.0;
normal.z = 1.0;
const int create_rc = tpCreate(&tp, TP_DEFAULT_QUEUE_SIZE, 2);
const int set_cycle_rc = tpSetCycleTime(&tp, 0.001);
const int set_pos_rc = tpSetPos(&tp, &start);
const int set_vmax_rc = tpSetVmax(&tp, 1.0, 1.0);
const int set_vlimit_rc = tpSetVlimit(&tp, 1.0);
const int set_amax_rc = tpSetAmax(&tp, 10.0);
const int set_term_rc = tpSetTermCond(&tp, TC_TERM_COND_STOP, 0.0);
const int add_circle_rc = tpAddCircle(
&tp,
end,
center,
normal,
0,
EMC_MOTION_TYPE_ARC,
1.0,
1.0,
10.0,
100.0,
status.enables_new,
0,
tag);
int cycle_rc = 0;
int cycles = 0;
for (; cycles < 10000 && !tpIsDone(&tp); ++cycles) {
cycle_rc = tpRunCycle(&tp, 1000000);
if (cycle_rc < 0) {
break;
}
}
EmcPose final_pos;
memset(&final_pos, 0, sizeof(final_pos));
const int get_pos_rc = tpGetPos(&tp, &final_pos);
const int near_end = pose_near(&final_pos, &end);
char line[1024];
snprintf(line, sizeof(line),
"tp_arc_create=%d\n"
"tp_arc_set_cycle_time=%d\n"
"tp_arc_set_pos=%d\n"
"tp_arc_set_vmax=%d\n"
"tp_arc_set_vlimit=%d\n"
"tp_arc_set_amax=%d\n"
"tp_arc_set_term_cond=%d\n"
"tp_add_circle=%d\n"
"tp_arc_cycle_rc=%d\n"
"tp_arc_cycles=%d\n"
"tp_arc_get_pos=%d\n"
"tp_done_after_arc=%d\n"
"tp_arc_queue_depth=%d\n"
"tp_arc_final_pos_near_end=%d\n"
"tp_arc_final_pos=%.12g,%.12g,%.12g\n",
create_rc,
set_cycle_rc,
set_pos_rc,
set_vmax_rc,
set_vlimit_rc,
set_amax_rc,
set_term_rc,
add_circle_rc,
cycle_rc,
cycles,
get_pos_rc,
tpIsDone(&tp),
tpQueueDepth(&tp),
near_end,
final_pos.tran.x,
final_pos.tran.y,
final_pos.tran.z);
append(out, size, offset, line);
}
static void append_queued_lines_probe(char *out, size_t size, size_t *offset)
{
init_motion_state();
TP_STRUCT tp;
EmcPose start;
EmcPose end1;
EmcPose end2;
struct state_tag_t tag;
memset(&tp, 0, sizeof(tp));
memset(&start, 0, sizeof(start));
memset(&end1, 0, sizeof(end1));
memset(&end2, 0, sizeof(end2));
memset(&tag, 0, sizeof(tag));
end1.tran.x = 1.0;
end2.tran.x = 1.0;
end2.tran.y = 1.0;
const int create_rc = tpCreate(&tp, TP_DEFAULT_QUEUE_SIZE, 3);
const int set_cycle_rc = tpSetCycleTime(&tp, 0.001);
const int set_pos_rc = tpSetPos(&tp, &start);
const int set_vmax_rc = tpSetVmax(&tp, 1.0, 1.0);
const int set_vlimit_rc = tpSetVlimit(&tp, 1.0);
const int set_amax_rc = tpSetAmax(&tp, 10.0);
const int set_term_rc = tpSetTermCond(&tp, TC_TERM_COND_STOP, 0.0);
const int add_line1_rc = tpAddLine(
&tp,
end1,
EMC_MOTION_TYPE_FEED,
1.0,
1.0,
10.0,
100.0,
status.enables_new,
0,
-1,
tag);
const int depth_after_line1 = tpQueueDepth(&tp);
const int add_line2_rc = tpAddLine(
&tp,
end2,
EMC_MOTION_TYPE_FEED,
1.0,
1.0,
10.0,
100.0,
status.enables_new,
0,
-1,
tag);
const int depth_after_line2 = tpQueueDepth(&tp);
int cycle_rc = 0;
int cycles = 0;
for (; cycles < 20000 && !tpIsDone(&tp); ++cycles) {
cycle_rc = tpRunCycle(&tp, 1000000);
if (cycle_rc < 0) {
break;
}
}
EmcPose final_pos;
memset(&final_pos, 0, sizeof(final_pos));
const int get_pos_rc = tpGetPos(&tp, &final_pos);
const int near_end = pose_near(&final_pos, &end2);
char line[1024];
snprintf(line, sizeof(line),
"tp_queue_create=%d\n"
"tp_queue_set_cycle_time=%d\n"
"tp_queue_set_pos=%d\n"
"tp_queue_set_vmax=%d\n"
"tp_queue_set_vlimit=%d\n"
"tp_queue_set_amax=%d\n"
"tp_queue_set_term_cond=%d\n"
"tp_queue_add_line1=%d\n"
"tp_queue_depth_after_line1=%d\n"
"tp_queue_add_line2=%d\n"
"tp_queue_depth_after_line2=%d\n"
"tp_queue_cycle_rc=%d\n"
"tp_queue_cycles=%d\n"
"tp_queue_get_pos=%d\n"
"tp_done_after_queued_lines=%d\n"
"tp_queue_final_depth=%d\n"
"tp_queue_final_pos_near_end=%d\n"
"tp_queue_final_pos=%.12g,%.12g,%.12g\n",
create_rc,
set_cycle_rc,
set_pos_rc,
set_vmax_rc,
set_vlimit_rc,
set_amax_rc,
set_term_rc,
add_line1_rc,
depth_after_line1,
add_line2_rc,
depth_after_line2,
cycle_rc,
cycles,
get_pos_rc,
tpIsDone(&tp),
tpQueueDepth(&tp),
near_end,
final_pos.tran.x,
final_pos.tran.y,
final_pos.tran.z);
append(out, size, offset, line);
}
EMSCRIPTEN_KEEPALIVE
char *lctp_run_probe(void)
{
char *out = (char *)malloc(8192);
if (!out) {
return NULL;
}
out[0] = '\0';
size_t offset = 0;
TP_STRUCT tp;
TC_STRUCT tc;
EmcPose pose;
memset(&tp, 0, sizeof(tp));
memset(&tc, 0, sizeof(tc));
memset(&pose, 0, sizeof(pose));
pose.tran.x = 1.0;
pose.tran.y = 2.0;
pose.tran.z = 3.0;
char line[512];
snprintf(line, sizeof(line),
"sizeof_TP_STRUCT=%zu\n"
"sizeof_TC_STRUCT=%zu\n"
"tp_default_queue_size=%d\n"
"tp_err_ok=%d\n"
"tc_linear=%d\n"
"tc_circular=%d\n"
"pose_xyz=%.12g,%.12g,%.12g\n",
sizeof(tp),
sizeof(tc),
TP_DEFAULT_QUEUE_SIZE,
TP_ERR_OK,
TC_LINEAR,
TC_CIRCULAR,
pose.tran.x,
pose.tran.y,
pose.tran.z);
append(out, 8192, &offset, line);
append_linear_probe(out, 8192, &offset);
append_arc_probe(out, 8192, &offset);
append_queued_lines_probe(out, 8192, &offset);
return out;
}
EMSCRIPTEN_KEEPALIVE
void lctp_free_string(char *value)
{
free(value);
}

View File

@@ -5,8 +5,10 @@ ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
"$ROOT_DIR/tools/build_ini_panel.sh"
"$ROOT_DIR/tools/build_wasm_core.sh"
"$ROOT_DIR/tools/build_tp_wasm.sh"
SKIP_INI_BUILD=1 "$ROOT_DIR/tests/wasm/node/verify_ini_wasm.sh"
SKIP_INTERP_BUILD=1 "$ROOT_DIR/tests/wasm/node/verify_interp_wasm.sh"
SKIP_TP_BUILD=1 "$ROOT_DIR/tests/wasm/node/verify_tp_wasm.sh"
"$ROOT_DIR/tests/opfs/node/verify_file_service.sh"
SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 "$ROOT_DIR/tests/browser/verify_ini_panel_browser.sh"
SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 "$ROOT_DIR/tests/browser/verify_interp_browser.sh"

View File

@@ -0,0 +1,83 @@
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname, resolve } from "node:path";
import assert from "node:assert/strict";
import createLinuxCncTpModule from "../../../build/wasm/tp/linuxcnc_tp.js";
function verifyExpectedOutput(fixtureName, output, expectedText) {
const expectedLines = expectedText.split("\n").filter(Boolean);
for (const expectedLine of expectedLines) {
assert.equal(
output.includes(expectedLine),
true,
`${fixtureName}: ${expectedLine}`,
);
}
}
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const rootDir = resolve(__dirname, "../../..");
const wasmPath = resolve(rootDir, "build/wasm/tp/linuxcnc_tp.wasm");
const tp = await createLinuxCncTpModule({
wasmBinary: readFileSync(wasmPath),
print() {},
printErr(message) {
console.error(message);
},
});
const resultPtr = tp._lctp_run_probe();
assert.notEqual(resultPtr, 0);
try {
const output = tp.UTF8ToString(resultPtr);
verifyExpectedOutput(
"tp_wasm_probe",
output,
[
"tp_default_queue_size=32",
"tp_err_ok=0",
"tc_linear=1",
"tc_circular=2",
"pose_xyz=1,2,3",
"tp_create=0",
"tp_set_cycle_time=0",
"tp_set_pos=0",
"tp_set_vmax=0",
"tp_set_vlimit=0",
"tp_set_amax=0",
"tp_set_term_cond=0",
"tp_add_line=0",
"tp_cycle_rc=4",
"tp_get_pos=0",
"tp_done_after_line=1",
"tp_queue_depth=0",
"tp_final_pos=1,0,0",
"tp_arc_create=0",
"tp_add_circle=0",
"tp_arc_cycle_rc=4",
"tp_arc_get_pos=0",
"tp_done_after_arc=1",
"tp_arc_queue_depth=0",
"tp_arc_final_pos_near_end=1",
"tp_queue_create=0",
"tp_queue_add_line1=0",
"tp_queue_depth_after_line1=1",
"tp_queue_add_line2=0",
"tp_queue_depth_after_line2=2",
"tp_queue_cycle_rc=4",
"tp_queue_get_pos=0",
"tp_done_after_queued_lines=1",
"tp_queue_final_depth=0",
"tp_queue_final_pos_near_end=1",
"tp_queue_final_pos=1,1,0",
].join("\n"),
);
} finally {
tp._lctp_free_string(resultPtr);
}
console.log("tp_wasm_node_smoke=ok");

View File

@@ -0,0 +1,9 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "$0")/../../.." && pwd)"
if [[ "${SKIP_TP_BUILD:-0}" != "1" ]]; then
"$ROOT_DIR/tools/build_tp_wasm.sh"
fi
node "$ROOT_DIR/tests/wasm/node/verify_tp_wasm.mjs"

108
wasm-port/tools/build_tp_wasm.sh Executable file
View File

@@ -0,0 +1,108 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
VENDOR_DIR="$ROOT_DIR/vendor/linuxcnc"
WRAP_DIR="$ROOT_DIR/runtime/core/linuxcnc_wrap"
SHIM_DIR="$ROOT_DIR/runtime/core/shims"
INCLUDE_DIR="$ROOT_DIR/runtime/core/include"
OUT_DIR="$ROOT_DIR/build/wasm/tp"
OBJ_DIR="$OUT_DIR/obj"
source "$ROOT_DIR/tools/wasm_incremental_build_lib.sh"
mkdir -p "$OUT_DIR" "$OBJ_DIR"
TARGET="linuxcnc_tp_wasm"
: > "$OUT_DIR/$TARGET.stdout.log"
: > "$OUT_DIR/$TARGET.stderr.log"
COMMON_FLAGS=(
-O2
-D_GNU_SOURCE
-DM_PI=3.14159265358979323846
-I"$WRAP_DIR"
-I"$SHIM_DIR"
-I"$INCLUDE_DIR"
-I"$VENDOR_DIR/src"
-I"$VENDOR_DIR/src/rtapi"
-I"$VENDOR_DIR/src/emc"
-I"$VENDOR_DIR/src/emc/nml_intf"
-I"$VENDOR_DIR/src/emc/motion"
-I"$VENDOR_DIR/src/emc/tp"
-I"$VENDOR_DIR/src/emc/kinematics"
-I"$VENDOR_DIR/src/libnml/posemath"
)
C_FLAGS=(
-std=gnu11
"${COMMON_FLAGS[@]}"
)
CXX_FLAGS=(
-std=c++20
-fpermissive
"${COMMON_FLAGS[@]}"
)
SOURCES=(
"$VENDOR_DIR/src/emc/tp/tp.c"
"$VENDOR_DIR/src/emc/tp/tc.c"
"$VENDOR_DIR/src/emc/tp/tcq.c"
"$VENDOR_DIR/src/emc/tp/spherical_arc.c"
"$VENDOR_DIR/src/emc/tp/blendmath.c"
"$VENDOR_DIR/src/emc/tp/sp_scurve.c"
"$VENDOR_DIR/src/emc/tp/ruckig_wrapper.c"
"$VENDOR_DIR/src/emc/tp/cruckig/block.c"
"$VENDOR_DIR/src/emc/tp/cruckig/brake.c"
"$VENDOR_DIR/src/emc/tp/cruckig/calculator.c"
"$VENDOR_DIR/src/emc/tp/cruckig/cruckig.c"
"$VENDOR_DIR/src/emc/tp/cruckig/input_parameter.c"
"$VENDOR_DIR/src/emc/tp/cruckig/output_parameter.c"
"$VENDOR_DIR/src/emc/tp/cruckig/profile.c"
"$VENDOR_DIR/src/emc/tp/cruckig/roots.c"
"$VENDOR_DIR/src/emc/tp/cruckig/trajectory.c"
"$VENDOR_DIR/src/emc/tp/cruckig/position_first_step1.c"
"$VENDOR_DIR/src/emc/tp/cruckig/position_first_step2.c"
"$VENDOR_DIR/src/emc/tp/cruckig/position_second_step1.c"
"$VENDOR_DIR/src/emc/tp/cruckig/position_second_step2.c"
"$VENDOR_DIR/src/emc/tp/cruckig/position_third_step1.c"
"$VENDOR_DIR/src/emc/tp/cruckig/position_third_step2.c"
"$VENDOR_DIR/src/emc/tp/cruckig/velocity_second_step1.c"
"$VENDOR_DIR/src/emc/tp/cruckig/velocity_second_step2.c"
"$VENDOR_DIR/src/emc/tp/cruckig/velocity_third_step1.c"
"$VENDOR_DIR/src/emc/tp/cruckig/velocity_third_step2.c"
"$VENDOR_DIR/src/emc/nml_intf/emcpose.c"
"$VENDOR_DIR/src/libnml/posemath/posemath.cc"
"$VENDOR_DIR/src/libnml/posemath/_posemath.c"
"$VENDOR_DIR/src/libnml/posemath/sincos.c"
"$WRAP_DIR/linuxcnc_tp_wasm.c"
)
OBJECTS=()
for source in "${SOURCES[@]}"; do
obj="$(source_object_path "$source" "$ROOT_DIR" "$OBJ_DIR")"
OBJECTS+=("$obj")
case "$source" in
*.cc|*.cpp|*.cxx)
compile_wasm_object "$TARGET" "$source" "$obj" "$OUT_DIR" "${CXX_FLAGS[@]}"
;;
*)
compile_wasm_object "$TARGET" "$source" "$obj" "$OUT_DIR" "${C_FLAGS[@]}"
;;
esac
done
link_wasm_module \
"$TARGET" \
"$OUT_DIR/linuxcnc_tp.js" \
"$OUT_DIR" \
-O2 \
"${OBJECTS[@]}" \
-s MODULARIZE=1 \
-s EXPORT_ES6=1 \
-s ENVIRONMENT=web,node \
-s ALLOW_MEMORY_GROWTH=1 \
-s NO_EXIT_RUNTIME=1 \
-s EXPORTED_FUNCTIONS='["_malloc","_free","_lctp_run_probe","_lctp_free_string"]' \
-s EXPORTED_RUNTIME_METHODS='["UTF8ToString"]'