满庭芳·源流归一

结论:LinuxCNC rs274ngc 源码移植边界继续收拢。已将 rs274ngc 头文件纳入 manifest 跟踪,core 编译组仍只包含可编译源文件;启用 CNC_SIM_ENABLE_LINUXCNC_RS274_BACKEND 时 API 默认使用 LinuxCNC rs274 后端;M428/M429/M430 与 5axis/TRT/TDR/RTCP 相关路径继续保持来自 LinuxCNC 源码或 remap 配置的薄桥接。

验证:./test-linuxcnc-source-syntax.sh;./test-linuxcnc-api-native.sh;./test-native.sh;./test-linuxcnc-source-link.sh。
This commit is contained in:
cnc
2026-05-28 18:00:53 +08:00
parent 40a63c76f7
commit b40914747d
65 changed files with 5189 additions and 1117 deletions

View File

@@ -1,5 +1,6 @@
#include "canon_event_sink.h"
#include <cmath>
#include <iostream>
#include <vector>
@@ -25,6 +26,18 @@ bool expect(bool value, const char *message) {
return true;
}
bool near(double actual, double expected) {
return std::fabs(actual - expected) < 1e-9;
}
bool expect_near(double actual, double expected, const char *message) {
if (!near(actual, expected)) {
std::cerr << "FAIL: " << message << " actual=" << actual << " expected=" << expected << '\n';
return false;
}
return true;
}
} // namespace
int main() {
@@ -32,6 +45,8 @@ int main() {
std::vector<CncSimEvent> events;
CanonEventSink sink;
sink.set_callback(collect_event, &events);
ok &= expect(sink.kinematics_type() == 0,
"expected LinuxCNC xyzbc-trt identityfirst startup kinematics type 0");
CncSimPose end{};
end.x = 10.0;
@@ -49,6 +64,8 @@ int main() {
int abort_count = 0;
sink.reset();
ok &= expect(sink.kinematics_type() == 0,
"expected LinuxCNC xyzbc-trt identityfirst reset kinematics type 0");
sink.set_callback(abort_on_second_event, &abort_count);
sink.select_plane(17, 1);
sink.set_feed_rate(100.0, 2);
@@ -56,6 +73,251 @@ int main() {
ok &= expect(sink.callback_aborted(), "expected callback abort state");
ok &= expect(abort_count == 2, "expected no further callbacks after abort");
events.clear();
sink.reset();
sink.set_callback(collect_event, &events);
sink.configure_rtcp(false, 250.0);
sink.switch_kinematics(2, 1);
CncSimPose five_axis_end{};
five_axis_end.x = 260.0;
five_axis_end.y = 20.0;
five_axis_end.z = 280.0;
five_axis_end.b = 90.0;
five_axis_end.c = 45.0;
five_axis_end.w = 12.0;
sink.straight_feed(2, five_axis_end);
bool saw_userk_before_g434 = false;
for (const auto &event : events) {
saw_userk_before_g434 = saw_userk_before_g434 || event.type == CNC_SIM_EVENT_RTCP_PIVOT;
}
ok &= expect(!saw_userk_before_g434, "expected M430 not to enable RTCP without G43.4/G43.5");
events.clear();
sink.set_rtcp_state(true, 0, 2);
sink.straight_feed(3, five_axis_end);
bool saw_userk_pivot = false;
for (const auto &event : events) {
saw_userk_pivot = saw_userk_pivot ||
(event.type == CNC_SIM_EVENT_RTCP_PIVOT &&
event.line == 3 &&
event.reserved == 2 &&
near(event.dwell_seconds, 250.0) &&
near(event.end.x, 260.0) &&
near(event.end.y, 20.0) &&
near(event.end.z, 280.0) &&
near(event.end.b, 90.0) &&
near(event.end.c, 45.0) &&
near(event.end.w, 0.0));
}
ok &= expect(saw_userk_pivot, "expected M430-style RTCP pivot to use LinuxCNC userk identity inverse");
events.clear();
sink.configure_rtcp(true, 0.0);
sink.switch_kinematics(2, 3);
bool saw_userk_remap_m68 = false;
bool saw_userk_remap_m66 = false;
bool saw_userk_switch_rtcp_feed = false;
for (const auto &event : events) {
saw_userk_remap_m68 = saw_userk_remap_m68 ||
(event.type == CNC_SIM_EVENT_COMMENT &&
event.line == 3 &&
event.reserved == 68 &&
event.tool == 3 &&
near(event.feed, 2.0));
saw_userk_remap_m66 = saw_userk_remap_m66 ||
(event.type == CNC_SIM_EVENT_COMMENT &&
event.line == 3 &&
event.reserved == 66 &&
event.tool == 0 &&
near(event.feed, 0.0) &&
near(event.dwell_seconds, 0.0));
saw_userk_switch_rtcp_feed = saw_userk_switch_rtcp_feed ||
(event.type == CNC_SIM_EVENT_KINEMATICS_SWITCH &&
event.line == 3 &&
event.reserved == 2 &&
near(event.feed, 1.0));
}
ok &= expect(saw_userk_remap_m68, "expected M430 remap to emit LinuxCNC M68 E3 Q2 side effect");
ok &= expect(saw_userk_remap_m66, "expected M430 remap to emit LinuxCNC M66 E0 L0 side effect");
ok &= expect(!saw_userk_switch_rtcp_feed,
"expected kinematics switch event not to carry RTCP state");
events.clear();
CncSimPose zero_tool_end{};
zero_tool_end.x = 7.0;
zero_tool_end.y = 8.0;
zero_tool_end.z = 9.0;
zero_tool_end.b = 10.0;
zero_tool_end.c = 11.0;
sink.straight_feed(4, zero_tool_end);
bool saw_zero_tool_userk_pivot = false;
for (const auto &event : events) {
saw_zero_tool_userk_pivot = saw_zero_tool_userk_pivot ||
(event.type == CNC_SIM_EVENT_RTCP_PIVOT &&
event.line == 4 &&
event.reserved == 2 &&
near(event.dwell_seconds, 0.0) &&
near(event.end.x, 7.0) &&
near(event.end.y, 8.0) &&
near(event.end.z, 9.0) &&
near(event.end.b, 10.0) &&
near(event.end.c, 11.0));
}
ok &= expect(saw_zero_tool_userk_pivot, "expected M430 userk inverse to run with zero tool offset");
events.clear();
sink.configure_rtcp(false, 250.0);
sink.switch_kinematics(1, 3);
CncSimPose trt_end{};
trt_end.x = 225.0;
trt_end.y = 20.0;
trt_end.z = -205.0;
trt_end.b = 90.0;
sink.straight_feed(4, trt_end);
bool saw_trt_before_g434 = false;
for (const auto &event : events) {
saw_trt_before_g434 = saw_trt_before_g434 || event.type == CNC_SIM_EVENT_RTCP_PIVOT;
}
ok &= expect(!saw_trt_before_g434, "expected M428 not to enable RTCP without G43.4/G43.5");
events.clear();
sink.set_rtcp_state(true, 0, 4);
sink.straight_feed(5, trt_end);
bool saw_xyzbc_trt_pivot = false;
for (const auto &event : events) {
saw_xyzbc_trt_pivot = saw_xyzbc_trt_pivot ||
(event.type == CNC_SIM_EVENT_RTCP_PIVOT &&
event.line == 5 &&
event.reserved == 1 &&
near(event.dwell_seconds, 250.0) &&
near(event.end.x, 10.0) &&
near(event.end.y, 20.0) &&
near(event.end.z, 30.0) &&
near(event.end.b, 90.0));
}
ok &= expect(saw_xyzbc_trt_pivot, "expected M428-style RTCP pivot to use LinuxCNC xyzbc-trt inverse");
events.clear();
LinuxCncXyzbcTrtParameters trt_parameters = linuxcnc_xyzbc_trt_default_parameters(0.0);
trt_parameters.x_rot_point = 3.0;
trt_parameters.y_rot_point = -4.0;
trt_parameters.z_rot_point = 5.0;
trt_parameters.x_offset = 2.0;
trt_parameters.z_offset = 7.0;
trt_parameters.conventional_directions = true;
sink.configure_rtcp(false, 11.0);
sink.configure_xyzbc_trt(trt_parameters);
sink.switch_kinematics(1, 5);
sink.set_rtcp_state(true, 0, 5);
CncSimPose configured_trt_end{};
configured_trt_end.x = 30.0;
configured_trt_end.y = 40.0;
configured_trt_end.z = 50.0;
configured_trt_end.b = 20.0;
configured_trt_end.c = -30.0;
sink.straight_feed(6, configured_trt_end);
LinuxCncXyzbcTrtParameters expected_trt_parameters = trt_parameters;
expected_trt_parameters.tool_offset = 11.0;
const LinuxCncFiveAxisJoints expected_configured_trt =
linuxcnc_xyzbc_trt_inverse(configured_trt_end, expected_trt_parameters);
bool saw_configured_trt_pivot = false;
for (const auto &event : events) {
saw_configured_trt_pivot = saw_configured_trt_pivot ||
(event.type == CNC_SIM_EVENT_RTCP_PIVOT &&
event.line == 6 &&
event.reserved == 1 &&
near(event.dwell_seconds, 11.0) &&
near(event.end.x, expected_configured_trt.x) &&
near(event.end.y, expected_configured_trt.y) &&
near(event.end.z, expected_configured_trt.z) &&
near(event.end.b, expected_configured_trt.b) &&
near(event.end.c, expected_configured_trt.c));
}
ok &= expect(saw_configured_trt_pivot, "expected M428 xyzbc-trt inverse to use configured HAL-style pins");
events.clear();
LinuxCncXyzbcTrtParameters xyzac_parameters = linuxcnc_xyzbc_trt_default_parameters(0.0);
xyzac_parameters.y_offset = 20.0;
xyzac_parameters.z_offset = 10.0;
sink.configure_rtcp(false, 7.0);
sink.configure_xyzac_trt(xyzac_parameters);
sink.switch_kinematics(1, 6);
sink.set_rtcp_state(true, 0, 6);
CncSimPose xyzac_end{};
xyzac_end.x = 12.0;
xyzac_end.y = -8.0;
xyzac_end.z = 42.0;
xyzac_end.a = 35.0;
xyzac_end.c = -25.0;
sink.straight_feed(7, xyzac_end);
LinuxCncXyzbcTrtParameters expected_xyzac_parameters = xyzac_parameters;
expected_xyzac_parameters.tool_offset = 7.0;
const LinuxCncFiveAxisJoints expected_xyzac =
linuxcnc_xyzac_trt_inverse(xyzac_end, expected_xyzac_parameters);
bool saw_xyzac_trt_pivot = false;
for (const auto &event : events) {
saw_xyzac_trt_pivot = saw_xyzac_trt_pivot ||
(event.type == CNC_SIM_EVENT_RTCP_PIVOT &&
event.line == 7 &&
event.reserved == 1 &&
near(event.dwell_seconds, 7.0) &&
near(event.end.x, expected_xyzac.x) &&
near(event.end.y, expected_xyzac.y) &&
near(event.end.z, expected_xyzac.z) &&
near(event.end.a, expected_xyzac.a) &&
near(event.end.c, expected_xyzac.c));
}
ok &= expect(saw_xyzac_trt_pivot, "expected M428 xyzac-trt inverse to use LinuxCNC xyzacKinematicsInverse");
std::vector<CncSimEvent> fiveaxis_events;
CanonEventSink fiveaxis_sink;
fiveaxis_sink.set_callback(collect_event, &fiveaxis_events);
fiveaxis_sink.configure_rtcp(true, 250.0);
fiveaxis_sink.configure_fiveaxis(250.0, 0);
fiveaxis_sink.switch_m_code_kinematics(428, 1, 8);
CncSimPose bridgemill_end{};
bridgemill_end.x = 260.0;
bridgemill_end.y = 20.0;
bridgemill_end.z = 280.0;
bridgemill_end.b = 90.0;
bridgemill_end.c = 0.0;
fiveaxis_sink.straight_feed(9, bridgemill_end);
bool saw_fiveaxis_pivot = false;
for (const auto &event : fiveaxis_events) {
saw_fiveaxis_pivot = saw_fiveaxis_pivot ||
(event.type == CNC_SIM_EVENT_RTCP_PIVOT &&
event.line == 9 &&
event.reserved == 0 &&
near(event.dwell_seconds, 250.0) &&
near(event.end.x, 10.0) &&
near(event.end.y, 20.0) &&
near(event.end.z, 30.0) &&
near(event.end.b, 90.0) &&
near(event.end.c, 0.0));
}
ok &= expect(saw_fiveaxis_pivot, "expected M428 bridgemill RTCP pivot to use LinuxCNC 5axiskins inverse");
events.clear();
sink.configure_rtcp(true, 250.0);
sink.switch_kinematics(0, 3);
five_axis_end.x = 300.0;
sink.straight_feed(4, five_axis_end);
bool saw_disabled_pivot = false;
for (const auto &event : events) {
saw_disabled_pivot = saw_disabled_pivot || event.type == CNC_SIM_EVENT_RTCP_PIVOT;
}
ok &= expect(!saw_disabled_pivot, "expected M429-style kinematics switch to disable RTCP pivot events");
events.clear();
sink.configure_rtcp(true, 250.0);
sink.switch_kinematics(3, 5);
sink.straight_feed(6, five_axis_end);
bool saw_unknown_switchkins_pivot = false;
for (const auto &event : events) {
saw_unknown_switchkins_pivot = saw_unknown_switchkins_pivot || event.type == CNC_SIM_EVENT_RTCP_PIVOT;
}
ok &= expect(!saw_unknown_switchkins_pivot,
"expected unsupported switchkins type to avoid generic RTCP fallback");
return ok ? 0 : 1;
}

View File

@@ -6,6 +6,10 @@
namespace {
constexpr int kToolLengthOffsetEvent = 430;
constexpr int kCssSpindleModeEvent = 960;
constexpr int kConstantRpmSpindleModeEvent = 970;
int collect_event(const CncSimEvent *event, void *user_data) {
auto *events = static_cast<std::vector<CncSimEvent> *>(user_data);
events->push_back(*event);
@@ -54,6 +58,34 @@ bool saw_comment_state(const std::vector<CncSimEvent> &events,
} // namespace
int main() {
{
std::vector<CncSimEvent> default_events;
CncSimHandle *default_sim = cnc_sim_create();
cnc_sim_set_event_callback(default_sim, collect_event, &default_events);
const char default_program[] =
"G21 G90 G17\n"
"G0 X1\n"
"M30\n";
bool default_ok = true;
default_ok &= expect(cnc_sim_parse_program(default_sim,
default_program,
sizeof(default_program) - 1) == 0,
cnc_sim_last_error(default_sim));
bool saw_default_linuxcnc_rapid = false;
for (const auto &event : default_events) {
saw_default_linuxcnc_rapid = saw_default_linuxcnc_rapid ||
(event.type == CNC_SIM_EVENT_RAPID &&
event.line == 2 &&
near(event.end.x, 1.0));
}
default_ok &= expect(saw_default_linuxcnc_rapid,
"expected compiled API default backend to use LinuxCNC rs274");
cnc_sim_destroy(default_sim);
if (!default_ok) {
return 1;
}
}
const char config[] =
"{\"backend\":\"linuxcnc-rs274\",\"rtcp\":{\"enabled\":true,\"toolLength\":100,\"toolLengths\":{\"7\":125}}}";
const char program[] =
@@ -62,6 +94,7 @@ int main() {
"M428 M429 M430\n"
"G49\n"
"G43.4 H7\n"
"G43.5 H7\n"
"S8000 M3\n"
"G0 X0 Y0 Z5\n"
"F600\n"
@@ -91,6 +124,7 @@ int main() {
bool saw_m430 = false;
bool saw_g49 = false;
bool saw_g434 = false;
bool saw_g435 = false;
bool saw_g5x_offset = false;
bool saw_xy_rotation = false;
bool saw_g92_clear = false;
@@ -180,22 +214,20 @@ int main() {
saw_end = saw_end || event.type == CNC_SIM_EVENT_PROGRAM_END;
saw_rtcp = saw_rtcp ||
(event.type == CNC_SIM_EVENT_RTCP_PIVOT &&
event.end.z == 130.0);
event.reserved == 2 &&
event.end.z == 5.0);
saw_m428 = saw_m428 ||
(event.type == CNC_SIM_EVENT_KINEMATICS_SWITCH &&
event.line == 3 &&
event.reserved == 1 &&
event.feed == 1.0);
event.reserved == 1);
saw_m429 = saw_m429 ||
(event.type == CNC_SIM_EVENT_KINEMATICS_SWITCH &&
event.line == 3 &&
event.reserved == 0 &&
event.feed == 0.0);
event.reserved == 0);
saw_m430 = saw_m430 ||
(event.type == CNC_SIM_EVENT_KINEMATICS_SWITCH &&
event.line == 3 &&
event.reserved == 2 &&
event.feed == 1.0);
event.reserved == 2);
saw_g49 = saw_g49 ||
(event.type == CNC_SIM_EVENT_RTCP_STATE &&
event.line == 4 &&
@@ -206,6 +238,12 @@ int main() {
event.feed == 1.0 &&
event.tool == 7 &&
event.dwell_seconds == 125.0);
saw_g435 = saw_g435 ||
(event.type == CNC_SIM_EVENT_RTCP_STATE &&
event.line == 6 &&
event.feed == 1.0 &&
event.tool == 7 &&
event.dwell_seconds == 125.0);
}
ok &= expect(saw_tool, "expected LinuxCNC tool-change event");
@@ -217,8 +255,17 @@ int main() {
ok &= expect(saw_m428, "expected LinuxCNC M428 kinematics switch");
ok &= expect(saw_m429, "expected LinuxCNC M429 kinematics switch");
ok &= expect(saw_m430, "expected LinuxCNC M430 kinematics switch");
ok &= expect(saw_comment_state(events, 3, 68, 3, 1.0),
"expected LinuxCNC M428 remap M68 E3 Q1 side effect");
ok &= expect(saw_comment_state(events, 3, 68, 3, 0.0),
"expected LinuxCNC M429 remap M68 E3 Q0 side effect");
ok &= expect(saw_comment_state(events, 3, 68, 3, 2.0),
"expected LinuxCNC M430 remap M68 E3 Q2 side effect");
ok &= expect(saw_comment_state(events, 3, 66, 0, 0.0),
"expected LinuxCNC M428/M429/M430 remap M66 E0 L0 side effect");
ok &= expect(saw_g49, "expected LinuxCNC G49 RTCP off state");
ok &= expect(saw_g434, "expected LinuxCNC G43.4 RTCP on state with H code");
ok &= expect(saw_g435, "expected LinuxCNC G43.5 RTCP on state with H code");
const char arc_planes_program[] =
"G21 G90 G17\n"
@@ -416,25 +463,25 @@ int main() {
saw_g431_tool_length = saw_g431_tool_length ||
(event.type == CNC_SIM_EVENT_COMMENT &&
event.line == 2 &&
event.reserved == 430 &&
event.reserved == kToolLengthOffsetEvent &&
event.start.x == 1.0 &&
event.start.z == 2.0);
saw_g432_tool_length = saw_g432_tool_length ||
(event.type == CNC_SIM_EVENT_COMMENT &&
event.line == 3 &&
event.reserved == 430 &&
event.reserved == kToolLengthOffsetEvent &&
event.start.x == 1.25 &&
event.start.z == 2.5);
saw_g49_dynamic_tool_length_clear = saw_g49_dynamic_tool_length_clear ||
(event.type == CNC_SIM_EVENT_COMMENT &&
event.line == 5 &&
event.reserved == 430 &&
event.reserved == kToolLengthOffsetEvent &&
event.start.x == 0.0 &&
event.start.z == 0.0);
saw_g432_h_tool_length = saw_g432_h_tool_length ||
(event.type == CNC_SIM_EVENT_COMMENT &&
event.line == 4 &&
event.reserved == 430 &&
event.reserved == kToolLengthOffsetEvent &&
event.start.x == 1.25 &&
event.start.z == 15.0);
}
@@ -517,12 +564,12 @@ int main() {
saw_g97_mode = saw_g97_mode ||
(event.type == CNC_SIM_EVENT_COMMENT &&
event.line == 2 &&
event.reserved == 970 &&
event.reserved == kConstantRpmSpindleModeEvent &&
event.feed == 0.0);
saw_g96_mode = saw_g96_mode ||
(event.type == CNC_SIM_EVENT_COMMENT &&
event.line == 3 &&
event.reserved == 960 &&
event.reserved == kCssSpindleModeEvent &&
event.feed == 2500.0);
}
ok &= expect(saw_g97_mode, "expected LinuxCNC G97 constant-RPM spindle mode state");
@@ -903,12 +950,12 @@ int main() {
saw_tool_length = saw_tool_length ||
(event.type == CNC_SIM_EVENT_COMMENT &&
event.line == 2 &&
event.reserved == 430 &&
event.reserved == kToolLengthOffsetEvent &&
near(event.start.z, 12.5));
saw_tool_length_clear = saw_tool_length_clear ||
(event.type == CNC_SIM_EVENT_COMMENT &&
event.line == 3 &&
event.reserved == 430 &&
event.reserved == kToolLengthOffsetEvent &&
event.start.z == 0.0);
}
ok &= expect(saw_tool_length, "expected LinuxCNC G43 H1 tool length event");
@@ -1272,6 +1319,157 @@ int main() {
}
ok &= expect(saw_block_delete_disabled_line, "expected LinuxCNC disabled block-delete line to execute");
const char block_delete_rtcp_controls_program[] =
"G21 G90 G17\n"
"/N10 M428\n"
"/N20 G43.4 H7\n"
"M30\n";
events.clear();
ok &= expect(cnc_sim_load_config_json(sim, block_delete_on_config, sizeof(block_delete_on_config) - 1) == 0,
cnc_sim_last_error(sim));
ok &= expect(cnc_sim_parse_program(sim,
block_delete_rtcp_controls_program,
sizeof(block_delete_rtcp_controls_program) - 1) == 0,
cnc_sim_last_error(sim));
bool saw_block_delete_skipped_m428 = false;
bool saw_block_delete_skipped_g434 = false;
for (const auto &event : events) {
saw_block_delete_skipped_m428 = saw_block_delete_skipped_m428 ||
(event.type == CNC_SIM_EVENT_KINEMATICS_SWITCH &&
event.line == 2);
saw_block_delete_skipped_g434 = saw_block_delete_skipped_g434 ||
(event.type == CNC_SIM_EVENT_RTCP_STATE &&
event.line == 3);
}
ok &= expect(!saw_block_delete_skipped_m428, "expected LinuxCNC block-delete M428 line to be skipped");
ok &= expect(!saw_block_delete_skipped_g434, "expected LinuxCNC block-delete G43.4 line to be skipped");
events.clear();
ok &= expect(cnc_sim_load_config_json(sim, block_delete_off_config, sizeof(block_delete_off_config) - 1) == 0,
cnc_sim_last_error(sim));
ok &= expect(cnc_sim_parse_program(sim,
block_delete_rtcp_controls_program,
sizeof(block_delete_rtcp_controls_program) - 1) == 0,
cnc_sim_last_error(sim));
bool saw_block_delete_disabled_m428 = false;
bool saw_block_delete_disabled_g434 = false;
for (const auto &event : events) {
saw_block_delete_disabled_m428 = saw_block_delete_disabled_m428 ||
(event.type == CNC_SIM_EVENT_KINEMATICS_SWITCH &&
event.line == 2 &&
event.reserved == 1);
saw_block_delete_disabled_g434 = saw_block_delete_disabled_g434 ||
(event.type == CNC_SIM_EVENT_RTCP_STATE &&
event.line == 3 &&
event.tool == 7);
}
ok &= expect(saw_block_delete_disabled_m428, "expected LinuxCNC disabled block-delete M428 to execute");
ok &= expect(saw_block_delete_disabled_g434, "expected LinuxCNC disabled block-delete G43.4 to execute");
const char unary_sign_rtcp_controls_program[] =
"G21 G90 G17\n"
"M++428\n"
"G+43.4 H++7\n"
"M30\n";
events.clear();
ok &= expect(cnc_sim_load_config_json(sim, config, sizeof(config) - 1) == 0,
cnc_sim_last_error(sim));
ok &= expect(cnc_sim_parse_program(sim,
unary_sign_rtcp_controls_program,
sizeof(unary_sign_rtcp_controls_program) - 1) == 0,
cnc_sim_last_error(sim));
bool saw_unary_sign_m428 = false;
bool saw_unary_sign_g434 = false;
for (const auto &event : events) {
saw_unary_sign_m428 = saw_unary_sign_m428 ||
(event.type == CNC_SIM_EVENT_KINEMATICS_SWITCH &&
event.line == 2 &&
event.reserved == 1);
saw_unary_sign_g434 = saw_unary_sign_g434 ||
(event.type == CNC_SIM_EVENT_RTCP_STATE &&
event.line == 3 &&
event.tool == 7);
}
ok &= expect(saw_unary_sign_m428, "expected LinuxCNC source M++428 to execute as M428");
ok &= expect(saw_unary_sign_g434, "expected LinuxCNC source G+43.4 H++7 to execute as G43.4 H7");
const char numbered_rtcp_controls_program[] =
"G21 G90 G17\n"
"N10 M428\n"
"N20 G43.4 H7\n"
"M30\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
numbered_rtcp_controls_program,
sizeof(numbered_rtcp_controls_program) - 1) == 0,
cnc_sim_last_error(sim));
bool saw_numbered_m428 = false;
bool saw_numbered_g434 = false;
for (const auto &event : events) {
saw_numbered_m428 = saw_numbered_m428 ||
(event.type == CNC_SIM_EVENT_KINEMATICS_SWITCH &&
event.line == 2 &&
event.reserved == 1);
saw_numbered_g434 = saw_numbered_g434 ||
(event.type == CNC_SIM_EVENT_RTCP_STATE &&
event.line == 3 &&
event.tool == 7);
}
ok &= expect(saw_numbered_m428, "expected LinuxCNC source N10 M428 to execute as M428");
ok &= expect(saw_numbered_g434, "expected LinuxCNC source N20 G43.4 H7 to execute as G43.4 H7");
const char bracket_rtcp_controls_program[] =
"G21 G90 G17\n"
"M[428]\n"
"G[43.4] H[7]\n"
"M30\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
bracket_rtcp_controls_program,
sizeof(bracket_rtcp_controls_program) - 1) == 0,
cnc_sim_last_error(sim));
bool saw_bracket_m428 = false;
bool saw_bracket_g434 = false;
for (const auto &event : events) {
saw_bracket_m428 = saw_bracket_m428 ||
(event.type == CNC_SIM_EVENT_KINEMATICS_SWITCH &&
event.line == 2 &&
event.reserved == 1);
saw_bracket_g434 = saw_bracket_g434 ||
(event.type == CNC_SIM_EVENT_RTCP_STATE &&
event.line == 3 &&
event.tool == 7);
}
ok &= expect(saw_bracket_m428, "expected LinuxCNC source M[428] to execute as M428");
ok &= expect(saw_bracket_g434, "expected LinuxCNC source G[43.4] H[7] to execute as G43.4 H7");
const char exponent_m428_control_program[] =
"G21 G90\n"
"M4.28e2\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
exponent_m428_control_program,
sizeof(exponent_m428_control_program) - 1) != 0,
"expected LinuxCNC source backend to reject exponent M428 control syntax");
const char exponent_g434_control_program[] =
"G21 G90\n"
"G4.34e1 H7\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
exponent_g434_control_program,
sizeof(exponent_g434_control_program) - 1) != 0,
"expected LinuxCNC source backend to reject exponent G43.4 control syntax");
const char exponent_h_control_program[] =
"G21 G90\n"
"G43.4 H7e0\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
exponent_h_control_program,
sizeof(exponent_h_control_program) - 1) != 0,
"expected LinuxCNC source backend to reject exponent H control syntax");
cnc_sim_destroy(sim);
int abort_count = 0;

View File

@@ -11,6 +11,12 @@
namespace {
constexpr int kToolLengthOffsetEvent = 430;
constexpr int kCssSpindleModeEvent = 960;
constexpr int kConstantRpmSpindleModeEvent = 970;
constexpr int kCutterCompOffEvent = 400;
constexpr int kCutterCompLeftExplicitEvent = 411;
int collect_event(const CncSimEvent *event, void *user_data) {
auto *events = static_cast<std::vector<CncSimEvent> *>(user_data);
events->push_back(*event);
@@ -114,6 +120,7 @@ int main() {
"M428 M429 M430\n"
"G49\n"
"G43.4 H7\n"
"G43.5 H7\n"
"S8000 M3\n"
"G0 X0 Y0 Z5\n"
"F600\n"
@@ -154,6 +161,7 @@ int main() {
bool saw_m430 = false;
bool saw_g49 = false;
bool saw_g434 = false;
bool saw_g435 = false;
bool saw_spindle_cw = false;
bool saw_spindle_orient = false;
bool saw_spindle_orient_wait = false;
@@ -379,23 +387,21 @@ int main() {
saw_plane = saw_plane || event.type == CNC_SIM_EVENT_SET_PLANE;
saw_rtcp = saw_rtcp ||
(event.type == CNC_SIM_EVENT_RTCP_PIVOT &&
event.line == 7 &&
event.end.z == 130.0);
event.line == 8 &&
event.reserved == 2 &&
event.end.z == 5.0);
saw_m428 = saw_m428 ||
(event.type == CNC_SIM_EVENT_KINEMATICS_SWITCH &&
event.line == 3 &&
event.reserved == 1 &&
event.feed == 1.0);
event.reserved == 1);
saw_m429 = saw_m429 ||
(event.type == CNC_SIM_EVENT_KINEMATICS_SWITCH &&
event.line == 3 &&
event.reserved == 0 &&
event.feed == 0.0);
event.reserved == 0);
saw_m430 = saw_m430 ||
(event.type == CNC_SIM_EVENT_KINEMATICS_SWITCH &&
event.line == 3 &&
event.reserved == 2 &&
event.feed == 1.0);
event.reserved == 2);
saw_g49 = saw_g49 ||
(event.type == CNC_SIM_EVENT_RTCP_STATE &&
event.line == 4 &&
@@ -406,21 +412,27 @@ int main() {
event.feed == 1.0 &&
event.tool == 7 &&
event.dwell_seconds == 125.0);
saw_g435 = saw_g435 ||
(event.type == CNC_SIM_EVENT_RTCP_STATE &&
event.line == 6 &&
event.feed == 1.0 &&
event.tool == 7 &&
event.dwell_seconds == 125.0);
saw_spindle_cw = saw_spindle_cw ||
(event.type == CNC_SIM_EVENT_SET_SPINDLE &&
event.line == 6 &&
event.line == 7 &&
event.spindle == 8000.0 &&
event.reserved == 1);
saw_spindle_orient = saw_spindle_orient ||
(event.type == CNC_SIM_EVENT_COMMENT &&
event.line == 15 &&
event.line == 16 &&
event.reserved == 1901 &&
event.tool == 0 &&
event.feed == 45.0 &&
event.arc_turns == 1);
saw_spindle_orient_wait = saw_spindle_orient_wait ||
(event.type == CNC_SIM_EVENT_COMMENT &&
event.line == 15 &&
event.line == 16 &&
event.reserved == 1902 &&
event.tool == 0 &&
event.dwell_seconds == 2.0);
@@ -429,7 +441,7 @@ int main() {
event.start.x == 20.0 && event.end.x == 25.0);
saw_r_arc_center = saw_r_arc_center ||
(event.type == CNC_SIM_EVENT_ARC_FEED &&
event.line == 14 &&
event.line == 15 &&
(event.center.x != event.start.x || event.center.y != event.start.y));
}
ok &= expect(saw_rapid, "expected rapid event");
@@ -442,15 +454,97 @@ int main() {
ok &= expect(saw_rtcp, "expected RTCP pivot event from config");
ok &= expect(saw_m428, "expected M428 original kinematics switch");
ok &= expect(saw_m429, "expected M429 identity kinematics switch");
ok &= expect(saw_m430, "expected M430 five-axis BC kinematics switch");
ok &= expect(saw_m430, "expected M430 userk kinematics switch");
ok &= expect(saw_comment_state(events, 3, 68, 3, 1.0),
"expected M428 remap M68 E3 Q1 side effect");
ok &= expect(saw_comment_state(events, 3, 68, 3, 0.0),
"expected M429 remap M68 E3 Q0 side effect");
ok &= expect(saw_comment_state(events, 3, 68, 3, 2.0),
"expected M430 remap M68 E3 Q2 side effect");
ok &= expect(saw_comment_state(events, 3, 66, 0, 0.0),
"expected M428/M429/M430 remap M66 E0 L0 side effect");
ok &= expect(saw_g49, "expected G49 RTCP off state");
ok &= expect(saw_g434, "expected G43.4 RTCP on state with H code");
ok &= expect(saw_g435, "expected G43.5 RTCP on state with H code");
ok &= expect(saw_spindle_cw, "expected M3 clockwise spindle state");
ok &= expect(saw_spindle_orient, "expected M19 spindle orient state");
ok &= expect(saw_spindle_orient_wait, "expected M19 spindle orient wait state");
ok &= expect(saw_incremental_move, "expected G91 incremental move");
ok &= expect(saw_r_arc_center, "expected R arc center calculation");
{
std::vector<CncSimEvent> configured_events;
CncSimHandle *configured_sim = cnc_sim_create();
cnc_sim_set_event_callback(configured_sim, collect_event, &configured_events);
const char configured_rtcp[] =
"{\"backend\":\"smoke\",\"rtcp\":{\"enabled\":false,\"toolLength\":11},"
"\"xyzbcTrt\":{\"xRotPoint\":3,\"yRotPoint\":-4,\"zRotPoint\":5,"
"\"xOffset\":2,\"zOffset\":7,\"conventionalDirections\":true}}";
const char configured_program[] =
"M428\n"
"G43.4\n"
"G0 X30 Y40 Z50 B20 C-30\n";
ok &= expect(cnc_sim_load_config_json(configured_sim,
configured_rtcp,
sizeof(configured_rtcp) - 1) == 0,
cnc_sim_last_error(configured_sim));
ok &= expect(cnc_sim_parse_program(configured_sim,
configured_program,
sizeof(configured_program) - 1) == 0,
cnc_sim_last_error(configured_sim));
bool saw_configured_rtcp = false;
for (const auto &event : configured_events) {
saw_configured_rtcp = saw_configured_rtcp ||
(event.type == CNC_SIM_EVENT_RTCP_PIVOT &&
event.reserved == 1 &&
near(event.dwell_seconds, 11.0) &&
near(event.end.x, -4.814629) &&
near(event.end.y, 47.605118) &&
near(event.end.z, 48.160567) &&
near(event.end.b, 20.0) &&
near(event.end.c, -30.0));
}
ok &= expect(saw_configured_rtcp, "expected JSON-configured xyzbc-trt pins to affect M428 inverse");
cnc_sim_destroy(configured_sim);
}
{
std::vector<CncSimEvent> fiveaxis_events;
CncSimHandle *fiveaxis_sim = cnc_sim_create();
cnc_sim_set_event_callback(fiveaxis_sim, collect_event, &fiveaxis_events);
const char fiveaxis_config[] =
"{\"backend\":\"smoke\",\"rtcp\":{\"enabled\":true,\"toolLength\":250},"
"\"kinematics\":\"5axiskins\",\"pivotLength\":250}";
const char fiveaxis_program[] =
"M428\n"
"G0 X260 Y20 Z280 B90 C0\n";
ok &= expect(cnc_sim_load_config_json(fiveaxis_sim,
fiveaxis_config,
sizeof(fiveaxis_config) - 1) == 0,
cnc_sim_last_error(fiveaxis_sim));
ok &= expect(cnc_sim_parse_program(fiveaxis_sim,
fiveaxis_program,
sizeof(fiveaxis_program) - 1) == 0,
cnc_sim_last_error(fiveaxis_sim));
bool saw_fiveaxis_rtcp = false;
bool saw_fiveaxis_m428 = false;
for (const auto &event : fiveaxis_events) {
saw_fiveaxis_m428 = saw_fiveaxis_m428 ||
(event.type == CNC_SIM_EVENT_KINEMATICS_SWITCH &&
event.reserved == 0);
saw_fiveaxis_rtcp = saw_fiveaxis_rtcp ||
(event.type == CNC_SIM_EVENT_RTCP_PIVOT &&
event.reserved == 0 &&
near(event.end.x, 10.0) &&
near(event.end.y, 20.0) &&
near(event.end.z, 30.0) &&
near(event.end.b, 90.0));
}
ok &= expect(saw_fiveaxis_m428, "expected LinuxCNC bridgemill M428 to select 5axiskins type 0");
ok &= expect(saw_fiveaxis_rtcp, "expected 5axiskins inverse from LinuxCNC 5axiskins.c for M428");
cnc_sim_destroy(fiveaxis_sim);
}
const char io_controls_program[] =
"G21 G90 G17\n"
"M62 P1\n"
@@ -1282,19 +1376,19 @@ int main() {
saw_g431_tool_length = saw_g431_tool_length ||
(event.type == CNC_SIM_EVENT_COMMENT &&
event.line == 2 &&
event.reserved == 430 &&
event.reserved == kToolLengthOffsetEvent &&
event.start.x == 1.0 &&
event.start.z == 2.0);
saw_g432_tool_length = saw_g432_tool_length ||
(event.type == CNC_SIM_EVENT_COMMENT &&
event.line == 3 &&
event.reserved == 430 &&
event.reserved == kToolLengthOffsetEvent &&
event.start.x == 1.25 &&
event.start.z == 2.5);
saw_g49_tool_length_clear = saw_g49_tool_length_clear ||
(event.type == CNC_SIM_EVENT_COMMENT &&
event.line == 5 &&
event.reserved == 430 &&
event.reserved == kToolLengthOffsetEvent &&
event.start.x == 0.0 &&
event.start.z == 0.0);
saw_g432_followup_comment = saw_g432_followup_comment ||
@@ -1304,7 +1398,7 @@ int main() {
saw_g432_h_tool_length = saw_g432_h_tool_length ||
(event.type == CNC_SIM_EVENT_COMMENT &&
event.line == 4 &&
event.reserved == 430 &&
event.reserved == kToolLengthOffsetEvent &&
event.start.x == 1.25 &&
event.start.z == 15.0);
}
@@ -1428,12 +1522,12 @@ int main() {
saw_g43_h_tool_length = saw_g43_h_tool_length ||
(event.type == CNC_SIM_EVENT_COMMENT &&
event.line == 2 &&
event.reserved == 430 &&
event.reserved == kToolLengthOffsetEvent &&
event.start.z == 12.5);
saw_g43_current_tool_length = saw_g43_current_tool_length ||
(event.type == CNC_SIM_EVENT_COMMENT &&
event.line == 5 &&
event.reserved == 430 &&
event.reserved == kToolLengthOffsetEvent &&
event.start.z == 0.0);
}
ok &= expect(saw_g43_h_tool_length, "expected smoke G43 H1 tool-table offset");
@@ -1814,6 +1908,50 @@ int main() {
"D word with no G41, G41.1, G42, G42.1, G71, G71.1, G71.2 G73, G83 or G96 to use it",
"expected LinuxCNC modal-motion-D error text");
const char negative_cutter_comp_d_program[] =
"G21 G90 G17\n"
"G41 D-1\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
negative_cutter_comp_d_program,
sizeof(negative_cutter_comp_d_program) - 1) != 0,
"expected LinuxCNC G41 to reject a negative D tool index");
ok &= expect(std::string(cnc_sim_last_error(sim)) == "Negative d word tool radius index used",
"expected LinuxCNC negative cutter-comp D error text");
const char noninteger_cutter_comp_d_program[] =
"G21 G90 G17\n"
"G41 D1.5\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
noninteger_cutter_comp_d_program,
sizeof(noninteger_cutter_comp_d_program) - 1) != 0,
"expected LinuxCNC G41 to reject a non-integer D tool index");
ok &= expect(std::string(cnc_sim_last_error(sim)) == "G41 requires D word to be a whole number",
"expected LinuxCNC non-integer cutter-comp D error text");
const char missing_explicit_cutter_comp_d_program[] =
"G21 G90 G17\n"
"G42.1\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
missing_explicit_cutter_comp_d_program,
sizeof(missing_explicit_cutter_comp_d_program) - 1) != 0,
"expected LinuxCNC G42.1 to require a D word");
ok &= expect(std::string(cnc_sim_last_error(sim)) == "G42.1 with no D word",
"expected LinuxCNC missing explicit cutter-comp D error text");
const char explicit_cutter_comp_l_wrong_plane_program[] =
"G21 G90 G17\n"
"G41.1 D0.25 L1\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
explicit_cutter_comp_l_wrong_plane_program,
sizeof(explicit_cutter_comp_l_wrong_plane_program) - 1) != 0,
"expected LinuxCNC G41.1 L word to require G18");
ok &= expect(std::string(cnc_sim_last_error(sim)) == "G41.1 with L word, but plane is not G18",
"expected LinuxCNC explicit cutter-comp L-plane error text");
const char cutter_comp_program[] =
"G21 G90 G17\n"
"G0 X0 Y0 Z0\n"
@@ -1828,8 +1966,12 @@ int main() {
bool saw_g41 = false;
bool saw_g40 = false;
for (const auto &event : events) {
saw_g41 = saw_g41 || (event.type == CNC_SIM_EVENT_COMMENT && event.reserved == 411);
saw_g40 = saw_g40 || (event.type == CNC_SIM_EVENT_COMMENT && event.reserved == 400);
saw_g41 = saw_g41 ||
(event.type == CNC_SIM_EVENT_COMMENT &&
event.reserved == kCutterCompLeftExplicitEvent);
saw_g40 = saw_g40 ||
(event.type == CNC_SIM_EVENT_COMMENT &&
event.reserved == kCutterCompOffEvent);
}
ok &= expect(saw_g41, "expected G41.1 cutter compensation state event");
ok &= expect(saw_g40, "expected G40 cutter compensation clear event");
@@ -2095,12 +2237,12 @@ int main() {
saw_g97_mode = saw_g97_mode ||
(event.type == CNC_SIM_EVENT_COMMENT &&
event.line == 2 &&
event.reserved == 970 &&
event.reserved == kConstantRpmSpindleModeEvent &&
event.feed == 0.0);
saw_g96_mode = saw_g96_mode ||
(event.type == CNC_SIM_EVENT_COMMENT &&
event.line == 3 &&
event.reserved == 960 &&
event.reserved == kCssSpindleModeEvent &&
event.feed == 2500.0);
}
ok &= expect(saw_g97_mode, "expected G97 constant-RPM spindle mode state");
@@ -3787,6 +3929,18 @@ int main() {
ok &= expect(std::string(cnc_sim_last_error(sim)) == "Cannot do zero repeats of cycle",
cnc_sim_last_error(sim));
const char noninteger_cycle_l_program[] =
"G21 G90 G17\n"
"F100\n"
"G81 X1 Z-1 R1 L1.5\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
noninteger_cycle_l_program,
sizeof(noninteger_cycle_l_program) - 1) != 0,
"expected noninteger canned cycle L word to fail");
ok &= expect(std::string(cnc_sim_last_error(sim)) == "Non integer value for integer",
cnc_sim_last_error(sim));
const char missing_cycle_feed_program[] =
"G21 G90 G17\n"
"G81 X1 Z-1 R1\n";
@@ -3875,6 +4029,16 @@ int main() {
ok &= expect(std::string(cnc_sim_last_error(sim)) == "Negative or zero q value used",
cnc_sim_last_error(sim));
const char negative_then_positive_g83_q_program[] =
"G21 G90 G17\n"
"F100\n"
"G83 X1 Z-1 R1 Q-2 Q0.5\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
negative_then_positive_g83_q_program,
sizeof(negative_then_positive_g83_q_program) - 1) == 0,
cnc_sim_last_error(sim));
const char cross_cycle_q_program[] =
"G21 G90 G17\n"
"G73 X1 Z-1 R1 Q0.5\n"
@@ -5904,6 +6068,86 @@ int main() {
sizeof(readonly_numbered_parameter_assignment_program) - 1) != 0,
"expected LinuxCNC to reject assignment to read-only numbered parameter");
const char cutter_comp_read_current_position_program[] =
"G21 G90 G17\n"
"G41.1 D1\n"
"#1 = #5420\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
cutter_comp_read_current_position_program,
sizeof(cutter_comp_read_current_position_program) - 1) != 0,
"expected LinuxCNC to reject reading #5420 with cutter compensation on");
ok &= expect(std::string(cnc_sim_last_error(sim)) ==
"Cannot read current position with cutter radius compensation on",
"expected LinuxCNC cutter-comp current-position read error text");
const char cutter_comp_read_indexed_current_position_program[] =
"G21 G90 G17\n"
"G41.1 D1\n"
"#1 = #[5420]\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
cutter_comp_read_indexed_current_position_program,
sizeof(cutter_comp_read_indexed_current_position_program) - 1) != 0,
"expected LinuxCNC to reject reading #[5420] with cutter compensation on");
ok &= expect(std::string(cnc_sim_last_error(sim)) ==
"Cannot read current position with cutter radius compensation on",
"expected LinuxCNC cutter-comp indexed current-position read error text");
const char cutter_comp_read_spaced_indexed_current_position_program[] =
"G21 G90 G17\n"
"G41.1 D1\n"
"G1 X# [5420]\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
cutter_comp_read_spaced_indexed_current_position_program,
sizeof(cutter_comp_read_spaced_indexed_current_position_program) - 1) != 0,
"expected LinuxCNC to reject reading # [5420] with cutter compensation on");
ok &= expect(std::string(cnc_sim_last_error(sim)) ==
"Cannot read current position with cutter radius compensation on",
"expected LinuxCNC cutter-comp spaced indexed current-position read error text");
const char cutter_comp_read_expression_indexed_current_position_program[] =
"G21 G90 G17\n"
"G41.1 D1\n"
"#1 = #[5400 + 20]\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
cutter_comp_read_expression_indexed_current_position_program,
sizeof(cutter_comp_read_expression_indexed_current_position_program) - 1) != 0,
"expected LinuxCNC to reject reading #[5400 + 20] with cutter compensation on");
ok &= expect(std::string(cnc_sim_last_error(sim)) ==
"Cannot read current position with cutter radius compensation on",
"expected LinuxCNC cutter-comp expression-indexed current-position read error text");
const char cutter_comp_read_parameter_indexed_current_position_program[] =
"G21 G90 G17\n"
"#1 = 5420\n"
"G41.1 D1\n"
"#2 = #[#1]\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
cutter_comp_read_parameter_indexed_current_position_program,
sizeof(cutter_comp_read_parameter_indexed_current_position_program) - 1) != 0,
"expected LinuxCNC to reject reading #[#1] with cutter compensation on");
ok &= expect(std::string(cnc_sim_last_error(sim)) ==
"Cannot read current position with cutter radius compensation on",
"expected LinuxCNC cutter-comp parameter-indexed current-position read error text");
const char cutter_comp_read_named_indexed_current_position_program[] =
"G21 G90 G17\n"
"#<idx> = 5420\n"
"G41.1 D1\n"
"#2 = #[#<idx>]\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
cutter_comp_read_named_indexed_current_position_program,
sizeof(cutter_comp_read_named_indexed_current_position_program) - 1) != 0,
"expected LinuxCNC to reject reading #[#<idx>] with cutter compensation on");
ok &= expect(std::string(cnc_sim_last_error(sim)) ==
"Cannot read current position with cutter radius compensation on",
"expected LinuxCNC cutter-comp named-indexed current-position read error text");
const char arc_center_tolerance_good_imperial_program[] =
"G20\n"
"F1\n"
@@ -6378,6 +6622,98 @@ int main() {
ok &= expect(std::string(cnc_sim_last_error(sim)) == "bad number format (conversion failed) parsing '1.2.3'",
"expected LinuxCNC malformed-real-value error text");
const char unclosed_word_expression_program[] =
"G21 G90\n"
"F100\n"
"G1 X[1 + 2\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
unclosed_word_expression_program,
sizeof(unclosed_word_expression_program) - 1) != 0,
"expected LinuxCNC to reject an unclosed word expression");
ok &= expect(std::string(cnc_sim_last_error(sim)) == "Unclosed expression",
"expected LinuxCNC unclosed-word-expression error text");
const char unclosed_parameter_assignment_expression_program[] =
"#1 = [1 + 2\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
unclosed_parameter_assignment_expression_program,
sizeof(unclosed_parameter_assignment_expression_program) - 1) != 0,
"expected LinuxCNC to reject an unclosed parameter assignment expression");
ok &= expect(std::string(cnc_sim_last_error(sim)) == "Unclosed expression",
"expected LinuxCNC unclosed-parameter-assignment-expression error text");
const char unclosed_indexed_parameter_reference_program[] =
"G21 G90\n"
"F100\n"
"G1 X#[1 + 2\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
unclosed_indexed_parameter_reference_program,
sizeof(unclosed_indexed_parameter_reference_program) - 1) != 0,
"expected LinuxCNC to reject an unclosed indexed parameter reference");
ok &= expect(std::string(cnc_sim_last_error(sim)) == "Unclosed expression",
"expected LinuxCNC unclosed-indexed-parameter-reference error text");
const char unclosed_indexed_parameter_assignment_program[] =
"#[1 + 2 = 3\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
unclosed_indexed_parameter_assignment_program,
sizeof(unclosed_indexed_parameter_assignment_program) - 1) != 0,
"expected LinuxCNC to reject an unclosed indexed parameter assignment");
ok &= expect(std::string(cnc_sim_last_error(sim)) == "Unclosed expression",
"expected LinuxCNC unclosed-indexed-parameter-assignment error text");
const char unclosed_indirect_parameter_reference_program[] =
"#1 = 3\n"
"G21 G90\n"
"F100\n"
"G1 X##[1 + 2\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
unclosed_indirect_parameter_reference_program,
sizeof(unclosed_indirect_parameter_reference_program) - 1) != 0,
"expected LinuxCNC to reject an unclosed indirect parameter reference");
ok &= expect(std::string(cnc_sim_last_error(sim)) == "Unclosed expression",
"expected LinuxCNC unclosed-indirect-parameter-reference error text");
const char unterminated_named_parameter_reference_program[] =
"G21 G90\n"
"F100\n"
"G1 X#<missing\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
unterminated_named_parameter_reference_program,
sizeof(unterminated_named_parameter_reference_program) - 1) != 0,
"expected LinuxCNC to reject an unterminated named parameter reference");
ok &= expect(std::string(cnc_sim_last_error(sim)) == "Named parameter not terminated",
"expected LinuxCNC unterminated-named-parameter-reference error text");
const char unterminated_indirect_named_parameter_reference_program[] =
"#1 = 3\n"
"G21 G90\n"
"F100\n"
"G1 X##<missing\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
unterminated_indirect_named_parameter_reference_program,
sizeof(unterminated_indirect_named_parameter_reference_program) - 1) != 0,
"expected LinuxCNC to reject an unterminated indirect named parameter reference");
ok &= expect(std::string(cnc_sim_last_error(sim)) == "Named parameter not terminated",
"expected LinuxCNC unterminated-indirect-named-parameter-reference error text");
const char unterminated_named_parameter_assignment_program[] =
"#<missing = 3\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
unterminated_named_parameter_assignment_program,
sizeof(unterminated_named_parameter_assignment_program) - 1) != 0,
"expected LinuxCNC to reject an unterminated named parameter assignment");
ok &= expect(std::string(cnc_sim_last_error(sim)) == "Named parameter not terminated",
"expected LinuxCNC unterminated-named-parameter-assignment error text");
const char unused_p_word_program[] =
"G21 G90\n"
"F100\n"
@@ -6398,6 +6734,115 @@ int main() {
sizeof(invalid_arc_p_word_program) - 1) != 0,
"expected LinuxCNC to reject P value less than 1 with G2/G3");
const char absolute_center_arc_missing_axis_program[] =
"G21 G90 G17\n"
"F100\n"
"G90.1\n"
"G2 X1 Y0 I0.5\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
absolute_center_arc_missing_axis_program,
sizeof(absolute_center_arc_missing_axis_program) - 1) != 0,
"expected LinuxCNC absolute-center arc to reject a missing center axis");
ok &= expect(std::string(cnc_sim_last_error(sim)) == "J word missing in absolute center arc",
"expected LinuxCNC missing absolute-center arc axis error text");
const char mixed_radius_ijk_arc_program[] =
"G21 G90 G17\n"
"F100\n"
"G2 X1 Y0 R1 I0.5 J0\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
mixed_radius_ijk_arc_program,
sizeof(mixed_radius_ijk_arc_program) - 1) != 0,
"expected LinuxCNC to reject mixed R and IJK arc format");
ok &= expect(std::string(cnc_sim_last_error(sim)) == "Mixed radius ijk format for arc",
"expected LinuxCNC mixed R/IJK arc error text");
const char missing_arc_format_words_program[] =
"G21 G90 G17\n"
"F100\n"
"G2 X1 Y0\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
missing_arc_format_words_program,
sizeof(missing_arc_format_words_program) - 1) != 0,
"expected LinuxCNC to reject arc with no R/I/J/K words");
ok &= expect(std::string(cnc_sim_last_error(sim)) == "R i j k words all missing for arc",
"expected LinuxCNC missing R/I/J/K arc error text");
const char xy_arc_with_k_program[] =
"G21 G90 G17\n"
"F100\n"
"G2 X1 Y0 I0.5 J0 K0\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
xy_arc_with_k_program,
sizeof(xy_arc_with_k_program) - 1) != 0,
"expected LinuxCNC to reject K word in XY-plane arc");
ok &= expect(std::string(cnc_sim_last_error(sim)) == "K word given for arc in xy plane",
"expected LinuxCNC XY-plane arc K-word error text");
const char xz_arc_with_j_program[] =
"G21 G90 G18\n"
"F100\n"
"G2 X1 Z0 I0.5 K0 J0\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
xz_arc_with_j_program,
sizeof(xz_arc_with_j_program) - 1) != 0,
"expected LinuxCNC to reject J word in XZ-plane arc");
ok &= expect(std::string(cnc_sim_last_error(sim)) == "J word given for arc in xz plane",
"expected LinuxCNC XZ-plane arc J-word error text");
const char yz_arc_with_i_program[] =
"G21 G90 G19\n"
"F100\n"
"G2 Y1 Z0 J0.5 K0 I0\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
yz_arc_with_i_program,
sizeof(yz_arc_with_i_program) - 1) != 0,
"expected LinuxCNC to reject I word in YZ-plane arc");
ok &= expect(std::string(cnc_sim_last_error(sim)) == "I word given for arc in yz plane",
"expected LinuxCNC YZ-plane arc I-word error text");
const char xy_r_arc_missing_endpoint_words_program[] =
"G21 G90 G17\n"
"F100\n"
"G2 R1\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
xy_r_arc_missing_endpoint_words_program,
sizeof(xy_r_arc_missing_endpoint_words_program) - 1) != 0,
"expected LinuxCNC to reject XY R-format arc with no endpoint words");
ok &= expect(std::string(cnc_sim_last_error(sim)) == "X and y words missing for arc in xy plane",
"expected LinuxCNC XY R-format missing endpoint error text");
const char xz_r_arc_missing_endpoint_words_program[] =
"G21 G90 G18\n"
"F100\n"
"G2 R1\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
xz_r_arc_missing_endpoint_words_program,
sizeof(xz_r_arc_missing_endpoint_words_program) - 1) != 0,
"expected LinuxCNC to reject XZ R-format arc with no endpoint words");
ok &= expect(std::string(cnc_sim_last_error(sim)) == "X and z words missing for arc in xz plane",
"expected LinuxCNC XZ R-format missing endpoint error text");
const char yz_r_arc_missing_endpoint_words_program[] =
"G21 G90 G19\n"
"F100\n"
"G2 R1\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
yz_r_arc_missing_endpoint_words_program,
sizeof(yz_r_arc_missing_endpoint_words_program) - 1) != 0,
"expected LinuxCNC to reject YZ R-format arc with no endpoint words");
ok &= expect(std::string(cnc_sim_last_error(sim)) == "Y and z words missing for arc in yz plane",
"expected LinuxCNC YZ R-format missing endpoint error text");
const char r_arc_too_small_program[] =
"G21 G90 G17\n"
"F100\n"
@@ -6628,6 +7073,8 @@ int main() {
g10_l0_loaded_tool_program,
sizeof(g10_l0_loaded_tool_program) - 1) != 0,
"expected LinuxCNC to reject G10 L0 with a loaded tool");
ok &= expect(std::string(cnc_sim_last_error(sim)) == "G10 L0 not allowed with loaded tool <1>",
"expected LinuxCNC G10 L0 loaded-tool error text");
}
const char out_of_range_g10_l1_p_word_program[] =
@@ -7104,6 +7551,24 @@ int main() {
ok &= expect(saw_near_negative_one_g4_dwell,
"expected LinuxCNC to execute G4 with P just above -1");
const char negative_then_positive_g4_p_word_program[] =
"G21 G90\n"
"G4 P-2 P3\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
negative_then_positive_g4_p_word_program,
sizeof(negative_then_positive_g4_p_word_program) - 1) == 0,
cnc_sim_last_error(sim));
bool saw_overridden_g4_dwell = false;
for (const auto &event : events) {
saw_overridden_g4_dwell = saw_overridden_g4_dwell ||
(event.type == CNC_SIM_EVENT_DWELL &&
event.line == 2 &&
near(event.dwell_seconds, 3.0));
}
ok &= expect(saw_overridden_g4_dwell,
"expected LinuxCNC P sentinel duplicate rule to keep the later G4 P value");
const char g4_with_arc_program[] =
"G21 G90 G17\n"
"F100\n"
@@ -7513,6 +7978,18 @@ int main() {
ok &= expect(std::string(cnc_sim_last_error(sim)) == "Multiple x words on one line",
"expected LinuxCNC duplicate-X error text");
const char duplicate_u_word_program[] =
"G21 G90\n"
"F100\n"
"G1 U1 U2\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
duplicate_u_word_program,
sizeof(duplicate_u_word_program) - 1) != 0,
"expected LinuxCNC to reject multiple U words on one line");
ok &= expect(std::string(cnc_sim_last_error(sim)) == "Multiple U words on one line",
"expected LinuxCNC duplicate-U error text");
const char duplicate_spindle_selector_program[] =
"G21 G90\n"
"M3 $0 $1\n";
@@ -7767,6 +8244,33 @@ int main() {
ok &= expect(std::string(cnc_sim_last_error(sim)) == "Unknown g code used",
"expected mixed G43.4 motion to use LinuxCNC unknown-G error text");
const char exponent_m428_control_program[] =
"G21 G90\n"
"M4.28e2\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
exponent_m428_control_program,
sizeof(exponent_m428_control_program) - 1) != 0,
"expected LinuxCNC-style M parsing to reject exponent M428 control syntax");
const char exponent_g434_control_program[] =
"G21 G90\n"
"G4.34e1 H7\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
exponent_g434_control_program,
sizeof(exponent_g434_control_program) - 1) != 0,
"expected LinuxCNC-style G parsing to reject exponent G43.4 control syntax");
const char exponent_h_control_program[] =
"G21 G90\n"
"G43.4 H7e0\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
exponent_h_control_program,
sizeof(exponent_h_control_program) - 1) != 0,
"expected LinuxCNC-style H parsing to reject exponent H control syntax");
const char invalid_character_program[] =
"G21 G90\n"
"?\n";
@@ -7902,6 +8406,42 @@ int main() {
ok &= expect(saw_spindle_all_cw, "expected LinuxCNC M3 $-1 spindle state");
ok &= expect(saw_spindle_all_stop, "expected LinuxCNC M5 $-1 spindle state");
const char m3_dollar_word_out_of_range_program[] =
"G21\n"
"M3 $1\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
m3_dollar_word_out_of_range_program,
sizeof(m3_dollar_word_out_of_range_program) - 1) != 0,
"expected LinuxCNC to reject out-of-range M3 $ spindle selector");
ok &= expect(std::string(cnc_sim_last_error(sim)) ==
"Spindle ($) number out of range in M3 Command\nnum_spindles =1. $=1\n",
"expected LinuxCNC out-of-range M3 $ spindle error text");
const char m4_dollar_word_out_of_range_program[] =
"G21\n"
"M4 $1\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
m4_dollar_word_out_of_range_program,
sizeof(m4_dollar_word_out_of_range_program) - 1) != 0,
"expected LinuxCNC to reject out-of-range M4 $ spindle selector");
ok &= expect(std::string(cnc_sim_last_error(sim)) ==
"Spindle ($) number out of range in M4 Command\nnum_spindles =1. $=1\n",
"expected LinuxCNC out-of-range M4 $ spindle error text");
const char m5_dollar_word_out_of_range_program[] =
"G21\n"
"M5 $1\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
m5_dollar_word_out_of_range_program,
sizeof(m5_dollar_word_out_of_range_program) - 1) != 0,
"expected LinuxCNC to reject out-of-range M5 $ spindle selector");
ok &= expect(std::string(cnc_sim_last_error(sim)) ==
"Spindle ($) number out of range in M5 Command\nnum_spindles =1. $=1\n",
"expected LinuxCNC out-of-range M5 $ spindle error text");
const char spindle_mode_dollar_program[] =
"G21\n"
"G97 S1000 $0\n"
@@ -7919,12 +8459,12 @@ int main() {
saw_g97_dollar_mode = saw_g97_dollar_mode ||
(event.type == CNC_SIM_EVENT_COMMENT &&
event.line == 2 &&
event.reserved == 970 &&
event.reserved == kConstantRpmSpindleModeEvent &&
event.tool == 0);
saw_g96_dollar_mode = saw_g96_dollar_mode ||
(event.type == CNC_SIM_EVENT_COMMENT &&
event.line == 3 &&
event.reserved == 960 &&
event.reserved == kCssSpindleModeEvent &&
event.tool == 0 &&
near(event.feed, 2500.0));
saw_g95_dollar_feed_mode = saw_g95_dollar_feed_mode ||
@@ -8487,6 +9027,28 @@ int main() {
sizeof(polar_g92_program) - 1) != 0,
"expected LinuxCNC to reject polar coordinates with G92");
const char polar_g52_program[] =
"G21 G90 G17\n"
"G52 @1 ^90\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
polar_g52_program,
sizeof(polar_g52_program) - 1) != 0,
"expected LinuxCNC to reject polar coordinates with G52");
ok &= expect(std::string(cnc_sim_last_error(sim)) == "All axes missing with g52 or g92",
"expected LinuxCNC G52 polar missing-axis error text");
const char polar_g52_with_x_program[] =
"G21 G90 G17\n"
"G52 X1 @1 ^90\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
polar_g52_with_x_program,
sizeof(polar_g52_with_x_program) - 1) != 0,
"expected LinuxCNC to reject X word mixed with polar coordinate in G52");
ok &= expect(std::string(cnc_sim_last_error(sim)) == "Cannot specify both polar coordinate and X word",
"expected LinuxCNC G52 polar/X conflict error text");
const char polar_g10_program[] =
"G21 G90 G17\n"
"G10 L2 P1 @1 ^90\n";
@@ -8667,6 +9229,18 @@ int main() {
sizeof(bad_precision_g_code_program) - 1) != 0,
"expected LinuxCNC to reject G code not on a tenth boundary");
const char boundary_noninteger_g_code_program[] =
"G21 G90\n"
"F100\n"
"G1.9999 X1\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
boundary_noninteger_g_code_program,
sizeof(boundary_noninteger_g_code_program) - 1) != 0,
"expected LinuxCNC to reject G code exactly at the read_g upper tolerance boundary");
ok &= expect(std::string(cnc_sim_last_error(sim)) == "G-code out of range",
"expected LinuxCNC boundary-G error text");
const char scientific_notation_word_program[] =
"G21 G90\n"
"F100\n"
@@ -9819,6 +10393,130 @@ int main() {
}
ok &= expect(saw_block_delete_disabled_line, "expected smoke disabled block-delete line to execute");
const char block_delete_rtcp_controls_program[] =
"G21 G90 G17\n"
"/N10 M428\n"
"/N20 G43.4 H7\n"
"M30\n";
config_rc = cnc_sim_load_config_json(sim, block_delete_on_config, sizeof(block_delete_on_config) - 1);
events.clear();
ok &= expect(config_rc == 0, cnc_sim_last_error(sim));
ok &= expect(cnc_sim_parse_program(sim,
block_delete_rtcp_controls_program,
sizeof(block_delete_rtcp_controls_program) - 1) == 0,
cnc_sim_last_error(sim));
bool saw_smoke_block_delete_skipped_m428 = false;
bool saw_smoke_block_delete_skipped_g434 = false;
for (const auto &event : events) {
saw_smoke_block_delete_skipped_m428 = saw_smoke_block_delete_skipped_m428 ||
(event.type == CNC_SIM_EVENT_KINEMATICS_SWITCH &&
event.line == 2);
saw_smoke_block_delete_skipped_g434 = saw_smoke_block_delete_skipped_g434 ||
(event.type == CNC_SIM_EVENT_RTCP_STATE &&
event.line == 3);
}
ok &= expect(!saw_smoke_block_delete_skipped_m428, "expected smoke block-delete M428 line to be skipped");
ok &= expect(!saw_smoke_block_delete_skipped_g434, "expected smoke block-delete G43.4 line to be skipped");
config_rc = cnc_sim_load_config_json(sim, block_delete_off_config, sizeof(block_delete_off_config) - 1);
events.clear();
ok &= expect(config_rc == 0, cnc_sim_last_error(sim));
ok &= expect(cnc_sim_parse_program(sim,
block_delete_rtcp_controls_program,
sizeof(block_delete_rtcp_controls_program) - 1) == 0,
cnc_sim_last_error(sim));
bool saw_smoke_block_delete_disabled_m428 = false;
bool saw_smoke_block_delete_disabled_g434 = false;
for (const auto &event : events) {
saw_smoke_block_delete_disabled_m428 = saw_smoke_block_delete_disabled_m428 ||
(event.type == CNC_SIM_EVENT_KINEMATICS_SWITCH &&
event.line == 2 &&
event.reserved == 1);
saw_smoke_block_delete_disabled_g434 = saw_smoke_block_delete_disabled_g434 ||
(event.type == CNC_SIM_EVENT_RTCP_STATE &&
event.line == 3 &&
event.tool == 7);
}
ok &= expect(saw_smoke_block_delete_disabled_m428, "expected smoke disabled block-delete M428 to execute");
ok &= expect(saw_smoke_block_delete_disabled_g434, "expected smoke disabled block-delete G43.4 to execute");
const char unary_sign_rtcp_controls_program[] =
"G21 G90 G17\n"
"M++428\n"
"G+43.4 H++7\n"
"M30\n";
config_rc = cnc_sim_load_config_json(sim, smoke_config, sizeof(smoke_config) - 1);
events.clear();
ok &= expect(config_rc == 0, cnc_sim_last_error(sim));
ok &= expect(cnc_sim_parse_program(sim,
unary_sign_rtcp_controls_program,
sizeof(unary_sign_rtcp_controls_program) - 1) == 0,
cnc_sim_last_error(sim));
bool saw_smoke_unary_sign_m428 = false;
bool saw_smoke_unary_sign_g434 = false;
for (const auto &event : events) {
saw_smoke_unary_sign_m428 = saw_smoke_unary_sign_m428 ||
(event.type == CNC_SIM_EVENT_KINEMATICS_SWITCH &&
event.line == 2 &&
event.reserved == 1);
saw_smoke_unary_sign_g434 = saw_smoke_unary_sign_g434 ||
(event.type == CNC_SIM_EVENT_RTCP_STATE &&
event.line == 3 &&
event.tool == 7);
}
ok &= expect(saw_smoke_unary_sign_m428, "expected smoke M++428 to execute as M428");
ok &= expect(saw_smoke_unary_sign_g434, "expected smoke G+43.4 H++7 to execute as G43.4 H7");
const char numbered_rtcp_controls_program[] =
"G21 G90 G17\n"
"N10 M428\n"
"N20 G43.4 H7\n"
"M30\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
numbered_rtcp_controls_program,
sizeof(numbered_rtcp_controls_program) - 1) == 0,
cnc_sim_last_error(sim));
bool saw_smoke_numbered_m428 = false;
bool saw_smoke_numbered_g434 = false;
for (const auto &event : events) {
saw_smoke_numbered_m428 = saw_smoke_numbered_m428 ||
(event.type == CNC_SIM_EVENT_KINEMATICS_SWITCH &&
event.line == 2 &&
event.reserved == 1);
saw_smoke_numbered_g434 = saw_smoke_numbered_g434 ||
(event.type == CNC_SIM_EVENT_RTCP_STATE &&
event.line == 3 &&
event.tool == 7);
}
ok &= expect(saw_smoke_numbered_m428, "expected smoke N10 M428 to execute as M428");
ok &= expect(saw_smoke_numbered_g434, "expected smoke N20 G43.4 H7 to execute as G43.4 H7");
const char bracket_rtcp_controls_program[] =
"G21 G90 G17\n"
"M[428]\n"
"G[43.4] H[7]\n"
"M30\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
bracket_rtcp_controls_program,
sizeof(bracket_rtcp_controls_program) - 1) == 0,
cnc_sim_last_error(sim));
bool saw_smoke_bracket_m428 = false;
bool saw_smoke_bracket_g434 = false;
for (const auto &event : events) {
saw_smoke_bracket_m428 = saw_smoke_bracket_m428 ||
(event.type == CNC_SIM_EVENT_KINEMATICS_SWITCH &&
event.line == 2 &&
event.reserved == 1);
saw_smoke_bracket_g434 = saw_smoke_bracket_g434 ||
(event.type == CNC_SIM_EVENT_RTCP_STATE &&
event.line == 3 &&
event.tool == 7);
}
ok &= expect(saw_smoke_bracket_m428, "expected smoke M[428] to execute as M428");
ok &= expect(saw_smoke_bracket_g434, "expected smoke G[43.4] H[7] to execute as G43.4 H7");
const char comment_then_slash_program[] =
"G21 G90 F100\n"
"(comment) /G1 X5\n"

View File

@@ -8,6 +8,9 @@
namespace {
constexpr int kToolLengthOffsetEvent = 430;
constexpr int kCssSpindleModeEvent = 960;
int collect_event(const CncSimEvent *event, void *user_data) {
auto *events = static_cast<std::vector<CncSimEvent> *>(user_data);
events->push_back(*event);
@@ -189,7 +192,7 @@ int main() {
event.spindle == 0.0 &&
event.reserved == 0);
saw_spindle_mode = saw_spindle_mode || (event.type == CNC_SIM_EVENT_COMMENT &&
event.reserved == 960 &&
event.reserved == kCssSpindleModeEvent &&
event.feed == 2500.0 &&
event.tool == 0);
saw_feed_mode = saw_feed_mode || (event.type == CNC_SIM_EVENT_SET_FEED &&
@@ -320,7 +323,7 @@ int main() {
event.feed == 15.0);
saw_tool_length = saw_tool_length || (event.type == CNC_SIM_EVENT_COMMENT &&
event.line == 19 &&
event.reserved == 430 &&
event.reserved == kToolLengthOffsetEvent &&
event.start.z == 12.5);
saw_comment = saw_comment || (event.type == CNC_SIM_EVENT_COMMENT &&
event.line == 20);

View File

@@ -105,6 +105,15 @@ int parse_named_integer_constant(const std::string &source, const std::string &n
return std::stoi(match[1].str());
}
int parse_named_enum_value(const std::string &source, const std::string &name) {
const std::regex pattern(R"(\b)" + name + R"(\s*=\s*(-?\d+))");
std::smatch match;
if (!std::regex_search(source, match, pattern)) {
return -1;
}
return std::stoi(match[1].str());
}
int parse_constexpr_integer(const std::string &source, const std::string &name) {
const std::regex pattern(R"(constexpr\s+int\s+)" + name + R"(\s*=\s*(\d+))");
std::smatch match;
@@ -822,6 +831,8 @@ int main() {
const std::string interp_internal_source = read_file(interp_internal_path);
const std::string parameter_def_source = read_file(parameter_def_path);
const std::string linuxcnc_h_source = read_file(linuxcnc_h_path);
const std::string canon_event_sink_source = read_file("core/src/canon_event_sink.cpp");
const std::string linuxcnc_canon_bridge_source = read_file("core/src/linuxcnc_canon_bridge.cpp");
const std::string smoke_source = read_file("core/src/smoke_gcode_parser.cpp");
bool ok = true;
@@ -832,6 +843,8 @@ int main() {
ok &= expect(!interp_internal_source.empty(), "expected to read LinuxCNC interp_internal.hh");
ok &= expect(!parameter_def_source.empty(), "expected to read LinuxCNC interp_parameter_def.hh");
ok &= expect(!linuxcnc_h_source.empty(), "expected to read LinuxCNC linuxcnc.h");
ok &= expect(!canon_event_sink_source.empty(), "expected to read canon_event_sink.cpp");
ok &= expect(!linuxcnc_canon_bridge_source.empty(), "expected to read linuxcnc_canon_bridge.cpp");
ok &= expect(!smoke_source.empty(), "expected to read smoke_gcode_parser.cpp");
const int linuxcnc_max_parameters =
@@ -1034,6 +1047,56 @@ int main() {
ok &= expect(smoke_min_analog_output_index == linuxcnc_min_analog_output_index,
"expected smoke M67/M68 minimum analog output index to match LinuxCNC");
const int linuxcnc_cutter_comp_off = parse_named_enum_value(interp_internal_source, "G_40");
const int linuxcnc_cutter_comp_left = parse_named_enum_value(interp_internal_source, "G_41");
const int linuxcnc_cutter_comp_left_explicit = parse_named_enum_value(interp_internal_source, "G_41_1");
const int linuxcnc_cutter_comp_right = parse_named_enum_value(interp_internal_source, "G_42");
const int linuxcnc_cutter_comp_right_explicit = parse_named_enum_value(interp_internal_source, "G_42_1");
ok &= expect(parse_constexpr_integer(smoke_source, "kCutterCompOffEvent") == linuxcnc_cutter_comp_off,
"expected smoke cutter-comp off event to match LinuxCNC G40 enum");
ok &= expect(parse_constexpr_integer(smoke_source, "kCutterCompLeftEvent") == linuxcnc_cutter_comp_left,
"expected smoke cutter-comp left event to match LinuxCNC G41 enum");
ok &= expect(parse_constexpr_integer(smoke_source, "kCutterCompLeftExplicitEvent") ==
linuxcnc_cutter_comp_left_explicit,
"expected smoke explicit cutter-comp left event to match LinuxCNC G41.1 enum");
ok &= expect(parse_constexpr_integer(smoke_source, "kCutterCompRightEvent") == linuxcnc_cutter_comp_right,
"expected smoke cutter-comp right event to match LinuxCNC G42 enum");
ok &= expect(parse_constexpr_integer(smoke_source, "kCutterCompRightExplicitEvent") ==
linuxcnc_cutter_comp_right_explicit,
"expected smoke explicit cutter-comp right event to match LinuxCNC G42.1 enum");
const int linuxcnc_tool_length_offset = parse_named_enum_value(interp_internal_source, "G_43");
const int linuxcnc_dynamic_tool_length_offset =
parse_named_enum_value(interp_internal_source, "G_43_1");
const int linuxcnc_additive_tool_length_offset =
parse_named_enum_value(interp_internal_source, "G_43_2");
const int linuxcnc_cancel_tool_length_offset =
parse_named_enum_value(interp_internal_source, "G_49");
ok &= expect(parse_constexpr_integer(smoke_source, "kToolLengthOffsetEvent") ==
linuxcnc_tool_length_offset,
"expected smoke tool-length offset event to match LinuxCNC G43 enum");
ok &= expect(parse_constexpr_integer(linuxcnc_canon_bridge_source, "kToolLengthOffsetEvent") ==
linuxcnc_tool_length_offset,
"expected LinuxCNC bridge tool-length offset event to match LinuxCNC G43 enum");
ok &= expect(linuxcnc_dynamic_tool_length_offset == 431,
"expected LinuxCNC G43.1 enum to remain dynamic tool-length offset");
ok &= expect(linuxcnc_additive_tool_length_offset == 432,
"expected LinuxCNC G43.2 enum to remain additive tool-length offset");
ok &= expect(linuxcnc_cancel_tool_length_offset == 490,
"expected LinuxCNC G49 enum to remain cancel tool-length offset");
const int linuxcnc_css_spindle_mode = parse_named_enum_value(interp_internal_source, "G_96");
const int linuxcnc_constant_rpm_spindle_mode = parse_named_enum_value(interp_internal_source, "G_97");
ok &= expect(parse_constexpr_integer(canon_event_sink_source, "kCssSpindleModeEvent") ==
linuxcnc_css_spindle_mode,
"expected canon sink CSS spindle mode event to match LinuxCNC G96 enum");
ok &= expect(parse_constexpr_integer(canon_event_sink_source, "kConstantRpmSpindleModeEvent") ==
linuxcnc_constant_rpm_spindle_mode,
"expected canon sink constant-RPM spindle mode event to match LinuxCNC G97 enum");
ok &= expect(parse_constexpr_integer(smoke_source, "kConstantRpmSpindleModeEvent") ==
linuxcnc_constant_rpm_spindle_mode,
"expected smoke constant-RPM spindle mode event to match LinuxCNC G97 enum");
const std::vector<double> linuxcnc_g_code_read_real_constants =
parse_g_code_read_real_constants(interp_read_source);
const double smoke_g_code_scale = parse_constexpr_double(smoke_source, "kLinuxCncGCodeScale");
@@ -1184,11 +1247,21 @@ int main() {
"expected complete smoke G modal table");
ok &= expect(smoke_max_g_code == static_cast<int>(gees.size()) - 1,
"expected smoke max G code to match LinuxCNC gees[] table");
if (gees.size() > 490) {
ok &= expect(gees[430] == 8, "expected LinuxCNC G43 to remain tool-length modal group 8");
if (gees.size() > static_cast<std::size_t>(linuxcnc_cancel_tool_length_offset) &&
linuxcnc_tool_length_offset >= 0 &&
linuxcnc_dynamic_tool_length_offset >= 0 &&
linuxcnc_additive_tool_length_offset >= 0 &&
linuxcnc_cancel_tool_length_offset >= 0) {
ok &= expect(gees[linuxcnc_tool_length_offset] == 8,
"expected LinuxCNC G43 to remain tool-length modal group 8");
ok &= expect(gees[linuxcnc_dynamic_tool_length_offset] == 8,
"expected LinuxCNC G43.1 to remain tool-length modal group 8");
ok &= expect(gees[linuxcnc_additive_tool_length_offset] == 8,
"expected LinuxCNC G43.2 to remain tool-length modal group 8");
ok &= expect(gees[434] == -1, "expected LinuxCNC G43.4 to stay out of gees[]");
ok &= expect(gees[435] == -1, "expected LinuxCNC G43.5 to stay out of gees[]");
ok &= expect(gees[490] == 8, "expected LinuxCNC G49 to remain tool-length modal group 8");
ok &= expect(gees[linuxcnc_cancel_tool_length_offset] == 8,
"expected LinuxCNC G49 to remain tool-length modal group 8");
}
if (gees.size() == smoke_g.size()) {
ok &= expect_tables_equal(smoke_g, gees, "G modal group");

View File

@@ -17,7 +17,39 @@ bool expect_near(double actual, double expected, const char *message) {
return true;
}
bool expect_pose_near(const CncSimPose &actual, const CncSimPose &expected, const char *message) {
bool expect(bool condition, const char *message) {
if (!condition) {
std::cerr << "FAIL: " << message << '\n';
return false;
}
return true;
}
bool expect_near_eps(double actual, double expected, double epsilon, const char *message) {
if (std::fabs(actual - expected) >= epsilon) {
std::cerr << "FAIL: " << message << " actual=" << actual << " expected=" << expected
<< " epsilon=" << epsilon << '\n';
return false;
}
return true;
}
bool expect_joints_near(const LinuxCncFiveAxisJoints &actual,
const LinuxCncFiveAxisJoints &expected,
const char *message) {
bool ok = true;
ok &= expect_near(actual.x, expected.x, message);
ok &= expect_near(actual.y, expected.y, message);
ok &= expect_near(actual.z, expected.z, message);
ok &= expect_near(actual.b, expected.b, message);
ok &= expect_near(actual.c, expected.c, message);
ok &= expect_near(actual.w, expected.w, message);
return ok;
}
bool expect_axis_joints_near(const LinuxCncAxisJoints &actual,
const LinuxCncAxisJoints &expected,
const char *message) {
bool ok = true;
ok &= expect_near(actual.x, expected.x, message);
ok &= expect_near(actual.y, expected.y, message);
@@ -25,6 +57,9 @@ bool expect_pose_near(const CncSimPose &actual, const CncSimPose &expected, cons
ok &= expect_near(actual.a, expected.a, message);
ok &= expect_near(actual.b, expected.b, message);
ok &= expect_near(actual.c, expected.c, message);
ok &= expect_near(actual.u, expected.u, message);
ok &= expect_near(actual.v, expected.v, message);
ok &= expect_near(actual.w, expected.w, message);
return ok;
}
@@ -33,43 +68,378 @@ bool expect_pose_near(const CncSimPose &actual, const CncSimPose &expected, cons
int main() {
bool ok = true;
CncSimPose tip{};
tip.x = 10.0;
tip.y = 20.0;
tip.z = 30.0;
LinuxCncAxisJoints axis_joints{};
axis_joints.x = 1.0;
axis_joints.y = 2.0;
axis_joints.z = 3.0;
axis_joints.a = 4.0;
axis_joints.b = 5.0;
axis_joints.c = 6.0;
axis_joints.u = 7.0;
axis_joints.v = 8.0;
axis_joints.w = 9.0;
CncSimPose identity_pose = linuxcnc_identity_forward(axis_joints);
ok &= expect_near(identity_pose.x, 1.0, "identity/trivkins forward x");
ok &= expect_near(identity_pose.c, 6.0, "identity/trivkins forward c");
ok &= expect_near(identity_pose.w, 9.0, "identity/trivkins forward w");
ok &= expect_axis_joints_near(linuxcnc_identity_inverse(identity_pose),
axis_joints,
"identity/trivkins forward/inverse roundtrip");
CncSimPose pivot = rtcp_pivot_from_tool_tip(tip, 100.0);
ok &= expect_near(pivot.x, 10.0, "zero-angle pivot x");
ok &= expect_near(pivot.y, 20.0, "zero-angle pivot y");
ok &= expect_near(pivot.z, 130.0, "zero-angle pivot z");
axis_joints = {};
axis_joints.x = 10.0;
axis_joints.y = 0.0;
axis_joints.z = 3.0;
axis_joints.a = 4.0;
axis_joints.b = 5.0;
axis_joints.c = 90.0;
axis_joints.u = 7.0;
axis_joints.v = 8.0;
axis_joints.w = 9.0;
CncSimPose rotate_pose = linuxcnc_rotatekins_forward(axis_joints);
ok &= expect_near(rotate_pose.x, 0.0, "rotatekins C90 forward x");
ok &= expect_near(rotate_pose.y, -10.0, "rotatekins C90 forward y");
ok &= expect_near(rotate_pose.z, 3.0, "rotatekins C90 forward z");
ok &= expect_axis_joints_near(linuxcnc_rotatekins_inverse(rotate_pose),
axis_joints,
"rotatekins forward/inverse roundtrip");
tip.a = 90.0;
pivot = rtcp_pivot_from_tool_tip(tip, 100.0);
ok &= expect_near(pivot.x, 10.0, "A90 pivot x");
ok &= expect_near(pivot.y, -80.0, "A90 pivot y");
ok &= expect_near(pivot.z, 30.0, "A90 pivot z");
LinuxCncFiveAxisJoints joints{};
joints.x = 10.0;
joints.y = 20.0;
joints.z = 30.0;
CncSimPose pose = linuxcnc_5axis_forward(joints, 250.0);
ok &= expect_near(pose.x, 10.0, "5axiskins B0 C0 forward x");
ok &= expect_near(pose.y, 20.0, "5axiskins B0 C0 forward y");
ok &= expect_near(pose.z, 30.0, "5axiskins B0 C0 forward z");
tip.a = 0.0;
tip.b = 90.0;
pivot = rtcp_pivot_from_tool_tip(tip, 100.0);
ok &= expect_near(pivot.x, 110.0, "B90 pivot x");
ok &= expect_near(pivot.y, 20.0, "B90 pivot y");
ok &= expect_near(pivot.z, 30.0, "B90 pivot z");
joints.b = 90.0;
joints.c = 0.0;
pose = linuxcnc_5axis_forward(joints, 250.0);
ok &= expect_near(pose.x, 260.0, "5axiskins B90 C0 forward x");
ok &= expect_near(pose.y, 20.0, "5axiskins B90 C0 forward y");
ok &= expect_near(pose.z, 280.0, "5axiskins B90 C0 forward z");
tip.a = 35.0;
tip.b = -20.0;
tip.c = 15.0;
pivot = rtcp_pivot_from_tool_tip(tip, 123.4);
const CncSimPose roundtrip = rtcp_tool_tip_from_pivot(pivot, 123.4);
ok &= expect_pose_near(roundtrip, tip, "RTCP pivot/tool-tip roundtrip");
joints.c = 90.0;
pose = linuxcnc_5axis_forward(joints, 250.0);
ok &= expect_near(pose.x, 10.0, "5axiskins B90 C90 forward x");
ok &= expect_near(pose.y, 270.0, "5axiskins B90 C90 forward y");
ok &= expect_near(pose.z, 280.0, "5axiskins B90 C90 forward z");
tip = {};
tip.a = 90.0;
tip.b = 90.0;
const RtcpVector vector = rtcp_tool_vector_from_pose(tip, 10.0);
ok &= expect_near(vector.x, 0.0, "A90 B90 tool vector x");
ok &= expect_near(vector.y, 10.0, "A90 B90 tool vector y");
ok &= expect_near(vector.z, 0.0, "A90 B90 tool vector z");
joints.x = -12.5;
joints.y = 4.0;
joints.z = 81.25;
joints.b = 37.0;
joints.c = -23.0;
joints.w = -15.0;
pose = linuxcnc_5axis_forward(joints, 250.0);
const LinuxCncFiveAxisJoints roundtrip_joints = linuxcnc_5axis_inverse(pose, 250.0);
ok &= expect_joints_near(roundtrip_joints, joints, "5axiskins forward/inverse roundtrip");
LinuxCncXyzbcTrtParameters trt_parameters = linuxcnc_xyzbc_trt_default_parameters(250.0);
joints = {};
joints.x = 10.0;
joints.y = 20.0;
joints.z = 30.0;
pose = linuxcnc_xyzbc_trt_forward(joints, trt_parameters);
ok &= expect_near(pose.x, 10.0, "xyzbc-trt B0 C0 forward x");
ok &= expect_near(pose.y, 20.0, "xyzbc-trt B0 C0 forward y");
ok &= expect_near(pose.z, 30.0, "xyzbc-trt B0 C0 forward z");
ok &= expect_near(pose.w, 0.0, "xyzbc-trt XYZBC forward drops unconfigured W");
joints.b = 90.0;
joints.c = 0.0;
pose = linuxcnc_xyzbc_trt_forward(joints, trt_parameters);
ok &= expect_near(pose.x, 185.0, "xyzbc-trt B90 C0 forward x");
ok &= expect_near(pose.y, 20.0, "xyzbc-trt B90 C0 forward y");
ok &= expect_near(pose.z, 265.0, "xyzbc-trt B90 C0 forward z");
joints.c = 90.0;
pose = linuxcnc_xyzbc_trt_forward(joints, trt_parameters);
ok &= expect_near(pose.x, 20.0, "xyzbc-trt B90 C90 forward x");
ok &= expect_near(pose.y, -185.0, "xyzbc-trt B90 C90 forward y");
ok &= expect_near(pose.z, 265.0, "xyzbc-trt B90 C90 forward z");
pose = {};
pose.x = 225.0;
pose.y = 20.0;
pose.z = -205.0;
pose.b = 90.0;
LinuxCncFiveAxisJoints trt_inverse_joints = linuxcnc_xyzbc_trt_inverse(pose, trt_parameters);
ok &= expect_near(trt_inverse_joints.x, 10.0, "xyzbc-trt B90 C0 inverse x");
ok &= expect_near(trt_inverse_joints.y, 20.0, "xyzbc-trt B90 C0 inverse y");
ok &= expect_near(trt_inverse_joints.z, 30.0, "xyzbc-trt B90 C0 inverse z");
ok &= expect_near(trt_inverse_joints.b, 90.0, "xyzbc-trt B90 C0 inverse b");
ok &= expect_near(trt_inverse_joints.w, 0.0, "xyzbc-trt XYZBC inverse drops unconfigured W");
trt_parameters.conventional_directions = true;
joints.x = -12.5;
joints.y = 4.0;
joints.z = 81.25;
joints.b = 37.0;
joints.c = -23.0;
joints.w = 0.0;
pose = linuxcnc_xyzbc_trt_forward(joints, trt_parameters);
const LinuxCncFiveAxisJoints trt_roundtrip_joints = linuxcnc_xyzbc_trt_inverse(pose, trt_parameters);
ok &= expect_joints_near(trt_roundtrip_joints, joints, "xyzbc-trt conventional forward/inverse roundtrip");
pose = {};
pose.x = 1.0;
pose.y = 2.0;
pose.z = 3.0;
pose.b = 4.0;
pose.c = 5.0;
pose.w = 6.0;
const LinuxCncFiveAxisJoints userk_joints = linuxcnc_xyzbc_userk_inverse(pose);
ok &= expect_near(userk_joints.x, 1.0, "xyzbc userk identity inverse x");
ok &= expect_near(userk_joints.y, 2.0, "xyzbc userk identity inverse y");
ok &= expect_near(userk_joints.z, 3.0, "xyzbc userk identity inverse z");
ok &= expect_near(userk_joints.b, 4.0, "xyzbc userk identity inverse b");
ok &= expect_near(userk_joints.c, 5.0, "xyzbc userk identity inverse c");
ok &= expect_near(userk_joints.w, 0.0, "xyzbc userk identity inverse drops unconfigured W");
joints = {};
joints.x = 6.0;
joints.y = 5.0;
joints.z = 4.0;
joints.a = 12.0;
joints.b = 3.0;
joints.c = 2.0;
joints.w = 1.0;
const CncSimPose userk_pose = linuxcnc_xyzbc_userk_forward(joints);
ok &= expect_near(userk_pose.x, 6.0, "xyzbc userk identity forward x");
ok &= expect_near(userk_pose.y, 5.0, "xyzbc userk identity forward y");
ok &= expect_near(userk_pose.z, 4.0, "xyzbc userk identity forward z");
ok &= expect_near(userk_pose.a, 0.0, "xyzbc userk identity forward drops unconfigured A");
ok &= expect_near(userk_pose.b, 3.0, "xyzbc userk identity forward b");
ok &= expect_near(userk_pose.c, 2.0, "xyzbc userk identity forward c");
ok &= expect_near(userk_pose.w, 0.0, "xyzbc userk identity forward drops unconfigured W");
trt_parameters = linuxcnc_xyzbc_trt_default_parameters(250.0);
joints = {};
joints.x = 10.0;
joints.y = 20.0;
joints.z = 30.0;
CncSimPose xyzac_pose = linuxcnc_xyzac_trt_forward(joints, trt_parameters);
ok &= expect_near(xyzac_pose.x, 10.0, "xyzac-trt A0 C0 forward x");
ok &= expect_near(xyzac_pose.y, 20.0, "xyzac-trt A0 C0 forward y");
ok &= expect_near(xyzac_pose.z, 30.0, "xyzac-trt A0 C0 forward z");
joints.a = 90.0;
joints.c = 0.0;
xyzac_pose = linuxcnc_xyzac_trt_forward(joints, trt_parameters);
ok &= expect_near(xyzac_pose.x, 10.0, "xyzac-trt A90 C0 forward x");
ok &= expect_near(xyzac_pose.y, -205.0, "xyzac-trt A90 C0 forward y");
ok &= expect_near(xyzac_pose.z, 215.0, "xyzac-trt A90 C0 forward z");
joints.c = 90.0;
xyzac_pose = linuxcnc_xyzac_trt_forward(joints, trt_parameters);
ok &= expect_near(xyzac_pose.x, -205.0, "xyzac-trt A90 C90 forward x");
ok &= expect_near(xyzac_pose.y, -10.0, "xyzac-trt A90 C90 forward y");
ok &= expect_near(xyzac_pose.z, 215.0, "xyzac-trt A90 C90 forward z");
trt_parameters.conventional_directions = true;
joints.x = -12.5;
joints.y = 4.0;
joints.z = 81.25;
joints.a = 37.0;
joints.c = -23.0;
joints.b = 0.0;
joints.w = 0.0;
xyzac_pose = linuxcnc_xyzac_trt_forward(joints, trt_parameters);
const LinuxCncFiveAxisJoints xyzac_roundtrip_joints =
linuxcnc_xyzac_trt_inverse(xyzac_pose, trt_parameters);
ok &= expect_near(xyzac_roundtrip_joints.x, joints.x, "xyzac-trt conventional roundtrip x");
ok &= expect_near(xyzac_roundtrip_joints.y, joints.y, "xyzac-trt conventional roundtrip y");
ok &= expect_near(xyzac_roundtrip_joints.z, joints.z, "xyzac-trt conventional roundtrip z");
ok &= expect_near(xyzac_roundtrip_joints.a, joints.a, "xyzac-trt conventional roundtrip a");
ok &= expect_near(xyzac_roundtrip_joints.c, joints.c, "xyzac-trt conventional roundtrip c");
ok &= expect_near(xyzac_roundtrip_joints.b, 0.0, "xyzac-trt inverse drops unconfigured B");
ok &= expect_near(xyzac_roundtrip_joints.w, 0.0, "xyzac-trt inverse drops unconfigured W");
LinuxCncScaraParameters scara_parameters = linuxcnc_scara_default_parameters();
LinuxCncScaraJoints scara_joints{};
scara_joints.j0 = 0.0;
scara_joints.j1 = 0.0;
scara_joints.j2 = 10.0;
scara_joints.j3 = 0.0;
scara_joints.j4 = 11.0;
scara_joints.j5 = 12.0;
int scara_iflags = 0;
CncSimPose scara_pose = linuxcnc_scara_forward(scara_joints, scara_parameters, &scara_iflags);
ok &= expect_near(scara_pose.x, 640.0, "scarakins default forward x");
ok &= expect_near(scara_pose.y, 0.0, "scarakins default forward y");
ok &= expect_near(scara_pose.z, 480.0, "scarakins default forward z");
ok &= expect_near(scara_pose.c, 0.0, "scarakins default forward c");
ok &= expect_near(scara_pose.a, 11.0, "scarakins default forward a");
ok &= expect_near(scara_pose.b, 12.0, "scarakins default forward b");
ok &= expect_near(scara_iflags, 1, "scarakins forward sets iflags for joint1 below 90");
LinuxCncScaraJoints scara_inverse_joints =
linuxcnc_scara_inverse(scara_pose, scara_parameters, scara_iflags);
ok &= expect_near(scara_inverse_joints.j0, scara_joints.j0, "scarakins roundtrip j0");
ok &= expect_near(scara_inverse_joints.j1, scara_joints.j1, "scarakins roundtrip j1");
ok &= expect_near(scara_inverse_joints.j2, scara_joints.j2, "scarakins roundtrip j2");
ok &= expect_near(scara_inverse_joints.j3, scara_joints.j3, "scarakins roundtrip j3");
ok &= expect_near(scara_inverse_joints.j4, scara_joints.j4, "scarakins roundtrip j4");
ok &= expect_near(scara_inverse_joints.j5, scara_joints.j5, "scarakins roundtrip j5");
scara_joints.j0 = 15.0;
scara_joints.j1 = 120.0;
scara_joints.j2 = -5.0;
scara_joints.j3 = 20.0;
scara_pose = linuxcnc_scara_forward(scara_joints, scara_parameters, &scara_iflags);
ok &= expect_near(scara_iflags, 0, "scarakins forward clears iflags for joint1 at or above 90");
scara_inverse_joints = linuxcnc_scara_inverse(scara_pose, scara_parameters, scara_iflags);
ok &= expect_near(scara_inverse_joints.j0, scara_joints.j0, "scarakins iflags0 roundtrip j0");
ok &= expect_near(scara_inverse_joints.j1, scara_joints.j1, "scarakins iflags0 roundtrip j1");
ok &= expect_near(scara_inverse_joints.j2, scara_joints.j2, "scarakins iflags0 roundtrip j2");
ok &= expect_near(scara_inverse_joints.j3, scara_joints.j3, "scarakins iflags0 roundtrip j3");
axis_joints = {};
axis_joints.x = 1.0;
axis_joints.y = 2.0;
axis_joints.z = 3.0;
axis_joints.a = 4.0;
axis_joints.b = 5.0;
axis_joints.c = 6.0;
CncSimPose xyzab_identity_pose = linuxcnc_xyzab_tdr_identity_forward(axis_joints);
ok &= expect_near(xyzab_identity_pose.x, 1.0, "xyzab-tdr type0 identity forward x");
ok &= expect_near(xyzab_identity_pose.y, 2.0, "xyzab-tdr type0 identity forward y");
ok &= expect_near(xyzab_identity_pose.z, 3.0, "xyzab-tdr type0 identity forward z");
ok &= expect_near(xyzab_identity_pose.a, 4.0, "xyzab-tdr type0 identity forward a");
ok &= expect_near(xyzab_identity_pose.b, 5.0, "xyzab-tdr type0 identity forward b");
ok &= expect_near(xyzab_identity_pose.c, 0.0, "xyzab-tdr type0 identity drops unconfigured c");
LinuxCncAxisJoints xyzab_identity_joints = linuxcnc_xyzab_tdr_identity_inverse(xyzab_identity_pose);
ok &= expect_near(xyzab_identity_joints.x, 1.0, "xyzab-tdr type0 identity inverse x");
ok &= expect_near(xyzab_identity_joints.y, 2.0, "xyzab-tdr type0 identity inverse y");
ok &= expect_near(xyzab_identity_joints.z, 3.0, "xyzab-tdr type0 identity inverse z");
ok &= expect_near(xyzab_identity_joints.a, 4.0, "xyzab-tdr type0 identity inverse a");
ok &= expect_near(xyzab_identity_joints.b, 5.0, "xyzab-tdr type0 identity inverse b");
ok &= expect_near(xyzab_identity_joints.c, 0.0, "xyzab-tdr type0 identity inverse drops c");
LinuxCncXyzabTdrParameters xyzab_parameters = linuxcnc_xyzab_tdr_default_parameters(250.0);
axis_joints = {};
axis_joints.x = 10.0;
axis_joints.y = 20.0;
axis_joints.z = 30.0;
axis_joints.a = 90.0;
axis_joints.b = 0.0;
CncSimPose xyzab_tcp_pose = linuxcnc_xyzab_tdr_tcp_forward(axis_joints, xyzab_parameters);
ok &= expect_near(xyzab_tcp_pose.x, 10.0, "xyzab-tdr type1 A90 B0 forward x");
ok &= expect_near(xyzab_tcp_pose.y, 220.0, "xyzab-tdr type1 A90 B0 forward y");
ok &= expect_near(xyzab_tcp_pose.z, 270.0, "xyzab-tdr type1 A90 B0 forward z");
axis_joints.a = 0.0;
axis_joints.b = 90.0;
xyzab_tcp_pose = linuxcnc_xyzab_tdr_tcp_forward(axis_joints, xyzab_parameters);
ok &= expect_near(xyzab_tcp_pose.x, -220.0, "xyzab-tdr type1 A0 B90 forward x");
ok &= expect_near(xyzab_tcp_pose.y, 20.0, "xyzab-tdr type1 A0 B90 forward y");
ok &= expect_near(xyzab_tcp_pose.z, 240.0, "xyzab-tdr type1 A0 B90 forward z");
xyzab_parameters.x_rot_point = 3.0;
xyzab_parameters.y_rot_point = -4.0;
xyzab_parameters.z_rot_point = 5.0;
xyzab_parameters.x_offset = 2.0;
xyzab_parameters.z_offset = 7.0;
xyzab_parameters.tool_offset_z = 11.0;
axis_joints.x = -12.5;
axis_joints.y = 4.0;
axis_joints.z = 81.25;
axis_joints.a = 37.0;
axis_joints.b = -23.0;
xyzab_tcp_pose = linuxcnc_xyzab_tdr_tcp_forward(axis_joints, xyzab_parameters);
LinuxCncAxisJoints xyzab_tcp_roundtrip =
linuxcnc_xyzab_tdr_tcp_inverse(xyzab_tcp_pose, xyzab_parameters);
ok &= expect_near(xyzab_tcp_roundtrip.x, axis_joints.x, "xyzab-tdr type1 roundtrip x");
ok &= expect_near(xyzab_tcp_roundtrip.y, axis_joints.y, "xyzab-tdr type1 roundtrip y");
ok &= expect_near(xyzab_tcp_roundtrip.z, axis_joints.z, "xyzab-tdr type1 roundtrip z");
ok &= expect_near(xyzab_tcp_roundtrip.a, axis_joints.a, "xyzab-tdr type1 roundtrip a");
ok &= expect_near(xyzab_tcp_roundtrip.b, axis_joints.b, "xyzab-tdr type1 roundtrip b");
LinuxCncPumaParameters puma_parameters = linuxcnc_puma_default_parameters();
LinuxCncAxisJoints puma_joints{};
puma_joints.x = 20.0;
puma_joints.y = -35.0;
puma_joints.z = 45.0;
puma_joints.a = 30.0;
puma_joints.b = 40.0;
puma_joints.c = -25.0;
int puma_iflags = 0;
CncSimPose puma_pose = linuxcnc_puma_forward(puma_joints, puma_parameters, &puma_iflags);
int puma_fflags = 0;
LinuxCncAxisJoints puma_roundtrip =
linuxcnc_puma_inverse(puma_pose, puma_joints, puma_parameters, puma_iflags, &puma_fflags);
ok &= expect_near(puma_roundtrip.x, puma_joints.x, "pumakins roundtrip j0");
ok &= expect_near(puma_roundtrip.y, puma_joints.y, "pumakins roundtrip j1");
ok &= expect_near(puma_roundtrip.z, puma_joints.z, "pumakins roundtrip j2");
ok &= expect_near(puma_roundtrip.a, puma_joints.a, "pumakins roundtrip j3");
ok &= expect_near(puma_roundtrip.b, puma_joints.b, "pumakins roundtrip j4");
ok &= expect_near(puma_roundtrip.c, puma_joints.c, "pumakins roundtrip j5");
ok &= expect_near(puma_fflags, 0, "pumakins non-singular inverse flags");
LinuxCncGenserParameters genser_parameters = linuxcnc_genser_puma560_parameters();
LinuxCncAxisJoints genser_joints{};
CncSimPose genser_pose = linuxcnc_genser_forward(genser_joints, genser_parameters);
ok &= expect_near(genser_pose.x, 17.0, "genserkins puma560 zero forward x");
ok &= expect_near(genser_pose.y, 5.4999999693964785, "genserkins puma560 zero forward y");
ok &= expect_near(genser_pose.z, 7.199999995628068, "genserkins puma560 zero forward z");
ok &= expect_near(genser_pose.a, 179.99999990891155, "genserkins puma560 zero forward a");
ok &= expect_near(genser_pose.b, 0.0, "genserkins puma560 zero forward b");
ok &= expect_near(genser_pose.c, 0.0, "genserkins puma560 zero forward c");
genser_joints.x = 20.0;
genser_joints.y = -35.0;
genser_joints.z = 45.0;
genser_joints.a = 30.0;
genser_joints.b = 40.0;
genser_joints.c = -25.0;
genser_pose = linuxcnc_genser_forward(genser_joints, genser_parameters);
ok &= expect_near(genser_pose.x, 15.636973119039572, "genserkins puma560 forward x");
ok &= expect_near(genser_pose.y, 10.791926162750677, "genserkins puma560 forward y");
ok &= expect_near(genser_pose.z, -1.5388033640849728, "genserkins puma560 forward z");
ok &= expect_near(genser_pose.a, 160.1443654010874, "genserkins puma560 forward a");
ok &= expect_near(genser_pose.b, -45.62792804902442, "genserkins puma560 forward b");
ok &= expect_near(genser_pose.c, 21.545586017440204, "genserkins puma560 forward c");
int genser_iterations = -1;
LinuxCncAxisJoints genser_inverse_joints =
linuxcnc_genser_inverse(genser_pose,
genser_joints,
genser_parameters,
&genser_iterations);
ok &= expect_near(genser_inverse_joints.x, genser_joints.x, "genserkins puma560 inverse x");
ok &= expect_near(genser_inverse_joints.y, genser_joints.y, "genserkins puma560 inverse y");
ok &= expect_near(genser_inverse_joints.z, genser_joints.z, "genserkins puma560 inverse z");
ok &= expect_near(genser_inverse_joints.a, genser_joints.a, "genserkins puma560 inverse a");
ok &= expect_near(genser_inverse_joints.b, genser_joints.b, "genserkins puma560 inverse b");
ok &= expect_near(genser_inverse_joints.c, genser_joints.c, "genserkins puma560 inverse c");
ok &= expect_near(genser_iterations, 0, "genserkins puma560 inverse exact-estimate iterations");
LinuxCncAxisJoints genser_estimate = genser_joints;
genser_estimate.x += 0.25;
genser_estimate.y -= 0.2;
genser_estimate.z += 0.15;
genser_estimate.a -= 0.1;
genser_estimate.b += 0.12;
genser_estimate.c -= 0.08;
genser_inverse_joints =
linuxcnc_genser_inverse(genser_pose,
genser_estimate,
genser_parameters,
&genser_iterations);
ok &= expect_near_eps(genser_inverse_joints.x, genser_joints.x, 1e-5,
"genserkins puma560 iterative inverse x");
ok &= expect_near_eps(genser_inverse_joints.y, genser_joints.y, 1e-5,
"genserkins puma560 iterative inverse y");
ok &= expect_near_eps(genser_inverse_joints.z, genser_joints.z, 1e-5,
"genserkins puma560 iterative inverse z");
ok &= expect_near_eps(genser_inverse_joints.a, genser_joints.a, 1e-5,
"genserkins puma560 iterative inverse a");
ok &= expect_near_eps(genser_inverse_joints.b, genser_joints.b, 1e-5,
"genserkins puma560 iterative inverse b");
ok &= expect_near_eps(genser_inverse_joints.c, genser_joints.c, 1e-5,
"genserkins puma560 iterative inverse c");
ok &= expect(genser_iterations > 0, "genserkins puma560 inverse should iterate from perturbed estimate");
return ok ? 0 : 1;
}

View File

@@ -24,16 +24,88 @@ int main() {
ok &= expect(actions.size() == 3, "expected three kinematics actions");
ok &= expect(actions[0].kind == SimulatorGcodeControlKind::KinematicsSwitch &&
actions[0].value == 1 &&
actions[0].rtcp_enabled,
"expected M428 original kinematics action");
!actions[0].rtcp_enabled,
"expected M428 kinematics switch without RTCP state change");
ok &= expect(actions[1].kind == SimulatorGcodeControlKind::KinematicsSwitch &&
actions[1].value == 0 &&
!actions[1].rtcp_enabled,
"expected M429 identity kinematics action");
ok &= expect(actions[2].kind == SimulatorGcodeControlKind::KinematicsSwitch &&
actions[2].value == 2 &&
actions[2].rtcp_enabled,
"expected M430 five-axis BC kinematics action");
!actions[2].rtcp_enabled,
"expected M430 userk switch without RTCP state change");
ok &= expect(parse_simulator_gcode_control_line("/M428", false, &actions),
"expected disabled block-delete M428 control line to execute");
ok &= expect(actions.size() == 1 &&
actions[0].kind == SimulatorGcodeControlKind::KinematicsSwitch &&
actions[0].value == 1,
"expected disabled block-delete M428 action");
ok &= expect(!parse_simulator_gcode_control_line("/M428", true, &actions),
"expected enabled block-delete M428 control line to be skipped");
ok &= expect(actions.empty(), "expected no actions for enabled block-delete M428");
ok &= expect(parse_simulator_gcode_control_line("/N10 M428", false, &actions),
"expected disabled block-delete leading-N M428 control line to execute");
ok &= expect(actions.size() == 1 &&
actions[0].kind == SimulatorGcodeControlKind::KinematicsSwitch &&
actions[0].value == 1,
"expected disabled block-delete /N10 M428 action");
ok &= expect(!parse_simulator_gcode_control_line("/N10 M428", true, &actions),
"expected enabled block-delete leading-N M428 control line to be skipped");
ok &= expect(actions.empty(), "expected no actions for enabled block-delete /N10 M428");
ok &= expect(!parse_simulator_gcode_control_line("M428 (unterminated", &actions),
"expected unclosed comment on M428 line to fall through to LinuxCNC error handling");
ok &= expect(actions.empty(), "expected no actions for M428 line with unclosed comment");
ok &= expect(!parse_simulator_gcode_control_line("M428 (outer (nested))", &actions),
"expected nested comment on M428 line to fall through to LinuxCNC error handling");
ok &= expect(actions.empty(), "expected no actions for M428 line with nested comment");
ok &= expect(!parse_simulator_gcode_control_line("(comment) /M428", false, &actions),
"expected comment-prefixed slash M428 to fall through to LinuxCNC error handling");
ok &= expect(actions.empty(), "expected no actions for comment-prefixed slash M428");
ok &= expect(parse_simulator_gcode_control_line("M428.00009", &actions),
"expected LinuxCNC integer tolerance to accept near-integer M428");
ok &= expect(actions.size() == 1 &&
actions[0].kind == SimulatorGcodeControlKind::KinematicsSwitch &&
actions[0].value == 1,
"expected near-integer M428 to parse as M428");
ok &= expect(parse_simulator_gcode_control_line("M427.99995", &actions),
"expected LinuxCNC integer tolerance to round high-fraction M word");
ok &= expect(actions.size() == 1 &&
actions[0].kind == SimulatorGcodeControlKind::KinematicsSwitch &&
actions[0].value == 1,
"expected high-fraction M427.99995 to parse as M428");
ok &= expect(parse_simulator_gcode_control_line("M++428", &actions),
"expected LinuxCNC unary plus M value to parse as M428");
ok &= expect(actions.size() == 1 &&
actions[0].kind == SimulatorGcodeControlKind::KinematicsSwitch &&
actions[0].value == 1,
"expected M++428 to parse as M428");
ok &= expect(parse_simulator_gcode_control_line("N10 M428", &actions),
"expected LinuxCNC leading N number before M428");
ok &= expect(actions.size() == 1 &&
actions[0].kind == SimulatorGcodeControlKind::KinematicsSwitch &&
actions[0].value == 1,
"expected N10 M428 to parse as M428");
ok &= expect(parse_simulator_gcode_control_line("N10.25 M428", &actions),
"expected LinuxCNC fractional N number before M428");
ok &= expect(actions.size() == 1 &&
actions[0].kind == SimulatorGcodeControlKind::KinematicsSwitch &&
actions[0].value == 1,
"expected N10.25 M428 to parse as M428");
ok &= expect(parse_simulator_gcode_control_line("M[428]", &actions),
"expected LinuxCNC bracketed M expression value to parse as M428");
ok &= expect(actions.size() == 1 &&
actions[0].kind == SimulatorGcodeControlKind::KinematicsSwitch &&
actions[0].value == 1,
"expected M[428] to parse as M428");
ok &= expect(!parse_simulator_gcode_control_line("M428.00011", &actions),
"expected non-integer M word above lower tolerance to fall through");
ok &= expect(actions.empty(), "expected no actions for lower-bound non-integer M word");
ok &= expect(!parse_simulator_gcode_control_line("M427.99989", &actions),
"expected M word below upper round-up tolerance to fall through");
ok &= expect(actions.empty(), "expected no actions for upper-bound non-integer M word");
ok &= expect(!parse_simulator_gcode_control_line("M4.28e2", &actions),
"expected LinuxCNC non-exponent M-number parsing to leave trailing e2");
ok &= expect(actions.empty(), "expected no actions for exponent M word");
ok &= expect(parse_simulator_gcode_control_line("G49 (cancel RTCP)", &actions),
"expected G49 control line");
@@ -42,6 +114,15 @@ int main() {
!actions[0].rtcp_enabled &&
actions[0].h_code == 0,
"expected G49 RTCP off action");
ok &= expect(!parse_simulator_gcode_control_line("G49 (unterminated", &actions),
"expected unclosed comment on G49 line to fall through to LinuxCNC error handling");
ok &= expect(actions.empty(), "expected no actions for G49 line with unclosed comment");
ok &= expect(!parse_simulator_gcode_control_line("G49 (outer (nested))", &actions),
"expected nested comment on G49 line to fall through to LinuxCNC error handling");
ok &= expect(actions.empty(), "expected no actions for G49 line with nested comment");
ok &= expect(!parse_simulator_gcode_control_line("(comment) /G49", false, &actions),
"expected comment-prefixed slash G49 to fall through to LinuxCNC error handling");
ok &= expect(actions.empty(), "expected no actions for comment-prefixed slash G49");
ok &= expect(parse_simulator_gcode_control_line("G43.4 H7", &actions),
"expected G43.4 H7 control line");
@@ -50,6 +131,116 @@ int main() {
actions[0].rtcp_enabled &&
actions[0].h_code == 7,
"expected G43.4 RTCP on action with H7");
ok &= expect(parse_simulator_gcode_control_line("G43.5 H7", &actions),
"expected G43.5 H7 control line");
ok &= expect(actions.size() == 1 &&
actions[0].kind == SimulatorGcodeControlKind::RtcpState &&
actions[0].value == 435 &&
actions[0].rtcp_enabled &&
actions[0].h_code == 7,
"expected G43.5 RTCP on action with H7");
ok &= expect(parse_simulator_gcode_control_line("/G43.4 H7", false, &actions),
"expected disabled block-delete G43.4 control line to execute");
ok &= expect(actions.size() == 1 &&
actions[0].kind == SimulatorGcodeControlKind::RtcpState &&
actions[0].rtcp_enabled &&
actions[0].h_code == 7,
"expected disabled block-delete G43.4 action");
ok &= expect(!parse_simulator_gcode_control_line("/G43.4 H7", true, &actions),
"expected enabled block-delete G43.4 control line to be skipped");
ok &= expect(actions.empty(), "expected no actions for enabled block-delete G43.4");
ok &= expect(parse_simulator_gcode_control_line("/N20 G43.4 H7", false, &actions),
"expected disabled block-delete leading-N G43.4 control line to execute");
ok &= expect(actions.size() == 1 &&
actions[0].kind == SimulatorGcodeControlKind::RtcpState &&
actions[0].rtcp_enabled &&
actions[0].h_code == 7,
"expected disabled block-delete /N20 G43.4 H7 action");
ok &= expect(!parse_simulator_gcode_control_line("/N20 G43.4 H7", true, &actions),
"expected enabled block-delete leading-N G43.4 control line to be skipped");
ok &= expect(actions.empty(), "expected no actions for enabled block-delete /N20 G43.4 H7");
ok &= expect(parse_simulator_gcode_control_line("G43.40009 H7", &actions),
"expected LinuxCNC read_g tolerance to accept near G43.4");
ok &= expect(actions.size() == 1 &&
actions[0].kind == SimulatorGcodeControlKind::RtcpState &&
actions[0].value == 434 &&
actions[0].rtcp_enabled &&
actions[0].h_code == 7,
"expected near G43.4 to parse as G43.4");
ok &= expect(parse_simulator_gcode_control_line("G43.39995 H7", &actions),
"expected LinuxCNC read_g tolerance to round high-fraction G word");
ok &= expect(actions.size() == 1 &&
actions[0].kind == SimulatorGcodeControlKind::RtcpState &&
actions[0].value == 434 &&
actions[0].rtcp_enabled &&
actions[0].h_code == 7,
"expected high-fraction G43.39995 to parse as G43.4");
ok &= expect(parse_simulator_gcode_control_line("G+43.4 H+7", &actions),
"expected LinuxCNC unary plus G/H values to parse");
ok &= expect(actions.size() == 1 &&
actions[0].kind == SimulatorGcodeControlKind::RtcpState &&
actions[0].value == 434 &&
actions[0].rtcp_enabled &&
actions[0].h_code == 7,
"expected G+43.4 H+7 to parse as G43.4 H7");
ok &= expect(parse_simulator_gcode_control_line("N20 G43.4 H7", &actions),
"expected LinuxCNC leading N number before G43.4");
ok &= expect(actions.size() == 1 &&
actions[0].kind == SimulatorGcodeControlKind::RtcpState &&
actions[0].value == 434 &&
actions[0].rtcp_enabled &&
actions[0].h_code == 7,
"expected N20 G43.4 H7 to parse as G43.4 H7");
ok &= expect(parse_simulator_gcode_control_line("G[43.4] H[7]", &actions),
"expected LinuxCNC bracketed G/H expression values to parse");
ok &= expect(actions.size() == 1 &&
actions[0].kind == SimulatorGcodeControlKind::RtcpState &&
actions[0].value == 434 &&
actions[0].rtcp_enabled &&
actions[0].h_code == 7,
"expected G[43.4] H[7] to parse as G43.4 H7");
ok &= expect(!parse_simulator_gcode_control_line("G43.4002 H7", &actions),
"expected G word above read_g out-of-range tolerance to fall through");
ok &= expect(actions.empty(), "expected no actions for out-of-range G word");
ok &= expect(!parse_simulator_gcode_control_line("G43.3998 H7", &actions),
"expected G word below read_g round-up tolerance to fall through");
ok &= expect(actions.empty(), "expected no actions below G word round-up tolerance");
ok &= expect(!parse_simulator_gcode_control_line("G4.34e1 H7", &actions),
"expected LinuxCNC non-exponent G-number parsing to leave trailing e1");
ok &= expect(actions.empty(), "expected no actions for exponent G word");
ok &= expect(parse_simulator_gcode_control_line("G43.4 H7.00009", &actions),
"expected LinuxCNC integer tolerance to accept near-integer H");
ok &= expect(actions.size() == 1 && actions[0].h_code == 7,
"expected near-integer H to keep the lower integer");
ok &= expect(parse_simulator_gcode_control_line("G43.4 H7.99995", &actions),
"expected LinuxCNC integer tolerance to round high-fraction H");
ok &= expect(actions.size() == 1 && actions[0].h_code == 8,
"expected high-fraction H to round up");
ok &= expect(parse_simulator_gcode_control_line("G43.4 H++7", &actions),
"expected LinuxCNC recursive unary plus H value to parse");
ok &= expect(actions.size() == 1 && actions[0].h_code == 7,
"expected H++7 to parse as H7");
ok &= expect(!parse_simulator_gcode_control_line("G43.4 H7.00011", &actions),
"expected non-integer H above lower tolerance to fall through");
ok &= expect(actions.empty(), "expected no actions for lower-bound non-integer H");
ok &= expect(!parse_simulator_gcode_control_line("G43.4 H7.99989", &actions),
"expected H below the upper round-up tolerance to fall through");
ok &= expect(actions.empty(), "expected no actions for upper-bound non-integer H");
ok &= expect(!parse_simulator_gcode_control_line("G43.4 H7e0", &actions),
"expected LinuxCNC non-exponent H-number parsing to leave trailing e0");
ok &= expect(actions.empty(), "expected no actions for exponent H word");
ok &= expect(!parse_simulator_gcode_control_line("G43.4 H-2", &actions),
"expected LinuxCNC negative-H rule to fall through");
ok &= expect(actions.empty(), "expected no actions for invalid negative H");
ok &= expect(!parse_simulator_gcode_control_line("G43.4 H7 H8", &actions),
"expected duplicate H words to fall through to LinuxCNC error handling");
ok &= expect(actions.empty(), "expected no actions for duplicate H words");
ok &= expect(!parse_simulator_gcode_control_line("G43.4 G49", &actions),
"expected duplicate RTCP-state G words to fall through to LinuxCNC modal-group handling");
ok &= expect(actions.empty(), "expected no actions for duplicate RTCP-state G words");
ok &= expect(!parse_simulator_gcode_control_line("G49 H7", &actions),
"expected G49 with unused H word to fall through to LinuxCNC error handling");
ok &= expect(actions.empty(), "expected no actions for G49 with unused H word");
ok &= expect(!parse_simulator_gcode_control_line("G43.4 H7 X10", &actions),
"expected mixed RTCP/motion line to fall through to interpreter");