diff --git a/core/tests/cnc_sim_api_linuxcnc_rs274_smoke.cpp b/core/tests/cnc_sim_api_linuxcnc_rs274_smoke.cpp index 316dd58..c6dfc9b 100644 --- a/core/tests/cnc_sim_api_linuxcnc_rs274_smoke.cpp +++ b/core/tests/cnc_sim_api_linuxcnc_rs274_smoke.cpp @@ -1,5 +1,6 @@ #include "cnc_sim_api.h" +#include #include #include @@ -25,6 +26,10 @@ bool expect(bool value, const char *message) { return true; } +bool near(double lhs, double rhs) { + return std::fabs(lhs - rhs) < 0.000001; +} + } // namespace int main() { @@ -96,6 +101,12 @@ int main() { bool saw_g64_mode = false; bool saw_tool_length = false; bool saw_tool_length_clear = false; + bool saw_cutter_left_first = false; + bool saw_cutter_left_second = false; + bool saw_cutter_left_off_move = false; + bool saw_cutter_right_first = false; + bool saw_cutter_right_second = false; + bool saw_cutter_right_off_move = 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; @@ -341,6 +352,61 @@ int main() { ok &= expect(saw_tool_length, "expected LinuxCNC G43 H1 tool length event"); ok &= expect(saw_tool_length_clear, "expected LinuxCNC G49 tool length clear event"); + const char cutter_comp_program[] = + "G21 G90 G17\n" + "G0 X0 Y0 Z0\n" + "F100\n" + "G41.1 D6\n" + "G1 X20 Y0\n" + "G1 X20 Y20\n" + "G40\n" + "G1 X0 Y0\n" + "G42.1 D6\n" + "G1 X20 Y0\n" + "G1 X20 Y-20\n" + "G40\n" + "G1 X0 Y0\n" + "M30\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, cutter_comp_program, sizeof(cutter_comp_program) - 1) == 0, + cnc_sim_last_error(sim)); + for (const auto &event : events) { + saw_cutter_left_first = saw_cutter_left_first || + (event.type == CNC_SIM_EVENT_LINEAR_FEED && + near(event.end.x, 17.0) && + near(event.end.y, 3.0)); + saw_cutter_left_second = saw_cutter_left_second || + (event.type == CNC_SIM_EVENT_LINEAR_FEED && + event.line == 7 && + near(event.end.x, 17.0) && + near(event.end.y, 20.0)); + saw_cutter_left_off_move = saw_cutter_left_off_move || + (event.type == CNC_SIM_EVENT_LINEAR_FEED && + event.line == 8 && + near(event.end.x, 0.0) && + near(event.end.y, 0.0)); + saw_cutter_right_first = saw_cutter_right_first || + (event.type == CNC_SIM_EVENT_LINEAR_FEED && + near(event.end.x, 17.0) && + near(event.end.y, -3.0)); + saw_cutter_right_second = saw_cutter_right_second || + (event.type == CNC_SIM_EVENT_LINEAR_FEED && + event.line == 12 && + near(event.end.x, 17.0) && + near(event.end.y, -20.0)); + saw_cutter_right_off_move = saw_cutter_right_off_move || + (event.type == CNC_SIM_EVENT_LINEAR_FEED && + event.line == 13 && + near(event.end.x, 0.0) && + near(event.end.y, 0.0)); + } + ok &= expect(saw_cutter_left_first, "expected LinuxCNC G41.1 compensated lead-in motion"); + ok &= expect(saw_cutter_left_second, "expected LinuxCNC G41.1 compensated side motion"); + ok &= expect(saw_cutter_left_off_move, "expected LinuxCNC G40 move after left compensation"); + ok &= expect(saw_cutter_right_first, "expected LinuxCNC G42.1 compensated lead-in motion"); + ok &= expect(saw_cutter_right_second, "expected LinuxCNC G42.1 compensated side motion"); + ok &= expect(saw_cutter_right_off_move, "expected LinuxCNC G40 move after right compensation"); + const char comment_program[] = "G21 G90 G17\n" "(operator note)\n" diff --git a/docs/linuxcnc-porting.md b/docs/linuxcnc-porting.md index bbe9e05..7766ca5 100644 --- a/docs/linuxcnc-porting.md +++ b/docs/linuxcnc-porting.md @@ -192,7 +192,7 @@ This matrix tracks LinuxCNC feature coverage for the web/WASM simulator. A featu | Kinematics switch `M428/M429/M430` | covered as simulator-owned control lines | `tests/gcode/linuxcnc_rtcp_controls.ngc` | | Canned cycle `G81/G80` | covered for drilling expand-to-canon path | `tests/gcode/linuxcnc_canned_cycle.ngc` | | Coordinate offset Canon events | bridge-level covered | `core/tests/linuxcnc_canon_bridge_smoke.cpp` | -| Cutter compensation | pending | add LinuxCNC corpus and tolerance checks | +| Cutter compensation | covered for explicit-radius `G41.1/G42.1/G40` through LinuxCNC native/source backends; tool-table `G41/G42 D...` remains pending | `tests/gcode/linuxcnc_cutter_comp.ngc` | | O-word subroutines and calls | covered for numeric `O... sub/call/return/endsub` through LinuxCNC file mode; smoke parser also covers named `O` sub/call/return/endsub | `tests/gcode/smoke_oword_subprogram.ngc` | | Broader canned cycles `G73`, `G82`-`G89` | partially covered: `G73`, `G82`, `G83`, `G85`, `G86`, `G89` | `tests/gcode/linuxcnc_canned_cycles_extended.ngc`, smoke API regression | | Full source-level wasm build | pending | replace Python/HAL/INI/tooldata support dependencies | diff --git a/test-linuxcnc-rs274-native.sh b/test-linuxcnc-rs274-native.sh index 91fb0c3..01bbc74 100755 --- a/test-linuxcnc-rs274-native.sh +++ b/test-linuxcnc-rs274-native.sh @@ -46,6 +46,7 @@ CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linux CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_tool_length.ngc >/tmp/cnc_sim_linuxcnc_tool_length.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_canned_cycles_extended.ngc >/tmp/cnc_sim_linuxcnc_canned_cycles_extended.json +CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_cutter_comp.ngc >/tmp/cnc_sim_linuxcnc_cutter_comp.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 CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/smoke_oword_subprogram.ngc >/tmp/cnc_sim_linuxcnc_oword_subprogram.json @@ -213,6 +214,28 @@ if not any( ): raise SystemExit("missing G83 final retract to R plane") +cutter_comp = json.loads(Path("/tmp/cnc_sim_linuxcnc_cutter_comp.json").read_text()) +cutter_comp_motions = [ + event + for event in cutter_comp + if event["type"] in {"rapid", "linear-feed", "arc-feed"} +] +expected_cutter_comp = [ + ("rapid", 2, 0, 0), + ("linear-feed", 6, 17, 3), + ("linear-feed", 7, 17, 20), + ("linear-feed", 8, 0, 0), + ("linear-feed", 11, 17, -3), + ("linear-feed", 12, 17, -20), + ("linear-feed", 13, 0, 0), +] +actual_cutter_comp = [ + (event["type"], event["line"], event["end"]["x"], event["end"]["y"]) + for event in cutter_comp_motions +] +if actual_cutter_comp != expected_cutter_comp: + raise SystemExit(f"unexpected cutter compensation path: {actual_cutter_comp!r}") + comments = json.loads(Path("/tmp/cnc_sim_linuxcnc_comments.json").read_text()) if not any(event["type"] == "comment" and event["line"] == 2 for event in comments): raise SystemExit("missing LinuxCNC comment line event") @@ -309,6 +332,7 @@ echo "dumped /tmp/cnc_sim_linuxcnc_motion_modes.json" echo "dumped /tmp/cnc_sim_linuxcnc_tool_length.json" echo "dumped /tmp/cnc_sim_linuxcnc_canned_cycle.json" echo "dumped /tmp/cnc_sim_linuxcnc_canned_cycles_extended.json" +echo "dumped /tmp/cnc_sim_linuxcnc_cutter_comp.json" echo "dumped /tmp/cnc_sim_linuxcnc_comments.json" echo "dumped /tmp/cnc_sim_linuxcnc_probe_no_error.json" echo "dumped /tmp/cnc_sim_linuxcnc_oword_subprogram.json" diff --git a/test-linuxcnc-source-link.sh b/test-linuxcnc-source-link.sh index 215f1ff..c15dc69 100755 --- a/test-linuxcnc-source-link.sh +++ b/test-linuxcnc-source-link.sh @@ -106,6 +106,9 @@ CNC_SIM_RS274_VAR="$var_file" \ CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_canned_cycles_extended.ngc \ >/tmp/cnc_sim_linuxcnc_source_canned_cycles_extended.json +CNC_SIM_RS274_VAR="$var_file" \ + "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_cutter_comp.ngc \ + >/tmp/cnc_sim_linuxcnc_source_cutter_comp.json CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/smoke_oword_subprogram.ngc \ >/tmp/cnc_sim_linuxcnc_source_oword_subprogram.json @@ -188,6 +191,28 @@ if not any( ): raise SystemExit("missing source-linked G83 final retract to R plane") +cutter_comp = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_cutter_comp.json").read_text()) +cutter_comp_motions = [ + event + for event in cutter_comp + if event["type"] in {"rapid", "linear-feed", "arc-feed"} +] +expected_cutter_comp = [ + ("rapid", 2, 0, 0), + ("linear-feed", 6, 17, 3), + ("linear-feed", 7, 17, 20), + ("linear-feed", 8, 0, 0), + ("linear-feed", 11, 17, -3), + ("linear-feed", 12, 17, -20), + ("linear-feed", 13, 0, 0), +] +actual_cutter_comp = [ + (event["type"], event["line"], event["end"]["x"], event["end"]["y"]) + for event in cutter_comp_motions +] +if actual_cutter_comp != expected_cutter_comp: + raise SystemExit(f"unexpected source-linked cutter compensation path: {actual_cutter_comp!r}") + oword = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_oword_subprogram.json").read_text()) if not any( event["type"] == "linear-feed" and @@ -219,4 +244,5 @@ echo "dumped /tmp/cnc_sim_linuxcnc_source_basic_mill.json" echo "dumped /tmp/cnc_sim_linuxcnc_source_rtcp_controls.json" echo "dumped /tmp/cnc_sim_linuxcnc_source_canned_cycle.json" echo "dumped /tmp/cnc_sim_linuxcnc_source_canned_cycles_extended.json" +echo "dumped /tmp/cnc_sim_linuxcnc_source_cutter_comp.json" echo "dumped /tmp/cnc_sim_linuxcnc_source_oword_subprogram.json" diff --git a/tests/gcode/linuxcnc_cutter_comp.ngc b/tests/gcode/linuxcnc_cutter_comp.ngc new file mode 100644 index 0000000..581c46b --- /dev/null +++ b/tests/gcode/linuxcnc_cutter_comp.ngc @@ -0,0 +1,14 @@ +G21 G90 G17 +G0 X0 Y0 Z0 +F100 +G41.1 D6 +G1 X20 Y0 +G1 X20 Y20 +G40 +G1 X0 Y0 +G42.1 D6 +G1 X20 Y0 +G1 X20 Y-20 +G40 +G1 X0 Y0 +M30