# G-code F 进给速度驱动 RUN 执行修复文档 生成时间:2026-06-22 ## 1. 问题结论 当前 `RUN` 链路已经能完成: ```text 1. 打开 LinuxCNC G-code 程序。 2. 通过 task/HAL runtime 推进状态。 3. 更新当前行、高亮行、DRO、axisPose、执行轨迹和 task/HAL feedback。 ``` 但当前 `RUN` **还没有按 G-code 的真实进给速度执行**。 当前关键问题: ```text G-code interpreter 能解析 F。 execution-timing.js 能基于 F / 距离 / INI 限速做 feed-based timing estimate。 但是 task/HAL RUN 执行链路没有把 F 用作真实运行速度。 C++ wrapper 层固定写入 velocity=60。 activeLine 按 task cycle 推进一行,而不是按 段距离 / F / elapsed time 推进。 ``` 因此现有浏览器 RUN 证据只能证明: ```text task/HAL feedback 正在产生; UI 当前行与 task/HAL active line 同步; DRO 与 runtime axisPose 同步; 执行轨迹可见; ``` 不能证明: ```text 程序按 G-code F 进给速度、G93/G94 模态、rapid/feed 区分、override、段距离真实定时执行。 ``` ## 2. 当前源码证据 ### 2.1 固定 velocity=60 文件: ```text wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_task_hal_wasm.cpp ``` 函数: ```text enqueue_linear_move_from_line(TaskRuntime &state, const std::string &line) ``` 当前逻辑: ```cpp command << ",\"velocity\":60}"; ``` 影响: ```text 1. 每条 G-code 运动行传给 motion runtime 的速度都是 60 units/s。 2. JS 状态层把 currentVel * 60 转成 mm/min。 3. 在 mm 单位下 UI 看到的速度固定为 3600 mm/min。 4. G-code 行中的 F159 / F318 / F636 等不会影响 RUN 实际速度。 ``` ### 2.2 按 task cycle 推进一行 文件: ```text wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_task_hal_wasm.cpp ``` 函数: ```text lctask_run_cycles(long task_period_ns, long servo_period_ns, int task_cycles) ``` 当前逻辑: ```cpp for (int i = 0; i < task_cycles; ++i) { state.task_cycle += 1; if (state.interp_state == "READING" && state.next_program_line < state.opened_line_count) { enqueue_linear_move_from_line(state, state.program_lines[state.next_program_line]); ... } lcmot_step_servo(...); } ``` 影响: ```text 1. 每个 task cycle 至多 enqueue 一条程序行。 2. active line 由 cycle 数推进。 3. 长段、短段、F 快、F 慢不会改变行推进节奏。 4. 这不是真实 LinuxCNC planner/task/motion 的时间语义。 ``` ### 2.3 interpreter 已有 F 信息 文件: ```text web-rtcp-5axis-sim-plan/app/src/runtime/linuxcnc-interpreter-runtime.js ``` 已有能力: ```text 1. feedRatesBySourceLine(programText) 从 G-code 中提取 F。 2. parseLinuxCncCanonicalMotion(...) 把 activeFeedRate 写入 motion event 的 feedRate。 3. linearUnitsBySourceLine(...) 跟踪 G20/G21。 ``` 输出 motion event 已包含: ```js { type, line, axes, feedRate, linearUnits, ... } ``` ### 2.4 timing estimate 已有 F 计算 文件: ```text web-rtcp-5axis-sim-plan/app/src/runtime/execution-timing.js ``` 已有能力: ```text 1. 按 motion event feedRate 计算 requestedLinearVelocity。 2. 按 profile/INI max velocity 限速。 3. 按 linear distance / angular distance 计算 segment durationSeconds。 4. 处理 feedOverride / rapidOverride。 ``` 但当前边界是: ```text semanticBoundary = linuxcnc_canonical_motion_timing_estimate_not_planner_queue ``` 说明它是估算,不是 task/HAL RUN 的权威执行节奏。 ## 3. 修复目标 ### 3.1 必须达成 修复后 `RUN` 必须满足: ```text 1. G-code 中 F 值进入 task/HAL RUN 执行链路。 2. G94 units/min 模式下,feed move 的速度来自当前 modal F。 3. G0/STRAIGHT_TRAVERSE 使用 rapid velocity / rapid override / INI max velocity。 4. activeLine 按 elapsed execution time 与当前段完成度推进。 5. 长距离低 F 段明显运行更久。 6. 短距离高 F 段明显更快完成。 7. UI currentVelocity 不再固定 3600 mm/min。 8. DRO/axisPose 可以在段内插值,而不是只在行边界跳变。 9. 现有 RUN gate、task/HAL session、status loop、STOP/ABORT/STEP 不回退。 ``` ### 3.2 第一阶段不强求 以下可以作为第二阶段: ```text 1. 完整 LinuxCNC trajectory planner queue 动力学一致性。 2. 加速度/jerk/圆弧真实插补完全对齐 LinuxCNC。 3. G93 inverse-time feed 的完整五轴角度/线性混合真实语义。 4. 硬件 realtime HAL 驱动。 ``` 但第一阶段至少不能继续固定 `velocity=60`。 ## 4. 推荐实现方案 ### 4.1 不要继续在 C++ wrapper 中逐行粗解析 G-code 当前 C++ wrapper 的 `enqueue_linear_move_from_line()` 是字符串扫描: ```text 查找 XYZABC 字母; strtod 读取数值; 没有模态; 没有 F; 没有 G90/G91; 没有 G20/G21; 没有 G93/G94; 没有 G0/G1/G2/G3 区分; ``` 这个方向继续扩展会很脆弱。 推荐改为: ```text JS interpreter runtime 继续负责生成 canonical motion events。 JS 侧把 canonical motion + timing segments 传入 task/HAL runtime session。 C++ task/HAL wrapper 不再直接解析 G-code 行来生成 velocity。 C++ task/HAL wrapper 按已解析 motion segment 执行。 ``` ### 4.2 新增 task/HAL program motion plan 在 `wasm-port/runtime/sdk/src/linuxcnc-task-hal.js` 增加可选 API: ```js loadProgramMotionPlan({ programPath, motion, timing, linearUnits, }) ``` 在 C++ wrapper 增加导出: ```cpp int lctask_load_program_motion_plan_json(const char *plan_json); ``` motion plan 中至少包含: ```json { "programPath": "...", "segments": [ { "line": 8, "type": "STRAIGHT_FEED", "motionClass": "feed", "axes": { "x": 6.302, "y": -11.560, "z": 27.743, "a": -71.841, "c": -35.930 }, "startAxes": { "...": 0 }, "feedRate": 318, "linearUnits": "mm", "velocityMmPerMin": 318, "durationSeconds": 0.42, "startSeconds": 1.25, "elapsedSeconds": 1.67 } ] } ``` ### 4.3 C++ task runtime 状态新增字段 `TaskRuntime` 增加: ```cpp struct MotionSegment { int line = 0; std::string type; std::string motion_class; double start_seconds = 0.0; double duration_seconds = 0.0; double velocity_mm_per_min = 0.0; std::map start_axes; std::map end_axes; }; std::vector motion_plan; int active_segment_index = 0; double run_elapsed_seconds = 0.0; double run_start_seconds = 0.0; ``` ### 4.4 RUN 周期推进改为按 elapsed time 当前: ```text 每 task cycle enqueue 一行。 ``` 修复后: ```text 每次 lctask_run_cycles 根据 task_period_ns * task_cycles 增加 run_elapsed_seconds。 根据 run_elapsed_seconds 找到 active segment。 按 segment progress 插值 axisPose。 把 currentVel/requestedVel 设置为 segment.velocityMmPerMin / 60。 activeLine = segment.line。 segment 完成后才进入下一 segment。 ``` 伪代码: ```cpp int lctask_run_cycles(long task_period_ns, long servo_period_ns, int task_cycles) { const double delta_seconds = (task_period_ns / 1e9) * task_cycles; state.run_elapsed_seconds += delta_seconds; const MotionSegment *segment = find_segment_at_time(state.motion_plan, state.run_elapsed_seconds); if (!segment) { state.interp_state = "IDLE"; state.exec_state = "DONE"; return step_servo(...); } const double local = state.run_elapsed_seconds - segment->start_seconds; const double progress = clamp(local / segment->duration_seconds, 0.0, 1.0); AxisPose pose = interpolate(segment->start_axes, segment->end_axes, progress); forward_motion_sample({ line: segment->line, axes: pose, currentVel: segment->velocity_mm_per_min / 60.0, requestedVel: segment->velocity_mm_per_min / 60.0, inPosition: progress >= 1.0 }); step_servo(...); } ``` ### 4.5 motion runtime command schema 当前 motion command 是: ```json { "type": "EMC_TRAJ_LINEAR_MOVE", "line": 12, "x": 1, "velocity": 60 } ``` 建议新增或扩展为: ```json { "type": "EMC_TRAJ_LINEAR_SAMPLE", "line": 12, "x": 1.2, "y": 3.4, "z": 5.6, "a": -70, "c": 20, "currentVel": 5.3, "requestedVel": 5.3, "segmentProgress": 0.35, "source": "feed_timed_motion_plan" } ``` 如果不想新增 command type,也可以继续使用 `EMC_TRAJ_LINEAR_MOVE`,但必须: ```text 1. velocity 来自 segment.velocityMmPerMin / 60。 2. axes 是当前插值位置,而不是只用 segment end。 3. status 能保留 line/progress/currentVel。 ``` ## 5. G94 速度规则 ### 5.1 G94 units per minute 在 `TRAJ.LINEAR_UNITS=mm` 时: ```text F318 => 318 mm/min ``` 在 `TRAJ.LINEAR_UNITS=inch` 或 G20 active 时: ```text F10 => 10 inch/min => 254 mm/min ``` 计算: ```text velocity_mm_per_min = feedRate * linearUnitScaleToMm * feedOverride duration_seconds = linearDistanceMm / (velocity_mm_per_min / 60) ``` ### 5.2 Rapid G0 / `STRAIGHT_TRAVERSE`: ```text velocity_mm_per_min = min(INI max velocity, profile max velocity) * rapidOverride duration_seconds = distance / velocity ``` ### 5.3 Angular axes 第一阶段可沿用 `execution-timing.js` 现有策略: ```text linearSeconds = linearDistanceMm / linearVelocity angularSeconds = angularDistanceDeg / angularVelocity durationSeconds = max(linearSeconds, angularSeconds) ``` 这至少能避免旋转轴运动被零时长吞掉。 ### 5.4 G93 inverse time LinuxCNC impeller 程序包含: ```text G93 ... G1 ... F159 ``` G93 的 F 不是 units/min,而是 inverse time。第一阶段有两种策略: ```text 方案 A:先检测 G93,明确标注 unsupported,RUN gate 阻止真实 feed mode 运行。 方案 B:实现基础 inverse-time:duration_minutes = 1 / F,duration_seconds = 60 / F。 ``` 推荐: ```text 第一阶段实现方案 B。 ``` 原因: ```text 1. test_linuxcnc_source/impeller-7bl-xyzac.ngc 使用 G93。 2. 如果不支持 G93,就无法验证用户当前指定测试文件的真实进给语义。 3. 对 inverse-time,F 直接定义该运动块完成时间,适合第一阶段验证。 ``` 需要在 interpreter motion event 中增加: ```js feedMode: "inverse-time" | "units-per-minute" ``` 或至少在 timing 阶段通过 source line 扫描维护 G93/G94 模态。 ## 6. 代码修改落点 ### 6.1 `linuxcnc-interpreter-runtime.js` 新增: ```text feedModeBySourceLine(programText) ``` 识别: ```text G93 => inverse-time G94 => units-per-minute ``` motion event 增加: ```js feedMode: activeFeedMode ``` ### 6.2 `execution-timing.js` 修改 `buildTimingSegment(...)`: ```text if event.feedMode === "inverse-time": durationSeconds = 60 / feedRate velocityMmPerMin = linearDistanceMm / durationSeconds * 60 else: 保持 G94 units/min 逻辑 ``` 注意: ```text 1. G93 中 F 必须大于 0。 2. G93 中如果缺 F,应保留上一个 modal F 或报错,按 LinuxCNC 语义确认。 3. velocity 仍要可被 INI max velocity 限制还是忠实 inverse-time,需要明确。第一阶段建议记录 requested 与 capped 两个值。 ``` ### 6.3 `store.js` 在 `initializeTaskHalSession` 后或 `RUN_INTERPRETER_PROGRAM` 完成后,把当前 `programExecution.motion` 与 `programExecutionTiming.segments` 传给 task/HAL runtime: ```js await state.taskHalRuntime.loadProgramMotionPlan({ programPath: session.programPath, motion: state.programExecution.motion, timing: state.programExecutionTiming, linearUnits: state.profile.traj.linearUnits, }); ``` 需要保证: ```text 1. LOAD_LINUXCNC_GCODE_SOURCE 后 interpreter 已完成。 2. task/HAL openProgram 的 programPath 与 motion plan programPath 一致。 3. 如果 motion plan 不存在,RUN gate 应阻止“真实进给速度 RUN”,不能悄悄回退 velocity=60。 ``` ### 6.4 `linuxcnc-task-hal.js` 新增 SDK 方法: ```js loadProgramMotionPlan(plan) { const rc = callWithJson(mod, "lctask_load_program_motion_plan_json", plan); if (rc !== 0) throw new Error(...); } ``` ### 6.5 `linuxcnc_task_hal_wasm.hh` 新增声明: ```cpp int lctask_load_program_motion_plan_json(const char *plan_json); ``` ### 6.6 `linuxcnc_task_hal_wasm.cpp` 新增: ```text 1. MotionSegment 数据结构。 2. 简单 JSON plan parser,或复用现有轻量 json_number_after 风格解析数组。 3. lctask_load_program_motion_plan_json。 4. lctask_run_cycles 按 elapsed time 找 segment。 5. 删除或隔离 fixed velocity=60 的 fallback。 ``` 要求: ```text fixed velocity=60 只能作为 explicit fixture fallback。 真实 RUN 路径不允许使用它。 ``` ## 7. 测试计划 ### 7.1 Node 单元测试:F 值改变 duration 新增测试: ```text web-rtcp-5axis-sim-plan/tests/node/verify_feed_rate_execution_timing.mjs ``` 用例: ```gcode G90 G94 G1 X10 F60 G1 X20 F600 M2 ``` 期望: ```text 第一段 10mm @ 60mm/min => 10s。 第二段 10mm @ 600mm/min => 1s。 duration ratio ≈ 10:1。 ``` ### 7.2 Node 单元测试:G93 inverse time 用例: ```gcode G90 G93 G1 X10 F2 G1 X20 F10 M2 ``` 期望: ```text F2 => 60/2 = 30s。 F10 => 60/10 = 6s。 ``` ### 7.3 task/HAL runtime 测试:速度不固定 新增或扩展: ```text web-rtcp-5axis-sim-plan/tests/node/verify_run_feedback_loop.mjs ``` 断言: ```text 1. RUN feedback currentVelocityMmPerMin 不等于固定 3600。 2. 不同 F 段采样到不同 requested/current velocity。 3. activeLine 不再每 task cycle 固定递增一行。 4. 慢速段停留采样数 > 快速段。 ``` ### 7.4 浏览器证据测试 扩展: ```text qa/web-rtcp-5axis-site-test/capture-test-linuxcnc-source-run.mjs ``` 新增报告字段: ```text feedMode feedRate segmentDurationSeconds segmentProgress requestedVelocityMmPerMin currentVelocityMmPerMin ``` 报告验收: ```text 1. impeller G93 行显示 inverse-time。 2. 采样 velocity 随当前 segment 变化。 3. 当前行停留时间与 F 值/段时长一致。 4. Word 报告中列出 F、feedMode、duration、velocity 的采样表。 ``` ## 8. 验收标准 修复完成必须通过: ```bash node web-rtcp-5axis-sim-plan/tests/node/verify_feed_rate_execution_timing.mjs node web-rtcp-5axis-sim-plan/tests/node/verify_run_feedback_loop.mjs node web-rtcp-5axis-sim-plan/tests/node/verify_linear_unit_conversion.mjs npm --prefix web-rtcp-5axis-sim-plan/app run build npm --prefix web-rtcp-5axis-sim-plan/app run smoke:node node qa/web-rtcp-5axis-site-test/capture-test-linuxcnc-source-run.mjs ``` 并满足: ```text 1. 不再出现所有 RUN 样本 velocity=3600 的固定速度现象。 2. C++ task/HAL RUN 主路径不再写死 velocity=60。 3. G94 F60/F600 用例体现 10:1 段时长差异。 4. G93 F2/F10 用例体现 5:1 段时长差异。 5. test_linuxcnc_source/impeller-7bl-xyzac.ngc 的 RUN 报告包含 feedMode/feedRate/duration 证据。 6. STOP/ABORT/STEP 现有测试不回退。 ``` ## 9. 风险与注意事项 ### 9.1 不要伪造 LinuxCNC 语义 如果还没有完整 planner,就必须在状态中明确: ```text semanticBoundary = feed_timed_canonical_motion_runtime ``` 不要标称为真实 LinuxCNC planner。 ### 9.2 不要把 estimate 当作硬件实时 第一阶段可以做到: ```text canonical motion + feed mode + timing driven browser simulation ``` 不能声称: ```text hardware realtime LinuxCNC execution ``` ### 9.3 G93 是当前 impeller 文件的关键 `test_linuxcnc_source/impeller-7bl-xyzac.ngc` 开头有: ```gcode M428 ;TCP:xyzac G93 S600 M3 ``` 因此如果不处理 G93,针对该文件的“真实 F 速度”验证仍然不完整。 ## 10. 建议实施顺序 ```text 1. 给 interpreter motion event 增加 feedMode。 2. 给 execution-timing.js 增加 G93 duration。 3. 写 verify_feed_rate_execution_timing.mjs,先证明 timing 正确。 4. 给 task/HAL SDK/WASM 增加 loadProgramMotionPlan。 5. 改 lctask_run_cycles,按 elapsed time 和 segment progress 推进。 6. 改 store,把 motion plan 注入 task/HAL session。 7. 扩展 verify_run_feedback_loop,证明 velocity 不固定、慢段停留更久。 8. 扩展浏览器 RUN 报告,输出 feedMode/feedRate/duration/progress。 9. 重跑 build、smoke、浏览器报告。 10. 更新 working_run 和 gptlog-process。 ```