Verify isolated joint edits and parent frame inheritance
This commit is contained in:
@@ -9,12 +9,310 @@
|
||||
},
|
||||
"checks": [
|
||||
"root and internal node transforms update their local position and posture relative to their parent",
|
||||
"MILL700 JT3 edits preserve parent and sibling transforms; child inherits movement and rotation around the joint origin; values survive reselection and dialog reopening",
|
||||
"reparenting an edited node preserves world pose and subsequent inputs edit that node under its new parent",
|
||||
"selected MILL700 joint displays its local XYZ coordinate system at local origin",
|
||||
"existing 132-node, depth-6 hierarchy displayed as a draggable tree with node origins",
|
||||
"each joint rotates about its own origin while children inherit parent motion",
|
||||
"metallic MILL700 surfaces rendered with studio PMREM lighting; screenshot captured for visual review",
|
||||
"imported model can be deleted and its tree is removed"
|
||||
],
|
||||
"nodeEditing": {
|
||||
"before": {
|
||||
"parent": {
|
||||
"local": [
|
||||
-0.003969311714172363,
|
||||
-0.20427599549293518,
|
||||
0.40563321113586426
|
||||
],
|
||||
"localQuaternion": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"world": [
|
||||
-1.7138478483851618,
|
||||
-0.515726496296395,
|
||||
1.8940227031707764
|
||||
],
|
||||
"worldQuaternion": [
|
||||
0,
|
||||
0,
|
||||
0.17364817766693033,
|
||||
0.9848077530122079
|
||||
]
|
||||
},
|
||||
"sibling": {
|
||||
"local": [
|
||||
0.0031614303588867188,
|
||||
0.12106472253799438,
|
||||
-0.2689887285232544
|
||||
],
|
||||
"localQuaternion": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"world": [
|
||||
-1.7522836493599145,
|
||||
-0.40088159702548765,
|
||||
1.625033974647522
|
||||
],
|
||||
"worldQuaternion": [
|
||||
0,
|
||||
0,
|
||||
0.17364817766693033,
|
||||
0.9848077530122079
|
||||
]
|
||||
},
|
||||
"child": {
|
||||
"local": [
|
||||
0.0013986825942993164,
|
||||
0.0009530782699584961,
|
||||
-0.8500750064849854
|
||||
],
|
||||
"localQuaternion": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"world": [
|
||||
-1.4232056214947701,
|
||||
-1.3709958977290349,
|
||||
1.4128730297088623
|
||||
],
|
||||
"worldQuaternion": [
|
||||
0,
|
||||
0,
|
||||
0.17364817766693033,
|
||||
0.9848077530122079
|
||||
]
|
||||
},
|
||||
"selected": {
|
||||
"local": [
|
||||
-0.02080368995666504,
|
||||
-0.9040489196777344,
|
||||
0.3689253330230713
|
||||
],
|
||||
"localQuaternion": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"world": [
|
||||
-1.4241939812409632,
|
||||
-1.3723698759677156,
|
||||
2.2629480361938477
|
||||
],
|
||||
"worldQuaternion": [
|
||||
0,
|
||||
0,
|
||||
0.17364817766693033,
|
||||
0.9848077530122079
|
||||
]
|
||||
}
|
||||
},
|
||||
"translated": {
|
||||
"parent": {
|
||||
"local": [
|
||||
-0.003969311714172363,
|
||||
-0.20427599549293518,
|
||||
0.40563321113586426
|
||||
],
|
||||
"localQuaternion": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"world": [
|
||||
-1.7138478483851618,
|
||||
-0.515726496296395,
|
||||
1.8940227031707764
|
||||
],
|
||||
"worldQuaternion": [
|
||||
0,
|
||||
0,
|
||||
0.17364817766693033,
|
||||
0.9848077530122079
|
||||
]
|
||||
},
|
||||
"sibling": {
|
||||
"local": [
|
||||
0.0031614303588867188,
|
||||
0.12106472253799438,
|
||||
-0.2689887285232544
|
||||
],
|
||||
"localQuaternion": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"world": [
|
||||
-1.7522836493599145,
|
||||
-0.40088159702548765,
|
||||
1.625033974647522
|
||||
],
|
||||
"worldQuaternion": [
|
||||
0,
|
||||
0,
|
||||
0.17364817766693033,
|
||||
0.9848077530122079
|
||||
]
|
||||
},
|
||||
"child": {
|
||||
"local": [
|
||||
0.0013986825942993164,
|
||||
0.0009530782699584961,
|
||||
-0.8500750064849854
|
||||
],
|
||||
"localQuaternion": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"world": [
|
||||
-1.2908934330630653,
|
||||
-1.3228381995092735,
|
||||
1.3688730297088623
|
||||
],
|
||||
"worldQuaternion": [
|
||||
0,
|
||||
0,
|
||||
0.17364817766693033,
|
||||
0.9848077530122079
|
||||
]
|
||||
},
|
||||
"selected": {
|
||||
"local": [
|
||||
0.12,
|
||||
-0.9040489196777344,
|
||||
0.3249253330230713
|
||||
],
|
||||
"localQuaternion": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"world": [
|
||||
-1.2918817928092583,
|
||||
-1.3242121777479543,
|
||||
2.2189480361938476
|
||||
],
|
||||
"worldQuaternion": [
|
||||
0,
|
||||
0,
|
||||
0.17364817766693033,
|
||||
0.9848077530122079
|
||||
]
|
||||
}
|
||||
},
|
||||
"rotated": {
|
||||
"parent": {
|
||||
"local": [
|
||||
-0.003969311714172363,
|
||||
-0.20427599549293518,
|
||||
0.40563321113586426
|
||||
],
|
||||
"localQuaternion": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"world": [
|
||||
-1.7138478483851618,
|
||||
-0.515726496296395,
|
||||
1.8940227031707764
|
||||
],
|
||||
"worldQuaternion": [
|
||||
0,
|
||||
0,
|
||||
0.17364817766693033,
|
||||
0.9848077530122079
|
||||
]
|
||||
},
|
||||
"sibling": {
|
||||
"local": [
|
||||
0.0031614303588867188,
|
||||
0.12106472253799438,
|
||||
-0.2689887285232544
|
||||
],
|
||||
"localQuaternion": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"world": [
|
||||
-1.7522836493599145,
|
||||
-0.40088159702548765,
|
||||
1.625033974647522
|
||||
],
|
||||
"worldQuaternion": [
|
||||
0,
|
||||
0,
|
||||
0.17364817766693033,
|
||||
0.9848077530122079
|
||||
]
|
||||
},
|
||||
"child": {
|
||||
"local": [
|
||||
0.0013986825942993164,
|
||||
0.0009530782699584961,
|
||||
-0.8500750064849854
|
||||
],
|
||||
"localQuaternion": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"world": [
|
||||
-1.2915667029697908,
|
||||
-1.3225492316931085,
|
||||
1.3688730297088623
|
||||
],
|
||||
"worldQuaternion": [
|
||||
0,
|
||||
0,
|
||||
0.38268343236508984,
|
||||
0.9238795325112867
|
||||
]
|
||||
},
|
||||
"selected": {
|
||||
"local": [
|
||||
0.12,
|
||||
-0.9040489196777344,
|
||||
0.3249253330230713
|
||||
],
|
||||
"localQuaternion": [
|
||||
0,
|
||||
0,
|
||||
0.21643961393810288,
|
||||
0.9762960071199334
|
||||
],
|
||||
"world": [
|
||||
-1.2918817928092583,
|
||||
-1.3242121777479543,
|
||||
2.2189480361938476
|
||||
],
|
||||
"worldQuaternion": [
|
||||
0,
|
||||
0,
|
||||
0.38268343236508984,
|
||||
0.9238795325112867
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"hierarchy": {
|
||||
"root": "MILL700-U_1",
|
||||
"nodes": 132,
|
||||
@@ -108,7 +406,7 @@
|
||||
"local": [
|
||||
0.12,
|
||||
-0.9040489196777344,
|
||||
0.3689253330230713
|
||||
0.3249253330230713
|
||||
],
|
||||
"localQuaternion": [
|
||||
0,
|
||||
@@ -119,7 +417,7 @@
|
||||
"world": [
|
||||
-1.2918817928092583,
|
||||
-1.3242121777479543,
|
||||
2.2629480361938477
|
||||
2.2189480361938476
|
||||
],
|
||||
"worldQuaternion": [
|
||||
0,
|
||||
@@ -254,7 +552,7 @@
|
||||
"local": [
|
||||
0.12,
|
||||
-0.9040489196777344,
|
||||
0.3989253330230713
|
||||
0.3549253330230713
|
||||
],
|
||||
"localQuaternion": [
|
||||
0,
|
||||
@@ -265,7 +563,7 @@
|
||||
"world": [
|
||||
-1.2893252694679127,
|
||||
-1.3019981238989793,
|
||||
2.292948036193848
|
||||
2.2489480361938474
|
||||
],
|
||||
"worldQuaternion": [
|
||||
0,
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 334 KiB After Width: | Height: | Size: 296 KiB |
@@ -48,3 +48,5 @@
|
||||
- 底座 `userData` 含完整几何及物理零点字段。
|
||||
- 运行控制面板后,X/Y/Z/A或B/C Jog 和五轴程序驱动对应部件;工件路径继续附着 C 轴,刀具长度来自 WASM。
|
||||
- 非法层级、重复角色、缺失部件、错误机型和外部资源 GLB 明确拒绝,已生效模型不改变。
|
||||
|
||||
15. 树中每个节点独立编辑相对父级的位置(mm)和 XYZ 欧拉姿态(deg)。数值输入回显节点当前局部值,旋转以自身原点为中心;子节点随动,父级和兄弟节点不变。源 GLB 保留,节点编辑保存在草稿并进入组合导出。
|
||||
|
||||
@@ -71,11 +71,14 @@
|
||||
- 当前任务:已完成。
|
||||
- 下一步:按新需求继续;本专项不重复执行。
|
||||
|
||||
## R09 / 2026-09-21 / 部件节点相对父级变换
|
||||
## R10 / 2026-09-21 / 部件节点相对父级变换
|
||||
|
||||
- 更正 R09(`63f726d`):上一轮错误地让内部节点输入移动整个 GLB 根。用户要求编辑当前选中节点,原 R09 的根节点验收不足以证明内部关节编辑正确。
|
||||
|
||||
- 用户明确要求“部件平移”和“部件旋转”修改当前选中节点相对其父节点的位置和姿态。
|
||||
- 根节点继续使用部件级 `translationMm`、`rotationDeg`;GLB 内部节点使用稳定源路径保存到 `nodeTransforms[path]`。输入值是当前节点的父级局部位置(mm)和 XYZ 欧拉姿态(deg),旋转中心是节点自身原点。
|
||||
- 预览克隆、组合导出和应用运行场景统一应用同一组局部节点变换;父节点运动时子节点继承,编辑子节点不会错误移动整个 GLB 根部件。
|
||||
- 拖放内部节点后保存当前局部姿态并重新计算新的父级局部姿态,保持拖放前的世界姿态。
|
||||
- MILL700 选择 `MILL700-A-主轴-JT3`,输入 X=120 mm、RZ=25° 后,节点局部位置和四元数分别变为 `0.120 m` 和 `25°`;8 部件 AC/BC 生产回归、导出和应用继续通过。
|
||||
- MGR-16 verified;验收证据 E11。
|
||||
- MILL700 补充断言:JT3 局部 Z 减少 44 mm 时 JT2 与兄弟滑轨不动、JT4 同步移动;RZ=25° 时 JT3 原点不动、JT4 绕该原点运动;父链预先旋转 20°,位移仍按父坐标系计算。切换选择和重开对话框保留输入;带编辑姿态的门节点换父级后世界姿态不变且能继续编辑。
|
||||
- MGR-16 verified;验收证据 E12。
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
- report_id:`GLB-MGR-E05`;报告 SHA-256 `09fd47f80dd2f741aa1d585ce5b1b40c2ff32e211b7b6e08b1b0da18327d798e`。
|
||||
- 结构:132 节点、126 网格、深度 6、126 材质,其中 110 个 `metalness >= 0.9`。
|
||||
- 检查:已有内部层级完整保留;管理器使用 `studio-pmrem`、sRGB 输出、ACES、曝光 `1.45` 和多向补光;浏览器错误为 0。
|
||||
- 截图:`coverage/glb-machine-manager/mill700/mill700-studio-lighting.png`,SHA-256 `8943b16fe774427306ccafb5dcbc9817bc87538edbf67bbd5807dd59bd700d8b`。
|
||||
- 截图:`coverage/glb-machine-manager/mill700/mill700-studio-lighting.png`,SHA-256 `98aa3915cc80255467c6be42d40da685ef43d4ed91441ec051e1830a8d5fafd4`。
|
||||
|
||||
## E06 / R04 / Z-up 坐标
|
||||
|
||||
@@ -87,12 +87,17 @@
|
||||
- 三轴箭头组的局部 position=(0,0,0)、quaternion=identity,父对象为所选 GLB 节点;X 红/Y 绿/Z 蓝且按整机尺寸缩放,表示关节自己的局部轴并继承完整父链。
|
||||
- 节点选择和轴值驱动期间 `context-generation=1`,浏览器 warning/error=0。
|
||||
|
||||
## E11 / R09 / 部件节点相对父级变换
|
||||
## E12 / R10 / 部件节点相对父级变换
|
||||
|
||||
- 替代 E11 的根节点测试结论;本轮同时验证内部关节及其父、兄弟、子节点。
|
||||
|
||||
- MILL700 命令:`PLAYWRIGHT_MODULE=/home/mes006/.npm/_npx/e41f203b7505f1fb/node_modules/playwright/index.js MACHINE_MANAGER_URL=http://127.0.0.1:5180/ node scripts/test_machine_manager_mill700_browser.mjs`。
|
||||
- MILL700 report_id:`GLB-MGR-E05`;报告 SHA-256:`d76e64bd946af2a2c5ea0b2e93230a62cd7455d547db041223ba3a77f1c8ae29`。
|
||||
- MILL700:先验证根节点 X 平移 `100 mm`、Z 旋转 `20°`;再选择内部节点 `MILL700-A-主轴-JT3`,输入相对父级 X=`120 mm`、RZ=`25°`,预览发布的 selected transform 为 local X=`0.120 m`、local quaternion 对应 `25°`;132 节点、126 网格和局部坐标系检查继续通过,浏览器 warning/error=0。
|
||||
- MILL700 report_id:`GLB-MGR-E05`;报告 SHA-256:`3555f9efe0cebba79a0bec8afbff8a4a44cc1278d0f36ff737fae9a74e18b9e1`。
|
||||
- MILL700:先验证根节点 X 平移 `100 mm`、Z 旋转 `20°`;再选择内部节点 `MILL700-A-主轴-JT3`,输入相对父级 X=`120 mm`、Z 从 `368.925 mm` 减少到 `324.925 mm`、RZ=`25°`,预览发布的 selected transform 为 local X=`0.120 m`、local quaternion 对应 `25°`;132 节点、126 网格和局部坐标系检查继续通过,浏览器 warning/error=0。
|
||||
- AC/BC 生产回归命令:`PLAYWRIGHT_MODULE=/home/mes006/.npm/_npx/e41f203b7505f1fb/node_modules/playwright/index.js MACHINE_MANAGER_SOURCE_MODULES=0 MACHINE_MANAGER_URL=http://127.0.0.1:5180/ node scripts/test_machine_manager_browser.mjs`。
|
||||
- AC/BC report_id:`GLB-MGR-E02`;报告 SHA-256:`9549e8e731803c86780b1113afda3adb78e8846626bf4b6006d8c9241ac457a9`;浏览器错误 0。
|
||||
- 8 部件验证:根部件输入 X 平移 `10 mm`、Z 旋转 `15°` 后,预览中的根节点位置和姿态变化;应用生成的 geometry userData 保存 `translationMm[0]=10`、`rotationDeg[2]=15`,随后 XYZAC 切换 XYZBC 和 BC 五轴 WASM 程序运行通过。
|
||||
- 截图:`coverage/glb-machine-manager/mill700/mill700-studio-lighting.png`,SHA-256 `d422f2f19525d772cfb15c817f943fed6da2b183cfaa3dd9fc626cabed25a365`;AC 配置 `coverage/glb-machine-manager/manager-ac-config.png`,SHA-256 `ddfc10ebc03d0c72860ce4f21a3c7e290f9c8eeaa9dcaa97ff0a1c8b3e2bc860`。
|
||||
- 截图:`coverage/glb-machine-manager/mill700/mill700-studio-lighting.png`,SHA-256 `98aa3915cc80255467c6be42d40da685ef43d4ed91441ec051e1830a8d5fafd4`;AC 配置 `coverage/glb-machine-manager/manager-ac-config.png`,SHA-256 `ddfc10ebc03d0c72860ce4f21a3c7e290f9c8eeaa9dcaa97ff0a1c8b3e2bc860`。
|
||||
|
||||
- 新增内部网格验收:mesh_0 相对父级 X=35 mm、RZ=30°;根部件仍为 X=10 mm、RZ=15°。直接解析导出 GLB 的节点 translation/rotation 并检查应用后 nodeTransforms 元数据,均一致。
|
||||
- JT3 的父、兄弟、子节点编辑前后位置与四元数记录于 MILL700 报告的 `nodeEditing`;验证重选、重开对话框、编辑后换父级和再次输入。
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
现有整机 GLB + 内嵌 URDF 导入能力继续保留。新管理器生成与现有 `linuxcnc.machine/1` 相同的严格结构,再通过已有配置、Worker、WASM 和 Three.js 投影链运行。
|
||||
|
||||
当前状态:MGR-01 至 MGR-16 全部 verified,MILL700 实测覆盖照明、Z-up、132 节点树、拖放、节点原点、关节局部坐标系、节点相对父级平移旋转、模型删除、轴绑定、XYZ/AC 层级运动和 WebGL 资源降级。最新台账 R09,证据 `GLB-MGR-E01/E02/E05/E07/E08/E09/E10/E11` 位于 `coverage/glb-machine-manager/`。
|
||||
当前状态:MGR-01 至 MGR-16 全部 verified,MILL700 实测覆盖照明、Z-up、132 节点树、拖放、节点原点、关节局部坐标系、节点相对父级平移旋转、模型删除、轴绑定、XYZ/AC 层级运动和 WebGL 资源降级。最新台账 R10,证据 `GLB-MGR-E01/E02/E05/E07/E08/E09/E10/E12` 位于 `coverage/glb-machine-manager/`。
|
||||
|
||||
文档:
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import assert from "node:assert/strict";
|
||||
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
||||
import { createHash } from "node:crypto";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import * as THREE from "../web/react/node_modules/three/build/three.module.js";
|
||||
|
||||
const modulePath = process.env.PLAYWRIGHT_MODULE;
|
||||
if (!modulePath) { console.log(JSON.stringify({ schema: "machine-manager-mill700-browser.v1", status: "blocked", code: "playwright_unavailable" })); process.exit(0); }
|
||||
@@ -27,8 +28,69 @@ try {
|
||||
await page.waitForFunction(() => document.querySelector("[data-machine-manager-preview]")?.dataset.machineManagerPartCount === "1" && document.querySelector("[data-machine-manager-preview]")?.dataset.machineManagerRendered === "true" && document.querySelectorAll('[data-axis-control="machine-tree-node"]').length >= 132, null, { timeout: 60000 });
|
||||
assert.equal(hierarchy.root, "MILL700-U_1"); assert.equal(hierarchy.nodes, 132); assert.equal(hierarchy.meshes, 126); assert.ok(hierarchy.maxDepth >= 6); assert.ok(hierarchy.metallic > 20);
|
||||
const transformPreview = page.locator("[data-machine-manager-preview]"); const beforePartTransform = JSON.parse(await transformPreview.getAttribute("data-machine-manager-selected-joint-world-origin")); await control("machine-manager-translation").locator("input").nth(0).fill("100"); await page.waitForFunction(({ before }) => { const current = JSON.parse(document.querySelector("[data-machine-manager-preview]")?.getAttribute("data-machine-manager-selected-joint-world-origin") ?? "[]"); return current.length === 3 && Math.abs(current[0] - before[0] - .1) < 1e-7; }, { before: beforePartTransform }); const beforePartRotation = JSON.parse(await transformPreview.getAttribute("data-machine-manager-selected-joint-world-quaternion")); await control("machine-manager-rotation").locator("input").nth(2).fill("20"); await page.waitForFunction(({ before }) => { const current = JSON.parse(document.querySelector("[data-machine-manager-preview]")?.getAttribute("data-machine-manager-selected-joint-world-quaternion") ?? "[]"); return current.length === 4 && Math.abs(current[3] - before[3]) > 1e-4; }, { before: beforePartRotation }); report.checks.push("root and internal node transforms update their local position and posture relative to their parent");
|
||||
assert.ok(await page.locator('[data-axis-control="machine-tree-node"]').count() >= 132); const selectedNode = page.locator('[data-axis-control="machine-tree-node"]').filter({ hasText: "MILL700-A-主轴-JT3" }).first(); await selectedNode.dispatchEvent("click"); assert.match(await control("machine-tree-origin").textContent(), /节点自身局部原点.*装配位置.*装配姿态/s); const selectedPath = await selectedNode.locator("xpath=ancestor::li[1]").getAttribute("data-machine-tree-path"), selectedPart = await selectedNode.locator("xpath=ancestor::li[1]").getAttribute("data-machine-tree-part"); await page.waitForFunction(({ selectedPart, selectedPath }) => { const preview = document.querySelector("[data-machine-manager-preview]"); return preview?.dataset.machineManagerSelectedJoint === `${selectedPart}:${selectedPath}` && preview?.dataset.machineManagerSelectedJointAxes === "local-xyz" && preview?.dataset.machineManagerTransformTarget === "none-internal-joint"; }, { selectedPart, selectedPath }); await control("machine-manager-translation").locator("input").nth(0).fill("120"); await page.waitForFunction(() => { const current = JSON.parse(document.querySelector("[data-machine-manager-preview]")?.getAttribute("data-machine-manager-selected-transform") ?? "{}"); return Math.abs(current.local?.[0] - .12) < 1e-7; }); await control("machine-manager-rotation").locator("input").nth(2).fill("25"); await page.waitForFunction(() => { const current = JSON.parse(document.querySelector("[data-machine-manager-preview]")?.getAttribute("data-machine-manager-selected-transform") ?? "{}"); return Math.abs(current.localQuaternion?.[2] - Math.sin(25 * Math.PI / 360)) < 1e-7 && Math.abs(current.localQuaternion?.[3] - Math.cos(25 * Math.PI / 360)) < 1e-7; }); const jointWorldOrigin = JSON.parse(await page.locator("[data-machine-manager-preview]").getAttribute("data-machine-manager-selected-joint-world-origin")); assert.ok(jointWorldOrigin.length === 3 && jointWorldOrigin.every(Number.isFinite)); assert.ok(Number(await page.locator("[data-machine-manager-preview]").getAttribute("data-machine-manager-selected-joint-axes-scale")) >= 1); await page.screenshot({ path: new URL("mill700-studio-lighting.png", output).pathname, timeout: 60000 });
|
||||
const source = page.locator('[data-axis-control="machine-tree-node"]').filter({ hasText: "MILL700-A-门1-JT1" }).first(), target = page.locator('[data-axis-control="machine-tree-node"]').filter({ hasText: "MILL700-A底座" }).first(); const sourceItem = source.locator("xpath=ancestor::li[1]"), targetItem = target.locator("xpath=ancestor::li[1]"), sourcePath = await sourceItem.getAttribute("data-machine-tree-path"), targetPath = await targetItem.getAttribute("data-machine-tree-path"), partId = await sourceItem.getAttribute("data-machine-tree-part"); await target.evaluate((element, payload) => { const transfer = new DataTransfer(); transfer.setData("application/x-linuxcnc-machine-tree", JSON.stringify({ partId: payload.partId, path: payload.sourcePath })); element.dispatchEvent(new DragEvent("drop", { bubbles: true, cancelable: true, dataTransfer: transfer })); }, { partId, sourcePath }); await page.waitForFunction(({ sourcePath, targetPath }) => document.querySelector(`[data-machine-tree-path="${sourcePath}"]`)?.getAttribute("data-machine-tree-parent") === targetPath, { sourcePath, targetPath }, { timeout: 60000 }); assert.equal(await page.locator("[data-machine-manager-preview]").getAttribute("data-machine-manager-context-generation"), "1");
|
||||
const readSelected = async () => JSON.parse(await transformPreview.getAttribute("data-machine-manager-selected-transform"));
|
||||
const choose = async (name) => {
|
||||
const button = control("machine-tree-node").filter({ has: page.locator("span").filter({ hasText: new RegExp(`^${name}$`) }) }).first();
|
||||
const path = await button.locator("xpath=ancestor::li[1]").getAttribute("data-machine-tree-path");
|
||||
await button.dispatchEvent("click");
|
||||
await page.waitForFunction((path) => document.querySelector("[data-machine-manager-preview]")?.dataset.machineManagerSelectedJoint?.endsWith(`:${path}`), path);
|
||||
return readSelected();
|
||||
};
|
||||
const closeVector = (actual, expected, label) => actual.forEach((value, index) => assert.ok(Math.abs(value - expected[index]) < 1e-7, `${label}[${index}]: ${value} != ${expected[index]}`));
|
||||
const sampleBranch = async () => ({
|
||||
parent: await choose("MILL700-A-主轴-JT2"),
|
||||
sibling: await choose("SHS25C100020_Track_TSXXX-0000058522002"),
|
||||
child: await choose("MILL700-A-主轴-JT4"),
|
||||
selected: await choose("MILL700-A-主轴-JT3"),
|
||||
});
|
||||
const beforeEdit = await sampleBranch();
|
||||
const translationInputs = control("machine-manager-translation").locator("input");
|
||||
for (let i = 0; i < 3; i++) assert.ok(Math.abs(Number(await translationInputs.nth(i).inputValue()) - beforeEdit.selected.local[i] * 1000) < 1e-7);
|
||||
assert.match(await control("machine-tree-origin").textContent(), /节点自身局部原点.*装配位置.*装配姿态/s);
|
||||
await translationInputs.nth(0).fill("120");
|
||||
// Screenshot complaint: move only the selected joint by -44 mm in its parent's Z direction.
|
||||
await translationInputs.nth(2).fill(String(beforeEdit.selected.local[2] * 1000 - 44));
|
||||
const translated = await sampleBranch();
|
||||
const delta = new THREE.Vector3(.12 - beforeEdit.selected.local[0], 0, -.044)
|
||||
.applyQuaternion(new THREE.Quaternion(...beforeEdit.parent.worldQuaternion));
|
||||
closeVector(translated.selected.world, new THREE.Vector3(...beforeEdit.selected.world).add(delta).toArray(), "translation follows rotated parent axes");
|
||||
closeVector(translated.child.world, new THREE.Vector3(...beforeEdit.child.world).add(delta).toArray(), "child inherits translation");
|
||||
for (const key of ["parent", "sibling"]) assert.deepEqual(translated[key], beforeEdit[key], `${key} must not move`);
|
||||
closeVector(translated.child.local, beforeEdit.child.local, "child local position unchanged");
|
||||
await control("machine-manager-rotation").locator("input").nth(2).fill("25");
|
||||
const rotated = await sampleBranch();
|
||||
closeVector(rotated.selected.localQuaternion, new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 0, 1), 25 * Math.PI / 180).toArray(), "selected local rotation");
|
||||
closeVector(rotated.selected.world, translated.selected.world, "rotation about own origin");
|
||||
for (const key of ["parent", "sibling"]) assert.deepEqual(rotated[key], beforeEdit[key], `${key} must not rotate`);
|
||||
closeVector(rotated.child.local, beforeEdit.child.local, "child local origin unchanged");
|
||||
const childWorld = new THREE.Vector3(...rotated.child.local).applyQuaternion(new THREE.Quaternion(...rotated.selected.worldQuaternion)).add(new THREE.Vector3(...rotated.selected.world));
|
||||
closeVector(rotated.child.world, childWorld.toArray(), "child inherits selected rotation");
|
||||
assert.ok(rotated.child.world.some((value, i) => Math.abs(value - translated.child.world[i]) > 1e-6));
|
||||
assert.equal(await transformPreview.getAttribute("data-machine-manager-selected-joint-axes"), "local-xyz");
|
||||
await choose("MILL700-A-主轴-JT2");
|
||||
assert.equal(Number(await control("machine-manager-rotation").locator("input").nth(2).inputValue()), 0);
|
||||
await choose("MILL700-A-主轴-JT3");
|
||||
assert.equal(Number(await translationInputs.nth(0).inputValue()), 120);
|
||||
assert.equal(Number(await control("machine-manager-rotation").locator("input").nth(2).inputValue()), 25);
|
||||
await page.keyboard.press("Escape");
|
||||
await control("machine-manager-settings").click();
|
||||
await page.waitForFunction(() => document.querySelector("[data-machine-manager-preview]")?.dataset.machineManagerRendered === "true");
|
||||
closeVector((await readSelected()).local, rotated.selected.local, "reopening preserves node edit");
|
||||
report.nodeEditing = { before: beforeEdit, translated, rotated };
|
||||
report.checks.push("MILL700 JT3 edits preserve parent and sibling transforms; child inherits movement and rotation around the joint origin; values survive reselection and dialog reopening");
|
||||
await page.screenshot({ path: new URL("mill700-studio-lighting.png", output).pathname, timeout: 60000 });
|
||||
const source = page.locator('[data-axis-control="machine-tree-node"]').filter({ hasText: "MILL700-A-门1-JT1" }).first(), target = page.locator('[data-axis-control="machine-tree-node"]').filter({ hasText: "MILL700-A底座" }).first(); await source.dispatchEvent("click");
|
||||
await control("machine-manager-rotation").locator("input").nth(2).fill("-15");
|
||||
const doorBefore = await readSelected();
|
||||
const sourceItem = source.locator("xpath=ancestor::li[1]"), targetItem = target.locator("xpath=ancestor::li[1]"), sourcePath = await sourceItem.getAttribute("data-machine-tree-path"), targetPath = await targetItem.getAttribute("data-machine-tree-path"), partId = await sourceItem.getAttribute("data-machine-tree-part"); await target.evaluate((element, payload) => { const transfer = new DataTransfer(); transfer.setData("application/x-linuxcnc-machine-tree", JSON.stringify({ partId: payload.partId, path: payload.sourcePath })); element.dispatchEvent(new DragEvent("drop", { bubbles: true, cancelable: true, dataTransfer: transfer })); }, { partId, sourcePath }); await page.waitForFunction(({ sourcePath, targetPath }) => document.querySelector(`[data-machine-tree-path="${sourcePath}"]`)?.getAttribute("data-machine-tree-parent") === targetPath, { sourcePath, targetPath }, { timeout: 60000 }); assert.equal(await page.locator("[data-machine-manager-preview]").getAttribute("data-machine-manager-context-generation"), "1");
|
||||
const doorAfter = await readSelected();
|
||||
closeVector(doorAfter.world, doorBefore.world, "reparent preserves edited world position");
|
||||
closeVector(doorAfter.worldQuaternion, doorBefore.worldQuaternion, "reparent preserves edited world orientation");
|
||||
const doorLocalX = Number(await translationInputs.nth(0).inputValue());
|
||||
await translationInputs.nth(0).fill(String(doorLocalX + 12));
|
||||
const doorEdited = await readSelected();
|
||||
closeVector(doorEdited.local, [doorAfter.local[0] + .012, ...doorAfter.local.slice(1)], "editing uses stable path after reparent");
|
||||
report.checks.push("reparenting an edited node preserves world pose and subsequent inputs edit that node under its new parent");
|
||||
report.hierarchy = hierarchy; report.checks.push("selected MILL700 joint displays its local XYZ coordinate system at local origin"); report.checks.push("existing 132-node, depth-6 hierarchy displayed as a draggable tree with node origins");
|
||||
const preview = page.locator("[data-machine-manager-preview]"); assert.equal(await preview.getAttribute("data-machine-manager-lighting"), "studio-pmrem"); assert.equal(await preview.getAttribute("data-machine-manager-coordinate-system"), "machine-z-up"); assert.equal(await preview.getAttribute("data-machine-manager-axis-map"), "X:x,Y:y,Z:z"); assert.equal(await preview.getAttribute("data-machine-manager-camera-up"), "0,0,1"); assert.equal(await preview.getAttribute("data-machine-manager-model-transform"), "none-on-import"); assert.equal(await preview.getAttribute("data-machine-manager-webgl-antialias"), "false"); assert.equal(await preview.getAttribute("data-machine-manager-render-state"), "rendered"); assert.equal(await preview.getAttribute("data-machine-manager-context-generation"), "1"); assert.equal(Number(await preview.getAttribute("data-machine-manager-scene-meshes")), 126); assert.ok(Number(await preview.getAttribute("data-machine-manager-scene-objects")) >= 132); const bounds = JSON.parse(await preview.getAttribute("data-machine-manager-bounds")); assert.ok(bounds.min.every(Number.isFinite) && bounds.max.every(Number.isFinite) && bounds.size > 0); const [bufferWidth, bufferHeight] = (await preview.getAttribute("data-machine-manager-buffer-size")).split("x").map(Number), maxBuffer = Number(await preview.getAttribute("data-machine-manager-max-buffer")); assert.ok(bufferWidth > 0 && bufferHeight > 0 && bufferWidth <= maxBuffer && bufferHeight <= maxBuffer);
|
||||
const bind = async (name, role) => { const node = page.locator('[data-axis-control="machine-tree-node"]').filter({ hasText: name }).first(); await node.dispatchEvent("click"); await control("machine-tree-axis-role").selectOption(role); };
|
||||
|
||||
Reference in New Issue
Block a user