96 lines
6.5 KiB
JavaScript
96 lines
6.5 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { readFile } from "node:fs/promises";
|
|
import { projectXyzacVisual, XYZAC_MACHINE_GROUPS } from "./xyzac-visual-adapter.ts";
|
|
import * as THREE from "three";
|
|
import { buildXyzacMachine, buildXyzbcMachine } from "./xyzac-machine-model.ts";
|
|
|
|
const source = await readFile(new URL("./XyzacMachineView.tsx", import.meta.url), "utf8") + await readFile(new URL("./xyzac-machine-model.ts", import.meta.url), "utf8");
|
|
const adapter = await readFile(new URL("./xyzac-visual-adapter.ts", import.meta.url), "utf8");
|
|
const snapshot = {
|
|
schema: "axis-panel-snapshot.v1",
|
|
simulation_tick: 42,
|
|
state_hash: "fnv1a64:xyzac-authoritative",
|
|
kinematics: {
|
|
schema: "wasm-xyzac-kinematics.v1",
|
|
status: "verified",
|
|
active_type: 1,
|
|
native_type: "xyzac-trt-kins",
|
|
coordinates: "XYZAC",
|
|
joint_count: 5,
|
|
linear_units: "mm",
|
|
angular_units: "deg",
|
|
pose: { x: 12, y: -5, z: 20, a: 30, b: 0, c: 45, u: 0, v: 0, w: 0 },
|
|
joints: [12, -5, 20, 30, 45],
|
|
offsets: { x_rot_point: 0, y_offset: 20, z_offset: 10, tool_offset: 25 },
|
|
generation: 2,
|
|
switch_count: 2,
|
|
state_hash: "fnv1a64:kinematics",
|
|
wasm_state_hash: "fnv1a64:wasm-kinematics",
|
|
},
|
|
visual: { active_segment_id: 7, active_progress: 0.5, pose: { x: 12, y: -5, z: 20, a: 30, c: 45 }, joints: [12, -5, 20, 30, 45], toolpath: [{ id: 7, active: true, start: { x: 0, y: 0, z: 0 }, end: { x: 12, y: -5, z: 20 }, motion_type: 1 }], machine_model: { schema: "linuxcnc-vismach-model.v1", id: "xyzac-trt", source: "linuxcnc-wasm-worker", source_path: "linuxcnc/src/hal/user_comps/vismach/xyzac-trt-gui.py", source_hash: "fnv1a64:model", coordinate_system: "XYZAC", joints: ["table-x", "saddle-y", "spindle-z", "tilt-a", "rotate-c"], components: ["base", "column"] } },
|
|
};
|
|
const visual = projectXyzacVisual(snapshot);
|
|
assert.equal(visual.schema, "xyzac-visual-state.v1");
|
|
assert.equal(visual.tick, 42);
|
|
assert.equal(visual.stateHash, snapshot.state_hash);
|
|
assert.deepEqual(visual.joints, [12, -5, 20, 30, 45]);
|
|
assert.deepEqual(visual.pose, snapshot.kinematics.pose);
|
|
assert.deepEqual(visual.offsets, snapshot.kinematics.offsets);
|
|
assert.equal(visual.activeSegmentId, 7);
|
|
assert.equal(visual.activeProgress, 0.5);
|
|
assert.equal(visual.source, "linuxcnc-wasm-worker");
|
|
assert.equal(visual.machineModel?.id, "xyzac-trt");
|
|
assert.equal(visual.machineModel?.source_hash, "fnv1a64:model");
|
|
assert.deepEqual(XYZAC_MACHINE_GROUPS, ["xyzac-machine", "table-x", "saddle-y", "spindle-z", "tilt-a", "rotate-c", "tool-offset", "xyzac-toolpath", "xyzac-live-toolpath"]);
|
|
for (const token of ["table-x", "saddle-y", "spindle-z", "tilt-a", "rotate-c", "tool-offset", "tableX.position.x = -tableXJoint * MM", "saddle.position.z = -saddleYJoint * MM", "zslide.position.y = (200 - spindleZJoint) * MM", "tilt.rotation.x = -tiltAJoint * DEG", "rotate.rotation.y = -rotateCJoint * DEG", "tool.position.y = -state.offsets.tool_offset * MM", "assembly.joints.rotate.add(pathGroup)", "assembly.joints.rotate.add(liveGroup)", "dataset.xyzacToolpathFrame", "dataset.xyzacToolpathAttached", "data-xyzac-tick", "data-xyzac-state-hash", "data-xyzac-active-segment", "data-xyzac-joints", "data-xyzac-offsets"]) assert.ok(source.includes(token), token);
|
|
for (const forbidden of [/accept\s*\(/, /postMessage\s*\(/, /_sim_/, /_malloc/, /navigator\.storage/, /HAL_SET/, /KINEMATICS_SELECT/]) assert.equal(forbidden.test(source), false, String(forbidden));
|
|
assert.ok(adapter.includes("source: \"linuxcnc-wasm-worker\""));
|
|
assert.equal(source.includes("axis-preview-hud xyzac-machine-hud"), false, "machine preview HUD must stay empty");
|
|
const close = (actual, expected) => assert.ok(Math.abs(actual - expected) < 1e-7, `${actual} != ${expected}`);
|
|
const model = buildXyzacMachine();
|
|
const xyzbcModel = buildXyzbcMachine();
|
|
assert.equal(xyzbcModel.machine.name, "xyzbc-machine");
|
|
assert.ok(xyzbcModel.machine.getObjectByName("tilt-b"));
|
|
assert.equal(xyzbcModel.machine.getObjectByName("tilt-a"), undefined);
|
|
xyzbcModel.update({ joints: [0, 0, 0, 30, 45], offsets: { x_rot_point: 0, y_rot_point: 0, z_rot_point: 0, x_offset: -20, y_offset: 0, z_offset: -15, tool_offset: 0 } });
|
|
close(xyzbcModel.joints.tilt.rotation.z, -30 * Math.PI / 180);
|
|
assert.equal(xyzbcModel.joints.tilt.rotation.y, Math.PI / 2);
|
|
xyzbcModel.dispose();
|
|
const original = structuredClone(visual);
|
|
const zero = { joints: [0, 0, 0, 0, 0], offsets: { x_rot_point: 0, y_offset: 20, z_offset: 10, tool_offset: 0 } };
|
|
const position = (node) => node.getWorldPosition(new THREE.Vector3());
|
|
model.update(zero);
|
|
close(position(model.toolTip).y, 2);
|
|
close(position(model.workOrigin).y, 2);
|
|
for (const length of [0, 15, 30, -5]) {
|
|
model.update({ ...zero, offsets: { ...zero.offsets, tool_offset: length } });
|
|
close(position(model.toolTip).y, (200 - length) * .01);
|
|
const cutter = model.machine.getObjectByName("four-flute-end-mill");
|
|
const cutterBounds = new THREE.Box3().setFromObject(cutter);
|
|
close(cutterBounds.min.y, position(model.toolTip).y);
|
|
close(cutterBounds.max.x - cutterBounds.min.x, .06);
|
|
close(position(model.machine.getObjectByName("collet-nut")).y, 2.095);
|
|
}
|
|
model.update({ ...zero, joints: [12, -5, 20, 0, 0] });
|
|
close(position(model.workOrigin).x, -.12);
|
|
close(position(model.workOrigin).y, 1.8);
|
|
close(position(model.workOrigin).z, .05);
|
|
model.update({ ...zero, joints: [0, 0, 0, 90, 45] });
|
|
close(position(model.workOrigin).y, 1.9);
|
|
close(position(model.workOrigin).z, .3);
|
|
assert.equal(model.joints.rotate.parent.name, "c-rotary-base");
|
|
assert.equal(model.machine.getObjectByName("c-drive-housing").parent, model.joints.rotate.parent);
|
|
model.update(visual);
|
|
assert.deepEqual(visual, original, "rendering must not change the WASM snapshot");
|
|
const geometrySet = new Set();
|
|
model.machine.traverse((node) => { if (node.isMesh) geometrySet.add(node.geometry); });
|
|
for (let tick = 0; tick < 100; tick++) model.update(visual);
|
|
const afterGeometrySet = new Set();
|
|
model.machine.traverse((node) => { if (node.isMesh) afterGeometrySet.add(node.geometry); });
|
|
assert.deepEqual(afterGeometrySet, geometrySet, "motion must reuse model geometry");
|
|
const disposed = new Set();
|
|
geometrySet.forEach((geometry) => geometry.addEventListener("dispose", () => disposed.add(geometry)));
|
|
model.dispose();
|
|
assert.equal(disposed.size, geometrySet.size);
|
|
console.log(JSON.stringify({ schema: "xyzac-threejs-rtcp-10.v1", status: "verified", tick: visual.tick, state_hash: visual.stateHash, groups: XYZAC_MACHINE_GROUPS, active_segment: visual.activeSegmentId, direction: { table_x: -1, saddle_y: -1, spindle_z: -1, tilt_a: "x+", rotate_c: "z+", tool_offset: "z-" }, clear_visual_nonmutating: true, source: visual.source }));
|