继续完成 web-rtcp-5axis-sim-plan
结论:完成 LinuxCNC kinematics WASM ABI 覆盖,并将 web-rtcp-5axis-sim-plan 的 RTCP frame/boundary adapter 接到 xyzac-trt kinematics SDK;Node、build、browser smoke 验证通过。
This commit is contained in:
@@ -210,6 +210,7 @@ wasm-port/build/wasm/sim-configs-inventory/runtime-boundary-native-alignment-sum
|
||||
wasm-port/build/wasm/sim-configs-inventory/native-runtime-probe-execution-plan.tsv
|
||||
wasm-port/build/wasm/sim-configs-inventory/promotion-candidates.tsv
|
||||
wasm-port/build/wasm/sim-configs-inventory/remaining-skip-main-program-promotion-audit.tsv
|
||||
wasm-port/build/wasm/sim-configs-inventory/remaining-skip-simulation-implementation-coverage.tsv
|
||||
wasm-port/build/wasm/sim-configs-inventory/blocked-runtime-promotion-lock.tsv
|
||||
wasm-port/build/wasm/sim-configs-inventory/next-boundary-worklist.tsv
|
||||
wasm-port/build/wasm/sim-configs-inventory/next-boundary-recommendations.tsv
|
||||
@@ -250,6 +251,12 @@ virtual HAL state-transition proof for `M429 -> M129` and `M428 -> M128`, but
|
||||
still lacks the LinuxCNC-owned native runtime pass and promotion-gate proof
|
||||
required to leave `L4-USER-M-PROCESS`. The upstream demo row remains
|
||||
non-promotable upstream demo evidence.
|
||||
`remaining-skip-simulation-implementation-coverage.tsv` is the full 77-row
|
||||
source-derived implementation ledger for the skipped set. It records each
|
||||
skipped row's LinuxCNC source path, implementation mode, implementation
|
||||
status, main-program eligibility, and remaining promotion block so the entire
|
||||
skip set is treated as implemented simulation coverage without falsely
|
||||
claiming standalone main-program PASS coverage.
|
||||
The project release readiness artifact and URL workflow publish the same
|
||||
candidate artifact summary for callers: `evidence-ready=8`,
|
||||
`inventory-ready=2`, total candidates `10`, and `promotion_allowed=0`.
|
||||
@@ -414,10 +421,10 @@ coverage: every TSV emitted under
|
||||
`full-process-boundary-design.md`. New gate artifacts must therefore be
|
||||
documented before the inventory can pass.
|
||||
The Node coverage gate also requires the generated WASM inventory artifact list
|
||||
to remain the exact duplicate-free 60-entry baseline and the native generated
|
||||
to remain the exact duplicate-free 61-entry baseline and the native generated
|
||||
TSV token list to remain the exact duplicate-free 8-token baseline. It also
|
||||
requires the corresponding `boundary-phase-completion-summary.tsv`
|
||||
documentation-coverage counts to remain `60` and `8`, respectively. It also
|
||||
documentation-coverage counts to remain `61` and `8`, respectively. It also
|
||||
checks that the browser smoke source lists the same generated WASM and native
|
||||
artifact tokens and preserves the same fixed-count, duplicate-free,
|
||||
fetchability/header, documentation-missing, and completion count-parity guards
|
||||
@@ -1051,6 +1058,7 @@ The validation fails if:
|
||||
| Harness | Purpose |
|
||||
| --- | --- |
|
||||
| `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_kinematics_wasm.sh` | Validates standalone kinematics WASM modules built from vendored LinuxCNC `src/emc/kinematics` plus required posemath/gomath sources for `trivkins`, `5axiskins`, `xyzac-trt`, `xyzbc-trt`, `corexy`, `rotate`, `rose`, `max`, `lineardelta`, `rotarydelta`, `scorbot`, `tripod`, `scara`, `puma`, `genser`, `genhex`, and `pentakins`. It loads each module through `createLinuxCncKinematicsSdk()`, checks the exported `lckins_*` ABI, validates forward/inverse or inverse/forward round trips using each module's LinuxCNC-native probe pattern, covers switchkins identity/alternate paths where valid, and verifies seed joint/pose buffer handling for iterative kinematics without adding JavaScript-owned kinematics math. |
|
||||
| `tests/wasm/node/verify_interp_wasm.sh` | Validates the interpreter-core WASM module can be built from vendored LinuxCNC interpreter/remap source, loaded through the interpreter JS SDK, run the first fixture group through `Interp::execute()` and selected file fixtures plus vendored upstream `tests/interp` regression files through `Interp::open()`/`read()`/`execute()`, match the native canonical event plus required state readback fixtures, emit `run_step` execution-status records with LinuxCNC line number, encoded source statement, return code, and interpreter axis positions for file execution, cover vendored `tests/interp/flowsnake` recursive O-word file execution, `tests/interp/g6164` path-control and naive-cam tolerance execution, `tests/interp/oword-unwind` continue-on-error stack unwind behavior through planner-staged INI context, selected `tests/interp/bad` file-error paths, and `tests/interp/g33.1` rigid-tap file execution, run vendored `xyzac-trt`/`xyzbc-trt` table-rotary-tilting and `xyzab-tdr` table-dual-rotary switchkins remap demo files through the WASM C ABI/SDK path, and run parameter-file restore/save through vendored LinuxCNC `Interp::restore_parameters()` and `Interp::save_parameters()`. |
|
||||
| `tests/wasm/node/verify_sim_configs_wasm.sh` | Validates representative vendored LinuxCNC `configs/sim` programs in Node WASM through `planSimConfigStaging()` plus `runSimConfigProgram()`. The generic `planIniFileContextStaging()` planner and its sim-config wrapper use INI text and `tools/source-manifest.txt` to collect the program, INI, tool table, parameter file when vendored, `SUBROUTINE_PATH` files, `USER_M_PATH` files, and remap-NGC files before forwarding to `runFileWithIni()` or `runFiveAxisRemapFile()`. This covers INI-driven `U/V/W` axis mask handling, real `USER_M_PATH` registration for executable `M110`/`M111`, all four current external-offset M111 expected-failure programs, deterministic `woodpecker/on_abort.ngc` file execution, plain INI/tool-table execution through `axis/gladevcp/probe.ngc`, `SUBROUTINE_PATH` staging for `opa_demo.ngc -> circles.ngc`, bridge-mill remap-subroutine staging, vendored LinuxCNC bridge-mill and `melfa-sim` NGC remap execution, and PUMA machine-context execution without spawning host processes. The same smoke includes synthetic staging-plan assertions for generic `TOOL_TABLE`, `PARAMETER_FILE`, multi-directory `SUBROUTINE_PATH`, `USER_M_PATH`, and `REMAP ... ngc=...` file collection. |
|
||||
| `tests/wasm/node/verify_sim_configs_inventory_wasm.sh` | Validates the first machine-readable Node inventory layer for `configs/sim`. It ensures native `build/native/sim-configs/summary.tsv`, generated `class-summary.tsv`, and generated `path-matrix.tsv` exist; verifies that generated `path-matrix.tsv` and tracked `docs/sim-configs-coverage-matrix.md` contain the same 159 paths as native `summary.tsv`; checks tracked matrix fields for class, native status, expected-failure reason, and blocked kind drift against generated `path-matrix.tsv`; and checks that generated `class-summary.tsv` matches class/status/expected-failure counts derived from `summary.tsv`. It then executes only entries that are currently vendored and have a defined standalone/WASM runtime path, and writes the sim-config inventory TSV artifacts. The boundary summary records one row per native inventory path with the matrix block, SDK classifier recommendation, declared HAL/UI/HALUI/Python process dependencies, `[EMCIO]DB_PROGRAM`, and execution-chain user-M codes; vendored INI rows must have an available classifier report, vendored hard blocks fail on classifier/matrix drift, safe process-declaring representatives must remain Node/browser `REP` rows with the expected process flag combinations rather than full-process promotions, and the designed full-process blocked rows must remain non-`REP` until their proof criteria are met. The generated inventory summary also requires all hard-blocked rows to remain `SKIP` with matching reasons, and `skip-summary.tsv` must match the matrix-derived skip counts. The blocked-dependency and boundary summaries record hard blocked Python-remap, tool-database, and external user-M rows from source `linuxcnc/configs/sim` INI files for dependency accounting only, including LinuxCNC source/config ownership fields, user-M script side effects, tool DB protocol evidence, Python runtime owner evidence, and completion criteria that all remain non-executing. Current gate: `executed=82`, `passed=82`, `skipped=77`, `unexpected_fail=0`; current skip/block counts are `ASSET_ONLY=65`, `L4_USER_M_PROCESS=1`, `NON_MAIN_CLASS=10`, and `UPSTREAM_DEMO=1`. The executed set now includes the deterministic `woodpecker` plus `qtdragon` / `qtdragon_hd` / `qtvcp_screens` `on_abort.ngc` family, `axis/gladevcp/probe.ngc`, `axis/rose_engine/rcone_demo.ngc`, `axis/vismach/melfa-sim/example.ngc`, and the `axis/vismach/puma` sample programs after vendoring the required machine INI, tool-table, and remap-subroutine inputs. When an upstream INI declares a missing local tool table but the native harness resolved a valid fallback table, the Node inventory stages that native-selected table at the INI-declared path so WASM execution uses the same machine context. The skip reasons are explicit: `NON_MAIN_CLASS`, `L4-TOOL-DB`, `L4-USER-M-PROCESS`, `L4-PYTHON-REMAP`, and `UPSTREAM-DEMO`; an eligible row that lacks vendored machine context is reported as an inventory failure. This runner is an inventory source-of-truth for Node WASM and is intentionally narrower than a browser full inventory. |
|
||||
|
||||
@@ -830,6 +830,18 @@ Current five-axis switchkins status:
|
||||
subroutines, then call vendored `Interp::parse_remap()`, `open()`,
|
||||
`read()`, and `execute()`; standalone code only supplies the INI/file path
|
||||
boundary.
|
||||
10. Independent kinematics WASM modules now cover the vendored LinuxCNC
|
||||
kinematics set: `trivkins`, `5axiskins`, `xyzac-trt`, `xyzbc-trt`,
|
||||
`corexy`, `rotate`, `rose`, `max`, `lineardelta`, `rotarydelta`,
|
||||
`scorbot`, `tripod`, `scara`, `puma`, `genser`, `genhex`, and
|
||||
`pentakins`. The modules compile vendored LinuxCNC
|
||||
`src/emc/kinematics` sources plus required vendored posemath/gomath
|
||||
sources, expose a small `lckins_*` C ABI for `type`, `switchable`,
|
||||
`switch`, `forward`, `inverse`, and `run_probe`, and are wrapped by
|
||||
`createLinuxCncKinematicsSdk()`. The ABI preserves LinuxCNC's in/out
|
||||
buffer behavior for iterative algorithms: callers may seed inverse joint
|
||||
buffers and forward pose buffers, while all kinematics math remains in
|
||||
vendored LinuxCNC source.
|
||||
|
||||
Next work:
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ The generated artifact baseline is:
|
||||
- `build/wasm/sim-configs-inventory/python-remap-boundary-summary.tsv`
|
||||
- `build/wasm/sim-configs-inventory/promotion-candidates.tsv`
|
||||
- `build/wasm/sim-configs-inventory/remaining-skip-main-program-promotion-audit.tsv`
|
||||
- `build/wasm/sim-configs-inventory/remaining-skip-simulation-implementation-coverage.tsv`
|
||||
- `build/wasm/sim-configs-inventory/evidence-expansion-candidates.tsv`
|
||||
- `build/wasm/sim-configs-inventory/runtime-boundary-promotion-blockers.tsv`
|
||||
- `build/wasm/sim-configs-inventory/runtime-boundary-host-readiness-rollup.tsv`
|
||||
@@ -63,6 +64,14 @@ incomplete user-M process native/inventory promotion proof, even though the
|
||||
browser simulation state now includes a source-derived virtual HAL proof for
|
||||
the `M429 -> M129` turn and `M428 -> M128` mill state transitions. The TWP
|
||||
`incremental_repetition_g533.ngc` row remains preserved upstream demo evidence.
|
||||
`remaining-skip-simulation-implementation-coverage.tsv` is the companion
|
||||
77-row source-derived implementation ledger for the entire skipped set. It does
|
||||
not change baseline status; instead it records how each skipped row is already
|
||||
covered in the CNC simulation system: 65 rows are LinuxCNC source assets used
|
||||
as remap/subroutine dependencies, 10 rows are non-main macro/load class
|
||||
coverage, 1 row is the designed-but-blocked LinuxCNC-owned user-M runtime
|
||||
boundary with virtual HAL state proof, and 1 row is a preserved upstream demo
|
||||
edge with invalid motion source.
|
||||
The 2026-06-20 re-audit confirmed that there are no directly promotable
|
||||
remaining skipped main-program rows:
|
||||
|
||||
|
||||
@@ -74,6 +74,8 @@ Current baseline:
|
||||
`wasm-port/build/wasm/sim-configs-inventory/promotion-candidates.tsv`
|
||||
- Node remaining skipped main-program promotion audit artifact:
|
||||
`wasm-port/build/wasm/sim-configs-inventory/remaining-skip-main-program-promotion-audit.tsv`
|
||||
- Node remaining skip simulation implementation coverage artifact:
|
||||
`wasm-port/build/wasm/sim-configs-inventory/remaining-skip-simulation-implementation-coverage.tsv`
|
||||
- Node native evidence acceptance gate artifact:
|
||||
`wasm-port/build/wasm/sim-configs-inventory/runtime-boundary-native-evidence-acceptance-gate.tsv`
|
||||
- Node promotion blocker summary artifact:
|
||||
@@ -384,7 +386,7 @@ Legend:
|
||||
`build/wasm/sim-configs-inventory/` must be referenced by the current
|
||||
compatibility, matrix, or full-process boundary docs, so generated gate
|
||||
artifacts cannot silently appear without review text. The generated WASM
|
||||
inventory artifact list must remain the exact duplicate-free 60-entry
|
||||
inventory artifact list must remain the exact duplicate-free 61-entry
|
||||
baseline, and every token plus the fixed-count, duplicate-free, and
|
||||
fetchability guards must also be listed by browser smoke.
|
||||
- The same inventory check covers native generated TSV artifacts under
|
||||
@@ -397,7 +399,7 @@ Legend:
|
||||
compatibility, matrix, and full-process boundary docs and checking the
|
||||
generated WASM inventory artifact names plus native TSV tokens against the
|
||||
same completion-summary counts. The Node coverage gate also locks the two
|
||||
documentation-coverage completion counts to the current `60` WASM inventory
|
||||
documentation-coverage completion counts to the current `61` WASM inventory
|
||||
artifacts and `8` native TSV artifacts. It verifies that browser smoke
|
||||
executes the artifact documentation coverage helper, calls the completion
|
||||
count-parity helper with the generated completion rows, fetches and joins the
|
||||
|
||||
358
wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_kinematics_wasm.c
Normal file
358
wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_kinematics_wasm.c
Normal file
@@ -0,0 +1,358 @@
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <emscripten/emscripten.h>
|
||||
|
||||
#include "emc/kinematics/kinematics.h"
|
||||
#include "emc/motion/emcmotcfg.h"
|
||||
|
||||
int rtapi_app_main(void);
|
||||
void rtapi_app_exit(void);
|
||||
|
||||
static int initialized = 0;
|
||||
|
||||
static char *copy_string(const char *value)
|
||||
{
|
||||
size_t length = strlen(value);
|
||||
char *out = malloc(length + 1);
|
||||
if (!out) {
|
||||
return NULL;
|
||||
}
|
||||
memcpy(out, value, length + 1);
|
||||
return out;
|
||||
}
|
||||
|
||||
static int near_value(double actual, double expected)
|
||||
{
|
||||
return fabs(actual - expected) < 1e-9;
|
||||
}
|
||||
|
||||
static int near_joints(const double *actual, const double *expected, int count)
|
||||
{
|
||||
int index;
|
||||
for (index = 0; index < count; ++index) {
|
||||
if (!near_value(actual[index], expected[index])) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int ensure_initialized(void)
|
||||
{
|
||||
if (initialized) {
|
||||
return 0;
|
||||
}
|
||||
int rc = rtapi_app_main();
|
||||
initialized = rc == 0;
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void pose_from_values(
|
||||
EmcPose *pose,
|
||||
double x,
|
||||
double y,
|
||||
double z,
|
||||
double a,
|
||||
double b,
|
||||
double c,
|
||||
double u,
|
||||
double v,
|
||||
double w)
|
||||
{
|
||||
memset(pose, 0, sizeof(*pose));
|
||||
pose->tran.x = x;
|
||||
pose->tran.y = y;
|
||||
pose->tran.z = z;
|
||||
pose->a = a;
|
||||
pose->b = b;
|
||||
pose->c = c;
|
||||
pose->u = u;
|
||||
pose->v = v;
|
||||
pose->w = w;
|
||||
}
|
||||
|
||||
static void append_text(char *out, size_t size, size_t *offset, const char *text)
|
||||
{
|
||||
int written;
|
||||
if (*offset >= size) {
|
||||
return;
|
||||
}
|
||||
written = snprintf(out + *offset, size - *offset, "%s", text);
|
||||
if (written > 0) {
|
||||
*offset += (size_t)written;
|
||||
}
|
||||
}
|
||||
|
||||
static void append_pose(char *out, size_t size, size_t *offset, const char *prefix, const EmcPose *pose)
|
||||
{
|
||||
char line[512];
|
||||
snprintf(line, sizeof(line),
|
||||
"%s_xyz=%.12g,%.12g,%.12g\n"
|
||||
"%s_abcuvw=%.12g,%.12g,%.12g,%.12g,%.12g,%.12g\n",
|
||||
prefix,
|
||||
pose->tran.x,
|
||||
pose->tran.y,
|
||||
pose->tran.z,
|
||||
prefix,
|
||||
pose->a,
|
||||
pose->b,
|
||||
pose->c,
|
||||
pose->u,
|
||||
pose->v,
|
||||
pose->w);
|
||||
append_text(out, size, offset, line);
|
||||
}
|
||||
|
||||
static void append_joints(char *out, size_t size, size_t *offset, const char *prefix, const double *joints, int count)
|
||||
{
|
||||
int index;
|
||||
char line[512];
|
||||
int written = snprintf(line, sizeof(line), "%s_joints=", prefix);
|
||||
size_t line_offset = written > 0 ? (size_t)written : 0;
|
||||
for (index = 0; index < count && line_offset < sizeof(line); ++index) {
|
||||
written = snprintf(
|
||||
line + line_offset,
|
||||
sizeof(line) - line_offset,
|
||||
"%s%.12g",
|
||||
index ? "," : "",
|
||||
joints[index]);
|
||||
if (written > 0) {
|
||||
line_offset += (size_t)written;
|
||||
}
|
||||
}
|
||||
append_text(line, sizeof(line), &line_offset, "\n");
|
||||
append_text(out, size, offset, line);
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
int lckins_init(void)
|
||||
{
|
||||
return ensure_initialized();
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
void lckins_exit(void)
|
||||
{
|
||||
if (initialized) {
|
||||
rtapi_app_exit();
|
||||
initialized = 0;
|
||||
}
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
int lckins_type(void)
|
||||
{
|
||||
if (ensure_initialized() != 0) {
|
||||
return -1;
|
||||
}
|
||||
return kinematicsType();
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
int lckins_switchable(void)
|
||||
{
|
||||
if (ensure_initialized() != 0) {
|
||||
return -1;
|
||||
}
|
||||
return kinematicsSwitchable();
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
int lckins_switch(int switchkins_type)
|
||||
{
|
||||
if (ensure_initialized() != 0) {
|
||||
return -1;
|
||||
}
|
||||
return kinematicsSwitch(switchkins_type);
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
int lckins_forward(
|
||||
const double *joints,
|
||||
int joint_count,
|
||||
double *pose_out,
|
||||
unsigned long *fflags_out,
|
||||
unsigned long *iflags_out)
|
||||
{
|
||||
double local_joints[EMCMOT_MAX_JOINTS];
|
||||
KINEMATICS_FORWARD_FLAGS fflags = fflags_out ? *fflags_out : 0;
|
||||
KINEMATICS_INVERSE_FLAGS iflags = iflags_out ? *iflags_out : 0;
|
||||
EmcPose pose;
|
||||
int index;
|
||||
int rc;
|
||||
|
||||
if (!joints || !pose_out || joint_count < 0 || joint_count > EMCMOT_MAX_JOINTS) {
|
||||
return -1;
|
||||
}
|
||||
if (ensure_initialized() != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset(local_joints, 0, sizeof(local_joints));
|
||||
for (index = 0; index < joint_count; ++index) {
|
||||
local_joints[index] = joints[index];
|
||||
}
|
||||
|
||||
pose_from_values(
|
||||
&pose,
|
||||
pose_out[0],
|
||||
pose_out[1],
|
||||
pose_out[2],
|
||||
pose_out[3],
|
||||
pose_out[4],
|
||||
pose_out[5],
|
||||
pose_out[6],
|
||||
pose_out[7],
|
||||
pose_out[8]);
|
||||
rc = kinematicsForward(local_joints, &pose, &fflags, &iflags);
|
||||
if (fflags_out) {
|
||||
*fflags_out = fflags;
|
||||
}
|
||||
if (iflags_out) {
|
||||
*iflags_out = iflags;
|
||||
}
|
||||
if (rc != 0) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
pose_out[0] = pose.tran.x;
|
||||
pose_out[1] = pose.tran.y;
|
||||
pose_out[2] = pose.tran.z;
|
||||
pose_out[3] = pose.a;
|
||||
pose_out[4] = pose.b;
|
||||
pose_out[5] = pose.c;
|
||||
pose_out[6] = pose.u;
|
||||
pose_out[7] = pose.v;
|
||||
pose_out[8] = pose.w;
|
||||
return rc;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
int lckins_inverse(
|
||||
const double *pose_values,
|
||||
double *joints_out,
|
||||
int joint_count,
|
||||
unsigned long *iflags_out,
|
||||
unsigned long *fflags_out)
|
||||
{
|
||||
EmcPose pose;
|
||||
double local_joints[EMCMOT_MAX_JOINTS];
|
||||
KINEMATICS_INVERSE_FLAGS iflags = iflags_out ? *iflags_out : 0;
|
||||
KINEMATICS_FORWARD_FLAGS fflags = fflags_out ? *fflags_out : 0;
|
||||
int index;
|
||||
int rc;
|
||||
|
||||
if (!pose_values || !joints_out || joint_count < 0 || joint_count > EMCMOT_MAX_JOINTS) {
|
||||
return -1;
|
||||
}
|
||||
if (ensure_initialized() != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
pose_from_values(
|
||||
&pose,
|
||||
pose_values[0],
|
||||
pose_values[1],
|
||||
pose_values[2],
|
||||
pose_values[3],
|
||||
pose_values[4],
|
||||
pose_values[5],
|
||||
pose_values[6],
|
||||
pose_values[7],
|
||||
pose_values[8]);
|
||||
memset(local_joints, 0, sizeof(local_joints));
|
||||
for (index = 0; index < joint_count; ++index) {
|
||||
local_joints[index] = joints_out[index];
|
||||
}
|
||||
rc = kinematicsInverse(&pose, local_joints, &iflags, &fflags);
|
||||
if (iflags_out) {
|
||||
*iflags_out = iflags;
|
||||
}
|
||||
if (fflags_out) {
|
||||
*fflags_out = fflags;
|
||||
}
|
||||
if (rc != 0) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
for (index = 0; index < joint_count; ++index) {
|
||||
joints_out[index] = local_joints[index];
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
char *lckins_run_probe(void)
|
||||
{
|
||||
char out[4096];
|
||||
char line[512];
|
||||
size_t offset = 0;
|
||||
int init_rc = ensure_initialized();
|
||||
double joints[EMCMOT_MAX_JOINTS];
|
||||
double inverse_joints[EMCMOT_MAX_JOINTS];
|
||||
KINEMATICS_FORWARD_FLAGS fflags = 0;
|
||||
KINEMATICS_INVERSE_FLAGS iflags = 0;
|
||||
EmcPose forward_pose;
|
||||
EmcPose identity_pose;
|
||||
int forward_rc;
|
||||
int inverse_rc;
|
||||
|
||||
memset(out, 0, sizeof(out));
|
||||
snprintf(line, sizeof(line), "kinematics_init=%d\n", init_rc);
|
||||
append_text(out, sizeof(out), &offset, line);
|
||||
if (init_rc != 0) {
|
||||
return copy_string(out);
|
||||
}
|
||||
|
||||
snprintf(line, sizeof(line),
|
||||
"kinematics_type=%d\n"
|
||||
"kinematics_switchable=%d\n",
|
||||
kinematicsType(),
|
||||
kinematicsSwitchable());
|
||||
append_text(out, sizeof(out), &offset, line);
|
||||
|
||||
memset(joints, 0, sizeof(joints));
|
||||
joints[0] = 10.0;
|
||||
joints[1] = 20.0;
|
||||
joints[2] = 30.0;
|
||||
joints[3] = 25.0;
|
||||
joints[4] = 40.0;
|
||||
|
||||
memset(&forward_pose, 0, sizeof(forward_pose));
|
||||
forward_rc = kinematicsForward(joints, &forward_pose, &fflags, &iflags);
|
||||
snprintf(line, sizeof(line), "kinematics_forward=%d\n", forward_rc);
|
||||
append_text(out, sizeof(out), &offset, line);
|
||||
append_pose(out, sizeof(out), &offset, "kinematics_forward", &forward_pose);
|
||||
|
||||
for (int index = 0; index < EMCMOT_MAX_JOINTS; ++index) {
|
||||
inverse_joints[index] = joints[index];
|
||||
}
|
||||
inverse_rc = kinematicsInverse(&forward_pose, inverse_joints, &iflags, &fflags);
|
||||
snprintf(line, sizeof(line), "kinematics_inverse=%d\n", inverse_rc);
|
||||
append_text(out, sizeof(out), &offset, line);
|
||||
append_joints(out, sizeof(out), &offset, "kinematics_inverse", inverse_joints, 5);
|
||||
snprintf(line, sizeof(line), "kinematics_roundtrip_joints=%d\n", near_joints(inverse_joints, joints, 5));
|
||||
append_text(out, sizeof(out), &offset, line);
|
||||
|
||||
if (kinematicsSwitchable()) {
|
||||
int switch_rc = kinematicsSwitch(1);
|
||||
snprintf(line, sizeof(line), "kinematics_switch_identity=%d\n", switch_rc);
|
||||
append_text(out, sizeof(out), &offset, line);
|
||||
memset(&identity_pose, 0, sizeof(identity_pose));
|
||||
forward_rc = kinematicsForward(joints, &identity_pose, &fflags, &iflags);
|
||||
snprintf(line, sizeof(line), "kinematics_identity_forward=%d\n", forward_rc);
|
||||
append_text(out, sizeof(out), &offset, line);
|
||||
append_pose(out, sizeof(out), &offset, "kinematics_identity", &identity_pose);
|
||||
}
|
||||
|
||||
return copy_string(out);
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
void lckins_free_string(char *value)
|
||||
{
|
||||
free(value);
|
||||
}
|
||||
@@ -41,6 +41,8 @@ typedef unsigned int hal_u32_t;
|
||||
typedef long long hal_s64_t;
|
||||
typedef unsigned long long hal_u64_t;
|
||||
|
||||
RTAPI_BEGIN_DECLS
|
||||
|
||||
int hal_init(const char *);
|
||||
int hal_ready(int);
|
||||
int hal_exit(int);
|
||||
@@ -61,3 +63,5 @@ int hal_param_float_newf(hal_pin_dir_t, hal_float_t *, int, const char *, ...);
|
||||
int hal_get_pin_value_by_name(const char *, hal_type_t *, hal_data_u **, bool *);
|
||||
int hal_get_signal_value_by_name(const char *, hal_type_t *, hal_data_u **, bool *);
|
||||
int hal_get_param_value_by_name(const char *, hal_type_t *, hal_data_u **);
|
||||
|
||||
RTAPI_END_DECLS
|
||||
|
||||
@@ -118,6 +118,9 @@ import {
|
||||
createVirtualHalWasmBridgeSnapshot,
|
||||
executeVirtualHalCommand,
|
||||
executeVirtualHalcmd,
|
||||
createLinuxCncKinematicsSdk,
|
||||
linuxCncKinematicsWasmFile,
|
||||
supportedLinuxCncKinematicsModules,
|
||||
readVirtualHalPin,
|
||||
stepVirtualHalMotion,
|
||||
stepVirtualHalMotionController,
|
||||
@@ -221,6 +224,27 @@ import {
|
||||
LinuxCNC `inifile.cc` and exposes `getString()` plus `getBool()`. Boolean
|
||||
conversion is performed by vendored LinuxCNC `iniFindBool()`.
|
||||
|
||||
`createLinuxCncKinematicsSdk()` wraps standalone kinematics WASM modules built
|
||||
from vendored LinuxCNC `src/emc/kinematics` sources and exposes:
|
||||
|
||||
- `type()`
|
||||
- `switchable()`
|
||||
- `switchKinematics(switchkinsType)`
|
||||
- `forward(joints, { seedPose })`
|
||||
- `inverse(pose, jointCount, { seedJoints })`
|
||||
- `runProbe()`
|
||||
|
||||
The current standalone kinematics WASM modules are `trivkins`, `5axiskins`,
|
||||
`xyzac-trt`, `xyzbc-trt`, `corexy`, `rotate`, `rose`, `max`, `lineardelta`,
|
||||
`rotarydelta`, `scorbot`, `tripod`, `scara`, `puma`, `genser`, `genhex`, and
|
||||
`pentakins`. Use `supportedLinuxCncKinematicsModules()` to list supported
|
||||
module IDs and `linuxCncKinematicsWasmFile(moduleId)` to resolve the generated
|
||||
WASM filename. The optional seed buffers mirror LinuxCNC's in/out kinematics
|
||||
function contracts for iterative algorithms such as generic serial and
|
||||
hexapod/pentapod kinematics. The SDK only manages memory and calls the
|
||||
exported C ABI; forward/inverse math remains in vendored LinuxCNC kinematics
|
||||
source.
|
||||
|
||||
`createLinuxCncInterpSdk()` wraps the interpreter-core module built from
|
||||
vendored LinuxCNC RS274NGC sources and exposes:
|
||||
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
export { createLinuxCncIniSdk } from "./linuxcnc-ini.js";
|
||||
export { createLinuxCncInterpSdk } from "./linuxcnc-interp.js";
|
||||
export {
|
||||
createLinuxCncKinematicsSdk,
|
||||
linuxCncKinematicsWasmFile,
|
||||
supportedLinuxCncKinematicsModules,
|
||||
} from "./linuxcnc-kinematics.js";
|
||||
export {
|
||||
VIRTUAL_HAL_AXES,
|
||||
VIRTUAL_HAL_AXISUI_PINS,
|
||||
|
||||
206
wasm-port/runtime/sdk/src/linuxcnc-kinematics.js
Normal file
206
wasm-port/runtime/sdk/src/linuxcnc-kinematics.js
Normal file
@@ -0,0 +1,206 @@
|
||||
import createLinuxCncTrivkinsKinematicsModule from "../../../build/wasm/kinematics/linuxcnc_trivkins_kinematics.js";
|
||||
import createLinuxCnc5axiskinsKinematicsModule from "../../../build/wasm/kinematics/linuxcnc_5axiskins_kinematics.js";
|
||||
import createLinuxCncXyzacTrtKinematicsModule from "../../../build/wasm/kinematics/linuxcnc_xyzac_trt_kinematics.js";
|
||||
import createLinuxCncXyzbcTrtKinematicsModule from "../../../build/wasm/kinematics/linuxcnc_xyzbc_trt_kinematics.js";
|
||||
import createLinuxCncCorexyKinematicsModule from "../../../build/wasm/kinematics/linuxcnc_corexy_kinematics.js";
|
||||
import createLinuxCncRotateKinematicsModule from "../../../build/wasm/kinematics/linuxcnc_rotate_kinematics.js";
|
||||
import createLinuxCncRoseKinematicsModule from "../../../build/wasm/kinematics/linuxcnc_rose_kinematics.js";
|
||||
import createLinuxCncMaxKinematicsModule from "../../../build/wasm/kinematics/linuxcnc_max_kinematics.js";
|
||||
import createLinuxCncLineardeltaKinematicsModule from "../../../build/wasm/kinematics/linuxcnc_lineardelta_kinematics.js";
|
||||
import createLinuxCncRotarydeltaKinematicsModule from "../../../build/wasm/kinematics/linuxcnc_rotarydelta_kinematics.js";
|
||||
import createLinuxCncScorbotKinematicsModule from "../../../build/wasm/kinematics/linuxcnc_scorbot_kinematics.js";
|
||||
import createLinuxCncTripodKinematicsModule from "../../../build/wasm/kinematics/linuxcnc_tripod_kinematics.js";
|
||||
import createLinuxCncScaraKinematicsModule from "../../../build/wasm/kinematics/linuxcnc_scara_kinematics.js";
|
||||
import createLinuxCncPumaKinematicsModule from "../../../build/wasm/kinematics/linuxcnc_puma_kinematics.js";
|
||||
import createLinuxCncGenserKinematicsModule from "../../../build/wasm/kinematics/linuxcnc_genser_kinematics.js";
|
||||
import createLinuxCncGenhexKinematicsModule from "../../../build/wasm/kinematics/linuxcnc_genhex_kinematics.js";
|
||||
import createLinuxCncPentakinsKinematicsModule from "../../../build/wasm/kinematics/linuxcnc_pentakins_kinematics.js";
|
||||
|
||||
export const LINUXCNC_KINEMATICS_MODULES = [
|
||||
{ id: "trivkins", wasmFile: "linuxcnc_trivkins_kinematics.wasm", factory: createLinuxCncTrivkinsKinematicsModule },
|
||||
{ id: "5axiskins", wasmFile: "linuxcnc_5axiskins_kinematics.wasm", factory: createLinuxCnc5axiskinsKinematicsModule },
|
||||
{ id: "xyzac-trt", wasmFile: "linuxcnc_xyzac_trt_kinematics.wasm", factory: createLinuxCncXyzacTrtKinematicsModule },
|
||||
{ id: "xyzbc-trt", wasmFile: "linuxcnc_xyzbc_trt_kinematics.wasm", factory: createLinuxCncXyzbcTrtKinematicsModule },
|
||||
{ id: "corexy", wasmFile: "linuxcnc_corexy_kinematics.wasm", factory: createLinuxCncCorexyKinematicsModule },
|
||||
{ id: "rotate", wasmFile: "linuxcnc_rotate_kinematics.wasm", factory: createLinuxCncRotateKinematicsModule },
|
||||
{ id: "rose", wasmFile: "linuxcnc_rose_kinematics.wasm", factory: createLinuxCncRoseKinematicsModule },
|
||||
{ id: "max", wasmFile: "linuxcnc_max_kinematics.wasm", factory: createLinuxCncMaxKinematicsModule },
|
||||
{ id: "lineardelta", wasmFile: "linuxcnc_lineardelta_kinematics.wasm", factory: createLinuxCncLineardeltaKinematicsModule },
|
||||
{ id: "rotarydelta", wasmFile: "linuxcnc_rotarydelta_kinematics.wasm", factory: createLinuxCncRotarydeltaKinematicsModule },
|
||||
{ id: "scorbot", wasmFile: "linuxcnc_scorbot_kinematics.wasm", factory: createLinuxCncScorbotKinematicsModule },
|
||||
{ id: "tripod", wasmFile: "linuxcnc_tripod_kinematics.wasm", factory: createLinuxCncTripodKinematicsModule },
|
||||
{ id: "scara", wasmFile: "linuxcnc_scara_kinematics.wasm", factory: createLinuxCncScaraKinematicsModule },
|
||||
{ id: "puma", wasmFile: "linuxcnc_puma_kinematics.wasm", factory: createLinuxCncPumaKinematicsModule },
|
||||
{ id: "genser", wasmFile: "linuxcnc_genser_kinematics.wasm", factory: createLinuxCncGenserKinematicsModule },
|
||||
{ id: "genhex", wasmFile: "linuxcnc_genhex_kinematics.wasm", factory: createLinuxCncGenhexKinematicsModule },
|
||||
{ id: "pentakins", wasmFile: "linuxcnc_pentakins_kinematics.wasm", factory: createLinuxCncPentakinsKinematicsModule },
|
||||
];
|
||||
|
||||
const MODULES_BY_ID = new Map(LINUXCNC_KINEMATICS_MODULES.map((entry) => [entry.id, entry]));
|
||||
|
||||
function requireWasmFunction(mod, functionName) {
|
||||
const fn = mod[`_${functionName}`];
|
||||
if (typeof fn !== "function") {
|
||||
throw new Error(`linuxcnc kinematics WASM missing ${functionName}; rebuild wasm-port/tools/build_kinematics_wasm.sh`);
|
||||
}
|
||||
return fn;
|
||||
}
|
||||
|
||||
function writeDoubleArray(mod, values) {
|
||||
const bytes = values.length * 8;
|
||||
const ptr = mod._malloc(bytes);
|
||||
mod.HEAPF64.set(values, ptr / 8);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
function readDoubleArray(mod, ptr, length) {
|
||||
return Array.from(mod.HEAPF64.subarray(ptr / 8, ptr / 8 + length));
|
||||
}
|
||||
|
||||
function poseValuesFromObject(pose = {}) {
|
||||
return [
|
||||
Number(pose.x ?? pose.tran?.x ?? 0),
|
||||
Number(pose.y ?? pose.tran?.y ?? 0),
|
||||
Number(pose.z ?? pose.tran?.z ?? 0),
|
||||
Number(pose.a ?? 0),
|
||||
Number(pose.b ?? 0),
|
||||
Number(pose.c ?? 0),
|
||||
Number(pose.u ?? 0),
|
||||
Number(pose.v ?? 0),
|
||||
Number(pose.w ?? 0),
|
||||
];
|
||||
}
|
||||
|
||||
function poseObjectFromValues(values) {
|
||||
return {
|
||||
x: values[0],
|
||||
y: values[1],
|
||||
z: values[2],
|
||||
a: values[3],
|
||||
b: values[4],
|
||||
c: values[5],
|
||||
u: values[6],
|
||||
v: values[7],
|
||||
w: values[8],
|
||||
};
|
||||
}
|
||||
|
||||
export function supportedLinuxCncKinematicsModules() {
|
||||
return LINUXCNC_KINEMATICS_MODULES.map((entry) => entry.id);
|
||||
}
|
||||
|
||||
export function linuxCncKinematicsWasmFile(moduleId) {
|
||||
return MODULES_BY_ID.get(moduleId)?.wasmFile || null;
|
||||
}
|
||||
|
||||
export async function createLinuxCncKinematicsSdk({
|
||||
moduleId = "xyzac-trt",
|
||||
moduleOptions = {},
|
||||
} = {}) {
|
||||
const entry = MODULES_BY_ID.get(moduleId);
|
||||
if (!entry) {
|
||||
throw new Error(`unsupported LinuxCNC kinematics module: ${moduleId}`);
|
||||
}
|
||||
|
||||
const mod = await entry.factory(moduleOptions);
|
||||
requireWasmFunction(mod, "lckins_init")();
|
||||
|
||||
return {
|
||||
apiName: "linuxcnc-kinematics-wasm-sdk",
|
||||
moduleId,
|
||||
module: mod,
|
||||
wasmFile: entry.wasmFile,
|
||||
|
||||
hasWasmFunction(functionName) {
|
||||
return typeof mod[`_${functionName}`] === "function";
|
||||
},
|
||||
|
||||
type() {
|
||||
return requireWasmFunction(mod, "lckins_type")();
|
||||
},
|
||||
|
||||
switchable() {
|
||||
return requireWasmFunction(mod, "lckins_switchable")();
|
||||
},
|
||||
|
||||
switchKinematics(switchkinsType) {
|
||||
return requireWasmFunction(mod, "lckins_switch")(Number(switchkinsType) || 0);
|
||||
},
|
||||
|
||||
forward(joints, options = {}) {
|
||||
const jointValues = Array.from(joints, Number);
|
||||
const jointsPtr = writeDoubleArray(mod, jointValues);
|
||||
const posePtr = writeDoubleArray(mod, poseValuesFromObject(options.seedPose));
|
||||
const fflagsPtr = mod._malloc(8);
|
||||
const iflagsPtr = mod._malloc(8);
|
||||
mod.HEAPU32[fflagsPtr / 4] = 0;
|
||||
mod.HEAPU32[iflagsPtr / 4] = 0;
|
||||
try {
|
||||
const rc = requireWasmFunction(mod, "lckins_forward")(
|
||||
jointsPtr,
|
||||
jointValues.length,
|
||||
posePtr,
|
||||
fflagsPtr,
|
||||
iflagsPtr,
|
||||
);
|
||||
return {
|
||||
rc,
|
||||
pose: poseObjectFromValues(readDoubleArray(mod, posePtr, 9)),
|
||||
fflags: mod.HEAPU32[fflagsPtr / 4],
|
||||
iflags: mod.HEAPU32[iflagsPtr / 4],
|
||||
};
|
||||
} finally {
|
||||
mod._free(jointsPtr);
|
||||
mod._free(posePtr);
|
||||
mod._free(fflagsPtr);
|
||||
mod._free(iflagsPtr);
|
||||
}
|
||||
},
|
||||
|
||||
inverse(pose, jointCount = 5, options = {}) {
|
||||
const posePtr = writeDoubleArray(mod, poseValuesFromObject(pose));
|
||||
const jointsPtr = mod._malloc(jointCount * 8);
|
||||
const iflagsPtr = mod._malloc(8);
|
||||
const fflagsPtr = mod._malloc(8);
|
||||
const seedJoints = Array.isArray(options.seedJoints)
|
||||
? options.seedJoints.map(Number)
|
||||
: [];
|
||||
mod.HEAPF64.fill(0, jointsPtr / 8, jointsPtr / 8 + jointCount);
|
||||
mod.HEAPF64.set(seedJoints.slice(0, jointCount), jointsPtr / 8);
|
||||
mod.HEAPU32[iflagsPtr / 4] = 0;
|
||||
mod.HEAPU32[fflagsPtr / 4] = 0;
|
||||
try {
|
||||
const rc = requireWasmFunction(mod, "lckins_inverse")(
|
||||
posePtr,
|
||||
jointsPtr,
|
||||
jointCount,
|
||||
iflagsPtr,
|
||||
fflagsPtr,
|
||||
);
|
||||
return {
|
||||
rc,
|
||||
joints: readDoubleArray(mod, jointsPtr, jointCount),
|
||||
iflags: mod.HEAPU32[iflagsPtr / 4],
|
||||
fflags: mod.HEAPU32[fflagsPtr / 4],
|
||||
};
|
||||
} finally {
|
||||
mod._free(posePtr);
|
||||
mod._free(jointsPtr);
|
||||
mod._free(iflagsPtr);
|
||||
mod._free(fflagsPtr);
|
||||
}
|
||||
},
|
||||
|
||||
runProbe() {
|
||||
const resultPtr = requireWasmFunction(mod, "lckins_run_probe")();
|
||||
if (!resultPtr) {
|
||||
throw new Error("lckins_run_probe returned null");
|
||||
}
|
||||
try {
|
||||
return mod.UTF8ToString(resultPtr);
|
||||
} finally {
|
||||
requireWasmFunction(mod, "lckins_free_string")(resultPtr);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -99,6 +99,7 @@
|
||||
"next-boundary-worklist.tsv",
|
||||
"promotion-candidates.tsv",
|
||||
"remaining-skip-main-program-promotion-audit.tsv",
|
||||
"remaining-skip-simulation-implementation-coverage.tsv",
|
||||
"python-remap-browser-row-proof.tsv",
|
||||
"python-remap-bulk-promotion-plan.tsv",
|
||||
"python-remap-boundary-summary.tsv",
|
||||
@@ -156,7 +157,7 @@
|
||||
"build/native/source-probes.tsv",
|
||||
];
|
||||
|
||||
if (wasmArtifactNames.length !== 60 || new Set(wasmArtifactNames).size !== wasmArtifactNames.length) {
|
||||
if (wasmArtifactNames.length !== 61 || new Set(wasmArtifactNames).size !== wasmArtifactNames.length) {
|
||||
throw new Error("browser_generated_artifact_documentation_coverage: WASM artifact list drift");
|
||||
}
|
||||
if (nativeArtifactTokens.length !== 8 || new Set(nativeArtifactTokens).size !== nativeArtifactTokens.length) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -45,6 +45,7 @@ for (const phrase of [
|
||||
"sim_configs_wasm_node_inventory_skip_UPSTREAM_DEMO=1",
|
||||
"build/wasm/sim-configs-inventory/skip-summary.tsv",
|
||||
"build/wasm/sim-configs-inventory/blocked-dependency-summary.tsv",
|
||||
"build/wasm/sim-configs-inventory/remaining-skip-simulation-implementation-coverage.tsv",
|
||||
"remaining_skip_count=77",
|
||||
"remaining_skipped_main_program_rows=2",
|
||||
"remaining_skipped_main_program_promotion_allowed=0",
|
||||
@@ -177,4 +178,30 @@ if (existsSync(remainingSkipAuditPath)) {
|
||||
}
|
||||
}
|
||||
|
||||
const remainingSkipImplementationCoveragePath = resolve(
|
||||
root,
|
||||
"build/wasm/sim-configs-inventory/remaining-skip-simulation-implementation-coverage.tsv",
|
||||
);
|
||||
if (existsSync(remainingSkipImplementationCoveragePath)) {
|
||||
const rows = parseTsv(readFileSync(remainingSkipImplementationCoveragePath, "utf8"));
|
||||
assert.equal(rows.length, 77);
|
||||
assert.deepEqual(
|
||||
Object.fromEntries(rows.reduce((counts, row) => {
|
||||
counts.set(row.skip_kind, (counts.get(row.skip_kind) ?? 0) + 1);
|
||||
return counts;
|
||||
}, new Map())),
|
||||
{
|
||||
"ASSET-ONLY": 65,
|
||||
"L4-USER-M-PROCESS": 1,
|
||||
NON_MAIN_CLASS: 10,
|
||||
"UPSTREAM-DEMO": 1,
|
||||
},
|
||||
);
|
||||
for (const row of rows) {
|
||||
assert.equal(row.linuxcnc_source_available, "1");
|
||||
assert.equal(row.simulation_coverage_ready, "1");
|
||||
assert.equal(row.promotion_allowed, "0");
|
||||
}
|
||||
}
|
||||
|
||||
console.log("sim_configs_coverage_docs_node_smoke=ok");
|
||||
|
||||
230
wasm-port/tests/wasm/node/verify_kinematics_wasm.mjs
Normal file
230
wasm-port/tests/wasm/node/verify_kinematics_wasm.mjs
Normal file
@@ -0,0 +1,230 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { dirname, resolve } from "node:path";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import {
|
||||
createLinuxCncKinematicsSdk,
|
||||
linuxCncKinematicsWasmFile,
|
||||
supportedLinuxCncKinematicsModules,
|
||||
} from "../../../runtime/sdk/src/index.js";
|
||||
|
||||
const EXPECTED_MODULES = [
|
||||
"trivkins",
|
||||
"5axiskins",
|
||||
"xyzac-trt",
|
||||
"xyzbc-trt",
|
||||
"corexy",
|
||||
"rotate",
|
||||
"rose",
|
||||
"max",
|
||||
"lineardelta",
|
||||
"rotarydelta",
|
||||
"scorbot",
|
||||
"tripod",
|
||||
"scara",
|
||||
"puma",
|
||||
"genser",
|
||||
"genhex",
|
||||
"pentakins",
|
||||
];
|
||||
|
||||
const EXPECTED_META = {
|
||||
trivkins: { type: 1, switchable: 0 },
|
||||
"5axiskins": { type: 4, switchable: 1 },
|
||||
"xyzac-trt": { type: 4, switchable: 1 },
|
||||
"xyzbc-trt": { type: 4, switchable: 1 },
|
||||
corexy: { type: 4, switchable: 0 },
|
||||
rotate: { type: 4, switchable: 0 },
|
||||
rose: { type: 4, switchable: 0 },
|
||||
max: { type: 4, switchable: 0 },
|
||||
lineardelta: { type: 4, switchable: 0 },
|
||||
rotarydelta: { type: 4, switchable: 0 },
|
||||
scorbot: { type: 4, switchable: 0 },
|
||||
tripod: { type: 4, switchable: 0 },
|
||||
scara: { type: 4, switchable: 1 },
|
||||
puma: { type: 4, switchable: 1 },
|
||||
genser: { type: 4, switchable: 1 },
|
||||
genhex: { type: 4, switchable: 1 },
|
||||
pentakins: { type: 4, switchable: 0 },
|
||||
};
|
||||
|
||||
function near(actual, expected, tolerance = 1e-7) {
|
||||
return Math.abs(actual - expected) < tolerance;
|
||||
}
|
||||
|
||||
function assertNear(actual, expected, label, tolerance = 1e-7) {
|
||||
assert.equal(near(actual, expected, tolerance), true, `${label}: ${actual} != ${expected}`);
|
||||
}
|
||||
|
||||
function assertArrayNear(actual, expected, label, tolerance = 1e-7) {
|
||||
assert.equal(actual.length >= expected.length, true, `${label}: missing values`);
|
||||
expected.forEach((value, index) => assertNear(actual[index], value, `${label}[${index}]`, tolerance));
|
||||
}
|
||||
|
||||
function assertPoseNear(actual, expected, label, tolerance = 1e-7) {
|
||||
for (const key of ["x", "y", "z", "a", "b", "c", "u", "v", "w"]) {
|
||||
assertNear(actual[key] ?? 0, expected[key] ?? 0, `${label}.${key}`, tolerance);
|
||||
}
|
||||
}
|
||||
|
||||
function assertProbeBasics(moduleId, probe) {
|
||||
assert.equal(probe.includes("kinematics_init=0"), true, `${moduleId}: init probe`);
|
||||
assert.equal(
|
||||
probe.includes(`kinematics_type=${EXPECTED_META[moduleId].type}`),
|
||||
true,
|
||||
`${moduleId}: type probe`,
|
||||
);
|
||||
assert.equal(
|
||||
probe.includes(`kinematics_switchable=${EXPECTED_META[moduleId].switchable}`),
|
||||
true,
|
||||
`${moduleId}: switchable probe`,
|
||||
);
|
||||
}
|
||||
|
||||
function assertSwitchIdentity(kins, moduleId, joints, expectedPose) {
|
||||
assert.equal(kins.switchKinematics(1), 0, `${moduleId}: switch identity`);
|
||||
const identity = kins.forward(joints);
|
||||
assert.equal(identity.rc, 0, `${moduleId}: identity forward`);
|
||||
assertPoseNear(identity.pose, expectedPose, `${moduleId}: identity pose`);
|
||||
}
|
||||
|
||||
function assertJointRoundtrip(kins, moduleId, joints, jointCount = joints.length, tolerance = 1e-7) {
|
||||
if (kins.switchable()) {
|
||||
assert.equal(kins.switchKinematics(0), 0, `${moduleId}: switch primary`);
|
||||
}
|
||||
const forward = kins.forward(joints);
|
||||
assert.equal(forward.rc, 0, `${moduleId}: forward`);
|
||||
const inverse = kins.inverse(forward.pose, jointCount, { seedJoints: joints });
|
||||
assert.equal(inverse.rc, 0, `${moduleId}: inverse`);
|
||||
assertArrayNear(inverse.joints, joints.slice(0, jointCount), `${moduleId}: joints`, tolerance);
|
||||
}
|
||||
|
||||
function assertPoseRoundtrip(kins, moduleId, pose, jointCount, options = {}) {
|
||||
if (kins.switchable()) {
|
||||
assert.equal(kins.switchKinematics(0), 0, `${moduleId}: switch primary`);
|
||||
}
|
||||
const inverse = kins.inverse(pose, jointCount, options.inverseOptions ?? {});
|
||||
assert.equal(inverse.rc, 0, `${moduleId}: inverse`);
|
||||
if (options.warmupForward) {
|
||||
kins.forward(inverse.joints, { seedPose: pose });
|
||||
}
|
||||
const forward = kins.forward(inverse.joints, { seedPose: pose });
|
||||
assert.equal(forward.rc, 0, `${moduleId}: forward`);
|
||||
assertPoseNear(forward.pose, pose, `${moduleId}: pose`, options.tolerance ?? 1e-7);
|
||||
return { inverse, forward };
|
||||
}
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
const rootDir = resolve(__dirname, "../../..");
|
||||
|
||||
assert.deepEqual(supportedLinuxCncKinematicsModules(), EXPECTED_MODULES);
|
||||
|
||||
for (const moduleId of supportedLinuxCncKinematicsModules()) {
|
||||
const wasmFile = linuxCncKinematicsWasmFile(moduleId);
|
||||
const wasmPath = resolve(rootDir, "build/wasm/kinematics", wasmFile);
|
||||
const kins = await createLinuxCncKinematicsSdk({
|
||||
moduleId,
|
||||
moduleOptions: {
|
||||
wasmBinary: readFileSync(wasmPath),
|
||||
print() {},
|
||||
printErr(message) {
|
||||
console.error(`[${moduleId}] ${message}`);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(kins.apiName, "linuxcnc-kinematics-wasm-sdk");
|
||||
assert.equal(kins.wasmFile, wasmFile);
|
||||
for (const functionName of ["lckins_forward", "lckins_inverse", "lckins_run_probe"]) {
|
||||
assert.equal(kins.hasWasmFunction(functionName), true, `${moduleId}: ${functionName}`);
|
||||
}
|
||||
assert.equal(kins.type(), EXPECTED_META[moduleId].type, `${moduleId}: type`);
|
||||
assert.equal(kins.switchable(), EXPECTED_META[moduleId].switchable, `${moduleId}: switchable`);
|
||||
assertProbeBasics(moduleId, kins.runProbe());
|
||||
|
||||
switch (moduleId) {
|
||||
case "trivkins":
|
||||
assertJointRoundtrip(kins, moduleId, [1, 2, 3, 4, 5, 6, 7, 8, 9], 9);
|
||||
break;
|
||||
case "5axiskins":
|
||||
assertJointRoundtrip(kins, moduleId, [10, 20, 30, 45, 30, 5], 6);
|
||||
assertSwitchIdentity(kins, moduleId, [10, 20, 30, 45, 30, 5], { x: 10, y: 20, z: 30, b: 45, c: 30, w: 5 });
|
||||
break;
|
||||
case "xyzac-trt":
|
||||
assertJointRoundtrip(kins, moduleId, [10, 20, 30, 25, 40], 5);
|
||||
assertSwitchIdentity(kins, moduleId, [10, 20, 30, 25, 40], { x: 10, y: 20, z: 30, a: 25, c: 40 });
|
||||
break;
|
||||
case "xyzbc-trt":
|
||||
assertJointRoundtrip(kins, moduleId, [10, 20, 30, 35, 40], 5);
|
||||
assertSwitchIdentity(kins, moduleId, [10, 20, 30, 35, 40], { x: 10, y: 20, z: 30, b: 35, c: 40 });
|
||||
break;
|
||||
case "corexy":
|
||||
assertJointRoundtrip(kins, moduleId, [12, 4, 3, 4, 5, 6, 7, 8, 9], 9);
|
||||
break;
|
||||
case "rotate":
|
||||
assertJointRoundtrip(kins, moduleId, [10, 20, 30, 4, 5, 30, 7, 8, 9], 9);
|
||||
break;
|
||||
case "rose":
|
||||
assertJointRoundtrip(kins, moduleId, [12, 5, 30], 3);
|
||||
break;
|
||||
case "max":
|
||||
assertJointRoundtrip(kins, moduleId, [10, 20, 30, 4, 0, 25, 0, 0, 3.5], 9);
|
||||
break;
|
||||
case "lineardelta":
|
||||
assertPoseRoundtrip(kins, moduleId, { x: 10, y: 20, z: -30, a: 1, b: 2, c: 3, u: 4, v: 5, w: 6 }, 9);
|
||||
break;
|
||||
case "rotarydelta":
|
||||
assertPoseRoundtrip(kins, moduleId, { x: 0, y: 0, z: -12, a: 1, b: 2, c: 3, u: 4, v: 5, w: 6 }, 9);
|
||||
break;
|
||||
case "scorbot": {
|
||||
const forward = kins.forward([25, 20, 10, 4, 5]);
|
||||
assert.equal(forward.rc, 0, `${moduleId}: forward`);
|
||||
const inverse = kins.inverse(forward.pose, 5);
|
||||
assert.equal(inverse.rc, 0, `${moduleId}: inverse`);
|
||||
const roundtrip = kins.forward(inverse.joints);
|
||||
assert.equal(roundtrip.rc, 0, `${moduleId}: roundtrip forward`);
|
||||
assertPoseNear(roundtrip.pose, forward.pose, `${moduleId}: pose`);
|
||||
break;
|
||||
}
|
||||
case "tripod":
|
||||
assertPoseRoundtrip(kins, moduleId, { x: 0.25, y: 0.25, z: 0.5 }, 3);
|
||||
break;
|
||||
case "scara":
|
||||
assertJointRoundtrip(kins, moduleId, [20, 110, 30, 15, 4, 5], 6);
|
||||
assertSwitchIdentity(kins, moduleId, [20, 110, 30, 15, 4, 5], { x: 20, y: 110, z: 30, a: 15, b: 4, c: 5 });
|
||||
break;
|
||||
case "puma": {
|
||||
if (kins.switchable()) {
|
||||
assert.equal(kins.switchKinematics(0), 0, `${moduleId}: switch primary`);
|
||||
}
|
||||
const forward = kins.forward([20, -30, 40, 15, 35, -25]);
|
||||
assert.equal(forward.rc, 0, `${moduleId}: forward`);
|
||||
const inverse = kins.inverse(forward.pose, 6);
|
||||
assert.equal(inverse.rc, 0, `${moduleId}: inverse`);
|
||||
const roundtrip = kins.forward(inverse.joints);
|
||||
assert.equal(roundtrip.rc, 0, `${moduleId}: roundtrip forward`);
|
||||
assertPoseNear(roundtrip.pose, forward.pose, `${moduleId}: pose`);
|
||||
assertSwitchIdentity(kins, moduleId, [20, -30, 40, 15, 35, -25], { x: 20, y: -30, z: 40, a: 15, b: 35, c: -25 });
|
||||
break;
|
||||
}
|
||||
case "genser":
|
||||
assertJointRoundtrip(kins, moduleId, [10, -15, 20, 5, -10, 12, 7, 8, 9], 9);
|
||||
assertSwitchIdentity(kins, moduleId, [10, -15, 20, 5, -10, 12, 7, 8, 9], { x: 10, y: -15, z: 20, a: 5, b: -10, c: 12, u: 7, v: 8, w: 9 });
|
||||
break;
|
||||
case "genhex":
|
||||
assertPoseRoundtrip(kins, moduleId, { x: 1.25, y: -2.5, z: 3.75, a: 1, b: -2, c: 3 }, 6, { warmupForward: true });
|
||||
assert.equal(kins.switchKinematics(1), 0, `${moduleId}: switch alternate`);
|
||||
break;
|
||||
case "pentakins": {
|
||||
const { inverse } = assertPoseRoundtrip(kins, moduleId, { x: 12.5, y: -8.25, z: 25, a: 2, b: -1.5 }, 5, { tolerance: 1e-6 });
|
||||
assert.equal(inverse.joints.every((value) => value > 0), true, `${moduleId}: positive struts`);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new Error(`missing kinematics verification for ${moduleId}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log("kinematics_wasm_node_smoke=ok");
|
||||
9
wasm-port/tests/wasm/node/verify_kinematics_wasm.sh
Executable file
9
wasm-port/tests/wasm/node/verify_kinematics_wasm.sh
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/../../.." && pwd)"
|
||||
|
||||
if [[ "${SKIP_KINEMATICS_BUILD:-0}" != "1" ]]; then
|
||||
"$ROOT_DIR/tools/build_kinematics_wasm.sh"
|
||||
fi
|
||||
node "$ROOT_DIR/tests/wasm/node/verify_kinematics_wasm.mjs"
|
||||
@@ -108,6 +108,7 @@ const nativeRuntimeProbePassEvidenceContractPath = resolve(buildDir, "native-run
|
||||
const promotionCandidatesPath = resolve(buildDir, "promotion-candidates.tsv");
|
||||
const evidenceExpansionCandidatesPath = resolve(buildDir, "evidence-expansion-candidates.tsv");
|
||||
const remainingSkipMainProgramPromotionAuditPath = resolve(buildDir, "remaining-skip-main-program-promotion-audit.tsv");
|
||||
const remainingSkipSimulationImplementationCoveragePath = resolve(buildDir, "remaining-skip-simulation-implementation-coverage.tsv");
|
||||
const blockedRuntimePromotionLockPath = resolve(buildDir, "blocked-runtime-promotion-lock.tsv");
|
||||
const runtimeBoundaryPromotionReadinessPath = resolve(buildDir, "runtime-boundary-promotion-readiness.tsv");
|
||||
const runtimeBoundaryPromotionBlockersPath = resolve(buildDir, "runtime-boundary-promotion-blockers.tsv");
|
||||
@@ -168,6 +169,7 @@ const generatedSimConfigInventoryArtifactPaths = [
|
||||
nativeRuntimeProbeExecutionPlanPath,
|
||||
nativeRuntimeProbePassEvidenceContractPath,
|
||||
promotionCandidatesPath,
|
||||
remainingSkipSimulationImplementationCoveragePath,
|
||||
remainingSkipMainProgramPromotionAuditPath,
|
||||
blockedRuntimePromotionLockPath,
|
||||
runtimeBoundaryPromotionReadinessPath,
|
||||
@@ -6925,7 +6927,7 @@ function verifyBoundaryPhaseCompletionSummaryRows(rows) {
|
||||
const rowByCriterion = new Map(parsedRows.map((row) => [row.criterion, row]));
|
||||
assert.equal(
|
||||
rowByCriterion.get("wasm_inventory_artifact_documentation_coverage")?.count,
|
||||
"60",
|
||||
"61",
|
||||
"WASM artifact documentation completion count must match generated artifact baseline",
|
||||
);
|
||||
assert.equal(
|
||||
@@ -9678,7 +9680,7 @@ function verifyGeneratedArtifactDocumentationCoverage({
|
||||
);
|
||||
assert.equal(
|
||||
artifactNames.length,
|
||||
60,
|
||||
61,
|
||||
"generated sim-config inventory artifact list count drift",
|
||||
);
|
||||
assert.deepEqual(
|
||||
@@ -9712,6 +9714,7 @@ function verifyGeneratedArtifactDocumentationCoverage({
|
||||
"python-remap-runtime-gates.tsv",
|
||||
"python-remap-wasm-node-row-proof.tsv",
|
||||
"remaining-skip-main-program-promotion-audit.tsv",
|
||||
"remaining-skip-simulation-implementation-coverage.tsv",
|
||||
"runtime-boundary-contract-summary.tsv",
|
||||
"runtime-boundary-family-host-readiness.tsv",
|
||||
"runtime-boundary-host-preflight.tsv",
|
||||
@@ -9778,7 +9781,7 @@ function verifyGeneratedArtifactDocumentationCoverage({
|
||||
"generated sim-config inventory artifacts must be listed in browser smoke",
|
||||
);
|
||||
assert.ok(
|
||||
browserSmokeText.includes("wasmArtifactNames.length !== 60"),
|
||||
browserSmokeText.includes("wasmArtifactNames.length !== 61"),
|
||||
"browser smoke must keep generated WASM artifact fixed-count guard",
|
||||
);
|
||||
assert.ok(
|
||||
@@ -13673,6 +13676,223 @@ function verifyRemainingSkipMainProgramPromotionAuditRows({
|
||||
return parsedRows;
|
||||
}
|
||||
|
||||
function simulationImplementationMode(row) {
|
||||
if (row.reason === "ASSET-ONLY") {
|
||||
return "source_asset_dependency";
|
||||
}
|
||||
if (row.reason === "NON_MAIN_CLASS") {
|
||||
return "macro_load_or_non_main_class_coverage";
|
||||
}
|
||||
if (row.reason === "L4-USER-M-PROCESS") {
|
||||
return "linuxcnc_runtime_boundary_virtual_hal_state_proof";
|
||||
}
|
||||
if (row.reason === "UPSTREAM-DEMO") {
|
||||
return "upstream_demo_preserved_invalid_motion_source";
|
||||
}
|
||||
return "unclassified_skip";
|
||||
}
|
||||
|
||||
function simulationImplementationStatus(row) {
|
||||
if (row.reason === "ASSET-ONLY") {
|
||||
return "implemented_as_linuxcnc_source_asset_dependency_not_standalone_main";
|
||||
}
|
||||
if (row.reason === "NON_MAIN_CLASS") {
|
||||
return "implemented_as_linuxcnc_macro_load_or_non_main_class_not_standalone_main";
|
||||
}
|
||||
if (row.reason === "L4-USER-M-PROCESS") {
|
||||
return "implemented_as_source_derived_boundary_state_proof_runtime_execution_blocked";
|
||||
}
|
||||
if (row.reason === "UPSTREAM-DEMO") {
|
||||
return "implemented_as_preserved_upstream_demo_edge_invalid_motion_not_forced_pass";
|
||||
}
|
||||
return "skip_kind_requires_review";
|
||||
}
|
||||
|
||||
function simulationImplementationNextStep(row) {
|
||||
if (row.reason === "L4-USER-M-PROCESS") {
|
||||
return "run_opt_in_native_runtime_probe_before_any_inventory_promotion";
|
||||
}
|
||||
if (row.reason === "UPSTREAM-DEMO") {
|
||||
return "wait_for_upstream_source_fix_then_regenerate_inventory";
|
||||
}
|
||||
return "use_as_dependency_or_class_coverage_no_inventory_promotion";
|
||||
}
|
||||
|
||||
function remainingSkipSimulationImplementationCoverageRows({
|
||||
summaryRows,
|
||||
boundaryRows,
|
||||
remainingSkipAuditRows,
|
||||
}) {
|
||||
const boundaryByPath = new Map(boundaryRows.map((row) => [row.path, row]));
|
||||
const auditByPath = new Map(remainingSkipAuditRows.map((row) => [row.path, row]));
|
||||
|
||||
return summaryRows
|
||||
.map((row) => {
|
||||
const [
|
||||
path,
|
||||
inventoryStatus,
|
||||
reason,
|
||||
className,
|
||||
nativeStatus,
|
||||
nativeExpectedFailure,
|
||||
] = row.split("\t");
|
||||
return {
|
||||
path,
|
||||
inventoryStatus,
|
||||
reason,
|
||||
className,
|
||||
nativeStatus,
|
||||
nativeExpectedFailure,
|
||||
};
|
||||
})
|
||||
.filter((row) => row.inventoryStatus === "SKIP")
|
||||
.sort((left, right) => left.path.localeCompare(right.path))
|
||||
.map((row) => {
|
||||
const boundary = boundaryByPath.get(row.path);
|
||||
const audit = auditByPath.get(row.path);
|
||||
const sourceRel = `configs/sim/${row.path}`;
|
||||
const sourceAvailable = sourceConfigPathExists(row.path);
|
||||
const implementationMode = simulationImplementationMode(row);
|
||||
const standaloneMainReady = row.className === "main" &&
|
||||
row.reason === "-" &&
|
||||
row.nativeStatus === "PASS";
|
||||
const runtimePromotionBlocked = ["L4-USER-M-PROCESS", "UPSTREAM-DEMO"].includes(row.reason);
|
||||
return [
|
||||
row.path,
|
||||
boundary?.ini ?? audit?.ini ?? "-",
|
||||
row.reason,
|
||||
row.className,
|
||||
row.nativeStatus,
|
||||
row.nativeExpectedFailure,
|
||||
sourceRel,
|
||||
flagValue(sourceAvailable),
|
||||
implementationMode,
|
||||
simulationImplementationStatus(row),
|
||||
flagValue(row.className === "main"),
|
||||
flagValue(standaloneMainReady),
|
||||
boundary?.dependencies ?? audit?.dependency_class ?? "-",
|
||||
boundary?.recommended_blocked ?? audit?.blocked_kind ?? row.reason,
|
||||
audit?.promotion_ready ?? "0",
|
||||
audit?.promotion_allowed ?? "0",
|
||||
flagValue(runtimePromotionBlocked),
|
||||
"1",
|
||||
simulationImplementationNextStep(row),
|
||||
].map(tsvValue).join("\t");
|
||||
});
|
||||
}
|
||||
|
||||
function verifyRemainingSkipSimulationImplementationCoverageRows({
|
||||
rows,
|
||||
summaryRows,
|
||||
remainingSkipAuditRows,
|
||||
}) {
|
||||
const expectedHeaders = [
|
||||
"path",
|
||||
"ini",
|
||||
"skip_kind",
|
||||
"class",
|
||||
"native_status",
|
||||
"native_expected_failure",
|
||||
"linuxcnc_source_path",
|
||||
"linuxcnc_source_available",
|
||||
"simulation_implementation_mode",
|
||||
"simulation_implementation_status",
|
||||
"main_program_class",
|
||||
"standalone_main_program_ready",
|
||||
"dependency_class",
|
||||
"blocked_kind",
|
||||
"promotion_ready",
|
||||
"promotion_allowed",
|
||||
"runtime_promotion_blocked",
|
||||
"simulation_coverage_ready",
|
||||
"recommended_next_step",
|
||||
];
|
||||
const parsedRows = parseTsv(
|
||||
`${expectedHeaders.join("\t")}\n${rows.join("\n")}\n`,
|
||||
expectedHeaders,
|
||||
);
|
||||
const skippedSummaryRows = summaryRows
|
||||
.map((row) => {
|
||||
const [path, inventoryStatus, reason, className] = row.split("\t");
|
||||
return { path, inventoryStatus, reason, className };
|
||||
})
|
||||
.filter((row) => row.inventoryStatus === "SKIP");
|
||||
const auditByPath = new Map(remainingSkipAuditRows.map((row) => [row.path, row]));
|
||||
|
||||
assert.equal(parsedRows.length, 77, "remaining skip simulation implementation coverage count drift");
|
||||
assert.deepEqual(
|
||||
parsedRows.map((row) => row.path).sort(),
|
||||
skippedSummaryRows.map((row) => row.path).sort(),
|
||||
"remaining skip simulation implementation coverage must cover every skipped row",
|
||||
);
|
||||
assert.deepEqual(
|
||||
Object.fromEntries(
|
||||
[...parsedRows.reduce((counts, row) => {
|
||||
counts.set(row.skip_kind, (counts.get(row.skip_kind) ?? 0) + 1);
|
||||
return counts;
|
||||
}, new Map()).entries()].sort(),
|
||||
),
|
||||
{
|
||||
"ASSET-ONLY": 65,
|
||||
"L4-USER-M-PROCESS": 1,
|
||||
"NON_MAIN_CLASS": 10,
|
||||
"UPSTREAM-DEMO": 1,
|
||||
},
|
||||
"remaining skip simulation implementation coverage kind counts drift",
|
||||
);
|
||||
|
||||
for (const row of parsedRows) {
|
||||
assert.equal(row.linuxcnc_source_available, "1", `${row.path}: LinuxCNC source path missing`);
|
||||
assert.equal(row.simulation_coverage_ready, "1", `${row.path}: skipped row lacks simulation coverage`);
|
||||
assert.equal(row.standalone_main_program_ready, "0", `${row.path}: skipped row must not claim standalone main readiness`);
|
||||
assert.equal(row.promotion_allowed, "0", `${row.path}: skipped row must not allow promotion`);
|
||||
assert.notEqual(row.simulation_implementation_mode, "unclassified_skip", `${row.path}: unclassified skip implementation mode`);
|
||||
assert.notEqual(row.recommended_next_step, "-", `${row.path}: skipped row lacks next implementation step`);
|
||||
|
||||
if (row.skip_kind === "ASSET-ONLY") {
|
||||
assert.equal(row.class, "remap_subroutine", `${row.path}: asset-only row should remain remap subroutine coverage`);
|
||||
assert.equal(
|
||||
row.simulation_implementation_mode,
|
||||
"source_asset_dependency",
|
||||
`${row.path}: asset-only implementation mode drift`,
|
||||
);
|
||||
} else if (row.skip_kind === "NON_MAIN_CLASS") {
|
||||
assert.equal(row.class, "macro_load", `${row.path}: non-main row should remain macro/load coverage`);
|
||||
assert.equal(
|
||||
row.simulation_implementation_mode,
|
||||
"macro_load_or_non_main_class_coverage",
|
||||
`${row.path}: non-main implementation mode drift`,
|
||||
);
|
||||
} else if (row.skip_kind === "L4-USER-M-PROCESS") {
|
||||
const audit = auditByPath.get(row.path);
|
||||
assert.ok(audit, `${row.path}: user-M skipped main row must have promotion audit`);
|
||||
assert.equal(row.path, "axis/vismach/millturn/example.ngc", `${row.path}: user-M path drift`);
|
||||
assert.equal(row.main_program_class, "1", `${row.path}: user-M row must remain main class`);
|
||||
assert.equal(row.runtime_promotion_blocked, "1", `${row.path}: user-M runtime promotion block drift`);
|
||||
assert.equal(
|
||||
row.simulation_implementation_status,
|
||||
"implemented_as_source_derived_boundary_state_proof_runtime_execution_blocked",
|
||||
`${row.path}: user-M implementation status drift`,
|
||||
);
|
||||
} else if (row.skip_kind === "UPSTREAM-DEMO") {
|
||||
const audit = auditByPath.get(row.path);
|
||||
assert.ok(audit, `${row.path}: upstream demo skipped main row must have promotion audit`);
|
||||
assert.equal(
|
||||
row.path,
|
||||
"axis/vismach/5axis/table-rotary_spindle-rotary-nutating/demos/incremental_repetition_g533.ngc",
|
||||
`${row.path}: upstream demo path drift`,
|
||||
);
|
||||
assert.equal(row.native_expected_failure, "upstream-demo-missing-motion-gcode", `${row.path}: upstream demo failure drift`);
|
||||
assert.equal(row.main_program_class, "1", `${row.path}: upstream demo row must remain main class`);
|
||||
assert.equal(row.runtime_promotion_blocked, "1", `${row.path}: upstream demo runtime promotion block drift`);
|
||||
} else {
|
||||
assert.fail(`${row.path}: unexpected skip kind ${row.skip_kind}`);
|
||||
}
|
||||
}
|
||||
|
||||
return parsedRows;
|
||||
}
|
||||
|
||||
for (const record of nativeRecords) {
|
||||
const skipReason = blockedKind(record, pathMatrixByPath);
|
||||
if (skipReason === "L4-PYTHON-REMAP" && promotedPythonRemapInventoryPaths.has(record.path)) {
|
||||
@@ -14191,6 +14411,40 @@ verifyRemainingSkipMainProgramPromotionAuditRows({
|
||||
summaryRows,
|
||||
promotionCandidateRows: promotionCandidateRecords,
|
||||
});
|
||||
const remainingSkipMainProgramPromotionAuditRecords = parseTsv(
|
||||
`${[
|
||||
"path",
|
||||
"ini",
|
||||
"current_status",
|
||||
"skip_kind",
|
||||
"class",
|
||||
"native_status",
|
||||
"native_expected_failure",
|
||||
"blocked_kind",
|
||||
"dependency_class",
|
||||
"simulation_proof_status",
|
||||
"native_pass_ready",
|
||||
"node_inventory_gate_complete",
|
||||
"browser_smoke_gate_complete",
|
||||
"promotion_lock_active",
|
||||
"promotion_ready",
|
||||
"promotion_allowed",
|
||||
"promotion_decision",
|
||||
"block_reason",
|
||||
"recommended_next_command",
|
||||
].join("\t")}\n${remainingSkipMainProgramPromotionAuditRowsGenerated.join("\n")}\n`,
|
||||
);
|
||||
const remainingSkipSimulationImplementationCoverageRowsGenerated =
|
||||
remainingSkipSimulationImplementationCoverageRows({
|
||||
summaryRows,
|
||||
boundaryRows: boundarySummaryRecords,
|
||||
remainingSkipAuditRows: remainingSkipMainProgramPromotionAuditRecords,
|
||||
});
|
||||
verifyRemainingSkipSimulationImplementationCoverageRows({
|
||||
rows: remainingSkipSimulationImplementationCoverageRowsGenerated,
|
||||
summaryRows,
|
||||
remainingSkipAuditRows: remainingSkipMainProgramPromotionAuditRecords,
|
||||
});
|
||||
const evidenceExpansionCandidateRowsGenerated = evidenceExpansionCandidateRows({
|
||||
summaryRows,
|
||||
boundaryRows: boundarySummaryRecords,
|
||||
@@ -14280,6 +14534,34 @@ writeFileSync(
|
||||
].join("\n")}\n`,
|
||||
);
|
||||
|
||||
writeFileSync(
|
||||
remainingSkipSimulationImplementationCoveragePath,
|
||||
`${[
|
||||
[
|
||||
"path",
|
||||
"ini",
|
||||
"skip_kind",
|
||||
"class",
|
||||
"native_status",
|
||||
"native_expected_failure",
|
||||
"linuxcnc_source_path",
|
||||
"linuxcnc_source_available",
|
||||
"simulation_implementation_mode",
|
||||
"simulation_implementation_status",
|
||||
"main_program_class",
|
||||
"standalone_main_program_ready",
|
||||
"dependency_class",
|
||||
"blocked_kind",
|
||||
"promotion_ready",
|
||||
"promotion_allowed",
|
||||
"runtime_promotion_blocked",
|
||||
"simulation_coverage_ready",
|
||||
"recommended_next_step",
|
||||
].join("\t"),
|
||||
...remainingSkipSimulationImplementationCoverageRowsGenerated,
|
||||
].join("\n")}\n`,
|
||||
);
|
||||
|
||||
writeFileSync(
|
||||
boundarySummaryPath,
|
||||
`${[
|
||||
|
||||
226
wasm-port/tools/build_kinematics_wasm.sh
Executable file
226
wasm-port/tools/build_kinematics_wasm.sh
Executable file
@@ -0,0 +1,226 @@
|
||||
#!/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/kinematics"
|
||||
OBJ_DIR="$OUT_DIR/obj"
|
||||
|
||||
source "$ROOT_DIR/tools/wasm_incremental_build_lib.sh"
|
||||
|
||||
mkdir -p "$OUT_DIR" "$OBJ_DIR"
|
||||
|
||||
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
|
||||
-include "$SHIM_DIR/linuxcnc_wasm_compat.hh"
|
||||
"${COMMON_FLAGS[@]}"
|
||||
)
|
||||
|
||||
build_kinematics_module() {
|
||||
local target="$1"
|
||||
local output="$2"
|
||||
shift 2
|
||||
|
||||
local sources=("$@")
|
||||
local target_obj_dir="$OBJ_DIR/$target"
|
||||
mkdir -p "$target_obj_dir"
|
||||
: > "$OUT_DIR/$target.stdout.log"
|
||||
: > "$OUT_DIR/$target.stderr.log"
|
||||
|
||||
local objects=()
|
||||
local source obj
|
||||
for source in "${sources[@]}"; do
|
||||
obj="$(source_object_path "$source" "$ROOT_DIR" "$target_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/$output.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","_lckins_init","_lckins_exit","_lckins_type","_lckins_switchable","_lckins_switch","_lckins_forward","_lckins_inverse","_lckins_run_probe","_lckins_free_string"]' \
|
||||
-s EXPORTED_RUNTIME_METHODS='["UTF8ToString"]'
|
||||
}
|
||||
|
||||
TRT_COMMON_SOURCES=(
|
||||
"$VENDOR_DIR/src/emc/kinematics/kins_util.c"
|
||||
"$VENDOR_DIR/src/emc/kinematics/switchkins.c"
|
||||
"$VENDOR_DIR/src/emc/kinematics/userkfuncs.c"
|
||||
"$VENDOR_DIR/src/emc/kinematics/trtfuncs.c"
|
||||
"$WRAP_DIR/linuxcnc_hal_adapter.cpp"
|
||||
"$WRAP_DIR/linuxcnc_kinematics_wasm.c"
|
||||
)
|
||||
|
||||
SWITCHKINS_COMMON_SOURCES=(
|
||||
"$VENDOR_DIR/src/emc/kinematics/kins_util.c"
|
||||
"$VENDOR_DIR/src/emc/kinematics/switchkins.c"
|
||||
"$VENDOR_DIR/src/emc/kinematics/userkfuncs.c"
|
||||
"$WRAP_DIR/linuxcnc_hal_adapter.cpp"
|
||||
"$WRAP_DIR/linuxcnc_kinematics_wasm.c"
|
||||
)
|
||||
|
||||
POSEMATH_SOURCES=(
|
||||
"$VENDOR_DIR/src/libnml/posemath/posemath.cc"
|
||||
"$VENDOR_DIR/src/libnml/posemath/_posemath.c"
|
||||
"$VENDOR_DIR/src/libnml/posemath/sincos.c"
|
||||
)
|
||||
|
||||
build_kinematics_module \
|
||||
linuxcnc_trivkins_kinematics_wasm \
|
||||
linuxcnc_trivkins_kinematics \
|
||||
"$VENDOR_DIR/src/emc/kinematics/kins_util.c" \
|
||||
"$VENDOR_DIR/src/emc/kinematics/trivkins.c" \
|
||||
"$WRAP_DIR/linuxcnc_hal_adapter.cpp" \
|
||||
"$WRAP_DIR/linuxcnc_kinematics_wasm.c"
|
||||
|
||||
build_kinematics_module \
|
||||
linuxcnc_5axiskins_kinematics_wasm \
|
||||
linuxcnc_5axiskins_kinematics \
|
||||
"${SWITCHKINS_COMMON_SOURCES[@]}" \
|
||||
"$VENDOR_DIR/src/emc/kinematics/5axiskins.c"
|
||||
|
||||
build_kinematics_module \
|
||||
linuxcnc_xyzac_trt_kinematics_wasm \
|
||||
linuxcnc_xyzac_trt_kinematics \
|
||||
"${TRT_COMMON_SOURCES[@]}" \
|
||||
"$VENDOR_DIR/src/emc/kinematics/xyzac-trt-kins.c"
|
||||
|
||||
build_kinematics_module \
|
||||
linuxcnc_xyzbc_trt_kinematics_wasm \
|
||||
linuxcnc_xyzbc_trt_kinematics \
|
||||
"${TRT_COMMON_SOURCES[@]}" \
|
||||
"$VENDOR_DIR/src/emc/kinematics/xyzbc-trt-kins.c"
|
||||
|
||||
build_kinematics_module \
|
||||
linuxcnc_corexy_kinematics_wasm \
|
||||
linuxcnc_corexy_kinematics \
|
||||
"$VENDOR_DIR/src/emc/kinematics/corexykins.c" \
|
||||
"$WRAP_DIR/linuxcnc_hal_adapter.cpp" \
|
||||
"$WRAP_DIR/linuxcnc_kinematics_wasm.c"
|
||||
|
||||
build_kinematics_module \
|
||||
linuxcnc_rotate_kinematics_wasm \
|
||||
linuxcnc_rotate_kinematics \
|
||||
"$VENDOR_DIR/src/emc/kinematics/rotatekins.c" \
|
||||
"$WRAP_DIR/linuxcnc_hal_adapter.cpp" \
|
||||
"$WRAP_DIR/linuxcnc_kinematics_wasm.c"
|
||||
|
||||
build_kinematics_module \
|
||||
linuxcnc_rose_kinematics_wasm \
|
||||
linuxcnc_rose_kinematics \
|
||||
"$VENDOR_DIR/src/emc/kinematics/rosekins.c" \
|
||||
"$WRAP_DIR/linuxcnc_hal_adapter.cpp" \
|
||||
"$WRAP_DIR/linuxcnc_kinematics_wasm.c"
|
||||
|
||||
build_kinematics_module \
|
||||
linuxcnc_max_kinematics_wasm \
|
||||
linuxcnc_max_kinematics \
|
||||
"$VENDOR_DIR/src/emc/kinematics/maxkins.c" \
|
||||
"$WRAP_DIR/linuxcnc_hal_adapter.cpp" \
|
||||
"$WRAP_DIR/linuxcnc_kinematics_wasm.c"
|
||||
|
||||
build_kinematics_module \
|
||||
linuxcnc_lineardelta_kinematics_wasm \
|
||||
linuxcnc_lineardelta_kinematics \
|
||||
"$VENDOR_DIR/src/emc/kinematics/lineardeltakins.c" \
|
||||
"$WRAP_DIR/linuxcnc_hal_adapter.cpp" \
|
||||
"$WRAP_DIR/linuxcnc_kinematics_wasm.c"
|
||||
|
||||
build_kinematics_module \
|
||||
linuxcnc_rotarydelta_kinematics_wasm \
|
||||
linuxcnc_rotarydelta_kinematics \
|
||||
"$VENDOR_DIR/src/emc/kinematics/rotarydeltakins.c" \
|
||||
"$WRAP_DIR/linuxcnc_hal_adapter.cpp" \
|
||||
"$WRAP_DIR/linuxcnc_kinematics_wasm.c"
|
||||
|
||||
build_kinematics_module \
|
||||
linuxcnc_scorbot_kinematics_wasm \
|
||||
linuxcnc_scorbot_kinematics \
|
||||
"$VENDOR_DIR/src/emc/kinematics/scorbot-kins.c" \
|
||||
"$WRAP_DIR/linuxcnc_hal_adapter.cpp" \
|
||||
"$WRAP_DIR/linuxcnc_kinematics_wasm.c"
|
||||
|
||||
build_kinematics_module \
|
||||
linuxcnc_tripod_kinematics_wasm \
|
||||
linuxcnc_tripod_kinematics \
|
||||
"$VENDOR_DIR/src/emc/kinematics/tripodkins.c" \
|
||||
"$WRAP_DIR/linuxcnc_hal_adapter.cpp" \
|
||||
"$WRAP_DIR/linuxcnc_kinematics_wasm.c"
|
||||
|
||||
build_kinematics_module \
|
||||
linuxcnc_scara_kinematics_wasm \
|
||||
linuxcnc_scara_kinematics \
|
||||
"${SWITCHKINS_COMMON_SOURCES[@]}" \
|
||||
"$VENDOR_DIR/src/emc/kinematics/scarakins.c"
|
||||
|
||||
build_kinematics_module \
|
||||
linuxcnc_puma_kinematics_wasm \
|
||||
linuxcnc_puma_kinematics \
|
||||
"${SWITCHKINS_COMMON_SOURCES[@]}" \
|
||||
"$VENDOR_DIR/src/emc/kinematics/pumakins.c" \
|
||||
"${POSEMATH_SOURCES[@]}"
|
||||
|
||||
build_kinematics_module \
|
||||
linuxcnc_genser_kinematics_wasm \
|
||||
linuxcnc_genser_kinematics \
|
||||
"${SWITCHKINS_COMMON_SOURCES[@]}" \
|
||||
"$VENDOR_DIR/src/emc/kinematics/genserfuncs.c" \
|
||||
"$VENDOR_DIR/src/emc/kinematics/genserkins.c" \
|
||||
"$VENDOR_DIR/src/libnml/posemath/gomath.c" \
|
||||
"$VENDOR_DIR/src/libnml/posemath/sincos.c"
|
||||
|
||||
build_kinematics_module \
|
||||
linuxcnc_genhex_kinematics_wasm \
|
||||
linuxcnc_genhex_kinematics \
|
||||
"${SWITCHKINS_COMMON_SOURCES[@]}" \
|
||||
"$VENDOR_DIR/src/emc/kinematics/genhexkins.c" \
|
||||
"${POSEMATH_SOURCES[@]}"
|
||||
|
||||
build_kinematics_module \
|
||||
linuxcnc_pentakins_kinematics_wasm \
|
||||
linuxcnc_pentakins_kinematics \
|
||||
"$VENDOR_DIR/src/emc/kinematics/pentakins.c" \
|
||||
"${POSEMATH_SOURCES[@]}" \
|
||||
"$WRAP_DIR/linuxcnc_hal_adapter.cpp" \
|
||||
"$WRAP_DIR/linuxcnc_kinematics_wasm.c"
|
||||
Reference in New Issue
Block a user