满庭芳·源流归一

结论: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

@@ -2,10 +2,11 @@
#include <cmath>
#include "rtcp_kinematics.h"
namespace {
constexpr int kCssSpindleModeEvent = 960;
constexpr int kConstantRpmSpindleModeEvent = 970;
void rotate_xy(double *x, double *y, double degrees) {
const double radians = degrees * std::acos(-1.0) / 180.0;
const double c = std::cos(radians);
@@ -32,7 +33,9 @@ void CanonEventSink::reset() {
tool_length_offset_ = {};
xy_rotation_degrees_ = 0.0;
callback_status_ = 0;
kinematics_type_ = 1;
// LinuxCNC xyzbc-trt-kins with sparm=identityfirst starts at switchkins-type 0.
// Source: linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.ini.
kinematics_type_ = 0;
feed_override_ = false;
speed_override_ = false;
adaptive_feed_ = false;
@@ -67,6 +70,24 @@ void CanonEventSink::configure_rtcp(bool enabled, double tool_length) {
rtcp_tool_length_ = tool_length;
}
void CanonEventSink::configure_xyzbc_trt(const LinuxCncXyzbcTrtParameters &parameters) {
xyzbc_trt_parameters_ = parameters;
xyzbc_trt_parameters_configured_ = true;
use_xyzac_trt_ = false;
}
void CanonEventSink::configure_xyzac_trt(const LinuxCncXyzbcTrtParameters &parameters) {
xyzbc_trt_parameters_ = parameters;
xyzbc_trt_parameters_configured_ = true;
use_xyzac_trt_ = true;
}
void CanonEventSink::configure_fiveaxis(double pivot_length, int kinematics_type) {
fiveaxis_configured_ = true;
fiveaxis_pivot_length_ = pivot_length;
fiveaxis_kinematics_type_ = kinematics_type;
}
void CanonEventSink::set_tool_length(int h_code, double tool_length) {
if (h_code <= 0) {
default_rtcp_tool_length_ = tool_length;
@@ -119,15 +140,37 @@ void CanonEventSink::set_rtcp_state(bool enabled, int h_code, int line) {
emit(event);
}
void CanonEventSink::switch_kinematics(int kinematics_type, bool rtcp_enabled, int line) {
void CanonEventSink::switch_kinematics(int kinematics_type, int line) {
kinematics_type_ = kinematics_type;
rtcp_enabled_ = rtcp_enabled;
// Source: linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/remap_subs/{428,429,430}remap.ngc.
// M68 E3 Q#<kinstype>, then M66 E0 L0 to force synchronization.
set_analog_output(3, static_cast<double>(kinematics_type_), false, line);
wait_input(0, 0, 0, 0.0, line);
CncSimEvent event = base_event(CNC_SIM_EVENT_KINEMATICS_SWITCH, line);
event.reserved = kinematics_type_;
event.feed = rtcp_enabled_ ? 1.0 : 0.0;
emit(event);
}
void CanonEventSink::switch_m_code_kinematics(int m_code, int default_kinematics_type, int line) {
if (fiveaxis_configured_) {
// Source: linuxcnc/configs/sim/axis/vismach/5axis/bridgemill/remap_subs/{428,429,430}remap.ngc
// M428 selects 5axiskins type 0, M429 selects identity type 1, M430 selects userk type 2.
if (m_code == 428) {
switch_kinematics(0, line);
return;
}
if (m_code == 429) {
switch_kinematics(1, line);
return;
}
if (m_code == 430) {
switch_kinematics(2, line);
return;
}
}
switch_kinematics(default_kinematics_type, line);
}
void CanonEventSink::set_g5x_offset(int index, const CncSimPose &offset, int line) {
CncSimEvent event = base_event(CNC_SIM_EVENT_SET_G5X_OFFSET, line);
event.tool = index;
@@ -198,7 +241,7 @@ void CanonEventSink::set_spindle_mode(int spindle, double mode, int line) {
CncSimEvent event = base_event(CNC_SIM_EVENT_COMMENT, line);
event.tool = spindle;
event.feed = mode;
event.reserved = mode == 0.0 ? 970 : 960;
event.reserved = mode == 0.0 ? kConstantRpmSpindleModeEvent : kCssSpindleModeEvent;
emit(event);
}
@@ -479,16 +522,67 @@ void CanonEventSink::emit(CncSimEvent event) {
}
void CanonEventSink::emit_rtcp_pivot(int line, const CncSimPose &start, const CncSimPose &end) {
if (!rtcp_enabled_ || rtcp_tool_length_ == 0.0 || callback_status_ != 0) {
if (!rtcp_enabled_ || callback_status_ != 0) {
return;
}
CncSimEvent event = base_event(CNC_SIM_EVENT_RTCP_PIVOT, line);
event.start = rtcp_pivot_from_tool_tip(start, rtcp_tool_length_);
event.end = rtcp_pivot_from_tool_tip(end, rtcp_tool_length_);
const RtcpVector tool = rtcp_tool_vector_from_pose(end, rtcp_tool_length_);
event.center.x = tool.x;
event.center.y = tool.y;
event.center.z = tool.z;
if (fiveaxis_configured_ && kinematics_type_ == fiveaxis_kinematics_type_) {
const LinuxCncFiveAxisJoints start_joints =
linuxcnc_5axis_inverse(start, fiveaxis_pivot_length_);
const LinuxCncFiveAxisJoints end_joints =
linuxcnc_5axis_inverse(end, fiveaxis_pivot_length_);
event.start.x = start_joints.x;
event.start.y = start_joints.y;
event.start.z = start_joints.z;
event.start.b = start_joints.b;
event.start.c = start_joints.c;
event.start.w = start_joints.w;
event.end.x = end_joints.x;
event.end.y = end_joints.y;
event.end.z = end_joints.z;
event.end.b = end_joints.b;
event.end.c = end_joints.c;
event.end.w = end_joints.w;
event.reserved = kinematics_type_;
event.dwell_seconds = fiveaxis_pivot_length_;
} else if (kinematics_type_ == 1 || kinematics_type_ == 2) {
LinuxCncFiveAxisJoints start_joints{};
LinuxCncFiveAxisJoints end_joints{};
if (kinematics_type_ == 1) {
LinuxCncXyzbcTrtParameters parameters = xyzbc_trt_parameters_configured_
? xyzbc_trt_parameters_
: linuxcnc_xyzbc_trt_default_parameters(rtcp_tool_length_);
parameters.tool_offset = rtcp_tool_length_;
if (use_xyzac_trt_) {
start_joints = linuxcnc_xyzac_trt_inverse(start, parameters);
end_joints = linuxcnc_xyzac_trt_inverse(end, parameters);
} else {
start_joints = linuxcnc_xyzbc_trt_inverse(start, parameters);
end_joints = linuxcnc_xyzbc_trt_inverse(end, parameters);
}
} else {
start_joints = linuxcnc_xyzbc_userk_inverse(start);
end_joints = linuxcnc_xyzbc_userk_inverse(end);
}
event.start.x = start_joints.x;
event.start.y = start_joints.y;
event.start.z = start_joints.z;
event.start.a = start_joints.a;
event.start.b = start_joints.b;
event.start.c = start_joints.c;
event.start.w = start_joints.w;
event.end.x = end_joints.x;
event.end.y = end_joints.y;
event.end.z = end_joints.z;
event.end.a = end_joints.a;
event.end.b = end_joints.b;
event.end.c = end_joints.c;
event.end.w = end_joints.w;
event.reserved = kinematics_type_;
event.dwell_seconds = rtcp_tool_length_;
} else {
return;
}
emit(event);
}

View File

@@ -1,6 +1,7 @@
#pragma once
#include "cnc_sim_api.h"
#include "rtcp_kinematics.h"
#include <unordered_map>
@@ -12,11 +13,15 @@ public:
bool callback_aborted() const;
void set_block_delete(bool enabled);
void configure_rtcp(bool enabled, double tool_length);
void configure_xyzbc_trt(const LinuxCncXyzbcTrtParameters &parameters);
void configure_xyzac_trt(const LinuxCncXyzbcTrtParameters &parameters);
void configure_fiveaxis(double pivot_length, int kinematics_type);
void set_tool_length(int h_code, double tool_length);
void set_tool_length_offset(const CncSimPose &offset);
void apply_tool_length_offset(const CncSimPose &offset);
void set_rtcp_state(bool enabled, int h_code, int line);
void switch_kinematics(int kinematics_type, bool rtcp_enabled, int line);
void switch_kinematics(int kinematics_type, int line);
void switch_m_code_kinematics(int m_code, int default_kinematics_type, int line);
void set_g5x_offset(int index, const CncSimPose &offset, int line);
void set_g92_offset(const CncSimPose &offset, int line);
void set_xy_rotation(double angle_degrees, int line);
@@ -97,9 +102,15 @@ private:
double default_rtcp_tool_length_ = 0.0;
double rtcp_tool_length_ = 0.0;
int rtcp_h_code_ = 0;
LinuxCncXyzbcTrtParameters xyzbc_trt_parameters_{};
bool xyzbc_trt_parameters_configured_ = false;
bool use_xyzac_trt_ = false;
bool fiveaxis_configured_ = false;
double fiveaxis_pivot_length_ = 250.0;
int fiveaxis_kinematics_type_ = 0;
CncSimPose tool_length_offset_{};
double xy_rotation_degrees_ = 0.0;
int kinematics_type_ = 1;
int kinematics_type_ = 0;
bool feed_override_ = false;
bool speed_override_ = false;
bool adaptive_feed_ = false;

View File

@@ -9,7 +9,11 @@
struct CncSimHandle {
CncSimDialect dialect = CNC_SIM_DIALECT_LINUXCNC;
#ifdef CNC_SIM_ENABLE_LINUXCNC_RS274_BACKEND
GcodeBackendKind backend = GcodeBackendKind::LinuxCncRs274;
#else
GcodeBackendKind backend = GcodeBackendKind::Smoke;
#endif
std::string last_error;
CanonEventSink sink;
};
@@ -128,6 +132,86 @@ void apply_tool_length_table(CanonEventSink &sink, const std::string &compact) {
}
}
bool find_number_any(const std::string &compact, double *value, const char *first) {
return find_number_value(compact, first, value);
}
template <typename... Keys>
bool find_number_any(const std::string &compact, double *value, const char *first, Keys... rest) {
return find_number_value(compact, first, value) || find_number_any(compact, value, rest...);
}
bool contains_bool_any(const std::string &compact, bool *value, const char *first) {
return contains_bool_value(compact, first, value);
}
template <typename... Keys>
bool contains_bool_any(const std::string &compact, bool *value, const char *first, Keys... rest) {
return contains_bool_value(compact, first, value) || contains_bool_any(compact, value, rest...);
}
void apply_xyzbc_trt_config(CanonEventSink &sink, const std::string &compact) {
LinuxCncXyzbcTrtParameters parameters = linuxcnc_xyzbc_trt_default_parameters(0.0);
bool configured = false;
const bool xyzac = compact.find("\"trt\":\"xyzac\"") != std::string::npos ||
compact.find("\"kinematics\":\"xyzac-trt\"") != std::string::npos ||
compact.find("\"switchkins\":\"xyzac-trt\"") != std::string::npos;
const bool xyzbc = compact.find("\"trt\":\"xyzbc\"") != std::string::npos ||
compact.find("\"kinematics\":\"xyzbc-trt\"") != std::string::npos ||
compact.find("\"switchkins\":\"xyzbc-trt\"") != std::string::npos;
configured = find_number_any(compact, &parameters.x_rot_point, "xrotpoint", "x_rot_point", "x-rot-point") ||
configured;
configured = find_number_any(compact, &parameters.y_rot_point, "yrotpoint", "y_rot_point", "y-rot-point") ||
configured;
configured = find_number_any(compact, &parameters.z_rot_point, "zrotpoint", "z_rot_point", "z-rot-point") ||
configured;
configured = find_number_any(compact, &parameters.x_offset, "xoffset", "x_offset", "x-offset") ||
configured;
configured = find_number_any(compact, &parameters.y_offset, "yoffset", "y_offset", "y-offset") ||
configured;
configured = find_number_any(compact, &parameters.z_offset, "zoffset", "z_offset", "z-offset") ||
configured;
configured = contains_bool_any(compact,
&parameters.conventional_directions,
"conventionaldirections",
"conventional_directions",
"conventional-directions") ||
configured;
if (configured || xyzac || xyzbc) {
configured = true;
}
if (configured && xyzac) {
sink.configure_xyzac_trt(parameters);
} else if (configured) {
sink.configure_xyzbc_trt(parameters);
}
}
void apply_fiveaxis_config(CanonEventSink &sink, const std::string &compact) {
const bool fiveaxis = compact.find("\"kinematics\":\"5axiskins\"") != std::string::npos ||
compact.find("\"switchkins\":\"5axiskins\"") != std::string::npos ||
compact.find("\"kinematics\":\"5axis\"") != std::string::npos;
double pivot_length = 250.0;
const bool has_pivot_length = find_number_any(compact,
&pivot_length,
"pivotlength",
"pivot_length",
"pivot-length");
double kinematics_type = 0.0;
const bool has_kinematics_type = find_number_any(compact,
&kinematics_type,
"fiveaxiskinematicstype",
"fiveaxis_kinematics_type",
"fiveaxis-kinematics-type",
"kinematicstype",
"kinematics_type");
if (fiveaxis || has_pivot_length || has_kinematics_type) {
sink.configure_fiveaxis(pivot_length, static_cast<int>(kinematics_type));
}
}
int apply_config(CncSimHandle *handle, const std::string &json) {
if (json.empty()) {
return 0;
@@ -158,6 +242,8 @@ int apply_config(CncSimHandle *handle, const std::string &json) {
handle->sink.set_block_delete(block_delete);
}
apply_tool_length_table(handle->sink, compact);
apply_xyzbc_trt_config(handle->sink, compact);
apply_fiveaxis_config(handle->sink, compact);
return 0;
}

View File

@@ -15,6 +15,8 @@
namespace {
constexpr int kToolLengthOffsetEvent = 430;
CanonEventSink *active_sink = nullptr;
int active_line = 0;
bool flood_on = false;
@@ -515,7 +517,7 @@ void USE_TOOL_LENGTH_OFFSET(const EmcPose &offset) {
event.version = 1;
event.type = CNC_SIM_EVENT_COMMENT;
event.line = event_line();
event.reserved = 430;
event.reserved = kToolLengthOffsetEvent;
event.start = tool_offset;
active_sink->emit_raw(event);
}

View File

@@ -217,7 +217,7 @@ int parse_linuxcnc_rs274_backend(CanonEventSink &sink,
++line_number;
cnc_sim_linuxcnc_set_current_line(line_number);
std::vector<SimulatorGcodeControlAction> control_actions;
if (parse_simulator_gcode_control_line(line, &control_actions)) {
if (parse_simulator_gcode_control_line(line, sink.block_delete(), &control_actions)) {
bool simulator_only_line = true;
for (const auto &action : control_actions) {
emit_simulator_gcode_control_action(sink, action, line_number);

File diff suppressed because it is too large Load Diff

View File

@@ -8,10 +8,134 @@ struct RtcpVector {
double z;
};
// Rotation order is intrinsic tool orientation A then B then C, represented as
// Rz(C) * Ry(B) * Rx(A) applied to a local tool vector.
RtcpVector rtcp_rotate_abc_degrees(RtcpVector vector, double a_deg, double b_deg, double c_deg);
struct LinuxCncFiveAxisJoints {
double x;
double y;
double z;
double a;
double b;
double c;
double w;
};
RtcpVector rtcp_tool_vector_from_pose(const CncSimPose &pose, double tool_length);
CncSimPose rtcp_pivot_from_tool_tip(const CncSimPose &tool_tip, double tool_length);
CncSimPose rtcp_tool_tip_from_pivot(const CncSimPose &pivot, double tool_length);
struct LinuxCncAxisJoints {
double x;
double y;
double z;
double a;
double b;
double c;
double u;
double v;
double w;
};
struct LinuxCncXyzbcTrtParameters {
double x_rot_point;
double y_rot_point;
double z_rot_point;
double x_offset;
double y_offset;
double z_offset;
double tool_offset;
bool conventional_directions;
};
struct LinuxCncScaraParameters {
double d1;
double d2;
double d3;
double d4;
double d5;
double d6;
};
struct LinuxCncXyzabTdrParameters {
double tool_offset_z;
double x_offset;
double z_offset;
double x_rot_point;
double y_rot_point;
double z_rot_point;
};
struct LinuxCncPumaParameters {
double a2;
double a3;
double d3;
double d4;
double d6;
};
struct LinuxCncGenserParameters {
double a[6];
double alpha[6];
double d[6];
int unrotate[6];
};
struct LinuxCncScaraJoints {
double j0;
double j1;
double j2;
double j3;
double j4;
double j5;
};
LinuxCncAxisJoints linuxcnc_identity_default_joints();
CncSimPose linuxcnc_identity_forward(const LinuxCncAxisJoints &joints);
LinuxCncAxisJoints linuxcnc_identity_inverse(const CncSimPose &pose);
CncSimPose linuxcnc_rotatekins_forward(const LinuxCncAxisJoints &joints);
LinuxCncAxisJoints linuxcnc_rotatekins_inverse(const CncSimPose &pose);
CncSimPose linuxcnc_5axis_forward(const LinuxCncFiveAxisJoints &joints, double pivot_length);
LinuxCncFiveAxisJoints linuxcnc_5axis_inverse(const CncSimPose &pose, double pivot_length);
LinuxCncXyzbcTrtParameters linuxcnc_xyzbc_trt_default_parameters(double tool_offset);
CncSimPose linuxcnc_xyzbc_trt_forward(const LinuxCncFiveAxisJoints &joints,
const LinuxCncXyzbcTrtParameters &parameters);
LinuxCncFiveAxisJoints linuxcnc_xyzbc_trt_inverse(const CncSimPose &pose,
const LinuxCncXyzbcTrtParameters &parameters);
CncSimPose linuxcnc_xyzac_trt_forward(const LinuxCncFiveAxisJoints &joints,
const LinuxCncXyzbcTrtParameters &parameters);
LinuxCncFiveAxisJoints linuxcnc_xyzac_trt_inverse(const CncSimPose &pose,
const LinuxCncXyzbcTrtParameters &parameters);
CncSimPose linuxcnc_xyzbc_userk_forward(const LinuxCncFiveAxisJoints &joints);
LinuxCncFiveAxisJoints linuxcnc_xyzbc_userk_inverse(const CncSimPose &pose);
LinuxCncScaraParameters linuxcnc_scara_default_parameters();
CncSimPose linuxcnc_scara_forward(const LinuxCncScaraJoints &joints,
const LinuxCncScaraParameters &parameters,
int *iflags);
LinuxCncScaraJoints linuxcnc_scara_inverse(const CncSimPose &pose,
const LinuxCncScaraParameters &parameters,
int iflags);
LinuxCncXyzabTdrParameters linuxcnc_xyzab_tdr_default_parameters(double tool_offset_z);
CncSimPose linuxcnc_xyzab_tdr_identity_forward(const LinuxCncAxisJoints &joints);
LinuxCncAxisJoints linuxcnc_xyzab_tdr_identity_inverse(const CncSimPose &pose);
CncSimPose linuxcnc_xyzab_tdr_tcp_forward(const LinuxCncAxisJoints &joints,
const LinuxCncXyzabTdrParameters &parameters);
LinuxCncAxisJoints linuxcnc_xyzab_tdr_tcp_inverse(const CncSimPose &pose,
const LinuxCncXyzabTdrParameters &parameters);
LinuxCncPumaParameters linuxcnc_puma_default_parameters();
CncSimPose linuxcnc_puma_forward(const LinuxCncAxisJoints &joints,
const LinuxCncPumaParameters &parameters,
int *iflags);
LinuxCncAxisJoints linuxcnc_puma_inverse(const CncSimPose &pose,
const LinuxCncAxisJoints &current_joints,
const LinuxCncPumaParameters &parameters,
int iflags,
int *fflags);
LinuxCncGenserParameters linuxcnc_genser_puma560_parameters();
CncSimPose linuxcnc_genser_forward(const LinuxCncAxisJoints &joints,
const LinuxCncGenserParameters &parameters);
LinuxCncAxisJoints linuxcnc_genser_inverse(const CncSimPose &pose,
const LinuxCncAxisJoints &joint_estimate,
const LinuxCncGenserParameters &parameters,
int *iterations,
int max_iterations = 100);

View File

@@ -6,31 +6,155 @@
namespace {
std::string strip_line_comment(const std::string &line) {
bool strip_line_comment(const std::string &line, bool block_delete, std::string *stripped) {
std::string out;
bool in_paren = false;
bool saw_code = false;
for (char raw : line) {
const char ch = static_cast<char>(std::toupper(static_cast<unsigned char>(raw)));
if (in_paren) {
if (ch == '(') {
return false;
}
if (ch == ')') {
in_paren = false;
}
continue;
}
if (ch == '(') {
saw_code = true;
in_paren = true;
continue;
}
if (ch == ';') {
break;
}
if (!saw_code && std::isspace(static_cast<unsigned char>(ch))) {
out.push_back(ch);
continue;
}
if (!saw_code && ch == '/') {
if (block_delete) {
return false;
}
saw_code = true;
continue;
}
saw_code = true;
out.push_back(ch);
}
return out;
if (in_paren) {
return false;
}
*stripped = out;
return true;
}
bool g_code_is(double actual, double expected) {
return std::fabs(actual - expected) < 0.0001;
bool linuxcnc_integer_word_value(double value, int *integer) {
*integer = static_cast<int>(std::floor(value));
const double fraction = value - *integer;
if (fraction > 0.9999) {
*integer = static_cast<int>(std::ceil(value));
return true;
}
return fraction <= 0.0001;
}
bool linuxcnc_g_word_value(double value, int *g_code) {
const double scaled = value * 10.0;
int parsed = static_cast<int>(std::floor(scaled));
const double fraction = scaled - parsed;
if (fraction > 0.999) {
parsed = static_cast<int>(std::ceil(scaled));
} else if (fraction > 0.001) {
return false;
}
if (parsed < 0 || parsed > 999) {
return false;
}
*g_code = parsed;
return true;
}
bool read_linuxcnc_real_number(const char *text, size_t start, double *value, size_t *end_index) {
const char first = text[start];
const char next = text[start + 1];
if (first == '[') {
double nested = 0.0;
size_t nested_end = start + 1;
if (!read_linuxcnc_real_number(text, start + 1, &nested, &nested_end) ||
text[nested_end] != ']') {
return false;
}
*value = nested;
*end_index = nested_end + 1;
return true;
}
if ((first == '+' || first == '-') &&
next != '\0' &&
!std::isdigit(static_cast<unsigned char>(next)) &&
next != '.') {
double nested = 0.0;
size_t nested_end = start + 1;
if (!read_linuxcnc_real_number(text, start + 1, &nested, &nested_end)) {
return false;
}
*value = first == '-' ? -nested : nested;
*end_index = nested_end;
return true;
}
size_t i = start;
while (text[i] == '+' || text[i] == '-') {
++i;
}
const size_t number_start = i;
while (std::isdigit(static_cast<unsigned char>(text[i])) || text[i] == '.') {
++i;
}
if (i == start || i == number_start) {
return false;
}
std::string token(text + start, i - start);
char *end = nullptr;
const double parsed = std::strtod(token.c_str(), &end);
if (end != token.c_str() + token.size()) {
return false;
}
*value = parsed;
*end_index = i;
return true;
}
bool skip_linuxcnc_leading_n_number(const std::string &stripped, std::string *without_n) {
size_t i = 0;
while (std::isspace(static_cast<unsigned char>(stripped[i]))) {
++i;
}
if (stripped[i] != 'N') {
*without_n = stripped;
return true;
}
++i;
const size_t integer_start = i;
while (std::isdigit(static_cast<unsigned char>(stripped[i]))) {
++i;
}
if (i == integer_start) {
return false;
}
if (stripped[i] == '.') {
++i;
const size_t fraction_start = i;
while (std::isdigit(static_cast<unsigned char>(stripped[i]))) {
++i;
}
if (i == fraction_start) {
return false;
}
}
*without_n = stripped.substr(i);
return true;
}
bool parse_m_control_line(const std::string &stripped,
@@ -46,9 +170,13 @@ bool parse_m_control_line(const std::string &stripped,
return false;
}
++i;
char *end = nullptr;
const long value = std::strtol(text + i, &end, 10);
if (end == text + i) {
double raw_value = 0.0;
size_t end = i;
if (!read_linuxcnc_real_number(text, i, &raw_value, &end)) {
return false;
}
int value = 0;
if (!linuxcnc_integer_word_value(raw_value, &value) || value < 0) {
return false;
}
@@ -56,19 +184,19 @@ bool parse_m_control_line(const std::string &stripped,
action.kind = SimulatorGcodeControlKind::KinematicsSwitch;
if (value == 428) {
action.value = 1;
action.rtcp_enabled = true;
action.m_code = 428;
} else if (value == 429) {
action.value = 0;
action.rtcp_enabled = false;
action.m_code = 429;
} else if (value == 430) {
action.value = 2;
action.rtcp_enabled = true;
action.m_code = 430;
} else {
return false;
}
actions->push_back(action);
saw_code = true;
i = static_cast<size_t>(end - text);
i = end;
}
return saw_code;
}
@@ -78,7 +206,9 @@ bool parse_rtcp_control_line(const std::string &stripped,
const char *text = stripped.c_str();
std::vector<SimulatorGcodeControlAction> parsed;
int h_code = 0;
bool saw_h_word = false;
bool saw_rtcp_code = false;
bool saw_rtcp_g_word = false;
for (size_t i = 0; text[i] != '\0';) {
if (std::isspace(static_cast<unsigned char>(text[i]))) {
@@ -90,26 +220,44 @@ bool parse_rtcp_control_line(const std::string &stripped,
return false;
}
++i;
char *end = nullptr;
const double value = std::strtod(text + i, &end);
if (end == text + i) {
double value = 0.0;
size_t end = i;
if (!read_linuxcnc_real_number(text, i, &value, &end)) {
return false;
}
if (letter == 'H') {
h_code = static_cast<int>(std::lround(value));
} else if (g_code_is(value, 43.4) || g_code_is(value, 43.5) || g_code_is(value, 49.0)) {
if (saw_h_word) {
return false;
}
if (!linuxcnc_integer_word_value(value, &h_code) || h_code < -1) {
return false;
}
saw_h_word = true;
} else {
int g_code = 0;
if (!linuxcnc_g_word_value(value, &g_code) ||
(g_code != 434 && g_code != 435 && g_code != 490)) {
return false;
}
if (saw_rtcp_g_word) {
return false;
}
SimulatorGcodeControlAction action{};
action.kind = SimulatorGcodeControlKind::RtcpState;
action.value = static_cast<int>(std::lround(value * 10.0));
action.rtcp_enabled = g_code_is(value, 43.4) || g_code_is(value, 43.5);
action.value = g_code;
action.rtcp_enabled = g_code == 434 || g_code == 435;
parsed.push_back(action);
saw_rtcp_code = true;
} else {
return false;
saw_rtcp_g_word = true;
}
i = static_cast<size_t>(end - text);
i = end;
}
for (const auto &action : parsed) {
if (!action.rtcp_enabled && saw_h_word) {
return false;
}
}
for (auto &action : parsed) {
action.h_code = action.rtcp_enabled ? h_code : 0;
actions->push_back(action);
@@ -121,13 +269,26 @@ bool parse_rtcp_control_line(const std::string &stripped,
bool parse_simulator_gcode_control_line(const std::string &line,
std::vector<SimulatorGcodeControlAction> *actions) {
return parse_simulator_gcode_control_line(line, false, actions);
}
bool parse_simulator_gcode_control_line(const std::string &line,
bool block_delete,
std::vector<SimulatorGcodeControlAction> *actions) {
actions->clear();
const std::string stripped = strip_line_comment(line);
if (parse_m_control_line(stripped, actions)) {
std::string stripped;
if (!strip_line_comment(line, block_delete, &stripped)) {
return false;
}
std::string control_line;
if (!skip_linuxcnc_leading_n_number(stripped, &control_line)) {
return false;
}
if (parse_m_control_line(control_line, actions)) {
return true;
}
actions->clear();
if (parse_rtcp_control_line(stripped, actions)) {
if (parse_rtcp_control_line(control_line, actions)) {
return true;
}
actions->clear();
@@ -138,7 +299,7 @@ void emit_simulator_gcode_control_action(CanonEventSink &sink,
const SimulatorGcodeControlAction &action,
int line) {
if (action.kind == SimulatorGcodeControlKind::KinematicsSwitch) {
sink.switch_kinematics(action.value, action.rtcp_enabled, line);
sink.switch_m_code_kinematics(action.m_code, action.value, line);
} else if (action.kind == SimulatorGcodeControlKind::RtcpState) {
sink.set_rtcp_state(action.rtcp_enabled, action.h_code, line);
}

View File

@@ -13,12 +13,16 @@ enum class SimulatorGcodeControlKind {
struct SimulatorGcodeControlAction {
SimulatorGcodeControlKind kind;
int value;
int m_code;
bool rtcp_enabled;
int h_code;
};
bool parse_simulator_gcode_control_line(const std::string &line,
std::vector<SimulatorGcodeControlAction> *actions);
bool parse_simulator_gcode_control_line(const std::string &line,
bool block_delete,
std::vector<SimulatorGcodeControlAction> *actions);
void emit_simulator_gcode_control_action(CanonEventSink &sink,
const SimulatorGcodeControlAction &action,
int line);

View File

@@ -149,11 +149,13 @@ constexpr double kRadiusToleranceMm = kRadiusToleranceInch * kMmPerInch;
constexpr double kCenterArcRadiusToleranceInch = 2.0 * 0.001 * M_SQRT2;
constexpr double kCenterArcRadiusToleranceMm = 2.0 * 0.01 * M_SQRT2;
constexpr double kSpiralRelativeTolerance = 0.001;
constexpr int kConstantRpmSpindleModeEvent = 970;
constexpr int kCutterCompOffEvent = 400;
constexpr int kCutterCompLeftEvent = 410;
constexpr int kCutterCompRightEvent = 420;
constexpr int kCutterCompLeftExplicitEvent = 411;
constexpr int kCutterCompRightExplicitEvent = 421;
constexpr int kToolLengthOffsetEvent = 430;
enum class LinuxCncReaderSymbol {
None,
@@ -178,6 +180,12 @@ size_t leading_special_word_start(const std::string &line);
bool is_numeric_m98_subprogram_header(const std::string &line);
OwordKind oword_kind(const std::string &line);
bool integer_word_value(const std::unordered_map<char, double> &words, char letter, int *value);
bool evaluate_expression(const std::string &text,
const std::vector<CallFrame> &call_stack,
const std::unordered_map<int, double> &parameters,
const std::unordered_map<std::string, double> &named_parameters,
double *value,
std::string *error);
template <size_t Size>
bool contains_int(const std::array<int, Size> &values, int value) {
@@ -685,6 +693,91 @@ LinuxCncIntegerReadResult read_linuxcnc_integer_value(const std::string &text,
return LinuxCncIntegerReadResult::Ok;
}
bool is_current_position_parameter_index(int index) {
return index >= 5420 && index <= 5428;
}
bool line_reads_current_position_parameter(const std::string &line,
const std::vector<CallFrame> &call_stack,
const std::unordered_map<int, double> &parameters,
const std::unordered_map<std::string, double> &named_parameters) {
for (size_t i = 0; i < line.size(); ++i) {
if (line[i] != '#') {
continue;
}
int index = 0;
size_t index_end = i + 1;
size_t parameter_start = i + 1;
while (parameter_start < line.size() &&
std::isspace(static_cast<unsigned char>(line[parameter_start]))) {
++parameter_start;
}
if (parameter_start < line.size() && line[parameter_start] == '[') {
const size_t close = find_matching_square_bracket(line, parameter_start);
if (close != std::string::npos) {
size_t bracket_index_end = parameter_start + 1;
const LinuxCncIntegerReadResult bracket_result =
read_linuxcnc_integer_value(line, parameter_start + 1, &index, &bracket_index_end);
size_t after_bracket_index = bracket_index_end;
while (after_bracket_index < close &&
std::isspace(static_cast<unsigned char>(line[after_bracket_index]))) {
++after_bracket_index;
}
bool has_current_position_index =
bracket_result == LinuxCncIntegerReadResult::Ok &&
after_bracket_index == close &&
is_current_position_parameter_index(index);
if (!has_current_position_index) {
double index_value = 0.0;
if (evaluate_expression(line.substr(parameter_start + 1,
close - parameter_start - 1),
call_stack,
parameters,
named_parameters,
&index_value,
nullptr)) {
int expression_index = 0;
has_current_position_index =
linuxcnc_integer_from_real(index_value, &expression_index) ==
LinuxCncIntegerReadResult::Ok &&
is_current_position_parameter_index(expression_index);
}
}
if (has_current_position_index) {
size_t after_close = close + 1;
while (after_close < line.size() &&
std::isspace(static_cast<unsigned char>(line[after_close]))) {
++after_close;
}
if (after_close < line.size() && line[after_close] == '=') {
continue;
}
return true;
}
}
continue;
}
const LinuxCncIntegerReadResult result =
read_linuxcnc_integer_value(line, i + 1, &index, &index_end);
if (result != LinuxCncIntegerReadResult::Ok ||
!is_current_position_parameter_index(index)) {
continue;
}
size_t after_index = index_end;
while (after_index < line.size() &&
std::isspace(static_cast<unsigned char>(line[after_index]))) {
++after_index;
}
if (after_index < line.size() && line[after_index] == '=') {
continue;
}
return true;
}
return false;
}
bool parse_linuxcnc_real_number(const char *text, double *value, size_t *consumed) {
size_t end = 0;
while (text[end] == '+' || text[end] == '-') {
@@ -1190,6 +1283,7 @@ bool parse_word_list_impl(const std::string &line, ParsedWordList *parsed, std::
parsed->theta_flag = false;
parsed->theta_value = 0.0;
std::array<bool, 26> seen_letters{};
std::array<double, 26> seen_letter_values{};
const char *text = line.c_str();
for (size_t i = 0; text[i] != '\0';) {
if (std::isspace(static_cast<unsigned char>(text[i]))) {
@@ -1338,13 +1432,27 @@ bool parse_word_list_impl(const std::string &line, ParsedWordList *parsed, std::
if (letter != 'G' && letter != 'M') {
const size_t letter_index = static_cast<size_t>(letter - 'A');
if (strict && seen_letters[letter_index]) {
if (error) {
*error = "Multiple " + std::string(1, static_cast<char>(std::tolower(static_cast<unsigned char>(letter)))) +
" words on one line";
if ((letter == 'P' || letter == 'Q') && seen_letter_values[letter_index] <= -1.0) {
parsed->words.erase(std::remove_if(parsed->words.begin(),
parsed->words.end(),
[letter](const Word &word) {
return word.letter == letter;
}),
parsed->words.end());
} else {
if (error) {
const char duplicate_letter =
(letter == 'U' || letter == 'V' || letter == 'W')
? letter
: static_cast<char>(std::tolower(static_cast<unsigned char>(letter)));
*error = "Multiple " + std::string(1, duplicate_letter) +
" words on one line";
}
return false;
}
return false;
}
seen_letters[letter_index] = true;
seen_letter_values[letter_index] = value;
}
parsed->words.push_back({letter, value});
i = next;
@@ -3309,7 +3417,7 @@ std::string expand_expressions_and_parameters(const std::string &line,
const size_t close = find_matching_square_bracket(line, function_start);
if (close == std::string::npos) {
if (error) {
*error = "unterminated bracket expression";
*error = "Unclosed expression";
}
return {};
}
@@ -3356,7 +3464,7 @@ std::string expand_expressions_and_parameters(const std::string &line,
const size_t close = find_matching_square_bracket(line, i);
if (close == std::string::npos) {
if (error) {
*error = "unterminated bracket expression";
*error = "Unclosed expression";
}
return {};
}
@@ -3391,7 +3499,7 @@ std::string expand_expressions_and_parameters(const std::string &line,
const size_t close = find_matching_square_bracket(line, end);
if (close == std::string::npos) {
if (error) {
*error = "unterminated indirect parameter expression";
*error = "Unclosed expression";
}
return {};
}
@@ -3400,7 +3508,7 @@ std::string expand_expressions_and_parameters(const std::string &line,
const size_t close = line.find('>', end + 1);
if (close == std::string::npos) {
if (error) {
*error = "unterminated indirect named parameter expression";
*error = "Named parameter not terminated";
}
return {};
}
@@ -3445,7 +3553,7 @@ std::string expand_expressions_and_parameters(const std::string &line,
const size_t close = find_matching_square_bracket(line, j);
if (close == std::string::npos) {
if (error) {
*error = "unterminated indexed parameter expression";
*error = "Unclosed expression";
}
return {};
}
@@ -3491,7 +3599,7 @@ std::string expand_expressions_and_parameters(const std::string &line,
const size_t close = line.find('>', j + 1);
if (close == std::string::npos) {
if (error) {
*error = "unterminated named parameter expression";
*error = "Named parameter not terminated";
}
return {};
}
@@ -3653,7 +3761,7 @@ bool parse_parameter_assignment(const std::string &line,
const size_t close = text.find('>', pos + 1);
if (close == std::string::npos) {
if (error) {
*error = "unterminated named parameter assignment";
*error = "Named parameter not terminated";
}
return true;
}
@@ -3669,7 +3777,7 @@ bool parse_parameter_assignment(const std::string &line,
const size_t close = find_matching_square_bracket(text, pos);
if (close == std::string::npos) {
if (error) {
*error = "unterminated indexed parameter assignment";
*error = "Unclosed expression";
}
return true;
}
@@ -3793,7 +3901,7 @@ bool parse_parameter_assignment(const std::string &line,
const size_t close = find_matching_square_bracket(text, pos);
if (close == std::string::npos) {
if (error) {
*error = "unterminated parameter assignment expression";
*error = "Unclosed expression";
}
return true;
}
@@ -4994,6 +5102,119 @@ bool validate_r_arc_reaches_end_point(const CncSimEvent &event, int plane, doubl
return true;
}
bool validate_absolute_center_arc_axis_words(const std::unordered_map<char, double> &words,
int plane,
bool absolute,
std::string *error) {
if (!absolute || words.count('R')) {
return true;
}
auto missing = [error](char axis) {
if (error) {
*error = std::string(1, axis) + " word missing in absolute center arc";
}
return false;
};
if (plane == 17 && (words.count('I') || words.count('J'))) {
if (!words.count('I')) {
return missing('I');
}
if (!words.count('J')) {
return missing('J');
}
} else if (plane == 18 && (words.count('I') || words.count('K'))) {
if (!words.count('I')) {
return missing('I');
}
if (!words.count('K')) {
return missing('K');
}
} else if (plane == 19 && (words.count('J') || words.count('K'))) {
if (!words.count('J')) {
return missing('J');
}
if (!words.count('K')) {
return missing('K');
}
}
return true;
}
bool validate_arc_format_words(const std::unordered_map<char, double> &words, std::string *error) {
const bool has_r = words.count('R') != 0;
const bool has_ijk = words.count('I') || words.count('J') || words.count('K');
if (has_r && has_ijk) {
if (error) {
*error = "Mixed radius ijk format for arc";
}
return false;
}
if (!has_r && !has_ijk) {
if (error) {
*error = "R i j k words all missing for arc";
}
return false;
}
return true;
}
bool validate_arc_plane_center_words(const std::unordered_map<char, double> &words,
int plane,
std::string *error) {
if (plane == 17 && words.count('K')) {
if (error) {
*error = "K word given for arc in xy plane";
}
return false;
}
if (plane == 18 && words.count('J')) {
if (error) {
*error = "J word given for arc in xz plane";
}
return false;
}
if (plane == 19 && words.count('I')) {
if (error) {
*error = "I word given for arc in yz plane";
}
return false;
}
return true;
}
bool validate_r_arc_endpoint_words(const std::unordered_map<char, double> &words,
const ParsedWordList &parsed,
int plane,
std::string *error) {
if (!words.count('R')) {
return true;
}
if (plane == 17) {
if (!words.count('X') && !words.count('Y') &&
!parsed.radius_flag && !parsed.theta_flag) {
if (error) {
*error = "X and y words missing for arc in xy plane";
}
return false;
}
} else if (plane == 18) {
if (!words.count('X') && !words.count('Z')) {
if (error) {
*error = "X and z words missing for arc in xz plane";
}
return false;
}
} else if (plane == 19) {
if (!words.count('Y') && !words.count('Z')) {
if (error) {
*error = "Y and z words missing for arc in yz plane";
}
return false;
}
}
return true;
}
bool modal_motion_is_threading(int modal_motion) {
return modal_motion == kModalMotionG33 ||
modal_motion == kModalMotionG331;
@@ -5101,7 +5322,7 @@ void emit_tool_length_offset(CanonEventSink &sink, const CncSimPose &offset, int
event.version = 1;
event.type = CNC_SIM_EVENT_COMMENT;
event.line = line;
event.reserved = 430;
event.reserved = kToolLengthOffsetEvent;
event.start = offset;
sink.emit_raw(event);
}
@@ -5645,6 +5866,13 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string
}
refresh_readonly_named_parameters(line_number);
if (cutter_comp_on_ &&
line_reads_current_position_parameter(line, call_stack, parameters, named_parameters)) {
if (error) {
*error = "Cannot read current position with cutter radius compensation on";
}
return -1;
}
std::string parameter_error;
std::vector<PendingParameterAssignment> pending_assignments;
if (!extract_parameter_assignments(line,
@@ -5726,7 +5954,7 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string
}
std::vector<SimulatorGcodeControlAction> simulator_control_actions;
if (!has_keyword_oword &&
parse_simulator_gcode_control_line(line, &simulator_control_actions)) {
parse_simulator_gcode_control_line(line, sink_.block_delete(), &simulator_control_actions)) {
bool simulator_only_line = true;
for (const auto &action : simulator_control_actions) {
emit_simulator_gcode_control_action(sink_, action, line_number);
@@ -6578,7 +6806,8 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string
if (l == 0) {
if (spindle_tool_id_ > 0) {
if (error) {
*error = "G10 L0 not allowed with loaded tool";
*error = "G10 L0 not allowed with loaded tool <" +
std::to_string(spindle_tool_id_) + ">";
}
return -1;
}
@@ -6761,21 +6990,30 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string
if (explicit_comp) {
if (!words.count('D')) {
if (error) {
*error = "G41.1/G42.1 requires a D word";
*error = std::string(cutter_comp_left ? "G41.1" : "G42.1") +
" with no D word";
}
return -1;
}
if (words.count('L') && sink_.plane() != 18) {
if (error) {
*error = "G41.1/G42.1 with L word requires G18";
*error = std::string(cutter_comp_left ? "G41.1" : "G42.1") +
" with L word, but plane is not G18";
}
return -1;
}
} else if (words.count('D')) {
const double d_value = words.at('D');
if (std::fabs(d_value - std::round(d_value)) > 0.0001 || d_value < 0.0) {
if (std::fabs(d_value - std::round(d_value)) > 0.0001) {
if (error) {
*error = "G41/G42 requires a non-negative whole-number D word";
*error = std::string(cutter_comp_left ? "G41" : "G42") +
" requires D word to be a whole number";
}
return -1;
}
if (d_value < 0.0) {
if (error) {
*error = "Negative d word tool radius index used";
}
return -1;
}
@@ -7626,6 +7864,16 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string
break;
}
const int spindle = selected_spindle;
if ((m == 3 || m == 4 || m == 5) &&
parsed_word_list.dollar_flag &&
!smoke_spindle_selector_valid_for_speed(parsed_word_list.dollar_value)) {
if (error) {
*error = "Spindle ($) number out of range in M" + std::to_string(m) +
" Command\nnum_spindles =" + std::to_string(kSmokeNumSpindles) +
". $=" + std::to_string(rounded_value(parsed_word_list.dollar_value)) + "\n";
}
return -1;
}
if (m == 3) {
sink_.start_spindle(spindle, 1, line_number);
if (stop_if_callback_aborted(sink_, error)) {
@@ -7944,17 +8192,17 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string
return -1;
}
} else if (m == 428) {
sink_.switch_kinematics(1, true, line_number);
sink_.switch_kinematics(1, line_number);
if (stop_if_callback_aborted(sink_, error)) {
return -1;
}
} else if (m == 429) {
sink_.switch_kinematics(0, false, line_number);
sink_.switch_kinematics(0, line_number);
if (stop_if_callback_aborted(sink_, error)) {
return -1;
}
} else if (m == 430) {
sink_.switch_kinematics(2, true, line_number);
sink_.switch_kinematics(2, line_number);
if (stop_if_callback_aborted(sink_, error)) {
return -1;
}
@@ -8077,8 +8325,15 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string
}
return -1;
}
const bool arc_center_or_radius_words =
(modal_motion == 2 || modal_motion == 3) &&
(words.count('I') || words.count('J') || words.count('K') ||
(words.count('R') && !has_m_code(word_list, 19)));
const bool current_line_requests_motion =
has_axis_words || parsed_word_list.radius_flag || parsed_word_list.theta_flag;
has_axis_words ||
parsed_word_list.radius_flag ||
parsed_word_list.theta_flag ||
arc_center_or_radius_words;
const bool explicit_threading_motion =
has_g_code(word_list, 33.0) || has_g_code(word_list, 33.1) || has_g_code(word_list, 76.0);
if ((current_line_requests_motion || explicit_threading_motion) && !has_axis_words &&
@@ -8723,11 +8978,23 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string
}
return -1;
}
if (!validate_arc_format_words(words, error)) {
return -1;
}
if (!validate_arc_plane_center_words(words, sink_.plane(), error)) {
return -1;
}
if (!validate_r_arc_endpoint_words(words, parsed_word_list, sink_.plane(), error)) {
return -1;
}
event.arc_turns = modal_motion == 2 ? -1 : 1;
if (words.count('R') &&
!validate_r_arc_reaches_end_point(event, sink_.plane(), words.at('R') * sink_.unit_scale(), error)) {
return -1;
}
if (!validate_absolute_center_arc_axis_words(words, sink_.plane(), arc_absolute_, error)) {
return -1;
}
set_arc_center(&event, words, sink_.plane(), sink_.unit_scale(), arc_absolute_);
if (!words.count('R') &&
!validate_arc_center_radius_consistency(event, sink_.plane(), sink_.unit_scale(), error)) {
@@ -8964,7 +9231,7 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string
if (stop_if_callback_aborted(sink_, error)) {
return -1;
}
emit_comment_state(sink_, line_number, 970);
emit_comment_state(sink_, line_number, kConstantRpmSpindleModeEvent);
if (stop_if_callback_aborted(sink_, error)) {
return -1;
}

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");