Add RTCP simulation QA updates
This commit is contained in:
488
web-rtcp-5axis-sim-plan/working1/03-implementation-steps.md
Normal file
488
web-rtcp-5axis-sim-plan/working1/03-implementation-steps.md
Normal file
@@ -0,0 +1,488 @@
|
||||
# 03 程序修复详细步骤
|
||||
|
||||
生成时间:2026-06-22
|
||||
|
||||
本文档面向实际编码人员,按问题给出具体修改步骤。执行前建议先创建修复分支。
|
||||
|
||||
```bash
|
||||
git status --short
|
||||
git switch -c fix/web-rtcp-5axis-working1
|
||||
```
|
||||
|
||||
## 1. D1:自动挂接 LinuxCNC kinematics frame
|
||||
|
||||
### Step 1.1 增加期望 frame source 状态
|
||||
|
||||
文件:
|
||||
|
||||
```text
|
||||
web-rtcp-5axis-sim-plan/app/src/state/store.js
|
||||
```
|
||||
|
||||
在 `initialState` 增加字段:
|
||||
|
||||
```js
|
||||
desiredFrameSourceMode: "fixture-ui-only",
|
||||
```
|
||||
|
||||
保留现有:
|
||||
|
||||
```js
|
||||
sourceMode
|
||||
frameSourceMode
|
||||
```
|
||||
|
||||
三者语义:
|
||||
|
||||
```text
|
||||
desiredFrameSourceMode: 用户/运行时希望使用的 frame 来源
|
||||
frameSourceMode: 当前 rtcpFrame 实际来源
|
||||
sourceMode: UI 总体展示来源,可继续跟当前 frame source 同步
|
||||
```
|
||||
|
||||
### Step 1.2 修改 ATTACH_KINEMATICS_RUNTIME
|
||||
|
||||
位置:
|
||||
|
||||
```text
|
||||
store.js -> case "ATTACH_KINEMATICS_RUNTIME"
|
||||
```
|
||||
|
||||
runtime loaded 时设置:
|
||||
|
||||
```js
|
||||
desiredFrameSourceMode: "source-derived-kinematics-wasm",
|
||||
```
|
||||
|
||||
runtime missing 时设置:
|
||||
|
||||
```js
|
||||
desiredFrameSourceMode: "fixture-ui-only",
|
||||
```
|
||||
|
||||
不要只依赖 `sourceMode` / `frameSourceMode`。
|
||||
|
||||
### Step 1.3 修改 buildFrameForState
|
||||
|
||||
当前逻辑大意:
|
||||
|
||||
```js
|
||||
const requestedSourceMode = state.frameSourceMode || state.sourceMode;
|
||||
...
|
||||
if (requestedSourceMode === "source-derived-kinematics-wasm") {
|
||||
if (runtime loaded && !async) {
|
||||
...
|
||||
} else {
|
||||
sourceMode = "fixture-ui-only";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
建议改为:
|
||||
|
||||
```js
|
||||
const requestedSourceMode =
|
||||
state.desiredFrameSourceMode ||
|
||||
state.frameSourceMode ||
|
||||
state.sourceMode;
|
||||
```
|
||||
|
||||
async runtime 已加载但尚未返回 frame 时,可以临时生成 fixture frame,但必须带上可诊断原因,不要覆盖 desired source。
|
||||
|
||||
### Step 1.4 修改 setState 写回策略
|
||||
|
||||
当前 `setState()` 中:
|
||||
|
||||
```js
|
||||
sourceMode: frame.sourceMode,
|
||||
frameSourceMode: frame.sourceMode,
|
||||
```
|
||||
|
||||
建议改为:
|
||||
|
||||
```js
|
||||
sourceMode: frame.sourceMode,
|
||||
frameSourceMode: frame.sourceMode,
|
||||
desiredFrameSourceMode: next.desiredFrameSourceMode || frame.sourceMode,
|
||||
```
|
||||
|
||||
关键点:不要因为临时 fixture frame 把 `desiredFrameSourceMode` 变成 fixture。
|
||||
|
||||
### Step 1.5 修改 scheduleAsyncKinematicsRefresh
|
||||
|
||||
当前 guard 不应依赖已解析 frame source:
|
||||
|
||||
```js
|
||||
if (state.frameSourceMode !== "source-derived-kinematics-wasm") return null;
|
||||
```
|
||||
|
||||
改为:
|
||||
|
||||
```js
|
||||
if (state.desiredFrameSourceMode !== "source-derived-kinematics-wasm") return null;
|
||||
if (!state.kinematicsRuntime?.loaded) return null;
|
||||
if (!isAsyncKinematicsRuntime(state.kinematicsRuntime)) return null;
|
||||
```
|
||||
|
||||
如果当前 frame 已经 ready 且 activeLine/axisPose/kinsType 未变化,可继续跳过刷新。
|
||||
|
||||
### Step 1.6 修改 refreshAsyncKinematicsFrame 成功写回
|
||||
|
||||
成功后确保:
|
||||
|
||||
```js
|
||||
sourceMode: "source-derived-kinematics-wasm",
|
||||
frameSourceMode: "source-derived-kinematics-wasm",
|
||||
desiredFrameSourceMode: "source-derived-kinematics-wasm",
|
||||
```
|
||||
|
||||
失败时:
|
||||
|
||||
```js
|
||||
operatorMessage: `LinuxCNC kinematics refresh failed: ${error.message}`
|
||||
```
|
||||
|
||||
并保留 retry 能力。
|
||||
|
||||
### Step 1.7 修改 main.js 初始化顺序
|
||||
|
||||
文件:
|
||||
|
||||
```text
|
||||
web-rtcp-5axis-sim-plan/app/src/main.js
|
||||
```
|
||||
|
||||
`attachDefaultKinematicsRuntime()` 已调用:
|
||||
|
||||
```js
|
||||
await store.refreshKinematicsFrame(...)
|
||||
```
|
||||
|
||||
修复后保留该调用,并在 profile/INI 变更订阅中,runtime attach 完成后再次刷新:
|
||||
|
||||
```js
|
||||
await attachDefaultKinematicsRuntime(...)
|
||||
await store.refreshKinematicsFrame({ operatorMessage: "..." })
|
||||
```
|
||||
|
||||
注意避免无限刷新。可通过 `asyncFrameRefreshSequence` 或 readiness 状态判断。
|
||||
|
||||
### Step 1.8 D1 测试
|
||||
|
||||
新增或更新测试:
|
||||
|
||||
```text
|
||||
web-rtcp-5axis-sim-plan/tests/browser/gmoccapy_shell_smoke.html
|
||||
```
|
||||
|
||||
或新增:
|
||||
|
||||
```text
|
||||
web-rtcp-5axis-sim-plan/tests/browser/kinematics_auto_refresh_smoke.html
|
||||
```
|
||||
|
||||
断言:
|
||||
|
||||
```js
|
||||
await waitUntil(() => window.webRtcp5AxisSimulation.getState().sourceMode === "source-derived-kinematics-wasm")
|
||||
assertText('[data-rtcp-diagnostic="boundary"]', 'linuxcnc_kinematics_wasm_c_abi')
|
||||
assertText('[data-rtcp-diagnostic="kinematics-ready"]', 'ready')
|
||||
```
|
||||
|
||||
## 2. D2:修复 3D 预览可见性
|
||||
|
||||
### Step 2.1 修复 updateToolpathPreview 未定义变量
|
||||
|
||||
文件:
|
||||
|
||||
```text
|
||||
web-rtcp-5axis-sim-plan/app/src/visualization/five-axis-scene.js
|
||||
```
|
||||
|
||||
在 `updateToolpathPreview(preview, state)` 中补齐:
|
||||
|
||||
```js
|
||||
const toolPosition = executionToolPosition(state, previewPoints);
|
||||
const fitPoints = collectFitPoints(previewPoints, executedPoints, currentSegmentPoints, toolPosition);
|
||||
const fitKey = [
|
||||
previewPoints.length,
|
||||
executedPoints.length,
|
||||
currentSegmentPoints.length,
|
||||
previewSourceMode(state),
|
||||
state.programExecutionMotionIndex || 0,
|
||||
state.programExecutionSampleIndex || 0,
|
||||
].join(":");
|
||||
```
|
||||
|
||||
确保 `updateToolExecutionMarker()` 使用同一个 `toolPosition`。
|
||||
|
||||
### Step 2.2 增加常驻机床参考模型
|
||||
|
||||
新增函数:
|
||||
|
||||
```js
|
||||
function createMachineReferenceModel() { ... }
|
||||
function updateMachineReferenceModel(preview, state) { ... }
|
||||
```
|
||||
|
||||
推荐对象:
|
||||
|
||||
```text
|
||||
grid/table base: dark gray plane/box
|
||||
X axis: red line
|
||||
Y axis: green line
|
||||
Z axis: blue line
|
||||
rotary ring: cyan/yellow ring
|
||||
tool holder: small cylinder/cone
|
||||
TCP marker: existing sphere
|
||||
```
|
||||
|
||||
在 `createScene()` 中:
|
||||
|
||||
```js
|
||||
const machineModel = createMachineReferenceModel();
|
||||
scene.add(machineModel.root);
|
||||
```
|
||||
|
||||
在 preview 对象中保存:
|
||||
|
||||
```js
|
||||
machineModel
|
||||
```
|
||||
|
||||
在 `updateToolpathPreview()` 中:
|
||||
|
||||
```js
|
||||
updateMachineReferenceModel(preview, state);
|
||||
```
|
||||
|
||||
### Step 2.3 提高 fallback 可见性
|
||||
|
||||
在 `renderFallbackPreview()` 中,路径为空也要绘制:
|
||||
|
||||
```text
|
||||
工作台矩形
|
||||
XYZ 坐标轴
|
||||
旋转中心
|
||||
TCP 点
|
||||
```
|
||||
|
||||
要求颜色和尺寸足够明显,避免黑底上不可见。
|
||||
|
||||
### Step 2.4 强化 canvas dataset
|
||||
|
||||
`exposePreviewDataset()` 已存在,修复后确保任意路径都稳定输出:
|
||||
|
||||
```text
|
||||
data-three-ready="true"
|
||||
data-three-renderer
|
||||
data-three-scene-objects
|
||||
data-three-path-points
|
||||
data-three-tool-execution-marker
|
||||
```
|
||||
|
||||
如果 fallback:
|
||||
|
||||
```text
|
||||
data-three-fallback-reason
|
||||
```
|
||||
|
||||
如果 WebGL:
|
||||
|
||||
```text
|
||||
data-three-renderer="webgl"
|
||||
```
|
||||
|
||||
### Step 2.5 D2 测试
|
||||
|
||||
新增 pixel 检查:
|
||||
|
||||
```js
|
||||
const stats = canvasPixelStats(canvas)
|
||||
assert(stats.nonBlackRatio > 0.02)
|
||||
assert(stats.averageLuminance > 5)
|
||||
```
|
||||
|
||||
同时检查:
|
||||
|
||||
```js
|
||||
Number(canvas.dataset.threeSceneObjects) > 0
|
||||
canvas.dataset.threeReady === "true"
|
||||
```
|
||||
|
||||
建议覆盖 desktop 和 mobile viewport。
|
||||
|
||||
## 3. D3:修复 HOME/JOG 坐标连续性
|
||||
|
||||
### Step 3.1 记录 pending JOG 上下文
|
||||
|
||||
文件:
|
||||
|
||||
```text
|
||||
web-rtcp-5axis-sim-plan/app/src/state/store.js
|
||||
```
|
||||
|
||||
在 `initialState` 增加:
|
||||
|
||||
```js
|
||||
pendingJogCommand: null,
|
||||
```
|
||||
|
||||
在 `case "JOG"` 的 task/HAL runtime 分支,发送命令前记录:
|
||||
|
||||
```js
|
||||
setState({
|
||||
pendingJogCommand: {
|
||||
axis,
|
||||
direction,
|
||||
increment,
|
||||
basePose: state.axisPose,
|
||||
createdAtLine: state.activeLine,
|
||||
},
|
||||
operatorMessage: `task/HAL jog ${axis.toUpperCase()} ...`,
|
||||
})
|
||||
```
|
||||
|
||||
注意现有代码直接调用 `runTaskHalCommandSequence()`,需要避免两次 setState 引发顺序混乱。可把 pending context 作为 `runTaskHalCommandSequence()` 的 options 传入,最终在 command started patch 中写入。
|
||||
|
||||
### Step 3.2 给 task/HAL status 增加坐标系元数据
|
||||
|
||||
文件:
|
||||
|
||||
```text
|
||||
web-rtcp-5axis-sim-plan/app/src/runtime/linuxcnc-task-hal-runtime.js
|
||||
```
|
||||
|
||||
在 `ui` 对象中增加:
|
||||
|
||||
```js
|
||||
axisPoseFrame: "task-local",
|
||||
```
|
||||
|
||||
如果 runtime 能确定是 work pose,则写:
|
||||
|
||||
```js
|
||||
axisPoseFrame: "work",
|
||||
```
|
||||
|
||||
如果能计算增量,增加:
|
||||
|
||||
```js
|
||||
axisPoseDelta: { x, y, z, a, b, c }
|
||||
```
|
||||
|
||||
短期不能准确判断时,不要伪装成 work。
|
||||
|
||||
### Step 3.3 修改 applyTaskHalStatusPatch
|
||||
|
||||
当前:
|
||||
|
||||
```js
|
||||
const axisPose = clampAxisPoseToProfile({
|
||||
...state.axisPose,
|
||||
...ui.axisPose,
|
||||
}, state.profile);
|
||||
```
|
||||
|
||||
改为单独函数:
|
||||
|
||||
```js
|
||||
const axisPose = resolveTaskHalAxisPose(state, status);
|
||||
```
|
||||
|
||||
建议实现:
|
||||
|
||||
```js
|
||||
function resolveTaskHalAxisPose(state, status) {
|
||||
const ui = status?.ui || {};
|
||||
if (ui.axisPoseFrame === "work") {
|
||||
return clampAxisPoseToProfile({ ...state.axisPose, ...ui.axisPose }, state.profile);
|
||||
}
|
||||
|
||||
if (ui.axisPoseDelta) {
|
||||
return addAxisDelta(state.axisPose, ui.axisPoseDelta, state.profile);
|
||||
}
|
||||
|
||||
if (state.pendingJogCommand && isJogStatus(status)) {
|
||||
const { axis, direction, increment, basePose } = state.pendingJogCommand;
|
||||
return clampAxisPoseToProfile({
|
||||
...basePose,
|
||||
[axis]: Number(basePose[axis] || 0) + direction * increment,
|
||||
}, state.profile);
|
||||
}
|
||||
|
||||
if (!ui.axisPoseFrame && wouldResetNonZeroPoseToLocalZero(state.axisPose, ui.axisPose)) {
|
||||
return state.axisPose;
|
||||
}
|
||||
|
||||
return clampAxisPoseToProfile({ ...state.axisPose, ...ui.axisPose }, state.profile);
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3.4 清理 pending JOG
|
||||
|
||||
`TASK_HAL_STATUS_APPLIED` 后,如果使用了 pending JOG:
|
||||
|
||||
```js
|
||||
pendingJogCommand: null
|
||||
```
|
||||
|
||||
如果 command failed:
|
||||
|
||||
```js
|
||||
pendingJogCommand: null
|
||||
```
|
||||
|
||||
### Step 3.5 HOME 同步
|
||||
|
||||
HOME 成功后,UI 和 task/HAL runtime 必须同一坐标基准。短期可在 HOME fallback patch 中明确:
|
||||
|
||||
```js
|
||||
axisPose: initialAxisPose
|
||||
```
|
||||
|
||||
task/HAL HOME status 如果返回局部原点,不能覆盖 `initialAxisPose`,除非 status 标记 `axisPoseFrame="work"`。
|
||||
|
||||
### Step 3.6 D3 测试
|
||||
|
||||
新增测试步骤:
|
||||
|
||||
```js
|
||||
power on
|
||||
manual
|
||||
home
|
||||
capture X/Y/Z
|
||||
jog X+
|
||||
assert X === previousX + jogIncrement
|
||||
assert Y/Z unchanged
|
||||
jog Y-
|
||||
assert Y === previousY - jogIncrement
|
||||
assert X/Z unchanged
|
||||
```
|
||||
|
||||
同时验证 DRO 文本:
|
||||
|
||||
```js
|
||||
document.querySelector('[data-region="dro"]').textContent
|
||||
```
|
||||
|
||||
## 4. 本地验证命令
|
||||
|
||||
建议按顺序执行:
|
||||
|
||||
```bash
|
||||
npm --prefix web-rtcp-5axis-sim-plan/app run build
|
||||
node web-rtcp-5axis-sim-plan/tests/node/verify_rtcp_store.mjs
|
||||
node web-rtcp-5axis-sim-plan/tests/node/verify_five_axis_session.mjs
|
||||
node web-rtcp-5axis-sim-plan/tests/node/verify_linuxcnc_kinematics_runtime.mjs
|
||||
node web-rtcp-5axis-sim-plan/tests/node/verify_linuxcnc_task_hal_runtime.mjs
|
||||
```
|
||||
|
||||
如果项目已有 browser smoke:
|
||||
|
||||
```bash
|
||||
web-rtcp-5axis-sim-plan/tests/browser/verify_gmoccapy_shell_browser.sh
|
||||
```
|
||||
|
||||
修复后再执行 QA 站点测试脚本或新增等价本地测试。
|
||||
|
||||
Reference in New Issue
Block a user