完善 xyzbc-trt 刀具偏置闭环

This commit is contained in:
mes123456
2026-07-02 09:41:10 -04:00
parent bb939644b9
commit 8fd9952dac
16 changed files with 15926 additions and 10971 deletions

View File

@@ -10758,3 +10758,75 @@ nginx: configuration file /etc/nginx/nginx.conf test is successful
17. 执行 `git status --short`,确认推送后仅剩未跟踪生成目录 `app/dist/``tools/__pycache__/` 17. 执行 `git status --short`,确认推送后仅剩未跟踪生成目录 `app/dist/``tools/__pycache__/`
18. 执行 `git log -1 --oneline``git ls-remote origin refs/heads/master`,确认本地和远端 `master` 均为 `fb128e6` 18. 执行 `git log -1 --oneline``git ls-remote origin refs/heads/master`,确认本地和远端 `master` 均为 `fb128e6`
19. 本轮结论:源码、证据和工作文档已提交并推送到云仓库;未跟踪构建产物和 Python 缓存未提交。 19. 本轮结论:源码、证据和工作文档已提交并推送到云仓库;未跟踪构建产物和 Python 缓存未提交。
## 2026-07-02 09:14:51 EDT Codex 执行过程日志
1. 接收用户要求:接续上一轮,按 `/home/mes123456/cnc_wams/web-rtcp-5axis-xyzbc-trt-sim-plan/working` 持续编写程序。
2. 读取 workspace 规则,确认每轮结束必须将中文过程日志追加到 `/home/mes123456/cnc_wams/web-rtcp-5axis-sim-plan/gptlog-process/gpdlog.md`
3. 检查目标目录文件,确认 `working` 下存在推进台账、任务矩阵、验收证据、全量对标矩阵和 evidence JSON。
4. 读取 `working/README.md``03-推进台账.md``04-任务矩阵.md`,确认当前主要缺口包含 T-014 浏览器 smoke、T-029 Vismach 3D pin 对标、T-034 tool-offset 闭环、T-019/T-037 native path 重采集。
5. 检查目标 app 源码、profile、Node smoke 和 package 脚本,确认默认 profile 已为 `xyzbc-trt`,已有 Web evidence 和 compare 脚本。
6. 读取 `five-axis-scene.js``store.js``main.js``collect-web-xyzbc-trt-evidence.mjs``linuxcnc-task-hal-runtime.js``tool-db-simulation.js`,确认 Three.js 机床参考模型仍是静态几何,尚未按 Vismach HAL pin 层级驱动。
7. 新增 `app/src/runtime/vismach-model-state.js`,统一生成 `table-x/saddle-y/spindle-z/tilt-b/rotate-c/tool-offset/x-offset/z-offset` pin 状态、模型变换、HAL net 对标关系和语义边界。
8. 修改 `app/src/visualization/five-axis-scene.js`,把参考模型拆成 `vismach-table-x``vismach-saddle-y``vismach-spindle-z``vismach-tilt-b``vismach-rotate-c``vismach-tool-offset` 层级,并在渲染时由 `buildVismachModelState()` 驱动位移和旋转。
9. 在 canvas dataset 中新增 `threeVismachPins``threeVismachTransforms``threeVismachPinDrivenModel`,供浏览器 smoke 验证 WebGL 模型和 HAL pin 语义。
10. 修改 `tools/collect-web-xyzbc-trt-evidence.mjs`,将 `vismachEquivalent` 从静态 pin 清单升级为包含 `pinValues``transforms``halNets``semanticBoundary` 的 evidence 字段。
11. 扩展 `tests/node/verify_xyzbc_trt_web_app.mjs`,增加 profile feedback/offset net 断言,以及 Vismach pin 到模型变换的 smoke 断言。
12. 运行 `npm --prefix web-rtcp-5axis-xyzbc-trt-sim-plan/app run smoke:node`,结果通过,输出 `xyzbc_trt_web_app_smoke=ok`
13. 运行 `npm --prefix web-rtcp-5axis-xyzbc-trt-sim-plan/app run evidence:web`,重新生成 `working/evidence/web-xyzbc-trt-evidence.json`
14. 抽查 Web evidence确认 `status=ready-for-wasm-runtime``blockers=[]``previewPath.sampleCount=3074``executionPath.sampleCount=569`,并确认 `vismachEquivalent` 已包含 pin 值、变换和 9 条 HAL net。
15. 运行 `npm --prefix web-rtcp-5axis-xyzbc-trt-sim-plan/app run evidence:compare`,重新生成 compare JSON结果为真实失败 `fail_count=5`,失败原因仍是 native `previewPath`/`executionPath` 样本和采样周期字段缺失。
16. 运行 `npm --prefix web-rtcp-5axis-xyzbc-trt-sim-plan/app run build`,结果通过,输出 `gmoccapy_static_build=ok`
17. 更新 `working/03-推进台账.md``04-任务矩阵.md``05-验收证据.md``07-全量对标追踪矩阵.md`,记录 T-029 完成、Web evidence 字段和剩余 native path 缺口。
18. 检查已有浏览器 smoke发现从参考项目复制的脚本仍指向 `web-rtcp-5axis-sim-plan`,并包含大量 `xyzac-trt` 假设,不适合作为当前目标项目 T-014 验收。
19. 新增 `tests/browser/xyzbc_trt_browser_smoke.html`,实现精简的 `xyzbc-trt` 浏览器验收:默认 profile、kinematics/interpreter/task-HAL worker runtime、默认程序预览、canvas 非空和 Vismach dataset。
20. 新增 `tests/browser/verify_xyzbc_trt_browser.sh`,通过临时 HTTP server 和 headless Chromium 加载 `app/dist/index.html`,执行浏览器 smoke。
21. 修改 `app/package.json`,增加 `smoke:browser` 脚本。
22. 执行 `chmod +x tests/browser/verify_xyzbc_trt_browser.sh`,然后运行 `npm --prefix web-rtcp-5axis-xyzbc-trt-sim-plan/app run smoke:browser`,结果通过,输出 `xyzbc_trt_browser_smoke=ok`
23. 再次更新 `working/03-推进台账.md``04-任务矩阵.md``05-验收证据.md``07-全量对标追踪矩阵.md`,将 T-014 标记为完成,并记录浏览器 smoke 验收内容。
24. 执行 `git diff --check -- web-rtcp-5axis-xyzbc-trt-sim-plan`,未发现空白错误。
25. 执行 `git status --short web-rtcp-5axis-xyzbc-trt-sim-plan`,确认本轮修改包含 app/package、Vismach runtime、Three.js 场景、Web evidence 脚本、Node/browser smoke、working 文档和 evidence JSON未跟踪的 `app/dist/``tools/__pycache__/` 为已有/生成物,未清理。
26. 本轮结论T-029 Vismach 3D pin 对标和 T-014 目标浏览器 smoke 已完成并通过验证compare 剩余失败仍来自 native path evidence 未重采集,下一步应推进 T-019/T-037 或 T-034。
27. 按前端应用规则尝试启动开发服务器:先运行 `npm --prefix web-rtcp-5axis-xyzbc-trt-sim-plan/app run dev`,端口 4174 已被占用,启动失败。
28. 尝试在 app 目录运行 `python3 -m http.server 4175`,端口 4175 也已被占用。
29. 通过 Python 端口探测找到 4176 可用。
30.`/home/mes123456/cnc_wams/web-rtcp-5axis-xyzbc-trt-sim-plan/app` 运行 `python3 -m http.server 4176`,服务器已启动,访问地址为 `http://127.0.0.1:4176/`
## 2026-07-02 Codex 执行过程日志 - T-034 tool-offset 闭环
1. 接收用户要求:接续上一轮,按 `/home/mes123456/cnc_wams/web-rtcp-5axis-xyzbc-trt-sim-plan/working` 持续编写程序。
2. 检查目标项目 `working` 目录、任务矩阵和推进台账,确认上一轮已完成 T-014 浏览器 smoke 与 T-029 Vismach pin 驱动模型,当前可继续推进 T-034 tool table 到 tool-offset 闭环。
3. 执行 `git status --short`,确认工作区已有上一轮未提交改动,包括 `app/package.json``five-axis-scene.js`、browser smoke、evidence JSON 和 working 文档;本轮在这些成果基础上继续,不回退用户或上一轮改动。
4. 读取 `app/src/runtime/tool-db-simulation.js`,确认已有 LinuxCNC tool table 解析、T/M6/G43/M61 命令模拟、activeToolOffset 和 interpreter parameter 5400-5413 生成能力。
5. 读取 `app/src/runtime/vismach-model-state.js`,确认 Vismach tool-offset 当时优先读取 `toolDbSimulation.activeToolOffset.offset.z`,否则回退 `toolPreview.length`
6. 读取 `tools/collect-web-xyzbc-trt-evidence.mjs`,确认 Web path 样本 tool 字段仍使用 `firstTool(profile)`,没有从 staged `xyzbc-trt.tbl` 和 active G43 偏置生成。
7. 读取 `wasm-port/vendor/linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.tbl`,确认真实工具表包含 `T1 P1 D1``T2 P2 Z10 D8`
8. 读取默认程序 `xyzbc_switchkins.ngc`,确认该程序调用子程序但自身没有 T/M6/G43因此本轮设计为 evidence 同时记录程序扫描命令和工具表默认激活证据,避免伪造程序命令。
9. 修改 `app/src/runtime/tool-db-simulation.js`,新增 `createToolRuntimeState()`统一输出当前工具、active offset、`motion.tooloffset.z` 等效值、下游 `xyzbc-trt-kins.tool-offset``xyzbc-trt-gui.tool-offset`、path tool 和 Vismach tool-offset。
10. 修改 `app/src/runtime/vismach-model-state.js`,让 `toolRuntimeState.vismach.toolOffset` 优先于旧的 `toolPreview.length`,使 Web 3D 模型刀具长度来自 active tool offset。
11. 修改 `tools/collect-web-xyzbc-trt-evidence.mjs`,引入 tool DB 解析和命令扫描逻辑,从 staged `xyzbc-trt.tbl` 创建 tool DB并扫描选定程序中的 T/M6/G43。
12. 在默认程序无工具命令时,`collect-web-xyzbc-trt-evidence.mjs` 选择工具表中第一个带 Z 偏置的工具 T2生成 T/M6/G43 默认激活证据,并记录 `defaultActivationUsed=true`
13. 修改 Web path 采集逻辑,使 `previewPath.samples[].tool``executionPath.samples[].tool` 使用 `toolRuntime.pathTool`,即 T2/P2/Z10/D8。
14. 在 Web evidence 中新增 `toolRuntime``toolOffsetClosure` 字段,记录 source tool table、selected program、programToolCommands、activeToolOffset、kinematicsToolOffsetZ、pathTool、vismachToolOffset 和 closed 状态。
15. 扩展 `coverage.toolTableToToolOffsetClosed`,要求 tool table ready、active offset applied、kinematics/path/Vismach 三方 tool length 一致才通过。
16. 修改 `tests/node/verify_xyzbc_trt_web_app.mjs`,断言 staged `xyzbc-trt.tbl` 包含 `T2 P2 Z10 D8`
17. 扩展 Node smoke使用 T2/M6/G43 创建 tool runtime state并断言 active tool number=2、pocket=2、current tool Z=10、diameter=8、kinematics toolOffsetZ=10、path tool length=10、Vismach toolOffset=10。
18. 扩展 Vismach smoke 断言,确认 `buildVismachModelState()` 在传入 toolRuntimeState 后输出 `pinValues.tool-offset=10`tool transform Z 为 `-25`
19. 运行 `npm --prefix app run smoke:node`,结果通过,输出 `xyzbc_trt_web_app_smoke=ok`
20. 运行 `npm --prefix app run evidence:web`,重新生成 `working/evidence/web-xyzbc-trt-evidence.json`
21. 抽查 Web evidence确认 `toolRuntime.ready=true``activeOffsetApplied=true``activeToolNumber=2``activePocket=2``kinematics.toolOffsetZ=10``pathTool.length=10``pathTool.diameter=8``vismach.toolOffset=10``toolOffsetClosure.closed=true``coverage.toolTableToToolOffsetClosed=true`
22. 运行 `npm --prefix app run evidence:compare`,重新生成 compare evidence仍为真实失败 `fail_count=5`,失败原因仍集中在 native preview/execution path 样本缺失。
23. 运行 `npm --prefix app run build`,结果通过,输出 `gmoccapy_static_build=ok`
24. 运行 `npm --prefix app run smoke:browser`,结果通过,输出 `xyzbc_trt_browser_smoke=ok`
25. 更新 `working/04-任务矩阵.md`,将 T-034 从“待实现”改为“完成”,验收标准改为 T2/P2/Z10/D8 驱动 kinematics、path tool 和 Vismach tool-offset。
26. 更新 `working/05-验收证据.md`,新增 “Tool table 到 tool-offset 闭环验收” 章节,记录 smoke、evidence、compare、build、browser smoke 命令和 JSON 摘要。
27. 更新 `working/03-推进台账.md`,新增本轮 T-034 实现记录、修改文件、验证结果和下一步。
28. 更新 `working/07-全量对标追踪矩阵.md`,把 `xyzbc-trt.tbl``tool-offset/x-offset/z-offset` 的状态改为 Web evidence 已完成 tool table active offset 闭环native 仍待重采集对比。
29. 修改 `tools/compare-xyzbc-trt-evidence.mjs`,新增 compare 检查 `web tool table active offset drives kinematics, path, and Vismach`,使 T-034 不只停留在 Web evidence而是进入自动对比报告。
30. 重跑 `npm --prefix app run evidence:compare`,确认新增 T-034 检查通过compare 摘要变为 `checkCount=24``passCount=19``failCount=5`
31. 重跑 `npm --prefix app run smoke:node`,结果仍通过。
32. 更新 `working/04-任务矩阵.md` 中 T-017 摘要为当前 `19/24` 通过,剩余 5 项均为 native preview/execution path 样本缺失。
33. 更新 `working/05-验收证据.md``working/03-推进台账.md`,记录 compare 当前 19/24 通过以及 T-034 compare 检查通过。
34. 执行 `git diff --check -- web-rtcp-5axis-xyzbc-trt-sim-plan`,未发现空白或格式错误。
35. 执行 `git status --short``git diff --stat -- web-rtcp-5axis-xyzbc-trt-sim-plan`,确认本轮改动包含 tool DB runtime、Vismach state、Web evidence、compare、Node smoke、working 文档和 evidence JSON未跟踪的 `app/dist/`、browser smoke 文件、Vismach state 文件等为上一轮/当前未提交工作区内容,未删除。
36. 本轮结论T-034 已完成Web evidence 与 compare 均证明 `xyzbc-trt.tbl` 的 T2/P2/Z10/D8 已闭环驱动 kinematics tool-offset、path JSON 和 Vismach tool-offset剩余主要任务仍是 T-019/T-037 native path 重采集、T-026/T-038 AXIS/UI 状态流复核、T-028/T-030 basic_sim 与 Ngcgui/remap 执行验收。

View File

@@ -8,6 +8,7 @@
"dev": "python3 -m http.server 4174", "dev": "python3 -m http.server 4174",
"smoke": "npm run smoke:node", "smoke": "npm run smoke:node",
"smoke:node": "node ../tests/node/verify_xyzbc_trt_web_app.mjs", "smoke:node": "node ../tests/node/verify_xyzbc_trt_web_app.mjs",
"smoke:browser": "bash ../tests/browser/verify_xyzbc_trt_browser.sh",
"evidence:web": "node ../tools/collect-web-xyzbc-trt-evidence.mjs", "evidence:web": "node ../tools/collect-web-xyzbc-trt-evidence.mjs",
"evidence:compare": "node ../tools/compare-xyzbc-trt-evidence.mjs" "evidence:compare": "node ../tools/compare-xyzbc-trt-evidence.mjs"
}, },

View File

@@ -553,6 +553,78 @@ export function createToolDbReadiness(toolDb) {
}; };
} }
export function createToolRuntimeState(toolDb, options = {}) {
if (!toolDb) {
return {
apiName: "web-rtcp-5axis-tool-runtime-state",
ready: false,
activeOffsetApplied: false,
toolInSpindle: 0,
toolFromPocket: 0,
currentPocket: 0,
activeToolNumber: 0,
activePocket: 0,
currentTool: null,
activeToolOffset: null,
kinematics: {
toolOffsetZ: 0,
source: "tool_db_not_ready",
},
pathTool: createPathTool(null, 0),
vismach: {
toolOffset: finiteNumber(options.fallbackToolLength, 0),
source: "fallback_tool_preview_length",
},
interpreterParameters: createToolParameters(null),
semanticBoundary: "linuxcnc_current_tool_offset_runtime_state_for_web_simulation",
};
}
const state = cloneToolDb(toolDb);
const activeOffset = state.activeToolOffset
? {
...state.activeToolOffset,
offset: { ...state.activeToolOffset.offset },
}
: null;
const currentTool = state.toolTableCurrent
? cloneToolEntry(state.toolTableCurrent)
: cloneToolEntry(state.toolTable.pockets[0] || createEmptyToolEntry({ idx: 0, pocket: 0, toolNumber: 0 }));
const activeToolNumber = activeOffset?.toolNumber
?? (currentTool.toolNumber > 0 ? currentTool.toolNumber : 0);
const activePocket = activeOffset?.pocket
?? (currentTool.toolNumber > 0 ? currentTool.pocket : 0);
const activeOffsetZ = activeOffset ? finiteNumber(activeOffset.offset?.z, 0) : 0;
return {
apiName: "web-rtcp-5axis-tool-runtime-state",
ready: state.processReady === true,
activeOffsetApplied: Boolean(activeOffset),
toolInSpindle: state.toolInSpindle,
toolFromPocket: state.toolFromPocket,
currentPocket: state.currentPocket,
activeToolNumber,
activePocket,
currentTool,
activeToolOffset: activeOffset,
kinematics: {
toolOffsetZ: activeOffsetZ,
source: activeOffset ? "activeToolOffset.offset.z" : "no_active_g43_tool_length_offset",
halPin: "motion.tooloffset.z",
downstreamPins: ["xyzbc-trt-kins.tool-offset", "xyzbc-trt-gui.tool-offset"],
},
pathTool: createPathTool(activeOffset || currentTool, activeOffsetZ),
vismach: {
toolOffset: activeOffset
? activeOffsetZ
: finiteNumber(options.fallbackToolLength, 0),
source: activeOffset ? "activeToolOffset.offset.z" : "fallback_tool_preview_length",
},
interpreterParameters: { ...state.interpreterParameters },
semanticBoundary: "linuxcnc_current_tool_offset_runtime_state_for_web_simulation",
};
}
export function detectToolDbStorageCapability(globalScope = globalThis) { export function detectToolDbStorageCapability(globalScope = globalThis) {
const forcedUnavailable = Boolean(globalScope.__WEB_RTCP_FORCE_OPFS_UNAVAILABLE__); const forcedUnavailable = Boolean(globalScope.__WEB_RTCP_FORCE_OPFS_UNAVAILABLE__);
const isBrowser = typeof globalScope.window === "object" || typeof globalScope.document === "object"; const isBrowser = typeof globalScope.window === "object" || typeof globalScope.document === "object";
@@ -855,6 +927,23 @@ function createToolParameters(activeOffset) {
return parameters; return parameters;
} }
function createPathTool(tool, length) {
return {
id: Number(tool?.toolNumber) || 0,
pocket: Number(tool?.pocket) || 0,
length: finiteNumber(length, 0),
diameter: finiteNumber(tool?.diameter, 0),
};
}
function finiteNumber(...values) {
for (const value of values) {
const number = Number(value);
if (Number.isFinite(number)) return number;
}
return 0;
}
function createToolDbEvent(code, payload = {}) { function createToolDbEvent(code, payload = {}) {
return { return {
apiName: "web-rtcp-5axis-tool-db-event", apiName: "web-rtcp-5axis-tool-db-event",

View File

@@ -0,0 +1,111 @@
const DEFAULT_LINEAR_UNITS = "mm";
export const XYZBC_TRT_VISMACH_PINS = [
"table-x",
"saddle-y",
"spindle-z",
"tilt-b",
"rotate-c",
"tool-offset",
"x-offset",
"z-offset",
];
export function buildVismachModelState(state = {}) {
const profile = state.profile || {};
const axisPose = resolveAxisPose(state);
const offsets = profile.offsets || {};
const toolOffset = resolveToolOffset(state);
const xOffset = finiteNumber(offsets.x, -20);
const zOffset = finiteNumber(offsets.z, -15);
const linearUnits = resolveLinearUnits(state);
return {
apiName: "web-rtcp-5axis-vismach-model-state",
profileId: profile.id || state.machineProfile || null,
sourceGui: "src/hal/user_comps/vismach/xyzbc-trt-gui.py",
webModel: "app/src/visualization/five-axis-scene.js",
linearUnits,
pins: {
"table-x": finiteNumber(axisPose.x, 0),
"saddle-y": finiteNumber(axisPose.y, 0),
"spindle-z": finiteNumber(axisPose.z, 0),
"tilt-b": finiteNumber(axisPose.b, 0),
"rotate-c": finiteNumber(axisPose.c, 0),
"tool-offset": toolOffset,
"x-offset": xOffset,
"z-offset": zOffset,
},
transforms: {
table: {
translate: { x: finiteNumber(axisPose.x, 0), y: 0, z: 0 },
sourcePins: ["table-x"],
},
saddle: {
translate: { x: 0, y: finiteNumber(axisPose.y, 0), z: 0 },
sourcePins: ["saddle-y"],
},
spindle: {
translate: { x: 0, y: 0, z: finiteNumber(axisPose.z, 0) },
sourcePins: ["spindle-z"],
},
tilt: {
rotateDeg: { x: 0, y: finiteNumber(axisPose.b, 0), z: 0 },
sourcePins: ["tilt-b"],
},
rotary: {
rotateDeg: { x: 0, y: 0, z: finiteNumber(axisPose.c, 0) },
sourcePins: ["rotate-c"],
},
tool: {
translate: { x: xOffset, y: 0, z: zOffset - toolOffset },
sourcePins: ["tool-offset", "x-offset", "z-offset"],
},
},
halNets: [
{ signal: "table-x", source: "joint.0.pos-fb", target: "xyzbc-trt-gui.table-x" },
{ signal: "saddle-y", source: "joint.1.pos-fb", target: "xyzbc-trt-gui.saddle-y" },
{ signal: "spindle-z", source: "joint.2.pos-fb", target: "xyzbc-trt-gui.spindle-z" },
{ signal: "tilt-b", source: "joint.3.pos-fb", target: "xyzbc-trt-gui.tilt-b" },
{ signal: "rotate-c", source: "joint.4.pos-fb", target: "xyzbc-trt-gui.rotate-c" },
{ signal: "tool-offset", source: "motion.tooloffset.z", target: "xyzbc-trt-kins.tool-offset" },
{ signal: "tool-offset", source: "xyzbc-trt-kins.tool-offset", target: "xyzbc-trt-gui.tool-offset" },
{ signal: "x-offset", source: "xyzbc-trt-kins.x-offset", target: "xyzbc-trt-gui.x-offset" },
{ signal: "z-offset", source: "xyzbc-trt-kins.z-offset", target: "xyzbc-trt-gui.z-offset" },
],
clearTraceSignal: "pyvcp.vismach-clear => vismach.plotclear",
semanticBoundary: "web_threejs_vismach_equivalent_driven_by_xyzbc_trt_hal_pins",
};
}
function resolveAxisPose(state) {
if (state.programRuntimeFeedback?.axisPose) return state.programRuntimeFeedback.axisPose;
if (state.taskHalStatus?.ui?.axisPose) return state.taskHalStatus.ui.axisPose;
return state.axisPose || {};
}
function resolveToolOffset(state) {
if (Number.isFinite(Number(state.toolRuntimeState?.vismach?.toolOffset))) {
return Number(state.toolRuntimeState.vismach.toolOffset);
}
return finiteNumber(
state.toolDbSimulation?.activeToolOffset?.offset?.z,
state.toolPreview?.length,
0,
);
}
function resolveLinearUnits(state) {
return state.linuxCncIniConfig?.traj?.linearUnits
|| state.programRuntimeFeedback?.linearUnits
|| state.programExecutionTiming?.linearUnits
|| DEFAULT_LINEAR_UNITS;
}
function finiteNumber(...values) {
for (const value of values) {
const number = Number(value);
if (Number.isFinite(number)) return number;
}
return 0;
}

View File

@@ -5,6 +5,7 @@ import {
linearValueToMeters, linearValueToMeters,
resolveStateLinearUnits, resolveStateLinearUnits,
} from "../runtime/linear-units.js"; } from "../runtime/linear-units.js";
import { buildVismachModelState } from "../runtime/vismach-model-state.js";
const scenes = new WeakMap(); const scenes = new WeakMap();
const CAMERA_PRESETS = { const CAMERA_PRESETS = {
@@ -62,6 +63,7 @@ export function renderFiveAxisScene(canvas, state) {
preview.executedPath.geometry, preview.executedPath.geometry,
preview.currentSegmentPath.geometry, preview.currentSegmentPath.geometry,
]), ]),
vismachModel: preview.currentVismachModelState,
}); });
} }
@@ -123,6 +125,7 @@ function createScene(canvas) {
toolMarker, toolMarker,
toolAxis, toolAxis,
currentToolhead: new THREE.Vector3(), currentToolhead: new THREE.Vector3(),
currentVismachModelState: null,
lastSelectedView: null, lastSelectedView: null,
lastCameraRevision: null, lastCameraRevision: null,
lastFitKey: null, lastFitKey: null,
@@ -177,6 +180,7 @@ function renderFallbackPreview(preview, state) {
currentSegmentPointCount: currentSegmentPoints.length, currentSegmentPointCount: currentSegmentPoints.length,
pathFitBounds: computePointBounds(previewPoints.concat(executedPoints, currentSegmentPoints)) !== null, pathFitBounds: computePointBounds(previewPoints.concat(executedPoints, currentSegmentPoints)) !== null,
pathBounds: summarizeBounds(computePointBounds(previewPoints.concat(executedPoints, currentSegmentPoints))), pathBounds: summarizeBounds(computePointBounds(previewPoints.concat(executedPoints, currentSegmentPoints))),
vismachModel: buildVismachModelState(state),
}); });
canvas.dataset.threeFallbackReason = preview.errorMessage; canvas.dataset.threeFallbackReason = preview.errorMessage;
@@ -268,6 +272,9 @@ function exposePreviewDataset(canvas, state, preview) {
canvas.dataset.threeArcPathPoints = String(preview.arcPointCount || 0); canvas.dataset.threeArcPathPoints = String(preview.arcPointCount || 0);
canvas.dataset.threeCurrentSegmentPoints = String(preview.currentSegmentPointCount || 0); canvas.dataset.threeCurrentSegmentPoints = String(preview.currentSegmentPointCount || 0);
canvas.dataset.threeCurrentSegmentType = currentSegmentType(state); canvas.dataset.threeCurrentSegmentType = currentSegmentType(state);
canvas.dataset.threeVismachPins = JSON.stringify(preview.vismachModel?.pins || {});
canvas.dataset.threeVismachTransforms = JSON.stringify(preview.vismachModel?.transforms || {});
canvas.dataset.threeVismachPinDrivenModel = preview.vismachModel?.semanticBoundary || "none";
} }
function createLine(color, opacity) { function createLine(color, opacity) {
@@ -291,6 +298,19 @@ function createMachineReferenceModel() {
); );
base.position.z = -0.016; base.position.z = -0.016;
const tableAxis = new THREE.Group();
tableAxis.name = "vismach-table-x";
const saddleAxis = new THREE.Group();
saddleAxis.name = "vismach-saddle-y";
const spindleAxis = new THREE.Group();
spindleAxis.name = "vismach-spindle-z";
const tiltAxis = new THREE.Group();
tiltAxis.name = "vismach-tilt-b";
const rotaryAxis = new THREE.Group();
rotaryAxis.name = "vismach-rotate-c";
const toolOffsetAxis = new THREE.Group();
toolOffsetAxis.name = "vismach-tool-offset";
const table = new THREE.Mesh( const table = new THREE.Mesh(
new THREE.BoxGeometry(0.37, 0.235, 0.005), new THREE.BoxGeometry(0.37, 0.235, 0.005),
new THREE.MeshBasicMaterial({ color: 0x3a444d, transparent: true, opacity: 0.78 }), new THREE.MeshBasicMaterial({ color: 0x3a444d, transparent: true, opacity: 0.78 }),
@@ -329,9 +349,21 @@ function createMachineReferenceModel() {
cutter.position.z = 0.008; cutter.position.z = 0.008;
toolHolder.add(holderBody, cutter); toolHolder.add(holderBody, cutter);
root.add(base, table, xAxis, yAxis, zAxis, rotaryA, rotaryC, toolHolder); toolOffsetAxis.add(toolHolder);
spindleAxis.add(toolOffsetAxis);
rotaryAxis.add(table, rotaryC);
tiltAxis.add(rotaryA, rotaryAxis);
saddleAxis.add(tiltAxis);
tableAxis.add(saddleAxis);
root.add(base, xAxis, yAxis, zAxis, tableAxis, spindleAxis);
return { return {
root, root,
tableAxis,
saddleAxis,
spindleAxis,
tiltAxis,
rotaryAxis,
toolOffsetAxis,
rotaryA, rotaryA,
rotaryC, rotaryC,
toolHolder, toolHolder,
@@ -410,15 +442,23 @@ function updateToolExecutionMarker(preview, state, toolPosition) {
function updateMachineReferenceModel(preview, state, toolPosition) { function updateMachineReferenceModel(preview, state, toolPosition) {
const model = preview.machineModel; const model = preview.machineModel;
if (!model) return; if (!model) return;
const a = degreesToRadians(state.axisPose?.a); const vismach = buildVismachModelState(state);
const b = degreesToRadians(state.axisPose?.b); preview.currentVismachModelState = summarizeVismachModelStateForDataset(vismach);
const c = degreesToRadians(state.axisPose?.c); const toMeters = (value) => linearValueToMeters(value, vismach.linearUnits);
model.rotaryA.rotation.x = a; model.tableAxis.position.x = toMeters(vismach.transforms.table.translate.x);
model.rotaryA.rotation.y = Math.PI / 2 + b; model.saddleAxis.position.y = toMeters(vismach.transforms.saddle.translate.y);
model.rotaryC.rotation.z = c; model.spindleAxis.position.z = toMeters(vismach.transforms.spindle.translate.z);
model.tiltAxis.rotation.y = degreesToRadians(vismach.transforms.tilt.rotateDeg.y);
model.rotaryAxis.rotation.z = degreesToRadians(vismach.transforms.rotary.rotateDeg.z);
model.toolOffsetAxis.position.set(
toMeters(vismach.transforms.tool.translate.x),
0,
toMeters(vismach.transforms.tool.translate.z),
);
model.rotaryA.rotation.y = Math.PI / 2;
model.rotaryC.rotation.x = Math.PI / 2;
const tcpPosition = toolPosition || toPreviewVector(state.tcpPose || state.axisPose, state); const tcpPosition = toolPosition || toPreviewVector(state.tcpPose || state.axisPose, state);
model.toolHolder.position.copy(tcpPosition);
const toolVector = toToolVector(state.toolAxisVector); const toolVector = toToolVector(state.toolAxisVector);
model.toolHolder.lookAt(tcpPosition.clone().add(toolVector)); model.toolHolder.lookAt(tcpPosition.clone().add(toolVector));
} }
@@ -652,6 +692,26 @@ function summarizeBounds(bounds) {
}; };
} }
function summarizeVismachModelStateForDataset(vismach) {
const summarizeVector = (vector = {}) => ({
x: round(vector.x),
y: round(vector.y),
z: round(vector.z),
});
return {
...vismach,
pins: Object.fromEntries(Object.entries(vismach.pins).map(([key, value]) => [key, round(value)])),
transforms: {
table: { ...vismach.transforms.table, translate: summarizeVector(vismach.transforms.table.translate) },
saddle: { ...vismach.transforms.saddle, translate: summarizeVector(vismach.transforms.saddle.translate) },
spindle: { ...vismach.transforms.spindle, translate: summarizeVector(vismach.transforms.spindle.translate) },
tilt: { ...vismach.transforms.tilt },
rotary: { ...vismach.transforms.rotary },
tool: { ...vismach.transforms.tool, translate: summarizeVector(vismach.transforms.tool.translate) },
},
};
}
function drawFallbackPolyline(ctx, points, cx, cy, scale) { function drawFallbackPolyline(ctx, points, cx, cy, scale) {
for (let index = 0; index < points.length; index += 1) { for (let index = 0; index < points.length; index += 1) {
const point = points[index]; const point = points[index];

View File

@@ -0,0 +1,78 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "$0")/../../.." && pwd)"
PROJECT_DIR="$ROOT_DIR/web-rtcp-5axis-xyzbc-trt-sim-plan"
CHROMIUM="${CHROMIUM:-$(command -v chromium || command -v chromium-browser || command -v google-chrome || command -v google-chrome-stable || true)}"
if [[ -z "$CHROMIUM" ]]; then
echo "missing Chromium-compatible browser; set CHROMIUM=/path/to/browser" >&2
exit 1
fi
npm --prefix "$PROJECT_DIR/app" run build >/dev/null
TMP_DIR="$(mktemp -d)"
PORT_FILE="$TMP_DIR/port"
SERVER_LOG="$TMP_DIR/server.log"
CHROME_PROFILE="$TMP_DIR/chrome-profile"
mkdir -p "$CHROME_PROFILE"
cleanup() {
if [[ -n "${SERVER_PID:-}" ]]; then
kill "$SERVER_PID" 2>/dev/null || true
wait "$SERVER_PID" 2>/dev/null || true
fi
rm -rf "$TMP_DIR"
}
trap cleanup EXIT
python3 - <<'PY' "$ROOT_DIR" "$PORT_FILE" >"$SERVER_LOG" 2>&1 &
import functools
import http.server
import pathlib
import socketserver
import sys
root = pathlib.Path(sys.argv[1])
port_file = pathlib.Path(sys.argv[2])
handler = functools.partial(http.server.SimpleHTTPRequestHandler, directory=str(root))
with socketserver.TCPServer(("127.0.0.1", 0), handler) as httpd:
port_file.write_text(str(httpd.server_address[1]), encoding="ascii")
httpd.serve_forever()
PY
SERVER_PID=$!
for _ in $(seq 1 100); do
[[ -s "$PORT_FILE" ]] && break
sleep 0.05
done
if [[ ! -s "$PORT_FILE" ]]; then
echo "xyzbc-trt browser smoke HTTP server did not start" >&2
cat "$SERVER_LOG" >&2 || true
exit 1
fi
PORT="$(cat "$PORT_FILE")"
APP_PATH="../../app/dist/index.html"
URL="http://127.0.0.1:$PORT/web-rtcp-5axis-xyzbc-trt-sim-plan/tests/browser/xyzbc_trt_browser_smoke.html?app=$APP_PATH"
OUT="$TMP_DIR/chromium-xyzbc-trt.out"
"$CHROMIUM" \
--headless=new \
--disable-gpu \
--no-sandbox \
--user-data-dir="$CHROME_PROFILE" \
--virtual-time-budget=15000 \
--dump-dom \
"$URL" >"$OUT" 2>&1
if ! grep -Fq "xyzbc_trt_browser_smoke=ok" "$OUT"; then
echo "xyzbc-trt browser smoke failed" >&2
sed -n '1,260p' "$OUT" >&2
exit 1
fi
echo "xyzbc_trt_browser_smoke=ok"

View File

@@ -0,0 +1,180 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>xyzbc-trt browser smoke</title>
</head>
<body>
<pre id="result">xyzbc_trt_browser_smoke=pending</pre>
<iframe id="app-frame" title="xyzbc-trt app"></iframe>
<script type="module">
const result = document.querySelector("#result");
const frame = document.querySelector("#app-frame");
const appSrc = new URLSearchParams(window.location.search).get("app") || "../../app/index.html";
frame.src = appSrc;
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
async function runSmoke() {
await new Promise((resolve, reject) => {
frame.addEventListener("load", resolve, { once: true });
frame.addEventListener("error", reject, { once: true });
});
const win = frame.contentWindow;
const doc = frame.contentDocument;
const runtimeErrors = [];
win.addEventListener("error", (event) => {
runtimeErrors.push(event.message || String(event.error || "window error"));
});
win.addEventListener("unhandledrejection", (event) => {
runtimeErrors.push(event.reason?.message || String(event.reason || "unhandled rejection"));
});
await waitFor(() => win.webRtcp5AxisSimulation, "public simulation API");
const api = win.webRtcp5AxisSimulation;
const [kinematics, interpreter, taskHal] = await Promise.all([
api.kinematicsRuntimeReady,
api.interpreterRuntimeReady,
api.taskHalRuntimeReady,
]);
assertRuntime(kinematics, {
name: "kinematics",
moduleId: "xyzbc-trt",
required: ["loaded"],
});
if (kinematics.executionContext !== "worker") {
throw new Error(`kinematics runtime should run in worker: ${JSON.stringify(kinematics)}`);
}
assertRuntime(interpreter, {
name: "interpreter",
required: ["loaded", "runProgramReady", "plannerRuntimeReady"],
});
if (interpreter.executionContext !== "worker") {
throw new Error(`interpreter runtime should run in worker: ${JSON.stringify(interpreter)}`);
}
assertRuntime(taskHal, {
name: "task/HAL",
required: ["loaded", "taskRuntimeReady", "motionRuntimeReady", "halRuntimeReady"],
});
if (taskHal.executionContext !== "worker") {
throw new Error(`task/HAL runtime should run in worker: ${JSON.stringify(taskHal)}`);
}
await api.machineFileSeedReady;
await waitFor(() => {
const state = api.getState();
return !state.interpreterExecutionPending
&& state.programExecution?.sourceMode === "linuxcnc-interpreter-wasm"
&& state.machineFileStaging?.status === "staged";
}, "default xyzbc program preview");
const state = api.getState();
if (state.machineProfile !== "xyzbc-trt" || state.profile?.traj?.coordinates !== "XYZBC") {
throw new Error(`default profile mismatch: ${state.machineProfile}/${state.profile?.traj?.coordinates}`);
}
if (state.profile?.kinematicsModuleId !== "xyzbc-trt") {
throw new Error(`profile kinematics module mismatch: ${state.profile?.kinematicsModuleId}`);
}
if (state.machineFileStaging?.profileId !== "xyzbc-trt") {
throw new Error(`staging profile mismatch: ${state.machineFileStaging?.profileId}`);
}
if (!state.machineFileStaging?.gcodeSources?.some((source) => source.filename === "xyzbc_switchkins.ngc")) {
throw new Error("staged sources missing xyzbc_switchkins.ngc");
}
if (!state.machineFileStaging?.gcodeSources?.some((source) => source.filename === "boat-xyzbc.ngc")) {
throw new Error("staged sources missing boat-xyzbc.ngc");
}
if (state.programExecution?.summary?.motionEventCount < 1) {
throw new Error(`default program did not produce canonical motion: ${JSON.stringify(state.programExecution?.summary)}`);
}
const canvas = await waitFor(() => doc.querySelector("[data-five-axis-canvas]"), "preview canvas");
await waitFor(() => canvas.dataset.threeReady === "true" && Number(canvas.dataset.threePathPoints || 0) > 0, "Three.js dataset");
if (
canvas.dataset.threeRenderer !== "webgl" ||
canvas.dataset.threeFrameApi !== "web-rtcp-5axis-motion-frame" ||
canvas.dataset.threeSceneMode !== "program-preview-and-tool-execution" ||
canvas.dataset.threeMachineReferenceModel !== "webgl-five-axis-reference" ||
canvas.dataset.threePreviewScope !== "machine-reference-and-toolpath" ||
canvas.dataset.threeProgramPreviewSource !== "linuxcnc-interpreter-wasm" ||
canvas.dataset.threeVismachPinDrivenModel !== "web_threejs_vismach_equivalent_driven_by_xyzbc_trt_hal_pins"
) {
throw new Error(`unexpected canvas dataset: ${JSON.stringify(canvas.dataset)}`);
}
if (Number(canvas.dataset.threeSceneObjects || 0) < 12) {
throw new Error(`scene object count too low: ${canvas.dataset.threeSceneObjects}`);
}
const vismachPins = JSON.parse(canvas.dataset.threeVismachPins || "{}");
for (const pin of ["table-x", "saddle-y", "spindle-z", "tilt-b", "rotate-c", "tool-offset", "x-offset", "z-offset"]) {
if (!Number.isFinite(Number(vismachPins[pin]))) {
throw new Error(`missing Vismach pin ${pin}: ${JSON.stringify(vismachPins)}`);
}
}
const vismachTransforms = JSON.parse(canvas.dataset.threeVismachTransforms || "{}");
if (
vismachTransforms.table?.sourcePins?.[0] !== "table-x" ||
vismachTransforms.saddle?.sourcePins?.[0] !== "saddle-y" ||
vismachTransforms.spindle?.sourcePins?.[0] !== "spindle-z" ||
vismachTransforms.tilt?.sourcePins?.[0] !== "tilt-b" ||
vismachTransforms.rotary?.sourcePins?.[0] !== "rotate-c" ||
!vismachTransforms.tool?.sourcePins?.includes("tool-offset")
) {
throw new Error(`Vismach transform source pins mismatch: ${JSON.stringify(vismachTransforms)}`);
}
assertCanvasNonblank(canvas);
if (runtimeErrors.length > 0) {
throw new Error(`runtime errors: ${runtimeErrors.join(" | ")}`);
}
result.textContent = "xyzbc_trt_browser_smoke=ok";
}
function assertRuntime(readiness, { name, moduleId = null, required }) {
for (const key of required) {
if (readiness?.[key] !== true) {
throw new Error(`${name} runtime missing ${key}: ${JSON.stringify(readiness)}`);
}
}
if (moduleId && readiness?.moduleId !== moduleId) {
throw new Error(`${name} runtime module mismatch: ${JSON.stringify(readiness)}`);
}
}
async function waitFor(predicate, label) {
for (let attempt = 0; attempt < 240; attempt += 1) {
const value = predicate();
if (value) return value;
await wait(50);
}
throw new Error(`timed out waiting for ${label}`);
}
function assertCanvasNonblank(canvas) {
const context = canvas.getContext("webgl2") || canvas.getContext("webgl") || canvas.getContext("2d");
if (!context) {
throw new Error("preview canvas has no readable context");
}
const width = canvas.width;
const height = canvas.height;
if (width <= 0 || height <= 0) {
throw new Error(`preview canvas has invalid size ${width}x${height}`);
}
if (context.readPixels) {
const pixels = new Uint8Array(4);
context.readPixels(Math.floor(width / 2), Math.floor(height / 2), 1, 1, context.RGBA, context.UNSIGNED_BYTE, pixels);
if (pixels.some((value) => value !== 0)) return;
}
const imageData = canvas.toDataURL("image/png");
if (!imageData || imageData.length < 2000) {
throw new Error("preview canvas appears blank");
}
}
runSmoke().catch((error) => {
result.textContent = `xyzbc_trt_browser_smoke=fail ${error.stack || error.message || error}`;
});
</script>
</body>
</html>

View File

@@ -17,6 +17,11 @@ import {
import { getFiveAxisProfile } from "../../app/src/profiles/index.js"; import { getFiveAxisProfile } from "../../app/src/profiles/index.js";
import { createSimulationStore } from "../../app/src/state/store.js"; import { createSimulationStore } from "../../app/src/state/store.js";
import { createLinuxCncTaskHalSdk } from "../../../wasm-port/runtime/sdk/src/linuxcnc-task-hal.js"; import { createLinuxCncTaskHalSdk } from "../../../wasm-port/runtime/sdk/src/linuxcnc-task-hal.js";
import { buildVismachModelState, XYZBC_TRT_VISMACH_PINS } from "../../app/src/runtime/vismach-model-state.js";
import {
applyToolCommandSequence,
createToolRuntimeState,
} from "../../app/src/runtime/tool-db-simulation.js";
const profile = getFiveAxisProfile(); const profile = getFiveAxisProfile();
@@ -30,6 +35,19 @@ assert.equal(profile.machineFileStaging.defaultProgramFilename, "xyzbc_switchkin
assert.equal(profile.panelSchema.id, "xyzbc-trt-switchkins-pyvcp"); assert.equal(profile.panelSchema.id, "xyzbc-trt-switchkins-pyvcp");
assert.ok(profile.halPins.includes("xyzbc-trt-kins.x-offset")); assert.ok(profile.halPins.includes("xyzbc-trt-kins.x-offset"));
assert.ok(profile.halPins.includes("motion.switchkins-type")); assert.ok(profile.halPins.includes("motion.switchkins-type"));
assert.deepEqual(profile.hal.halcmd.feedbackNets.map((net) => net.signal), [
"table-x",
"saddle-y",
"spindle-z",
"tilt-b",
"rotate-c",
]);
assert.deepEqual(profile.hal.halcmd.offsetNets.map((net) => net.target), [
"xyzbc-trt-kins.tool-offset",
"xyzbc-trt-gui.tool-offset",
"xyzbc-trt-gui.x-offset",
"xyzbc-trt-gui.z-offset",
]);
assert.deepEqual(profile.samplePrograms, [ assert.deepEqual(profile.samplePrograms, [
"configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/xyzbc_switchkins.ngc", "configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/xyzbc_switchkins.ngc",
"configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/boat-xyzbc.ngc", "configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/boat-xyzbc.ngc",
@@ -80,6 +98,7 @@ assert.ok(staged.save.files.some((file) => file.sourceRel.endsWith("remap_subs/h
assert.ok(staged.save.gcodeSources.some((source) => source.filename === "xyzbc_switchkins.ngc")); assert.ok(staged.save.gcodeSources.some((source) => source.filename === "xyzbc_switchkins.ngc"));
assert.ok(staged.save.gcodeSources.some((source) => source.filename === "boat-xyzbc.ngc")); assert.ok(staged.save.gcodeSources.some((source) => source.filename === "boat-xyzbc.ngc"));
assert.equal(staged.save.files.some((file) => file.sourceRel.endsWith("xyzbc.var") && file.kind === "parameters"), true); assert.equal(staged.save.files.some((file) => file.sourceRel.endsWith("xyzbc.var") && file.kind === "parameters"), true);
assert.equal(staged.save.files.find((file) => file.sourceRel.endsWith("xyzbc-trt.tbl")).text.includes("T2 P2 Z10 D8"), true);
const selectedPlan = selectMachineFileProgram( const selectedPlan = selectMachineFileProgram(
staged.plan, staged.plan,
@@ -145,6 +164,58 @@ assert.equal(state.sessionName, "xyzbc-trt-web-session");
assert.equal(state.kinematicsRuntimeReadiness, null); assert.equal(state.kinematicsRuntimeReadiness, null);
const storeStage = await store.stageMachineFiles({ storage: createMemorySessionStorage() }); const storeStage = await store.stageMachineFiles({ storage: createMemorySessionStorage() });
const toolOffsetDb = applyToolCommandSequence(store.getState().toolDbSimulation, [
{ code: "T", toolNumber: 2 },
{ code: "M6" },
{ code: "G43", h: 2 },
]);
const toolRuntimeState = createToolRuntimeState(toolOffsetDb, {
fallbackToolLength: state.toolPreview.length,
});
assert.equal(toolRuntimeState.ready, true);
assert.equal(toolRuntimeState.activeOffsetApplied, true);
assert.equal(toolRuntimeState.activeToolNumber, 2);
assert.equal(toolRuntimeState.activePocket, 2);
assert.equal(toolRuntimeState.currentTool.offset.z, 10);
assert.equal(toolRuntimeState.currentTool.diameter, 8);
assert.equal(toolRuntimeState.kinematics.toolOffsetZ, 10);
assert.equal(toolRuntimeState.kinematics.halPin, "motion.tooloffset.z");
assert.deepEqual(toolRuntimeState.kinematics.downstreamPins, [
"xyzbc-trt-kins.tool-offset",
"xyzbc-trt-gui.tool-offset",
]);
assert.equal(toolRuntimeState.pathTool.id, 2);
assert.equal(toolRuntimeState.pathTool.pocket, 2);
assert.equal(toolRuntimeState.pathTool.length, 10);
assert.equal(toolRuntimeState.pathTool.diameter, 8);
assert.equal(toolRuntimeState.vismach.toolOffset, 10);
const vismachState = buildVismachModelState({
...state,
axisPose: { x: 12.5, y: -6.25, z: 3.75, b: 35, c: -90 },
toolPreview: { ...state.toolPreview, length: 84.019 },
toolRuntimeState,
});
assert.deepEqual(Object.keys(vismachState.pins), XYZBC_TRT_VISMACH_PINS);
assert.equal(vismachState.pins["table-x"], 12.5);
assert.equal(vismachState.pins["saddle-y"], -6.25);
assert.equal(vismachState.pins["spindle-z"], 3.75);
assert.equal(vismachState.pins["tilt-b"], 35);
assert.equal(vismachState.pins["rotate-c"], -90);
assert.equal(vismachState.pins["x-offset"], -20);
assert.equal(vismachState.pins["z-offset"], -15);
assert.equal(vismachState.pins["tool-offset"], 10);
assert.deepEqual(vismachState.transforms.table.sourcePins, ["table-x"]);
assert.deepEqual(vismachState.transforms.saddle.sourcePins, ["saddle-y"]);
assert.deepEqual(vismachState.transforms.spindle.sourcePins, ["spindle-z"]);
assert.deepEqual(vismachState.transforms.tilt.sourcePins, ["tilt-b"]);
assert.deepEqual(vismachState.transforms.rotary.sourcePins, ["rotate-c"]);
assert.deepEqual(vismachState.transforms.tool.sourcePins, ["tool-offset", "x-offset", "z-offset"]);
assert.equal(vismachState.transforms.tool.translate.x, -20);
assert.equal(vismachState.transforms.tool.translate.z, -25);
assert.equal(vismachState.halNets.length, 9);
assert.equal(vismachState.semanticBoundary, "web_threejs_vismach_equivalent_driven_by_xyzbc_trt_hal_pins");
const stagedState = store.getState(); const stagedState = store.getState();
assert.equal(storeStage.save.profileId, "xyzbc-trt"); assert.equal(storeStage.save.profileId, "xyzbc-trt");
assert.equal(stagedState.machineFileStaging.profileId, "xyzbc-trt"); assert.equal(stagedState.machineFileStaging.profileId, "xyzbc-trt");

View File

@@ -8,8 +8,16 @@ import {
selectMachineFileProgram, selectMachineFileProgram,
stageProfileMachineFiles, stageProfileMachineFiles,
} from "../app/src/runtime/linuxcnc-machine-file-staging.js"; } from "../app/src/runtime/linuxcnc-machine-file-staging.js";
import {
applyToolCommandSequence,
createToolDbSimulation,
createToolRuntimeState,
extractToolCommandSequenceFromProgram,
parseLinuxCncToolTable,
} from "../app/src/runtime/tool-db-simulation.js";
import { createSimulationStore } from "../app/src/state/store.js"; import { createSimulationStore } from "../app/src/state/store.js";
import { getFiveAxisProfile } from "../app/src/profiles/index.js"; import { getFiveAxisProfile } from "../app/src/profiles/index.js";
import { buildVismachModelState } from "../app/src/runtime/vismach-model-state.js";
const SAMPLE_PERIOD_MS = 20; const SAMPLE_PERIOD_MS = 20;
const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../.."); const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
@@ -42,7 +50,13 @@ const paths = await collectPathEvidence({
selectedPlan, selectedPlan,
wasmArtifacts, wasmArtifacts,
}); });
const semanticFields = buildSemanticFields({ profile, ini, staged, state }); const toolRuntime = buildToolRuntimeEvidence({
profile,
staged,
selectedPlan,
fallbackToolLength: state.toolPreview?.length,
});
const semanticFields = buildSemanticFields({ profile, ini, staged, state, toolRuntime });
const evidence = { const evidence = {
apiName: "xyzbc-trt-web-opfs-wasm-evidence", apiName: "xyzbc-trt-web-opfs-wasm-evidence",
@@ -110,6 +124,7 @@ const evidence = {
pathSampling: createPathSampling(), pathSampling: createPathSampling(),
previewPath: paths.previewPath, previewPath: paths.previewPath,
executionPath: paths.executionPath, executionPath: paths.executionPath,
toolRuntime,
...semanticFields, ...semanticFields,
wasm: wasmArtifacts, wasm: wasmArtifacts,
coverage: { coverage: {
@@ -137,6 +152,11 @@ const evidence = {
&& Boolean(semanticFields.axisJointLimits.axisLimits.C), && Boolean(semanticFields.axisJointLimits.axisLimits.C),
previewPathAvailable: paths.previewPath.sampleCount > 0, previewPathAvailable: paths.previewPath.sampleCount > 0,
executionPathAvailable: paths.executionPath.sampleCount > 0, executionPathAvailable: paths.executionPath.sampleCount > 0,
toolTableToToolOffsetClosed: toolRuntime.ready
&& toolRuntime.toolTable.toolCount > 0
&& toolRuntime.activeOffsetApplied
&& toolRuntime.kinematics.toolOffsetZ === toolRuntime.pathTool.length
&& toolRuntime.vismach.toolOffset === toolRuntime.pathTool.length,
wasmArtifactsReady: wasmArtifacts.ready, wasmArtifactsReady: wasmArtifacts.ready,
}, },
blockers: [ blockers: [
@@ -222,11 +242,16 @@ async function collectPathEvidence({ profile, staged, selectedPlan, wasmArtifact
files: staged.save.files, files: staged.save.files,
executionMode: "fiveAxisRemap", executionMode: "fiveAxisRemap",
}); });
const previewPath = pathFromWebMotion(execution, profile); const toolRuntime = buildToolRuntimeEvidence({
profile,
staged,
selectedPlan,
});
const previewPath = pathFromWebMotion(execution, profile, toolRuntime.pathTool);
return { return {
previewPath, previewPath,
executionPath: wasmArtifacts.ready executionPath: wasmArtifacts.ready
? await pathFromTaskHalExecution({ profile, staged, selectedPlan, execution }) ? await pathFromTaskHalExecution({ profile, staged, selectedPlan, execution, toolRuntime })
: emptyPath("web-task-hal", "missing task/HAL WASM runtime artifacts"), : emptyPath("web-task-hal", "missing task/HAL WASM runtime artifacts"),
}; };
} catch (error) { } catch (error) {
@@ -237,7 +262,7 @@ async function collectPathEvidence({ profile, staged, selectedPlan, wasmArtifact
} }
} }
function pathFromWebMotion(execution, profile) { function pathFromWebMotion(execution, profile, pathTool = null) {
const plannerSamples = execution.plannerTiming?.samples || []; const plannerSamples = execution.plannerTiming?.samples || [];
const motion = execution.motion || []; const motion = execution.motion || [];
const motionByIndex = new Map(motion.map((event, index) => [index, event])); const motionByIndex = new Map(motion.map((event, index) => [index, event]));
@@ -252,7 +277,7 @@ function pathFromWebMotion(execution, profile) {
motionType: motionTypeFromCanonical(sample.type || event.type), motionType: motionTypeFromCanonical(sample.type || event.type),
activeKinematics: activeKinematics(event), activeKinematics: activeKinematics(event),
axes, axes,
tool: firstTool(profile), tool: pathTool || firstTool(profile),
feed: sample.currentVelocityMmPerMin ?? event.feedRate ?? 0, feed: sample.currentVelocityMmPerMin ?? event.feedRate ?? 0,
spindle: 0, spindle: 0,
}); });
@@ -311,7 +336,7 @@ function interpolatePlannerSample(left, right, timeMs) {
}; };
} }
async function pathFromTaskHalExecution({ profile, staged, selectedPlan, execution }) { async function pathFromTaskHalExecution({ profile, staged, selectedPlan, execution, toolRuntime = null }) {
try { try {
const { createLinuxCncTaskHalSdk } = await import("../../wasm-port/runtime/sdk/src/linuxcnc-task-hal.js"); const { createLinuxCncTaskHalSdk } = await import("../../wasm-port/runtime/sdk/src/linuxcnc-task-hal.js");
const { const {
@@ -377,7 +402,7 @@ async function pathFromTaskHalExecution({ profile, staged, selectedPlan, executi
switchkinsType: status.ui?.switchkinsType ?? motion.switchkinsType, switchkinsType: status.ui?.switchkinsType ?? motion.switchkinsType,
}), }),
axes: xyzbcAxesFromTaskHalStatus(status), axes: xyzbcAxesFromTaskHalStatus(status),
tool: firstTool(profile), tool: toolRuntime?.pathTool || firstTool(profile),
feed: status.ui?.currentVelocity ?? Number(motion.currentVel || motion.currentVelocity || 0) * 60, feed: status.ui?.currentVelocity ?? Number(motion.currentVel || motion.currentVelocity || 0) * 60,
spindle: spindleFromTaskHalStatus(status), spindle: spindleFromTaskHalStatus(status),
}); });
@@ -409,7 +434,7 @@ async function pathFromTaskHalExecution({ profile, staged, selectedPlan, executi
} }
} }
function buildSemanticFields({ profile, ini, staged, state }) { function buildSemanticFields({ profile, ini, staged, state, toolRuntime }) {
const postguiNets = [ const postguiNets = [
{ signal: "kinstype.is-0", source: "kinstype.is-0", target: "pyvcp.multilabel.0.legend0", boundary: "postgui-hal" }, { signal: "kinstype.is-0", source: "kinstype.is-0", target: "pyvcp.multilabel.0.legend0", boundary: "postgui-hal" },
{ signal: "kinstype.is-1", source: "kinstype.is-1", target: "pyvcp.multilabel.0.legend1", boundary: "postgui-hal" }, { signal: "kinstype.is-1", source: "kinstype.is-1", target: "pyvcp.multilabel.0.legend1", boundary: "postgui-hal" },
@@ -425,6 +450,10 @@ function buildSemanticFields({ profile, ini, staged, state }) {
...(profile.hal?.halcmd?.offsetNets || []), ...(profile.hal?.halcmd?.offsetNets || []),
].filter(Boolean).map((net) => ({ ...net, boundary: "ini-halcmd" })); ].filter(Boolean).map((net) => ({ ...net, boundary: "ini-halcmd" }));
const gcodeFiles = staged.save.gcodeFiles || []; const gcodeFiles = staged.save.gcodeFiles || [];
const vismachModelState = buildVismachModelState({
...state,
toolRuntimeState: toolRuntime,
});
return { return {
startupSequence: [ startupSequence: [
@@ -460,6 +489,8 @@ function buildSemanticFields({ profile, ini, staged, state }) {
zRotPoint: profile.offsets?.zRotPoint, zRotPoint: profile.offsets?.zRotPoint,
conventionalDirections: profile.offsets?.conventionalDirections, conventionalDirections: profile.offsets?.conventionalDirections,
toolOffsetSource: "motion.tooloffset.z", toolOffsetSource: "motion.tooloffset.z",
toolOffsetValue: toolRuntime.kinematics.toolOffsetZ,
toolOffsetToolNumber: toolRuntime.activeToolNumber,
pins: profile.halPins, pins: profile.halPins,
}, },
axisJointLimits: { axisJointLimits: {
@@ -498,17 +529,26 @@ function buildSemanticFields({ profile, ini, staged, state }) {
vismachEquivalent: { vismachEquivalent: {
sourceGui: "src/hal/user_comps/vismach/xyzbc-trt-gui.py", sourceGui: "src/hal/user_comps/vismach/xyzbc-trt-gui.py",
webModel: "app/src/visualization/five-axis-scene.js", webModel: "app/src/visualization/five-axis-scene.js",
pins: [ pins: Object.keys(vismachModelState.pins),
"table-x", pinValues: vismachModelState.pins,
"saddle-y", transforms: vismachModelState.transforms,
"spindle-z", halNets: vismachModelState.halNets,
"tilt-b",
"rotate-c",
"tool-offset",
"x-offset",
"z-offset",
],
clearTraceSignal: "pyvcp.vismach-clear => vismach.plotclear", clearTraceSignal: "pyvcp.vismach-clear => vismach.plotclear",
semanticBoundary: vismachModelState.semanticBoundary,
},
toolOffsetClosure: {
sourceToolTable: toolRuntime.toolTable.sourceRel,
selectedProgramSourceRel: toolRuntime.selectedProgramSourceRel,
programToolCommands: toolRuntime.programToolCommands,
activeToolOffset: toolRuntime.activeToolOffset,
kinematicsToolOffsetZ: toolRuntime.kinematics.toolOffsetZ,
pathTool: toolRuntime.pathTool,
vismachToolOffset: toolRuntime.vismach.toolOffset,
closed: toolRuntime.ready
&& toolRuntime.activeOffsetApplied
&& toolRuntime.kinematics.toolOffsetZ === toolRuntime.pathTool.length
&& toolRuntime.vismach.toolOffset === toolRuntime.pathTool.length,
semanticBoundary: "tool_table_current_t_p_z_d_drives_kinematics_path_and_vismach",
}, },
ngcguiSubroutines: ["xyzbc_switchkins_sub.ngc", "centering.ngc", "helix_bc.ngc"].map((filename) => ({ ngcguiSubroutines: ["xyzbc_switchkins_sub.ngc", "centering.ngc", "helix_bc.ngc"].map((filename) => ({
filename, filename,
@@ -524,6 +564,58 @@ function buildSemanticFields({ profile, ini, staged, state }) {
}; };
} }
function buildToolRuntimeEvidence({ profile, staged, selectedPlan, fallbackToolLength = 0 }) {
const toolTableFile = staged.save.files.find((file) => file.sourceRel === profile.toolTablePath)
|| staged.save.files.find((file) => file.kind === "toolTable");
const programFile = staged.save.files.find((file) => (
file.sourceRel === selectedPlan.selectedProgramSourceRel
|| (file.wasmPath || file.path) === selectedPlan.wasmProgramPath
));
const toolTable = parseLinuxCncToolTable(toolTableFile?.text || "", {
sourceRel: toolTableFile?.sourceRel || profile.toolTablePath,
path: toolTableFile?.wasmPath || toolTableFile?.path || null,
});
const programToolCommands = extractToolCommandSequenceFromProgram(programFile?.text || "");
const commands = programToolCommands.length > 0
? programToolCommands
: defaultToolActivationCommands(toolTable);
const toolDb = applyToolCommandSequence(createToolDbSimulation({
toolTable,
profile,
storageMode: staged.save.storageMode,
}), commands);
const runtimeState = createToolRuntimeState(toolDb, { fallbackToolLength });
return {
...runtimeState,
selectedProgramSourceRel: selectedPlan.selectedProgramSourceRel,
toolTable: {
sourceRel: toolTable.sourceRel,
toolCount: toolTable.toolCount,
entries: toolTable.entries.map((entry) => ({
idx: entry.idx,
toolNumber: entry.toolNumber,
pocket: entry.pocket,
z: entry.offset.z,
diameter: entry.diameter,
})),
},
programToolCommands,
appliedToolCommands: commands,
defaultActivationUsed: programToolCommands.length === 0,
};
}
function defaultToolActivationCommands(toolTable) {
const preferred = toolTable.entries.find((entry) => entry.offset.z !== 0)
|| toolTable.entries.find((entry) => entry.toolNumber > 0);
if (!preferred) return [];
return [
{ code: "T", toolNumber: preferred.toolNumber, source: "default-tool-table-activation" },
{ code: "M6", source: "default-tool-table-activation" },
{ code: "G43", h: preferred.toolNumber, toolNumber: preferred.toolNumber, source: "default-tool-table-activation" },
];
}
function createPathSampling() { function createPathSampling() {
return { return {
samplePeriodMs: SAMPLE_PERIOD_MS, samplePeriodMs: SAMPLE_PERIOD_MS,

View File

@@ -37,6 +37,17 @@ const checks = [
check("tool-table", "web tool table staged", webEvidence.coverage?.toolTableStaged === true, { check("tool-table", "web tool table staged", webEvidence.coverage?.toolTableStaged === true, {
toolTable: webEvidence.ini?.toolTable, toolTable: webEvidence.ini?.toolTable,
}), }),
check("tool-table", "web tool table active offset drives kinematics, path, and Vismach", webEvidence.coverage?.toolTableToToolOffsetClosed === true, {
toolRuntime: {
activeToolNumber: webEvidence.toolRuntime?.activeToolNumber,
activePocket: webEvidence.toolRuntime?.activePocket,
kinematicsToolOffsetZ: webEvidence.toolRuntime?.kinematics?.toolOffsetZ,
pathTool: webEvidence.toolRuntime?.pathTool,
vismachToolOffset: webEvidence.toolRuntime?.vismach?.toolOffset,
defaultActivationUsed: webEvidence.toolRuntime?.defaultActivationUsed,
},
toolOffsetClosure: webEvidence.toolOffsetClosure,
}),
check("pyvcp", "web PyVCP XML staged", webEvidence.coverage?.pyvcpXmlStaged === true, { check("pyvcp", "web PyVCP XML staged", webEvidence.coverage?.pyvcpXmlStaged === true, {
pyvcpXmlPath: webEvidence.profile?.pyvcpXmlPath, pyvcpXmlPath: webEvidence.profile?.pyvcpXmlPath,
}), }),

View File

@@ -1,5 +1,108 @@
# 03-推进台账 # 03-推进台账
## 2026-07-02 Tool table 到 tool-offset 闭环实现轮次
### 本轮目标
接续 `working` 任务矩阵,完成 T-034`xyzbc-trt.tbl` 的 T/P/Z/D 当前刀具偏置闭环接到 kinematics tool-offset、Web 模型刀长和 path JSON。
### 已做事项
- 修改 `app/src/runtime/tool-db-simulation.js`
- 新增 `createToolRuntimeState()`统一输出当前刀具、active G43 偏置、`motion.tooloffset.z` 等效值、path tool 和 Vismach tool-offset。
- 运行时状态记录 `xyzbc-trt-kins.tool-offset``xyzbc-trt-gui.tool-offset` 下游 pin 边界。
- 修改 `app/src/runtime/vismach-model-state.js`
- `toolRuntimeState.vismach.toolOffset` 优先于旧的静态 `toolPreview.length`,使模型刀具长度来自 active tool offset。
- 修改 `tools/collect-web-xyzbc-trt-evidence.mjs`
- 从 staged `xyzbc-trt.tbl` 解析工具表。
- 扫描选定程序 T/M6/G43默认程序无工具命令时使用工具表中第一个有 Z 偏置的 T2 生成默认激活证据。
- 新增 `toolRuntime``toolOffsetClosure`,并把 `previewPath.samples[].tool``executionPath.samples[].tool` 改为当前 active tool。
- 扩展 `tests/node/verify_xyzbc_trt_web_app.mjs`
- 断言 `T2 P2 Z10 D8` 进入 tool DB。
- 断言 kinematics tool offset、path tool 和 Vismach pin 均为 T2/Z10/D8 闭环结果。
- 重新生成:
- `working/evidence/web-xyzbc-trt-evidence.json`
- `working/evidence/compare-xyzbc-trt-evidence.json`
- 更新 `04-任务矩阵.md`,将 T-034 标记为完成。
### 验证情况
- `npm --prefix web-rtcp-5axis-xyzbc-trt-sim-plan/app run smoke:node` 通过。
- `npm --prefix web-rtcp-5axis-xyzbc-trt-sim-plan/app run evidence:web` 通过。
- `npm --prefix web-rtcp-5axis-xyzbc-trt-sim-plan/app run evidence:compare` 已生成真实失败报告:当前 19/24 通过T-034 compare 检查已通过,剩余 5 项失败均来自 native evidence 尚缺 `previewPath`/`executionPath` 样本和采样周期字段。
- `npm --prefix web-rtcp-5axis-xyzbc-trt-sim-plan/app run build` 通过,输出 `gmoccapy_static_build=ok`
- `npm --prefix web-rtcp-5axis-xyzbc-trt-sim-plan/app run smoke:browser` 通过,输出 `xyzbc_trt_browser_smoke=ok`
- `web-xyzbc-trt-evidence.json` 当前摘要:
- `toolRuntime.activeToolNumber=2`
- `toolRuntime.activePocket=2`
- `toolRuntime.kinematics.toolOffsetZ=10`
- `toolRuntime.pathTool.length=10`
- `toolRuntime.pathTool.diameter=8`
- `toolRuntime.vismach.toolOffset=10`
- `toolOffsetClosure.closed=true`
- `coverage.toolTableToToolOffsetClosed=true`
### 下一步
1. 继续 T-019/T-037`/home/mes123456/cnc_wams/linuxcnc/scripts/rip-environment` 下重跑 native evidence使 native path 进入 20ms 可比状态。
2. 继续 T-026/T-038基于新 browser smoke 扩展 AXIS 主界面与 native 状态流复核。
3. 继续 T-028/T-030补 basic_sim 等效 task/HAL 细节和 Ngcgui/remap 子程序执行验收。
## 2026-07-02 Vismach 3D pin 驱动模型实现轮次
### 本轮目标
接续 `working` 任务矩阵,完成 T-029让 Web 3D 机床参考模型由 `xyzbc-trt-gui.py` 对应的 Vismach HAL pin 驱动,并把 pin 值、HAL net 和模型变换写入 Web evidence。
### 已做事项
- 新增 `app/src/runtime/vismach-model-state.js`
- 统一生成 `table-x/saddle-y/spindle-z/tilt-b/rotate-c/tool-offset/x-offset/z-offset` pin 状态。
- 将 pin 映射到 Web 模型变换table X、saddle Y、spindle Z、tilt B、rotate C、tool offset。
- 记录 9 条 native HAL net 对标关系和 `web_threejs_vismach_equivalent_driven_by_xyzbc_trt_hal_pins` 语义边界。
- 修改 `app/src/visualization/five-axis-scene.js`
- 将静态参考模型拆成 `vismach-table-x``vismach-saddle-y``vismach-spindle-z``vismach-tilt-b``vismach-rotate-c``vismach-tool-offset` 层级。
- 渲染时按 `buildVismachModelState()` 输出更新 Three.js group 的位移和旋转。
- 在 canvas dataset 增加 `threeVismachPins``threeVismachTransforms``threeVismachPinDrivenModel`,供后续浏览器 smoke 直接断言。
- 修改 `tools/collect-web-xyzbc-trt-evidence.mjs`
- `vismachEquivalent` 从静态 pin 清单升级为 `pins``pinValues``transforms``halNets``semanticBoundary`
- 扩展 `tests/node/verify_xyzbc_trt_web_app.mjs`
- 断言 profile 中 Vismach feedback/offset net。
- 断言 pin 到模型变换的映射和 tool-offset/x-offset/z-offset 组合。
- 新增目标浏览器 smoke
- `tests/browser/xyzbc_trt_browser_smoke.html`
- `tests/browser/verify_xyzbc_trt_browser.sh`
- `app/package.json` 增加 `smoke:browser`
- 浏览器断言默认 profile 为 `xyzbc-trt`kinematics/interpreter/task-HAL 均在 worker 中 readycanvas 非空,`threeVismachPinDrivenModel` 和 Vismach pin/transform dataset 可读。
- 重新生成:
- `working/evidence/web-xyzbc-trt-evidence.json`
- `working/evidence/compare-xyzbc-trt-evidence.json`
- 更新 `04-任务矩阵.md`,将 T-014、T-029 标记为完成,并补充 T-035 Web evidence 字段。
### 验证情况
- `npm --prefix web-rtcp-5axis-xyzbc-trt-sim-plan/app run smoke:node` 通过。
- `npm --prefix web-rtcp-5axis-xyzbc-trt-sim-plan/app run evidence:web` 通过。
- `web-xyzbc-trt-evidence.json` 当前摘要:
- `status=ready-for-wasm-runtime`
- `blockers=[]`
- `vismachEquivalent.pinValues.table-x=43`
- `vismachEquivalent.pinValues.saddle-y=-32.15`
- `vismachEquivalent.pinValues.spindle-z=-11.306`
- `vismachEquivalent.pinValues.tool-offset=84.019`
- `vismachEquivalent.pinValues.x-offset=-20`
- `vismachEquivalent.pinValues.z-offset=-15`
- `vismachEquivalent.halNets.length=9`
- `npm --prefix web-rtcp-5axis-xyzbc-trt-sim-plan/app run evidence:compare` 已生成真实失败报告:仍为 5 项失败,均来自 native evidence 尚缺 `previewPath`/`executionPath` 样本和采样周期字段。
- `npm --prefix web-rtcp-5axis-xyzbc-trt-sim-plan/app run build` 通过,输出 `gmoccapy_static_build=ok`
- `npm --prefix web-rtcp-5axis-xyzbc-trt-sim-plan/app run smoke:browser` 通过,输出 `xyzbc_trt_browser_smoke=ok`
### 下一步
1. 继续 T-019/T-037`/home/mes123456/cnc_wams/linuxcnc/scripts/rip-environment` 下重跑 native evidence使 native path 进入 20ms 可比状态。
2. 继续 T-034把 tool table 当前 T/P/Z/D 的 active tool offset 闭环接到 kinematics/tool model/path JSON。
3. 继续 T-026/T-038基于新 browser smoke 扩展 AXIS 主界面与 native 状态流复核。
## 2026-07-02 Web task/HAL executionPath 采集实现轮次 ## 2026-07-02 Web task/HAL executionPath 采集实现轮次
### 本轮目标 ### 本轮目标

View File

@@ -15,10 +15,10 @@
| T-011 | 完整 kinematics WASM 验证 | 完成 | `SKIP_KINEMATICS_BUILD=1 wasm-port/tests/wasm/node/verify_kinematics_wasm.sh` 通过,覆盖 `xyzbc-trt` | | T-011 | 完整 kinematics WASM 验证 | 完成 | `SKIP_KINEMATICS_BUILD=1 wasm-port/tests/wasm/node/verify_kinematics_wasm.sh` 通过,覆盖 `xyzbc-trt` |
| T-012 | 完整 interpreter/remap WASM 验证 | 完成 | `wasm-port/tests/wasm/node/verify_interp_wasm.sh` 通过,输出 `interp_wasm_node_smoke=ok` | | T-012 | 完整 interpreter/remap WASM 验证 | 完成 | `wasm-port/tests/wasm/node/verify_interp_wasm.sh` 通过,输出 `interp_wasm_node_smoke=ok` |
| T-013 | 完整 task/HAL WASM 验证 | 完成 | `SKIP_TASK_HAL_BUILD=1 wasm-port/tests/wasm/node/verify_task_hal_wasm.sh` 通过 | | T-013 | 完整 task/HAL WASM 验证 | 完成 | `SKIP_TASK_HAL_BUILD=1 wasm-port/tests/wasm/node/verify_task_hal_wasm.sh` 通过 |
| T-014 | 目标浏览器 smoke | 待前置 | 页面默认显示 `xyzbc-trt`worker runtime readycanvas 非空 | | T-014 | 目标浏览器 smoke | 完成 | `npm run smoke:browser` 通过;页面默认显示 `xyzbc-trt`kinematics/interpreter/task-HAL worker runtime readycanvas 非空并暴露 Vismach dataset |
| T-015 | native 真实执行 JSON 采集 | 完成 | `native-xyzbc-trt-evidence.json` 记录真实执行事件,状态 completed | | T-015 | native 真实执行 JSON 采集 | 完成 | `native-xyzbc-trt-evidence.json` 记录真实执行事件,状态 completed |
| T-016 | Web OPFS/WASM readiness JSON 采集 | 完成 | `web-xyzbc-trt-evidence.json` 记录 Web profile/INI/staging/WASM readiness | | T-016 | Web OPFS/WASM readiness JSON 采集 | 完成 | `web-xyzbc-trt-evidence.json` 记录 Web profile/INI/staging/WASM readiness |
| T-017 | native/Web JSON 对比 | 部分完成 | `compare-xyzbc-trt-evidence.json` 已生成;当前 18/23 通过,剩余 5 项均为 native preview/execution path 样本缺失 | | T-017 | native/Web JSON 对比 | 部分完成 | `compare-xyzbc-trt-evidence.json` 已生成;当前 19/24 通过,剩余 5 项均为 native preview/execution path 样本缺失 |
| T-018 | WASM artifact 构建前置 | 完成 | 已生成 core/kinematics/tp/task-hal 所需 `.js/.wasm``web-xyzbc-trt-evidence.json.wasm.missing=[]` | | T-018 | WASM artifact 构建前置 | 完成 | 已生成 core/kinematics/tp/task-hal 所需 `.js/.wasm``web-xyzbc-trt-evidence.json.wasm.missing=[]` |
| T-019 | native 刀具预览路径 JSON 采集 | 待实现 | `native-xyzbc-trt-evidence.json.previewPath.samples` 使用 20ms 周期记录 LinuxCNC 预览刀路 | | T-019 | native 刀具预览路径 JSON 采集 | 待实现 | `native-xyzbc-trt-evidence.json.previewPath.samples` 使用 20ms 周期记录 LinuxCNC 预览刀路 |
| T-020 | native 刀具执行路径 JSON 采集 | 部分完成 | native 脚本已从 `stat()` 执行事件重采样为 20ms `executionPath`;需用新基线重跑生成证据 | | T-020 | native 刀具执行路径 JSON 采集 | 部分完成 | native 脚本已从 `stat()` 执行事件重采样为 20ms `executionPath`;需用新基线重跑生成证据 |
@@ -30,13 +30,13 @@
| T-026 | AXIS 主界面等效功能 | 待实现 | Web 首屏包含程序、坐标、状态、MDI/switchkins、override、工具和预览/执行区域 | | T-026 | AXIS 主界面等效功能 | 待实现 | Web 首屏包含程序、坐标、状态、MDI/switchkins、override、工具和预览/执行区域 |
| T-027 | POSTGUI HAL/PyVCP 连接等效 | 完成 | Web evidence JSON 记录 `pyvcp.* -> halui.mdi-command-* -> M428/M429/M430` 和 multilabel 状态 | | T-027 | POSTGUI HAL/PyVCP 连接等效 | 完成 | Web evidence JSON 记录 `pyvcp.* -> halui.mdi-command-* -> M428/M429/M430` 和 multilabel 状态 |
| T-028 | basic_sim 等效 task/HAL | 待前置 | Web task/HAL runtime 覆盖模拟回零、manual toolchange、spindle、joint feedback | | T-028 | basic_sim 等效 task/HAL | 待前置 | Web task/HAL runtime 覆盖模拟回零、manual toolchange、spindle、joint feedback |
| T-029 | Vismach 3D 模型 pin 对标 | 待实现 | Web 3D 模型由 `table-x/saddle-y/spindle-z/tilt-b/rotate-c/tool-offset/x-offset/z-offset` 驱动 | | T-029 | Vismach 3D 模型 pin 对标 | 完成 | Web 3D 模型由 `table-x/saddle-y/spindle-z/tilt-b/rotate-c/tool-offset/x-offset/z-offset` 驱动`web-xyzbc-trt-evidence.json.vismachEquivalent` 记录 pin 值、HAL net 和变换 |
| T-030 | Ngcgui/remap 子程序全集 | 部分完成 | staging 和 Web evidence 覆盖 `xyzbc_switchkins_sub.ngc``centering.ngc``helix_bc.ngc`;执行仍待 WASM | | T-030 | Ngcgui/remap 子程序全集 | 部分完成 | staging 和 Web evidence 覆盖 `xyzbc_switchkins_sub.ngc``centering.ngc``helix_bc.ngc`;执行仍待 WASM |
| T-031 | 演示程序全集 | 完成 | staging、profile samplePrograms 和 Web evidence 覆盖 `xyzbc_switchkins.ngc``boat-xyzbc.ngc` | | T-031 | 演示程序全集 | 完成 | staging、profile samplePrograms 和 Web evidence 覆盖 `xyzbc_switchkins.ngc``boat-xyzbc.ngc` |
| T-032 | TRAJ/AXIS/JOINT 限制对标 | 部分完成 | Web evidence 覆盖 XYZBC 单位、速度/加速度、B/C 限位、JOG_AXES、GEOMETRYUI 全量复核仍待浏览器/WASM | | T-032 | TRAJ/AXIS/JOINT 限制对标 | 部分完成 | Web evidence 覆盖 XYZBC 单位、速度/加速度、B/C 限位、JOG_AXES、GEOMETRYUI 全量复核仍待浏览器/WASM |
| T-033 | kinematics HAL pins 对标 | 完成 | Web/native evidence 脚本记录 `x-offset=-20``z-offset=-15`、rot-point、conventional-directions、tool-offset | | T-033 | kinematics HAL pins 对标 | 完成 | Web/native evidence 脚本记录 `x-offset=-20``z-offset=-15`、rot-point、conventional-directions、tool-offset |
| T-034 | tool table 到 tool-offset 闭环 | 待实现 | T/P/Z/D 解析影响 kinematics tool-offset、Web 模型刀长和 path JSON | | T-034 | tool table 到 tool-offset 闭环 | 完成 | `xyzbc-trt.tbl` T2/P2/Z10/D8 解析后驱动 `toolRuntime.kinematics.toolOffsetZ=10`、Web path tool length/diameter 和 Vismach `tool-offset=10` |
| T-035 | 全量 evidence JSON 字段 | 部分完成 | Web JSON 已包含 `startupSequence``iniDisplay``halNets``kinematicsPins``axisJointLimits``uiEquivalence`native 脚本已补字段,需重采集 | | T-035 | 全量 evidence JSON 字段 | 部分完成 | Web JSON 已包含 `startupSequence``iniDisplay``halNets``kinematicsPins``axisJointLimits``uiEquivalence``vismachEquivalent.pinValues/transforms`native 脚本已补字段,需重采集 |
| T-036 | native 基线切换到 `/home/mes123456/cnc_wams/linuxcnc` | 完成 | `scripts/rip-environment``bin/axis``bin/xyzbc-trt-gui``rtlib/xyzbc-trt-kins.so` 已生成RIP 环境可导入 `linuxcnc` Python 模块 | | T-036 | native 基线切换到 `/home/mes123456/cnc_wams/linuxcnc` | 完成 | `scripts/rip-environment``bin/axis``bin/xyzbc-trt-gui``rtlib/xyzbc-trt-kins.so` 已生成RIP 环境可导入 `linuxcnc` Python 模块 |
| T-037 | 基于新 native 基线重采集 JSON | 部分完成 | native 脚本默认基线已切到 `/home/mes123456/cnc_wams/linuxcnc`;需在 RIP 环境下重新运行采集命令 | | T-037 | 基于新 native 基线重采集 JSON | 部分完成 | native 脚本默认基线已切到 `/home/mes123456/cnc_wams/linuxcnc`;需在 RIP 环境下重新运行采集命令 |
| T-038 | Web 界面按新 native 运行结果复核 | 待实现 | UI/逻辑与 `/home/mes123456/cnc_wams/linuxcnc` 真实 `xyzbc-trt` 的状态流、按钮、路径、HAL pin 对齐 | | T-038 | Web 界面按新 native 运行结果复核 | 待实现 | UI/逻辑与 `/home/mes123456/cnc_wams/linuxcnc` 真实 `xyzbc-trt` 的状态流、按钮、路径、HAL pin 对齐 |

View File

@@ -1,5 +1,99 @@
# 05-验收证据 # 05-验收证据
## Tool table 到 tool-offset 闭环验收
### 命令
```bash
npm --prefix web-rtcp-5axis-xyzbc-trt-sim-plan/app run smoke:node
npm --prefix web-rtcp-5axis-xyzbc-trt-sim-plan/app run evidence:web
npm --prefix web-rtcp-5axis-xyzbc-trt-sim-plan/app run evidence:compare
npm --prefix web-rtcp-5axis-xyzbc-trt-sim-plan/app run build
npm --prefix web-rtcp-5axis-xyzbc-trt-sim-plan/app run smoke:browser
```
### 结果
```text
xyzbc_trt_web_app_smoke=ok
web_xyzbc_trt_evidence=.../working/evidence/web-xyzbc-trt-evidence.json
compare_xyzbc_trt_status=fail fail_count=5
gmoccapy_static_build=ok
xyzbc_trt_browser_smoke=ok
```
### JSON 摘要
```text
web.toolRuntime.ready = true
web.toolRuntime.activeOffsetApplied = true
web.toolRuntime.activeToolNumber = 2
web.toolRuntime.activePocket = 2
web.toolRuntime.kinematics.toolOffsetZ = 10
web.toolRuntime.pathTool.length = 10
web.toolRuntime.pathTool.diameter = 8
web.toolRuntime.vismach.toolOffset = 10
web.toolOffsetClosure.closed = true
web.coverage.toolTableToToolOffsetClosed = true
web.previewPath.samples[0].tool = { id: 2, pocket: 2, length: 10, diameter: 8 }
web.executionPath.samples[0].tool = { id: 2, pocket: 2, length: 10, diameter: 8 }
compare.summary.passCount = 19
compare.summary.failCount = 5
compare.checks["web tool table active offset drives kinematics, path, and Vismach"].status = pass
```
结论:
- `xyzbc-trt.tbl``T2 P2 Z10 D8` 已进入 Web tool DB 运行时摘要。
- 当前 active tool offset 已同时写入 kinematics `motion.tooloffset.z` 等效值、路径样本 tool 字段和 Vismach `tool-offset` pin。
- 默认程序本身无 T/M6/G43因此 Web evidence 记录 `defaultActivationUsed=true`,用于证明 tool table 偏置链路;带 T/M6/G43 的程序会使用程序扫描命令。
- compare 仍保持失败是正确结果:剩余 5 项仍来自 native path evidence 未重采集。
## Vismach 3D pin 驱动模型验收
### 命令
```bash
npm --prefix web-rtcp-5axis-xyzbc-trt-sim-plan/app run smoke:node
npm --prefix web-rtcp-5axis-xyzbc-trt-sim-plan/app run evidence:web
npm --prefix web-rtcp-5axis-xyzbc-trt-sim-plan/app run evidence:compare
npm --prefix web-rtcp-5axis-xyzbc-trt-sim-plan/app run build
npm --prefix web-rtcp-5axis-xyzbc-trt-sim-plan/app run smoke:browser
```
### 结果
```text
xyzbc_trt_web_app_smoke=ok
web_xyzbc_trt_evidence=.../working/evidence/web-xyzbc-trt-evidence.json
compare_xyzbc_trt_status=fail fail_count=5
gmoccapy_static_build=ok
xyzbc_trt_browser_smoke=ok
```
### JSON 摘要
```text
web.status = ready-for-wasm-runtime
web.blockers = []
web.vismachEquivalent.pins = table-x,saddle-y,spindle-z,tilt-b,rotate-c,tool-offset,x-offset,z-offset
web.vismachEquivalent.pinValues.table-x = 43
web.vismachEquivalent.pinValues.saddle-y = -32.15
web.vismachEquivalent.pinValues.spindle-z = -11.306
web.vismachEquivalent.pinValues.tool-offset = 84.019
web.vismachEquivalent.pinValues.x-offset = -20
web.vismachEquivalent.pinValues.z-offset = -15
web.vismachEquivalent.halNets.length = 9
web.vismachEquivalent.semanticBoundary = web_threejs_vismach_equivalent_driven_by_xyzbc_trt_hal_pins
```
结论:
- Web 3D 参考模型已由 Vismach 等效 pin 状态驱动。
- canvas dataset 已输出 `threeVismachPins``threeVismachTransforms``threeVismachPinDrivenModel`,后续浏览器 smoke 可直接断言。
- `smoke:browser` 已断言默认 `xyzbc-trt`、三类 worker runtime ready、canvas 非空、Vismach pin/transform dataset。
- compare 仍保持失败是正确结果:剩余 5 项仍来自 native path evidence 未重采集。
## evidence 字段与路径对比实现验收 ## evidence 字段与路径对比实现验收
### 命令 ### 命令
@@ -45,6 +139,8 @@ uiEquivalence
vismachEquivalent vismachEquivalent
ngcguiSubroutines ngcguiSubroutines
demoPrograms demoPrograms
vismachEquivalent.pinValues
vismachEquivalent.transforms
``` ```
当前 compare evidence 已包含: 当前 compare evidence 已包含:

View File

@@ -17,7 +17,7 @@
| `.desktop` 启动 `rip-environment linuxcnc xyzbc-trt.ini` | `/home/mes123456/cnc_wams/linuxcnc/linuxcnc-rtcp-5axis-shortcuts/.../xyzbc-trt.desktop` | Web profile 一键进入 `xyzbc-trt`,自动加载同一 INI/staging 文件集 | web JSON 记录 `profile=xyzbc-trt``iniPath``startupSequence` | | `.desktop` 启动 `rip-environment linuxcnc xyzbc-trt.ini` | `/home/mes123456/cnc_wams/linuxcnc/linuxcnc-rtcp-5axis-shortcuts/.../xyzbc-trt.desktop` | Web profile 一键进入 `xyzbc-trt`,自动加载同一 INI/staging 文件集 | web JSON 记录 `profile=xyzbc-trt``iniPath``startupSequence` |
| `linuxcncsvr``rtapi_app``milltask``halui` | 当前进程清单 | Web runtime 用 worker/WASM/task-HAL 表示 server/task/halui 边界 | compare 检查 `runtimeComponents` 包含 `interp``tp``taskHal``haluiEquivalent` | | `linuxcncsvr``rtapi_app``milltask``halui` | 当前进程清单 | Web runtime 用 worker/WASM/task-HAL 表示 server/task/halui 边界 | compare 检查 `runtimeComponents` 包含 `interp``tp``taskHal``haluiEquivalent` |
| AXIS 主界面进程 | `bin/axis -ini xyzbc-trt.ini` | Web 首屏直接进入 CNC 操作界面,不做营销页 | 浏览器 smoke 检查坐标、程序、switchkins、3D canvas 均可见 | | AXIS 主界面进程 | `bin/axis -ini xyzbc-trt.ini` | Web 首屏直接进入 CNC 操作界面,不做营销页 | 浏览器 smoke 检查坐标、程序、switchkins、3D canvas 均可见 |
| Vismach 进程 | `loadusr -W xyzbc-trt-gui` | Web 3D 机床模型按 HAL pin 驱动 | evidence 记录 Vismach pin 等效映射 | | Vismach 进程 | `loadusr -W xyzbc-trt-gui` | Web 3D 机床模型按 HAL pin 驱动 | evidence 记录 Vismach pin 值、HAL net 和模型变换 |
## INI 与显示配置对标 ## INI 与显示配置对标
@@ -45,10 +45,10 @@
| `switchkins_postgui.hal` | PyVCP 到 halui MDI 的连接 | Web 需记录 `pyvcp.* -> halui.mdi-command-*` | 待 compare 检查 | | `switchkins_postgui.hal` | PyVCP 到 halui MDI 的连接 | Web 需记录 `pyvcp.* -> halui.mdi-command-*` | 待 compare 检查 |
| `basic_sim.tcl` | 基础仿真 HAL | Web task/HAL runtime 等效模拟 joint/home/spindle/toolchange | 待 task-HAL 验收 | | `basic_sim.tcl` | 基础仿真 HAL | Web task/HAL runtime 等效模拟 joint/home/spindle/toolchange | 待 task-HAL 验收 |
| `xyzbc-trt_cmds.hal` | 运行后生成 HAL | Web evidence 需记录等效展开 HAL nets | 待实现 | | `xyzbc-trt_cmds.hal` | 运行后生成 HAL | Web evidence 需记录等效展开 HAL nets | 待实现 |
| `xyzbc-trt-gui.py` | Vismach 模型 | Web 3D pin/模型层级对标 | 待模型验收 | | `xyzbc-trt-gui.py` | Vismach 模型 | Web 3D pin/模型层级对标 | 已完成 Web 端模型层级、evidence 和浏览器 smoke 验收 |
| `axis.py` | AXIS 主界面行为 | Web 操作面板对标 AXIS 关键行为 | 待 UI 验收 | | `axis.py` | AXIS 主界面行为 | Web 操作面板对标 AXIS 关键行为 | 待 UI 验收 |
| `xyzbc-trt-kins.c` | 运动学真源 | WASM kinematics 使用 source-derived artifact | 待构建 artifact | | `xyzbc-trt-kins.c` | 运动学真源 | WASM kinematics 使用 source-derived artifact | 待构建 artifact |
| `xyzbc-trt.tbl` | 工具表 | Web tool DB 读取 T/P/Z/D | 已有 staging需 UI/tool-offset 验收 | | `xyzbc-trt.tbl` | 工具表 | Web tool DB 读取 T/P/Z/D | Web evidence 已证明 T2/P2/Z10/D8 驱动 kinematics、path tool 和 Vismach tool-offset |
| `xyzbc.var` | 参数文件 | OPFS 保存/恢复参数 | 已有 staging需运行后变更验证 | | `xyzbc.var` | 参数文件 | OPFS 保存/恢复参数 | 已有 staging需运行后变更验证 |
## HAL 与 switchkins 对标 ## HAL 与 switchkins 对标
@@ -74,8 +74,8 @@
| `z-offset = -15` | HALCMD sets | Web kinematics pin 默认值一致 | | `z-offset = -15` | HALCMD sets | Web kinematics pin 默认值一致 |
| `x/y/z-rot-point = 0` | HALCMD setp | Web kinematics pin 默认值一致 | | `x/y/z-rot-point = 0` | HALCMD setp | Web kinematics pin 默认值一致 |
| `conventional-directions = 0` | HALCMD setp | Web kinematics pin 默认值一致 | | `conventional-directions = 0` | HALCMD setp | Web kinematics pin 默认值一致 |
| `table-x/saddle-y/spindle-z/tilt-b/rotate-c` | Vismach pins | Web 模型层级按这些反馈驱动 | | `table-x/saddle-y/spindle-z/tilt-b/rotate-c` | Vismach pins | Web 模型层级按这些反馈驱动`vismachEquivalent.pinValues/transforms` 可验收 |
| `tool-offset/x-offset/z-offset` | Vismach/kines pins | Web 刀具和模型偏置一致 | | `tool-offset/x-offset/z-offset` | Vismach/kines pins | Web 刀具和模型偏置一致T-034 已完成 Web tool table active offset 闭环native 仍待重采集对比 |
## G-code、remap 与子程序对标 ## G-code、remap 与子程序对标
@@ -112,6 +112,6 @@ native evidence、Web evidence、compare evidence 必须覆盖:
1. WASM artifact 尚未构建,`wasm-port/build/wasm` 缺少 kinematics/core/tp/task-hal `.js/.wasm` 1. WASM artifact 尚未构建,`wasm-port/build/wasm` 缺少 kinematics/core/tp/task-hal `.js/.wasm`
2. Web evidence 还未记录完整 `startupSequence``halNets``kinematicsPins``axisJointLimits` 2. Web evidence 还未记录完整 `startupSequence``halNets``kinematicsPins``axisJointLimits`
3. Web UI 还需按 AXIS 等效面板补齐 jog、override、tool editor、Ngcgui 子程序入口。 3. Web UI 还需按 AXIS 等效面板补齐 jog、override、tool editor、Ngcgui 子程序入口。
4. Vismach 等效模型还需`xyzbc-trt-gui.py` 的 pin 和模型层级做验收 4. Vismach 等效模型`xyzbc-trt-gui.py` 的 pin 和模型层级完成 Web 端验收;`smoke:browser` 已覆盖 canvas 非空和 dataset后续可补截图归档
5. 刀具预览路径和执行路径 JSON 采集尚未实现。 5. 刀具预览路径和执行路径 JSON 采集尚未实现。
6. `boat-xyzbc.ngc` 和 Ngcgui 子程序的 Web 可执行验收尚未完成。 6. `boat-xyzbc.ngc` 和 Ngcgui 子程序的 Web 可执行验收尚未完成。

View File

@@ -1,12 +1,12 @@
{ {
"apiName": "xyzbc-trt-native-web-evidence-comparison", "apiName": "xyzbc-trt-native-web-evidence-comparison",
"status": "fail", "status": "fail",
"comparedAt": "2026-07-02T12:48:11.244Z", "comparedAt": "2026-07-02T13:36:36.368Z",
"nativePath": "/home/mes123456/cnc_wams/web-rtcp-5axis-xyzbc-trt-sim-plan/working/evidence/native-xyzbc-trt-evidence.json", "nativePath": "/home/mes123456/cnc_wams/web-rtcp-5axis-xyzbc-trt-sim-plan/working/evidence/native-xyzbc-trt-evidence.json",
"webPath": "/home/mes123456/cnc_wams/web-rtcp-5axis-xyzbc-trt-sim-plan/working/evidence/web-xyzbc-trt-evidence.json", "webPath": "/home/mes123456/cnc_wams/web-rtcp-5axis-xyzbc-trt-sim-plan/working/evidence/web-xyzbc-trt-evidence.json",
"summary": { "summary": {
"checkCount": 23, "checkCount": 24,
"passCount": 18, "passCount": 19,
"failCount": 5, "failCount": 5,
"blockers": [], "blockers": [],
"nativeStatus": "ok", "nativeStatus": "ok",
@@ -107,6 +107,61 @@
"toolTable": "xyzbc-trt.tbl" "toolTable": "xyzbc-trt.tbl"
} }
}, },
{
"category": "tool-table",
"requirement": "web tool table active offset drives kinematics, path, and Vismach",
"status": "pass",
"evidence": {
"toolRuntime": {
"activeToolNumber": 2,
"activePocket": 2,
"kinematicsToolOffsetZ": 10,
"pathTool": {
"id": 2,
"pocket": 2,
"length": 10,
"diameter": 8
},
"vismachToolOffset": 10,
"defaultActivationUsed": true
},
"toolOffsetClosure": {
"sourceToolTable": "configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.tbl",
"selectedProgramSourceRel": "configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/xyzbc_switchkins.ngc",
"programToolCommands": [],
"activeToolOffset": {
"idx": 2,
"toolNumber": 2,
"pocket": 2,
"offset": {
"x": 0,
"y": 0,
"z": 10,
"a": 0,
"b": 0,
"c": 0,
"u": 0,
"v": 0,
"w": 0
},
"diameter": 8,
"frontAngle": 0,
"backAngle": 0,
"orientation": 0
},
"kinematicsToolOffsetZ": 10,
"pathTool": {
"id": 2,
"pocket": 2,
"length": 10,
"diameter": 8
},
"vismachToolOffset": 10,
"closed": true,
"semanticBoundary": "tool_table_current_t_p_z_d_drives_kinematics_path_and_vismach"
}
}
},
{ {
"category": "pyvcp", "category": "pyvcp",
"requirement": "web PyVCP XML staged", "requirement": "web PyVCP XML staged",
@@ -317,6 +372,8 @@
"zRotPoint": 0, "zRotPoint": 0,
"conventionalDirections": 0, "conventionalDirections": 0,
"toolOffsetSource": "motion.tooloffset.z", "toolOffsetSource": "motion.tooloffset.z",
"toolOffsetValue": 10,
"toolOffsetToolNumber": 2,
"pins": [ "pins": [
"motion.switchkins-type", "motion.switchkins-type",
"motion.analog-out-03", "motion.analog-out-03",