import assert from "node:assert/strict"; import { existsSync, statSync } from "node:fs"; import { MACHINE_DEFINITIONS } from "./machine-definitions.mjs"; import { VISUAL_BINDINGS, bindingGlbPath, bindingJointModel, hasVisualBinding, visualBinding } from "./machine-visual-bindings.mjs"; const MAX_BYTES = 64 * 1024 * 1024; assert.equal(VISUAL_BINDINGS.cases.length, MACHINE_DEFINITIONS.length, "每个案例都必须有绑定"); for (const machine of MACHINE_DEFINITIONS) { const binding = visualBinding(machine.id); assert.ok(binding, `${machine.id} 缺少绑定`); assert.equal(binding.jointCount, machine.joint_count, `${machine.id} 槽位数`); assert.equal(binding.coordinates, machine.coordinates, `${machine.id} 坐标`); const file = `web/react/public/${bindingGlbPath(machine.id)}`; assert.ok(existsSync(file), `${machine.id} GLB 缺失`); assert.ok(statSync(file).size <= MAX_BYTES, `${machine.id} GLB 超限`); } assert.ok(hasVisualBinding("xyzac-trt")); assert.ok(visualBinding("linuxcnc/configs/sim/axis/vismach/5axis/max5/max5kins.ini"), "entrypoint 查找"); assert.equal(visualBinding("no-such-case"), null); assert.equal(visualBinding("XYZAB"), null, "坐标字母不得作为键(三个案例共用)"); // bindingJointModel reports per-slot gaps instead of guessing nodes. const bridge = visualBinding("bridge-mill-5axiskins"); const nodes = bridge.bindings.map((item) => item.nodeId); const full = bindingJointModel(bridge, nodes); assert.deepEqual(full.missing, []); assert.equal(full.slotMap.length, 7); assert.equal(full.controller.jointOrder[6].key, "Y#2"); const partial = bindingJointModel(bridge, nodes.filter((id) => id !== bridge.bindings.find((item) => item.node === "w").nodeId)); assert.deepEqual(partial.missing, ["W"]); assert.equal(partial.slotMap[5], null); // Passive slots are allowed to stay unbound (max5 joint 5 has no visual motion). const max5 = visualBinding("max5-maxkins"); const max5Model = bindingJointModel(max5, max5.bindings.map((item) => item.nodeId)); assert.deepEqual(max5Model.missing, []); console.log(JSON.stringify({ schema: "machine-visual-bindings.v1", status: "verified", cases: VISUAL_BINDINGS.cases.length, checks: ["13 案例全覆盖", "槽位数与 joint_count 一致", "GLB 存在且 ≤64 MiB", "坐标字母不作键", "缺节点逐槽报告", "被动槽允许不绑定"] }));