28 lines
3.5 KiB
JavaScript
28 lines
3.5 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import * as THREE from "../web/react/node_modules/three/build/three.module.js";
|
|
import { driveJoint, jointZero, validateJointModel, bindJointModel, defaultDof } from "../web/react/src/machine-joints.ts";
|
|
const near = (a, b) => a.forEach((v, i) => assert.ok(Math.abs(v - b[i]) < 1e-9));
|
|
const parent = new THREE.Group(); parent.rotation.z = Math.PI / 2;
|
|
const joint = new THREE.Group(); joint.position.set(.1, .2, .3); joint.rotation.y = Math.PI / 6; parent.add(joint);
|
|
const child = new THREE.Group(); child.position.set(.4, 0, 0); joint.add(child);
|
|
const zero = jointZero(joint), childLocal = child.position.toArray();
|
|
const dof = { axis: "X", direction: "x", sign: -1 };
|
|
for (let i = 0; i < 100; i++) driveJoint(joint, zero, dof, 30, 10);
|
|
near(joint.position.toArray(), [.08, .2, .3]); near(joint.quaternion.toArray(), zero.quaternion);
|
|
parent.updateMatrixWorld(true); near(joint.getWorldPosition(new THREE.Vector3()).toArray(), [-.2, .08, .3]); near(child.position.toArray(), childLocal);
|
|
driveJoint(joint, zero, { axis: "A", direction: "z", sign: 1 }, 100, 10);
|
|
near(joint.position.toArray(), zero.positionMm.map((v) => v / 1000));
|
|
near(joint.quaternion.toArray(), new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 0, 1), Math.PI / 2).multiply(new THREE.Quaternion(...zero.quaternion)).toArray());
|
|
assert.throws(() => driveJoint(joint, zero, dof, NaN), /无效/);
|
|
const scene = new THREE.Group(); const nodes = [..."XYZAC"].map((axis) => {
|
|
const n = new THREE.Group(); n.name = axis; n.userData.linuxcncNodeId = axis; scene.add(n);
|
|
const config = defaultDof(axis); return { id: axis, name: axis, parentId: null, role: "", source: { file: "test", hash: "hash", path: axis }, zero: jointZero(n), joint: { ...config, dof: 1, type: "XYZ".includes(axis) ? "prismatic" : "revolute", unit: "XYZ".includes(axis) ? "mm" : "deg", frame: "parent" } };
|
|
});
|
|
const model = { schema: "linuxcnc.joint-model/1", coordinates: "XYZAC", units: { length: "mm", angle: "deg", gltfLength: "m" }, controller: { source: "linuxcnc-wasm.visual.joints", axisOrder: [..."XYZAC"] }, calibration: { cOriginMm: [0,0,0], tiltOffsetMm: [0,0,0], physicalZero: { x: 2, y: 0, z: 0, a: 5, b: 0, c: 0 } }, axisMap: Object.fromEntries(nodes.map((n) => [n.id, n.id])), nodes };
|
|
validateJointModel(model); const driver = bindJointModel(scene, model); driver.update({ X: 12, Y: 0, Z: 0, A: 35, C: 0 });
|
|
near(driver.state().X.localMm, [10,0,0]); near(driver.state().A.quaternion, [Math.sin(Math.PI / 12),0,0,Math.cos(Math.PI / 12)]);
|
|
const beforeInvalid = driver.state(); assert.throws(() => driver.update({ X: 999, Y: 0, Z: 0, A: 0 }), /缺少/); assert.deepEqual(driver.state(), beforeInvalid);
|
|
const reject = (mutate) => { const copy = structuredClone(model); mutate(copy); assert.throws(() => validateJointModel(copy)); };
|
|
reject((m) => { m.nodes[1].joint.axis = "X"; }); reject((m) => { m.nodes[0].joint.dof = 2; }); reject((m) => { m.nodes[0].joint.sign = 0; }); reject((m) => { m.axisMap.X = "Y"; }); reject((m) => { m.nodes[0].parentId = "X"; }); reject((m) => { m.calibration.physicalZero.z = NaN; }); reject((m) => { m.units.length = "m"; }); reject((m) => { m.nodes[0].zero.quaternion = [0,0,0,0]; });
|
|
console.log(JSON.stringify({ report_id: "GLB-MGR-E13-MATH", status: "verified", checks: ["parent-frame motion with nonidentity zero orientation", "unit/sign/physical-zero conversion", "100 updates without drift", "child inheritance", "rotation about own origin", "malformed JSON rejected", "missing live axis rejected atomically"] }));
|