diff --git a/README.zh-CN.md b/README.zh-CN.md index 79f6c0f..3df01f3 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -62,6 +62,7 @@ The first useful target is: - 已有主轴方向状态回归语料,`M3/M4/M5` 会通过 `set-spindle.reserved` 输出 `1=顺时针`、`2=逆时针`、`0=停止`。 - 已有注释事件行号回归语料,LinuxCNC `COMMENT` 事件会保留当前 G-code 行号。 - 已有当前刀号事件行号回归语料,LinuxCNC `M61 Q...` 会输出带行号的 `tool-change` 事件。 +- 已有停止类事件回归语料,`M0/M1/M60` 会通过 `program-end.reserved` 输出 `1=程序停`、`2=可选停`、`3=托盘交换`。 - LinuxCNC Canon bridge 会把当前解释行号补到没有自带 `lineno` 的回调事件上,方便 Web 端诊断和源码高亮。 - 下一步是继续替换源码级链接中残留的 Python、INI/HAL、文件系统和动态加载依赖,并把 RTCP 内核接入 canon 事件流和机型配置。 @@ -84,6 +85,7 @@ The first useful target is: - `M3` / `M4` / `M5` 主轴方向事件回归 - `M61 Q...` 当前刀号事件行号回归 - LinuxCNC 注释事件行号回归 +- `M0` / `M1` / `M60` 停止类事件回归 - `G10 L2` / `G10 L20` / `G92.1` 坐标系事件回归 - `G38.3` 探针事件回归 diff --git a/core/src/canon_event_sink.cpp b/core/src/canon_event_sink.cpp index d7743e7..65dcdcf 100644 --- a/core/src/canon_event_sink.cpp +++ b/core/src/canon_event_sink.cpp @@ -192,6 +192,12 @@ void CanonEventSink::program_end(int line) { emit(base_event(CNC_SIM_EVENT_PROGRAM_END, line)); } +void CanonEventSink::program_stop(int line, int kind) { + CncSimEvent event = base_event(CNC_SIM_EVENT_PROGRAM_END, line); + event.reserved = kind; + emit(event); +} + void CanonEventSink::emit_raw(CncSimEvent event) { if (event.version == 0) { event.version = 1; diff --git a/core/src/canon_event_sink.h b/core/src/canon_event_sink.h index 66accf7..4fe39a7 100644 --- a/core/src/canon_event_sink.h +++ b/core/src/canon_event_sink.h @@ -32,6 +32,7 @@ public: void straight_probe(int line, const CncSimPose &end); void dwell(double seconds, int line); void program_end(int line); + void program_stop(int line, int kind); void emit_raw(CncSimEvent event); const CncSimPose &position() const; diff --git a/core/src/linuxcnc_canon_bridge.cpp b/core/src/linuxcnc_canon_bridge.cpp index dc477d9..c24085b 100644 --- a/core/src/linuxcnc_canon_bridge.cpp +++ b/core/src/linuxcnc_canon_bridge.cpp @@ -471,6 +471,9 @@ void MIST_ON() { } void PALLET_SHUTTLE() { + if (active_sink) { + active_sink->program_stop(event_line(), 3); + } } void TURN_PROBE_OFF() { @@ -499,6 +502,9 @@ bool GET_BLOCK_DELETE(void) { } void OPTIONAL_PROGRAM_STOP() { + if (active_sink) { + active_sink->program_stop(event_line(), 2); + } } void SET_OPTIONAL_PROGRAM_STOP(bool) { @@ -509,6 +515,9 @@ bool GET_OPTIONAL_PROGRAM_STOP() { } void PROGRAM_STOP() { + if (active_sink) { + active_sink->program_stop(event_line(), 1); + } } void SET_MOTION_OUTPUT_BIT(int) { diff --git a/core/src/smoke_gcode_parser.cpp b/core/src/smoke_gcode_parser.cpp index 7993f5c..2132a63 100644 --- a/core/src/smoke_gcode_parser.cpp +++ b/core/src/smoke_gcode_parser.cpp @@ -351,6 +351,16 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string if (stop_if_callback_aborted(sink_, error)) { return -1; } + } else if (m == 0) { + sink_.program_stop(line_number, 1); + if (stop_if_callback_aborted(sink_, error)) { + return -1; + } + } else if (m == 1) { + sink_.program_stop(line_number, 2); + if (stop_if_callback_aborted(sink_, error)) { + return -1; + } } else if (m == 6) { sink_.change_tool(line_number); if (stop_if_callback_aborted(sink_, error)) { @@ -371,6 +381,11 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string if (stop_if_callback_aborted(sink_, error)) { return -1; } + } else if (m == 60) { + sink_.program_stop(line_number, 3); + if (stop_if_callback_aborted(sink_, error)) { + return -1; + } } else if (m == 2 || m == 30) { sink_.program_end(line_number); if (stop_if_callback_aborted(sink_, error)) { diff --git a/core/tests/cnc_sim_api_linuxcnc_rs274_smoke.cpp b/core/tests/cnc_sim_api_linuxcnc_rs274_smoke.cpp index cb452b5..012b65d 100644 --- a/core/tests/cnc_sim_api_linuxcnc_rs274_smoke.cpp +++ b/core/tests/cnc_sim_api_linuxcnc_rs274_smoke.cpp @@ -78,6 +78,10 @@ int main() { bool saw_spindle_stop = false; bool saw_comment_line = false; bool saw_tool_number_line = false; + bool saw_program_stop = false; + bool saw_optional_stop = false; + bool saw_pallet_stop = false; + bool saw_final_end = false; for (const auto &event : events) { saw_tool = saw_tool || event.type == CNC_SIM_EVENT_TOOL_CHANGE; saw_rapid = saw_rapid || event.type == CNC_SIM_EVENT_RAPID; @@ -171,6 +175,38 @@ int main() { } ok &= expect(saw_tool_number_line, "expected LinuxCNC M61 tool-number event line"); + const char stop_program[] = + "G21 G90 G17\n" + "M0\n" + "M1\n" + "M60\n" + "M30\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, stop_program, sizeof(stop_program) - 1) == 0, + cnc_sim_last_error(sim)); + for (const auto &event : events) { + saw_program_stop = saw_program_stop || + (event.type == CNC_SIM_EVENT_PROGRAM_END && + event.line == 2 && + event.reserved == 1); + saw_optional_stop = saw_optional_stop || + (event.type == CNC_SIM_EVENT_PROGRAM_END && + event.line == 3 && + event.reserved == 2); + saw_pallet_stop = saw_pallet_stop || + (event.type == CNC_SIM_EVENT_PROGRAM_END && + event.line == 4 && + event.reserved == 3); + saw_final_end = saw_final_end || + (event.type == CNC_SIM_EVENT_PROGRAM_END && + event.line == 5 && + event.reserved == 0); + } + ok &= expect(saw_program_stop, "expected LinuxCNC M0 program stop event"); + ok &= expect(saw_optional_stop, "expected LinuxCNC M1 optional stop event"); + ok &= expect(saw_pallet_stop, "expected LinuxCNC M60 pallet shuttle event"); + ok &= expect(saw_final_end, "expected LinuxCNC M30 final program end event"); + const char comment_program[] = "G21 G90 G17\n" "(operator note)\n" diff --git a/core/tests/cnc_sim_api_smoke.cpp b/core/tests/cnc_sim_api_smoke.cpp index 256179c..f11ce2c 100644 --- a/core/tests/cnc_sim_api_smoke.cpp +++ b/core/tests/cnc_sim_api_smoke.cpp @@ -70,6 +70,9 @@ int main() { bool saw_spindle_cw = false; bool saw_spindle_ccw = false; bool saw_spindle_stop = false; + bool saw_program_stop = false; + bool saw_optional_stop = false; + bool saw_pallet_stop = false; bool saw_incremental_move = false; bool saw_r_arc_center = false; for (const auto &event : events) { @@ -162,6 +165,32 @@ int main() { ok &= expect(saw_spindle_ccw, "expected M4 counterclockwise spindle state"); ok &= expect(saw_spindle_stop, "expected M5 stopped spindle state"); + const char stop_program[] = + "G21\n" + "M0\n" + "M1\n" + "M60\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, stop_program, sizeof(stop_program) - 1) == 0, + cnc_sim_last_error(sim)); + for (const auto &event : events) { + saw_program_stop = saw_program_stop || + (event.type == CNC_SIM_EVENT_PROGRAM_END && + event.line == 2 && + event.reserved == 1); + saw_optional_stop = saw_optional_stop || + (event.type == CNC_SIM_EVENT_PROGRAM_END && + event.line == 3 && + event.reserved == 2); + saw_pallet_stop = saw_pallet_stop || + (event.type == CNC_SIM_EVENT_PROGRAM_END && + event.line == 4 && + event.reserved == 3); + } + ok &= expect(saw_program_stop, "expected M0 program stop event"); + ok &= expect(saw_optional_stop, "expected M1 optional stop event"); + ok &= expect(saw_pallet_stop, "expected M60 pallet shuttle event"); + const char linuxcnc_config[] = "{\"backend\":\"linuxcnc-rs274\"}"; config_rc = cnc_sim_load_config_json(sim, linuxcnc_config, sizeof(linuxcnc_config) - 1); rc = cnc_sim_parse_program(sim, program, sizeof(program) - 1); diff --git a/core/tests/linuxcnc_canon_bridge_smoke.cpp b/core/tests/linuxcnc_canon_bridge_smoke.cpp index 1dca82a..f64db78 100644 --- a/core/tests/linuxcnc_canon_bridge_smoke.cpp +++ b/core/tests/linuxcnc_canon_bridge_smoke.cpp @@ -51,6 +51,12 @@ int main() { SET_XY_ROTATION(15.0); cnc_sim_linuxcnc_set_current_line(20); COMMENT("bridge comment"); + cnc_sim_linuxcnc_set_current_line(21); + PROGRAM_STOP(); + cnc_sim_linuxcnc_set_current_line(22); + OPTIONAL_PROGRAM_STOP(); + cnc_sim_linuxcnc_set_current_line(23); + PALLET_SHUTTLE(); PROGRAM_END(); bool ok = true; @@ -71,6 +77,9 @@ int main() { bool saw_g92 = false; bool saw_rotation = false; bool saw_comment = false; + bool saw_program_stop = false; + bool saw_optional_stop = false; + bool saw_pallet_stop = false; bool saw_end = false; for (const auto &event : events) { saw_tool = saw_tool || (event.type == CNC_SIM_EVENT_TOOL_CHANGE && event.tool == 7); @@ -113,6 +122,15 @@ int main() { event.feed == 15.0); saw_comment = saw_comment || (event.type == CNC_SIM_EVENT_COMMENT && event.line == 20); + saw_program_stop = saw_program_stop || (event.type == CNC_SIM_EVENT_PROGRAM_END && + event.line == 21 && + event.reserved == 1); + saw_optional_stop = saw_optional_stop || (event.type == CNC_SIM_EVENT_PROGRAM_END && + event.line == 22 && + event.reserved == 2); + saw_pallet_stop = saw_pallet_stop || (event.type == CNC_SIM_EVENT_PROGRAM_END && + event.line == 23 && + event.reserved == 3); saw_end = saw_end || event.type == CNC_SIM_EVENT_PROGRAM_END; } @@ -129,6 +147,9 @@ int main() { ok &= expect(saw_g92, "expected G92 offset event"); ok &= expect(saw_rotation, "expected XY rotation event"); ok &= expect(saw_comment, "expected comment event line"); + ok &= expect(saw_program_stop, "expected program stop event"); + ok &= expect(saw_optional_stop, "expected optional stop event"); + ok &= expect(saw_pallet_stop, "expected pallet shuttle stop event"); ok &= expect(saw_end, "expected program end"); cnc_sim_linuxcnc_set_canon_sink(nullptr); diff --git a/test-linuxcnc-rs274-native.sh b/test-linuxcnc-rs274-native.sh index 34ea394..7596209 100755 --- a/test-linuxcnc-rs274-native.sh +++ b/test-linuxcnc-rs274-native.sh @@ -39,6 +39,7 @@ CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/basic CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_rtcp_controls.ngc >/tmp/cnc_sim_linuxcnc_rtcp_controls.json CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_spindle_direction.ngc >/tmp/cnc_sim_linuxcnc_spindle_direction.json CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_tool_number.ngc >/tmp/cnc_sim_linuxcnc_tool_number.json +CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_program_stops.ngc >/tmp/cnc_sim_linuxcnc_program_stops.json CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_canned_cycle.ngc >/tmp/cnc_sim_linuxcnc_canned_cycle.json CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_comments.ngc >/tmp/cnc_sim_linuxcnc_comments.json CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_probe_no_error.ngc >/tmp/cnc_sim_linuxcnc_probe_no_error.json @@ -95,6 +96,16 @@ if not any( ): raise SystemExit("missing M61 Q1 tool-number event line") +stops = json.loads(Path("/tmp/cnc_sim_linuxcnc_program_stops.json").read_text()) +stop_states = [ + (event["line"], event["reserved"]) + for event in stops + if event["type"] == "program-end" +] +for expected in [(2, 1), (3, 2), (4, 3), (4, 1), (5, 0)]: + if expected not in stop_states: + raise SystemExit(f"missing program stop state {expected!r}") + cycle = json.loads(Path("/tmp/cnc_sim_linuxcnc_canned_cycle.json").read_text()) motions = [event for event in cycle if event["type"] in {"rapid", "linear-feed"}] expected_cycle = [ @@ -172,6 +183,7 @@ echo "dumped /tmp/cnc_sim_linuxcnc_basic_mill.json" echo "dumped /tmp/cnc_sim_linuxcnc_rtcp_controls.json" echo "dumped /tmp/cnc_sim_linuxcnc_spindle_direction.json" echo "dumped /tmp/cnc_sim_linuxcnc_tool_number.json" +echo "dumped /tmp/cnc_sim_linuxcnc_program_stops.json" echo "dumped /tmp/cnc_sim_linuxcnc_canned_cycle.json" echo "dumped /tmp/cnc_sim_linuxcnc_comments.json" echo "dumped /tmp/cnc_sim_linuxcnc_probe_no_error.json" diff --git a/tests/gcode/linuxcnc_program_stops.ngc b/tests/gcode/linuxcnc_program_stops.ngc new file mode 100644 index 0000000..7ab958a --- /dev/null +++ b/tests/gcode/linuxcnc_program_stops.ngc @@ -0,0 +1,5 @@ +G21 G90 G17 +M0 +M1 +M60 +M30