diff --git a/scripts/test_xyzbc_panel_browser.mjs b/scripts/test_xyzbc_panel_browser.mjs index 6dfda33..9ffbce6 100644 --- a/scripts/test_xyzbc_panel_browser.mjs +++ b/scripts/test_xyzbc_panel_browser.mjs @@ -26,6 +26,8 @@ try { coordinates: document.querySelector(".xyzac-machine-view")?.dataset.xyzacModelSourcePath, canvases: document.querySelectorAll(".axis-preview-split canvas[data-engine]").length, previewHuds: document.querySelectorAll(".axis-preview-split .axis-preview-hud").length, + machineToolpathFrame: document.querySelector(".xyzac-machine-view")?.dataset.xyzacToolpathFrame, + machineToolpathAttached: document.querySelector(".xyzac-machine-view")?.dataset.xyzacToolpathAttached, })); assert.equal(result.machine, "XYZBC authoritative position"); assert.deepEqual(result.axes, ["X", "Y", "Z", "B", "C"]); @@ -35,6 +37,8 @@ try { assert.equal(result.coordinates, "linuxcnc/src/hal/user_comps/vismach/xyzbc-trt-gui.py"); assert.equal(result.canvases, 2); assert.equal(result.previewHuds, 0); + assert.equal(result.machineToolpathFrame, "rotate-c"); + assert.equal(result.machineToolpathAttached, "true"); assert.deepEqual(errors, []); - console.log(JSON.stringify({ schema: "xyzbc-panel-browser.v1", status: "verified", program: "boat-xyzbc.ngc", machine: result.model, axes: result.axes, canvases: result.canvases, preview_huds: result.previewHuds, browser_errors: errors })); + console.log(JSON.stringify({ schema: "xyzbc-panel-browser.v1", status: "verified", program: "boat-xyzbc.ngc", machine: result.model, axes: result.axes, canvases: result.canvases, preview_huds: result.previewHuds, machine_toolpath_frame: result.machineToolpathFrame, machine_toolpath_attached: result.machineToolpathAttached, browser_errors: errors })); } finally { await browser.close(); } diff --git a/web/react/src/App.tsx b/web/react/src/App.tsx index e83ee95..db610aa 100644 --- a/web/react/src/App.tsx +++ b/web/react/src/App.tsx @@ -762,7 +762,7 @@ export function App() { { setCasePath(path); void loadCase(path); }} /> -
+
{(["path", "live", "limits", "velocity", "offsets", "extents"] as const).map((layer) => )}
diff --git a/web/react/src/XyzacMachineView.tsx b/web/react/src/XyzacMachineView.tsx index eb29387..afcf4f6 100644 --- a/web/react/src/XyzacMachineView.tsx +++ b/web/react/src/XyzacMachineView.tsx @@ -131,8 +131,11 @@ export function XyzacMachineView({ visual, toolpath, view = "p", zoom = 1, rotat }, }; - const pathGroup = new THREE.Group(); pathGroup.name = "xyzac-toolpath"; scene.add(pathGroup); - const liveGroup = new THREE.Group(); liveGroup.name = "xyzac-live-toolpath"; scene.add(liveGroup); + // The programmed path represents the part fixed on the rotary cradle. + // Keep it in the table's local frame so A/B tilt and C rotation carry the + // workpiece path together with the physical table. + const pathGroup = new THREE.Group(); pathGroup.name = "xyzac-toolpath"; assembly.joints.rotate.add(pathGroup); + const liveGroup = new THREE.Group(); liveGroup.name = "xyzac-live-toolpath"; assembly.joints.rotate.add(liveGroup); const pathMaterials = [0xdce7e8, 0xff9a55, 0x5c9d9a].map((color) => new THREE.LineBasicMaterial({ color })); const clearLines = (group: THREE.Group) => { group.children.forEach((child) => (child as THREE.Line).geometry.dispose()); group.clear(); }; const limits = new THREE.Box3Helper(new THREE.Box3(), 0xdd7566); scene.add(limits); @@ -166,6 +169,8 @@ export function XyzacMachineView({ visual, toolpath, view = "p", zoom = 1, rotat if (preset !== lastPreset) { presetCamera(); lastPreset = preset; } controls.update(); renderer.render(scene, camera); element.dataset.xyzacRenderedTick = String(state.tick); + element.dataset.xyzacToolpathFrame = assembly.joints.rotate.name; + element.dataset.xyzacToolpathAttached = String(pathGroup.parent === assembly.joints.rotate && liveGroup.parent === assembly.joints.rotate); element.dataset.xyzacToolTip = JSON.stringify(assembly.toolTip.getWorldPosition(new THREE.Vector3()).toArray()); frame = requestAnimationFrame(render); }; diff --git a/web/react/src/test_xyzac_threejs.mjs b/web/react/src/test_xyzac_threejs.mjs index 6e34359..6199dd5 100644 --- a/web/react/src/test_xyzac_threejs.mjs +++ b/web/react/src/test_xyzac_threejs.mjs @@ -42,7 +42,7 @@ 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", "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 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");