Files
linuxcncWebWASM/docs/linuxcnc-web-wasm-dual-canvas-design.md

18 KiB
Raw Blame History

LinuxCNC Web/WASM 双 Canvas 预览设计与实施方案

文档状态:实施设计 v1.0 适用案例:linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini(同样适用于 xyzbc-trt.ini) 关联入口:run-5axis-rtcp.sh 现有前端:web/react/src/App.tsx、XyzacToolPreview.tsx、XyzacMachineView.tsx 协议类型:web/react/src/protocol.ts、xyzac-visual-adapter.ts

1. 背景与目标

当前 AXIS 风格面板的 Preview 区域需要同时表达两种不同的信息:

  1. 刀具在工件坐标中的 canonical 程序轨迹,以及执行过程中已经走过和正在执行的实时刀具轨迹。
  2. 由五个关节(table-x、saddle-y、spindle-z、tilt-a、rotate-c)驱动的完整机床三维模型运动。

本方案将 Preview 明确拆为两个相互独立的 <canvas>:

  • Canvas A:Toolpath/Simulation。只绘制程序预览段、快速移动、圆弧/螺旋插补、实时执行轨迹、当前段和刀尖;不绘制机床结构。
  • Canvas B:Machine 3D。只绘制 machine_model 结构、关节层级、刀具、转台、限位盒和当前姿态;可选显示从同一快照投影的当前刀具路径,但默认关闭以避免与 Canvas A 重复。

两个 canvas 不共享 WebGL 场景、相机、动画循环或可变对象。它们共享同一个经过校验的 XyzacVisualState,该状态由 LinuxCNC WASM 在 Dedicated Worker 中生成。React 不解析 G-code、不计算 RTCP、不根据墙钟推导姿态。

目标是:在运行、暂停、单步、急停、回零、M428/M429/M430 切换、清除可视轨迹和 Worker 恢复后,两个画面都能用相同的 simulation_tick、state_hash 和姿态数据重现同一时刻;任一画面缺数据时显示 pending/error,而不是使用本地猜测值。

2. 范围与非目标

2.1 范围

  • XYZAC/XYZBC TRT 案例的 exact-path 启动、配置资源闭包和 RTCP remap。
  • canonical 程序轨迹生成(直线、快速、圆弧、螺旋),每段的源行、采样和运动类型。
  • 固定 logical tick 的实时运动样本、活动段进度、姿态和五个 joint 值。
  • Vismach 模型身份、组件层级、来源文件哈希和关节到模型节点的映射。
  • 两个 canvas 的尺寸、生命周期、ResizeObserver、帧丢弃/补快照、相机控制和清理。
  • native/WASM/Worker 对照、浏览器验收和恢复测试。

2.2 非目标

  • 在浏览器中复刻 AXIS 的 Tk 绘图实现或像素级截图。
  • 用 JS preview 引擎替代 LinuxCNC RS274/TP/kinematics。
  • 通过宿主 LinuxCNC GUI、NML socket、shell 或真实硬件取得位置。
  • 在两个 canvas 之间复制一份可写的运动状态。

3. run-5axis-rtcp.sh 执行链

脚本是 native oracle 和案例资源定义的入口;Web/WASM 必须保留同样的路径和语义。

run-5axis-rtcp.sh [xyzac-trt.ini|xyzbc-trt.ini]
  -> 解析 REPO_ROOT/linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting
  -> 选择 demo/*.ngc 和 remap_subs/helix_{ac,bc}.ngc
  -> 校验 INI、demo、subroutine、M428 和 scripts/linuxcnc
  -> cd linuxcnc
  -> scripts/rip-environment linuxcnc -r <case>.ini
  -> [DISPLAY]/[RS274NGC]/[KINS]/[HAL]/[TRAJ]/[EMCIO] 初始化
  -> milltask 解析 G-code,TP 生成 canonical motion
  -> xyzac-trt-kins 做 forward/inverse kinematics 和 RTCP
  -> motion/servo-thread 每个周期更新 joints、pose、反馈和 HAL
  -> xyzac-trt-gui.py(native vismach)读取五个 joint 与 offset 绘制机床

xyzac-trt.ini 的关键输入必须进入 Web 资源图:OPEN_FILE、SUBROUTINE_PATH、PARAMETER_FILE、三个 REMAP、KINEMATICS=xyzac-trt-kins sparm=identityfirst、JOINTS=5、POSTGUI_HALFILE、TOOL_TABLE、SERVO_PERIOD=1000000 和 TASK CYCLE_TIME=0.010。xyzac-trt_cmds.hal 中五个 joint.*.pos-fb 到 xyzac-trt-gui.* 的 net 是 Canvas B 的关节数据来源;motion.switchkins-type 由 HAL analog-out 驱动,不能由 UI 直接改写。

demo 中的 helix_ac 先执行 M429(identity),设置 G54,再执行 M428(XYZAC),改变 A/C 后进行 G2 螺旋,最后恢复 M429 并回到起点。Web runner 必须记录每个 M428/M429/M430 的命令、HAL 反馈和确认 tick,以便两个 canvas 在模式切换处建立姿态屏障。

4. 总体架构

React App
  ├─ Panel snapshot reducer / projectXyzacVisual()
  ├─ Canvas A: ToolpathCanvas (独立 renderer/scene/RAF)
  └─ Canvas B: MachineCanvas  (独立 renderer/scene/RAF)
          │ 只读 XyzacVisualState
          ▼
WorkerClient -> Dedicated sim worker
  ├─ ProtocolGateway / schema validator
  ├─ LinuxCNC WASM task + RS274 + TP + motion + kinematics
  ├─ HAL/IO/Python virtual runtime
  ├─ Snapshot publisher / trajectory stream / backpressure
  └─ checkpoint / recovery / event ledger

Worker 每个 logical tick 依次执行 interpreter、TP、motion、kinematics、HAL/IO,再生成 snapshot。StatePublisher 可发送完整 STATE_SNAPSHOT 或 motion delta;React reducer 只接受 tick 不倒退且 schema/hash 合法的数据。Canvas A/B 在自己的 RAF 中读取同一个不可变快照引用:渲染时间可以不同,数据时间必须由 simulation_tick 标识。

5. 双 Canvas 设计

5.1 Canvas A:刀具预览和实时仿真路径

  • 场景节点:origin、grid、canonicalPath、rapidPath、liveHistory、activeLivePath、tool、可选 limits/offsets/extents。
  • canonical 段来自 visual.toolpath,保留 id、source_line、kind、motion_type、start、end、samples、圆心/法向/圈数和 samples_source。
  • 实时轨迹来自 visual.motion_trace;当前刀具位置使用 visual.pose,当前段和进度使用 active_segment_id/active_progress。
  • 使用正交投影显示 XY/XZ/YZ 和透视投影;相机状态只属于 Canvas A。暂停时保持最后权威点,清除命令只清除 live history,不删除 canonical 预览。
  • 轨迹颜色约定:canonical 完成段浅灰,rapid 虚线红,实时历史橙色,当前段/刀尖青色,错误或越限红色。颜色是显示约定,不改变数据语义。

5.2 Canvas B:机床三维模型运动

  • 场景节点必须按 XYZAC_MACHINE_GROUPS 建立:xyzac-machine、table-x、saddle-y、spindle-z、tilt-a、rotate-c、tool-offset。
  • 模型描述来自 visual.machine_model:id、source_path、source_hash、components、joints、coordinate_system。模型缺失或哈希不匹配时显示结构化错误并保留轴姿态 HUD。
  • 关节变换顺序固定为 table-x -> saddle-y -> spindle-z -> tilt-a -> rotate-c -> tool-offset;方向和单位从模型 manifest 定义,禁止组件自行猜测。
  • 使用 visual.joints 驱动机械层级,使用 visual.pose 驱动刀尖/姿态 HUD;二者必须在同一 tick 通过一致性检查。
  • 默认不绘制 Canvas A 的完整路径;showPath 仅作为明确的诊断层,且数据仍来自同一 snapshot。

5.3 生命周期与性能

每个组件在 mount 时创建自己的 renderer、scene、camera、controls 和 RAF,在 unmount 时停止 RAF、解除 ResizeObserver、释放 geometry/material/texture/render target 并移除 canvas。devicePixelRatio 上限为 2;尺寸小于 280x260 时保持稳定最小绘制区域。

Worker 流可以高于显示刷新率。Canvas 只消费最新可绘制快照,中间 motion chunk 可丢弃;发现 tick gap 时标记 data-visual-gap 并请求 SNAPSHOT_REQUEST。最终 pose、state hash、报警和事件不可丢失。渲染异常只影响对应 canvas,必须通过诊断事件上报且不能停止仿真。

6. 数据契约

6.1 权威快照

两个 canvas 的唯一输入是 panel_snapshot.visual 与配套的 kinematics/motion:

type DualCanvasSnapshot = {
  schema: "axis-panel-snapshot.v1";
  context_id: string;
  session_id: string;
  simulation_tick: number;
  state_hash: string;
  visual: {
    schema: "axis-visual.v1";
    source: "wasm_canonical_motion";
    pose: Record<"x"|"y"|"z"|"a"|"c", number>;
    joints: number[];                 // exactly 5: mm, mm, mm, deg, deg
    toolpath?: ToolpathSegment[];     // omitted in motion_delta
    motion_trace?: MotionSample[];
    active_segment_id?: number | null;
    active_progress?: number;         // [0, 1]
    pose_epoch?: number;
    live_path_epoch?: number;
    machine_model?: MachineModel;
  };
  kinematics: {
    schema: "wasm-xyzac-kinematics.v1";
    active_type: 0|1|2;
    pose: Record<string, number>;
    joints: number[];
    offsets: { x_rot_point: number; y_offset: number; z_offset: number; tool_offset: number };
    state_hash: string;
    wasm_state_hash: string;
  };
};

ToolpathSegment 的数值单位是 mm/deg,坐标顺序为 XYZAC;MotionSample 必须含 tick、五关节、pose、活动段和进度。machine_model.source 必须为 linuxcnc-wasm-worker,模型 source_hash 用于恢复和 native 对账。projectXyzacVisual() 负责默认值、有限值和长度校验;校验失败时 authoritative=false。

6.2 消息时序与一致性

HELLO -> CAPABILITIES -> SESSION_ATTACH -> SESSION_READY
PROGRAM_LOAD -> ACK -> STATE_SNAPSHOT(toolpath, model, tick T)
PROGRAM_RUN -> ACK
  每个 tick: WASM step -> motion delta/event -> STATE_SNAPSHOT/CHUNK
PROGRAM_PAUSE/ABORT/HOME/M428... -> ACK -> RESULT + full snapshot
WORKER_RESTART -> RECOVER -> SNAPSHOT_REQUEST -> SESSION_READY

seq 在 session 内递增,副作用命令带 operation_id。Canvas 只接受 simulation_tick >= lastTick 的数据;pose_epoch 变化清空插值队列但不清理 canonical path,live_path_epoch 变化清理实时历史并建立新轨迹段。state_hash 不一致触发诊断和完整快照请求。

7. 精美机床模型与刀具实施

xyzac-machine-model.ts 以原生 xyzac-trt-gui.py 的层级和轴方向为基准,使用圆角铸件、喷涂/钢件/硬质合金材质、环境反射、软阴影和工作平面构成完整模型。模型包含床身、底座前门、排屑盘、立柱侧筋、Z 导轨和丝杠、主轴箱、冷却喷嘴、工作灯、X/Y 导轨、A 轴支架、C 轴转台、T 型槽、沉头螺栓、转台刻度和 AXIS 铭牌。

刀具采用带夹头螺母、磨削刀柄、四条螺旋刃槽和平底刃口的 Ø6 mm 硬质合金立铣刀示意模型。直径和刃形是展示参数,目前不自动映射刀具表中的刀号/直径。

机床属于固定主轴、升降工作台结构,刀尖按原生 Vismach 基准位于 Z = 200 - tool_offset mm;X/Y/Z/A/C 相对运动由工作台关节完成,因此机床世界坐标中的刀尖不能直接等同于轨迹画布的 visual.pose。夹头保持在主轴上,刀柄随刀长偏置调整伸出量,刀尖仍落在原生基准位置。A 轴中心跟随 y_offset/z_offset,C 轴仅旋转台面,上游传动箱不随 C 轴转动。

tool 控制器提供刀具特写;重置按模型边界和 canvas 比例恢复整机取景。相机拖动不改变任何关节或 WASM 快照。geometry/material 缓存在模型创建阶段,运行只更新节点变换,卸载时统一释放。

机床 canvas 使用 Three.js OrbitControls 的标准鼠标映射:左键拖动旋转模型,中键拖动缩放,右键拖动平移,滚轮缩放;触摸单指旋转、双指缩放/平移。按钮控制与鼠标控制共用同一相机和 target,互不创建第二套视图状态。

已完成验证:npm run build;test_xyzac_threejs.mjs 验证轴向、转台层级、0/15/30/-5 mm 刀长的刀尖基准和资源释放;scripts/test_xyzac_machine_model_browser.mjs 在 Chromium 1600/1024/768/390 px 验证两个 canvas、八个相机操作、取景、WASM 运行和停止后的画面稳定性。运行浏览器测试需将 PLAYWRIGHT_MODULE 指向本机 Playwright 的 index.mjs;截图和 JSON 报告默认保存到 /tmp/xyzac-machine-model/。

8. 实施步骤

  1. 契约冻结:确认 axis-panel-snapshot.v1、axis-visual.v1、模型 manifest、单位、节点顺序、tick/hash 规则,并为字段增加运行时校验。
  2. Worker 资源闭包:实现 run-5axis-rtcp.sh 对应的 exact resolver;加载 INI、HAL、XML、TBL、VAR、NGC、remap 和模型来源,输出 source hash 和缺失边。
  3. WASM 轨迹/姿态发布:在固定 tick 中生成 canonical segments、motion trace、XYZAC kinematics、machine_model;支持 full snapshot 与 motion delta,保留 M428/M429/M430 的验证结果。
  4. Canvas A 重构:将 XyzacToolPreview 的 DOM host 改为显式 <canvas>,封装 ToolpathRenderer,实现路径层、实时层、插值队列、gap 恢复和视觉清除。
  5. Canvas B 重构:将 XyzacMachineView 的 DOM host 改为独立 <canvas>,封装 MachineRenderer,按 manifest 创建层级、驱动五关节和 tool offset,并实现模型缺失/哈希错误状态。
  6. App 接线:Preview split 保持两个独立 pane;通过同一个 store snapshot 调用两个 renderer。禁止组件之间传递可写场景对象;保留控制器、DRO、图层开关和无障碍标签。
  7. 性能与恢复:加入 transferable trajectory buffer、RAF 限频、ResizeObserver、WebGL context lost 恢复、Worker 重启恢复和 OPFS checkpoint 对账。
  8. 测试与发布门禁:先跑 Worker/source parity,再跑 React、canvas 像素和浏览器矩阵;所有阻断项写入 report,不以 mock 结果替代 WASM 证据。

9. 任务分配

任务 交付物 负责人角色 依赖
DUAL-01 契约/manifest schema、校验器、模型节点和单位表 协议与数据负责人 无
DUAL-02 exact runner 配置资源图、启动日志、source hash LinuxCNC/WASM 负责人 DUAL-01
DUAL-03 canonical preview segment、圆弧/螺旋采样、source line TP/解释器负责人 DUAL-02
DUAL-04 live snapshot fixed tick、motion trace、pose/joints、epoch motion/kinematics 负责人 DUAL-03
DUAL-05 ToolpathCanvas Canvas A renderer、图层和 gap 恢复 前端可视化负责人 DUAL-01、04
DUAL-06 MachineCanvas Canvas B 模型层级、关节变换、模型校验 Three.js/模型负责人 DUAL-01、04
DUAL-07 App/UX 双 pane 接线、控制器、无障碍、响应式 React 负责人 DUAL-05、06
DUAL-08 验收/回归 native/WASM diff、浏览器截图、恢复报告 QA/发布负责人 全部

任务状态使用 planned -> implementing -> verified;任一任务发现缺少 WASM ABI、oracle 或模型资源时标记 blocked,不得用 JS 近似结果升级状态。

10. 验收标准

10.1 功能验收

  • 加载 xyzac-trt.ini 后,Canvas A 的 canonical segment 数、源行、螺旋半径/圈数与 Worker golden 一致;Canvas B 显示 xyzac-trt 模型和五个关节。
  • 执行 demo 时,两个 canvas 的 data-xyzac-tick 和 state_hash 始终相同;A 的刀尖与 visual.pose 相同,B 的节点姿态与 visual.joints 相同。
  • M428/M429/M430 切换在相同 tick 产生姿态屏障;不允许出现旧关节队列回放或刀具跳回旧 pose。
  • Pause/Resume/Step/Abort/Home、feed hold、limit 和 ESTOP 后,实时轨迹和机床姿态均符合 LinuxCNC 状态;VISUAL_CLEAR 只清除实时层。
  • Worker 重启后恢复到最近 checkpoint,canonical path、model hash、active segment、pose、joints 和 epoch 可重建,无重复 IO/tool/remap 副作用。

10.2 自动化与浏览器验收

  • web/worker/test_xyzac_exact_preview.mjs、test_xyzac_machine_sequence.mjs、test_xyzac_live_path_lifecycle.mjs 及 source/native parity 报告通过。
  • React 测试断言两个 <canvas> 节点数量为 2、不同 DOM 节点/renderer、相同 tick/hash 输入;卸载后无 RAF、ResizeObserver 或 WebGL 资源泄漏。
  • Playwright 在 Chromium、Firefox、Safari(可用环境)验证桌面和窄屏布局;canvas 像素非空、模型和轨迹不重叠、相机控制有效、context lost 可恢复。
  • 性能目标:正常程序执行时主线程渲染 >= 30 FPS;Worker 流发生背压时 UI 不阻塞,最终快照延迟 < 200 ms;单帧内存和轨迹长度受 CAPABILITIES.limits 限制。

11. 风险与缓解

风险 影响 缓解/阻断条件
Canvas A/B 读取不同快照 视觉分裂、错误姿态 单 store snapshot 引用、tick/hash 断言;不一致即请求 full snapshot
用墙钟插值改变结果 与 LinuxCNC 不一致 插值仅用于显示,权威位置来自 logical tick;禁止 wall-clock 驱动 WASM
轨迹 chunk 丢失 实时线断裂 gap 标记 + SNAPSHOT_REQUEST;最终 pose/hash 不可丢
M428/M429 后旧队列回放 刀具跳变 pose_epoch 屏障、清空 playback queue、记录首个 divergent tick
模型来源或轴方向错误 机床镜像/错位 manifest source hash、固定节点顺序、native screenshot 对照
WebGL context lost/设备限制 单 canvas 黑屏 独立 renderer 重建;仿真继续运行;报告 visual_renderer_error
大型程序导致复制和 GC 卡顿/内存峰值 transferable buffer、分段缓存、限长 live history、按需加载 canonical path
Worker 恢复重复副作用 状态损坏 operation ledger、checkpoint tick/hash、结果重查后才重试
浏览器/ABI 缺失 无法宣称 parity CAPABILITIES 明确 exclusions;保持 blocked 并附证据

12. 交付物与回滚

交付必须包含本设计文档、协议/schema 变更、Canvas A/B 组件、模型 manifest、Worker/source parity 报告、浏览器截图和性能报告。发布包保留上一版本 renderer 和协议 schema;若任一 canvas 出现不可恢复错误,可降级为静态快照显示,但不得降级为本地 JS 运动 authority。回滚后仍应能读取旧 checkpoint,并在恢复报告中记录版本、context、source hash、tick 和 state hash。