继续完成 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:
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");
|
||||
Reference in New Issue
Block a user