接续上一轮:推进浏览器验证与M428配置收拢

结论:已完成浏览器侧真实加载验证,M428/M429/M430 的配置入口进一步从 LinuxCNC INI/HALFILE/REMAP 来源生成,native 与 source-link 验证通过。
This commit is contained in:
cnc
2026-05-30 10:03:21 +08:00
parent b40914747d
commit de273bf830
141 changed files with 11255 additions and 1259 deletions

View File

@@ -297,6 +297,45 @@ int main() {
}
ok &= expect(saw_fiveaxis_pivot, "expected M428 bridgemill RTCP pivot to use LinuxCNC 5axiskins inverse");
std::vector<CncSimEvent> switchkins_events;
CanonEventSink switchkins_sink;
switchkins_sink.set_callback(collect_event, &switchkins_events);
switchkins_sink.configure_switchkins_remap(0, 1, 2);
switchkins_sink.switch_m_code_kinematics(428, 1, 10);
switchkins_sink.switch_m_code_kinematics(429, 0, 11);
bool saw_scara_m428 = false;
bool saw_scara_m429 = false;
for (const auto &event : switchkins_events) {
saw_scara_m428 = saw_scara_m428 ||
(event.type == CNC_SIM_EVENT_KINEMATICS_SWITCH &&
event.line == 10 &&
event.reserved == 0);
saw_scara_m429 = saw_scara_m429 ||
(event.type == CNC_SIM_EVENT_KINEMATICS_SWITCH &&
event.line == 11 &&
event.reserved == 1);
}
ok &= expect(saw_scara_m428,
"expected M428 scara/bridgemill/melfa remap to select LinuxCNC kinstype 0");
ok &= expect(saw_scara_m429,
"expected M429 scara/bridgemill/melfa remap to select LinuxCNC identity kinstype 1");
std::vector<CncSimEvent> table_dual_events;
CanonEventSink table_dual_sink;
table_dual_sink.set_callback(collect_event, &table_dual_events);
table_dual_sink.configure_switchkins_remap(1, 0, -1);
const bool table_dual_m430_handled = table_dual_sink.switch_m_code_kinematics(430, 2, 12);
bool saw_table_dual_m430 = false;
for (const auto &event : table_dual_events) {
saw_table_dual_m430 = saw_table_dual_m430 ||
(event.type == CNC_SIM_EVENT_KINEMATICS_SWITCH &&
event.line == 12);
}
ok &= expect(!table_dual_m430_handled,
"expected LinuxCNC table-dual-rotary config without 430remap.ngc to leave M430 unhandled");
ok &= expect(!saw_table_dual_m430,
"expected LinuxCNC table-dual-rotary config without 430remap.ngc to skip M430 switch");
events.clear();
sink.configure_rtcp(true, 250.0);
sink.switch_kinematics(0, 3);

View File

@@ -2,6 +2,7 @@
#include <cmath>
#include <iostream>
#include <string>
#include <vector>
namespace {
@@ -55,6 +56,16 @@ bool saw_comment_state(const std::vector<CncSimEvent> &events,
return false;
}
bool saw_any_kinematics_switch(const std::vector<CncSimEvent> &events, int line) {
for (const auto &event : events) {
if (event.type == CNC_SIM_EVENT_KINEMATICS_SWITCH &&
event.line == line) {
return true;
}
}
return false;
}
} // namespace
int main() {
@@ -521,6 +532,109 @@ int main() {
ok &= expect(saw_g49_tool_offset_parameter_before && saw_g49_tool_offset_parameter_after,
"expected LinuxCNC G49 to clear the applied tool offset parameter before the next motion");
const char tool_pocket_named_parameter_program[] =
"G21 G90 G17\n"
"T1\n"
"O10 if [[#<_selected_tool> EQ 1] AND [#<_current_tool> EQ 0] AND [#<_selected_pocket> EQ 1] AND [#<_current_pocket> EQ 0]]\n"
"G1 X1 F100\n"
"O10 endif\n"
"M6\n"
"O20 if [[#<_selected_tool> EQ 1] AND [#<_current_tool> EQ 0] AND [#<_selected_pocket> EQ 1] AND [#<_current_pocket> EQ 1]]\n"
"G1 Y1 F100\n"
"O20 endif\n"
"M30\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
tool_pocket_named_parameter_program,
sizeof(tool_pocket_named_parameter_program) - 1) == 0,
cnc_sim_last_error(sim));
bool saw_tool_pocket_before_change = false;
bool saw_tool_pocket_after_change = false;
for (const auto &event : events) {
saw_tool_pocket_before_change = saw_tool_pocket_before_change ||
(event.type == CNC_SIM_EVENT_LINEAR_FEED &&
event.line == 4 &&
near(event.end.x, 1.0));
saw_tool_pocket_after_change = saw_tool_pocket_after_change ||
(event.type == CNC_SIM_EVENT_LINEAR_FEED &&
event.line == 8 &&
near(event.end.y, 1.0));
}
ok &= expect(saw_tool_pocket_before_change && saw_tool_pocket_after_change,
"expected LinuxCNC tool pocket named parameters to follow selected/current pocket indexes");
const char m61_tool_pocket_named_parameter_program[] =
"G21 G90 G17\n"
"M61 Q1\n"
"O10 if [[#<_selected_tool> EQ 0] AND [#<_current_tool> EQ 0] AND [#<_selected_pocket> EQ -1] AND [#<_current_pocket> EQ 1]]\n"
"G1 X1 F100\n"
"O10 endif\n"
"M30\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
m61_tool_pocket_named_parameter_program,
sizeof(m61_tool_pocket_named_parameter_program) - 1) == 0,
cnc_sim_last_error(sim));
bool saw_m61_tool_pocket_named_parameter = false;
for (const auto &event : events) {
saw_m61_tool_pocket_named_parameter = saw_m61_tool_pocket_named_parameter ||
(event.type == CNC_SIM_EVENT_LINEAR_FEED &&
event.line == 4 &&
near(event.end.x, 1.0));
}
ok &= expect(saw_m61_tool_pocket_named_parameter,
"expected LinuxCNC M61 to publish the current pocket without changing selected/current tool named parameters");
{
ScopedEnv spindle_tool_env("CNC_SIM_TOOL_IN_SPINDLE", "1");
const char m61_unload_spindle_program[] =
"G21 G90 G17\n"
"M61 Q0\n"
"O10 if [#5400 EQ 0]\n"
"G1 X1 F100\n"
"O10 endif\n"
"M30\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
m61_unload_spindle_program,
sizeof(m61_unload_spindle_program) - 1) == 0,
cnc_sim_last_error(sim));
bool saw_m61_unload_spindle = false;
for (const auto &event : events) {
saw_m61_unload_spindle = saw_m61_unload_spindle ||
(event.type == CNC_SIM_EVENT_LINEAR_FEED &&
event.line == 4 &&
near(event.end.x, 1.0));
}
ok &= expect(saw_m61_unload_spindle,
"expected LinuxCNC M61 Q0 to unload the spindle current tool number");
}
{
ScopedEnv spindle_tool_env("CNC_SIM_TOOL_IN_SPINDLE", "1");
const char t0_m6_unload_program[] =
"G21 G90 G17\n"
"T0 M6\n"
"O10 if [#5400 EQ 0]\n"
"G1 X1 F100\n"
"O10 endif\n"
"M30\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
t0_m6_unload_program,
sizeof(t0_m6_unload_program) - 1) == 0,
cnc_sim_last_error(sim));
bool saw_t0_m6_unload = false;
for (const auto &event : events) {
saw_t0_m6_unload = saw_t0_m6_unload ||
(event.type == CNC_SIM_EVENT_LINEAR_FEED &&
event.line == 4 &&
near(event.end.x, 1.0));
}
ok &= expect(saw_t0_m6_unload,
"expected LinuxCNC T0 M6 to unload the spindle current tool number");
}
const char spindle_program[] =
"G21 G90 G17\n"
"S1200 M3\n"
@@ -1443,6 +1557,26 @@ int main() {
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 table_dual_config[] =
"{\"backend\":\"linuxcnc-rs274\","
"\"config\":\"configs/sim/axis/vismach/5axis/table-dual-rotary/xyzab-tdr.ini\"}";
ok &= expect(cnc_sim_load_config_json(sim, table_dual_config, sizeof(table_dual_config) - 1) == 0,
cnc_sim_last_error(sim));
const char mixed_unremapped_m430_program[] =
"G21 G90\n"
"M428 M430\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
mixed_unremapped_m430_program,
sizeof(mixed_unremapped_m430_program) - 1) != 0,
"expected LinuxCNC source-backed config without 430remap.ngc to reject mixed M428/M430");
ok &= expect(std::string(cnc_sim_last_error(sim)) == "M-code greater than 199: M430",
"expected LinuxCNC source-backed mixed M428/M430 error to name unremapped M430");
ok &= expect(!saw_any_kinematics_switch(events, 2),
"expected LinuxCNC source-backed mixed M428/M430 error to emit no partial switch");
ok &= expect(cnc_sim_load_config_json(sim, config, sizeof(config) - 1) == 0,
cnc_sim_last_error(sim));
const char exponent_m428_control_program[] =
"G21 G90\n"
"M4.28e2\n";

View File

@@ -109,11 +109,92 @@ bool saw_comment_state(const std::vector<CncSimEvent> &events,
return false;
}
bool saw_kinematics_switch(const std::vector<CncSimEvent> &events, int line, int kinstype) {
for (const auto &event : events) {
if (event.type == CNC_SIM_EVENT_KINEMATICS_SWITCH &&
event.line == line &&
event.reserved == kinstype) {
return true;
}
}
return false;
}
bool saw_any_kinematics_switch(const std::vector<CncSimEvent> &events, int line) {
for (const auto &event : events) {
if (event.type == CNC_SIM_EVENT_KINEMATICS_SWITCH &&
event.line == line) {
return true;
}
}
return false;
}
bool expect_switchkins_remap_config(const std::string &json,
int m428_type,
int m429_type,
int m430_type,
const char *message) {
std::vector<CncSimEvent> events;
CncSimHandle *sim = cnc_sim_create();
cnc_sim_set_event_callback(sim, collect_event, &events);
const char supported_program[] =
"M428\n"
"M429\n";
const char full_program[] =
"M428\n"
"M429\n"
"M430\n";
const char *program = m430_type >= 0 ? full_program : supported_program;
const size_t program_len = m430_type >= 0 ? sizeof(full_program) - 1 : sizeof(supported_program) - 1;
bool ok = true;
ok &= expect(cnc_sim_load_config_json(sim, json.c_str(), json.size()) == 0, cnc_sim_last_error(sim));
ok &= expect(cnc_sim_parse_program(sim, program, program_len) == 0, cnc_sim_last_error(sim));
ok &= expect(saw_kinematics_switch(events, 1, m428_type), message);
ok &= expect(saw_kinematics_switch(events, 2, m429_type), message);
if (m430_type >= 0) {
ok &= expect(saw_kinematics_switch(events, 3, m430_type), message);
} else {
events.clear();
ok &= expect(cnc_sim_parse_program(sim, "M430\n", 5) != 0, message);
ok &= expect(std::string(cnc_sim_last_error(sim)) == "M-code greater than 199: M430", message);
ok &= expect(!saw_kinematics_switch(events, 1, 2), message);
std::vector<CncSimEvent> mixed_events;
CncSimHandle *mixed_sim = cnc_sim_create();
cnc_sim_set_event_callback(mixed_sim, collect_event, &mixed_events);
ok &= expect(cnc_sim_load_config_json(mixed_sim, json.c_str(), json.size()) == 0,
cnc_sim_last_error(mixed_sim));
ok &= expect(cnc_sim_parse_program(mixed_sim, "M428 M430\n", 9) != 0, message);
ok &= expect(std::string(cnc_sim_last_error(mixed_sim)) == "M-code greater than 199: M430", message);
ok &= expect(!saw_any_kinematics_switch(mixed_events, 1), message);
cnc_sim_destroy(mixed_sim);
}
cnc_sim_destroy(sim);
return ok;
}
} // namespace
int main() {
ScopedTempDir temp_dir("cnc_sim_api_smoke");
#ifndef CNC_SIM_ENABLE_LINUXCNC_RS274_BACKEND
{
CncSimHandle *default_sim = cnc_sim_create();
const char default_program[] = "G21\nM30\n";
const int default_rc = cnc_sim_parse_program(default_sim,
default_program,
sizeof(default_program) - 1);
const bool default_ok = expect(default_rc != 0,
"default LinuxCNC backend must not fall back to smoke parser");
cnc_sim_destroy(default_sim);
if (!default_ok) {
return 1;
}
}
#endif
const char program[] =
"G21 G90 G17\n"
"T1 M6\n"
@@ -1311,11 +1392,11 @@ int main() {
const char selected_current_tool_named_param_program[] =
"G21 G90 G17\n"
"T1\n"
"O10 if [[#<_selected_tool> EQ 1] AND [#<_current_tool> EQ 0]]\n"
"O10 if [[#<_selected_tool> EQ 1] AND [#<_current_tool> EQ 0] AND [#<_selected_pocket> EQ 1] AND [#<_current_pocket> EQ 0]]\n"
"G1 X1 F100\n"
"O10 endif\n"
"M6\n"
"O20 if [[#<_selected_tool> EQ 1] AND [#<_current_tool> EQ 0]]\n"
"O20 if [[#<_selected_tool> EQ 1] AND [#<_current_tool> EQ 0] AND [#<_selected_pocket> EQ 1] AND [#<_current_pocket> EQ 1]]\n"
"G1 Y1\n"
"O20 endif\n";
events.clear();
@@ -1336,14 +1417,14 @@ int main() {
near(event.end.y, 1.0));
}
ok &= expect(saw_selected_tool_before_change,
"expected LinuxCNC T word to update selected tool before current tool");
"expected LinuxCNC T word to update selected tool/pocket before current tool/pocket");
ok &= expect(saw_current_tool_after_change,
"expected LinuxCNC M6 to keep current tool tied to #5400 in the nonrandom fixture");
"expected LinuxCNC M6 to keep current tool tied to #5400 while updating current pocket in the nonrandom fixture");
const char m61_tool_named_param_program[] =
"G21 G90 G17\n"
"M61 Q1\n"
"O10 if [[#<_selected_tool> EQ 0] AND [#<_current_tool> EQ 0]]\n"
"O10 if [[#<_selected_tool> EQ 0] AND [#<_current_tool> EQ 0] AND [#<_selected_pocket> EQ -1] AND [#<_current_pocket> EQ 1]]\n"
"G1 X1 F100\n"
"O10 endif\n";
events.clear();
@@ -1359,7 +1440,7 @@ int main() {
near(event.end.x, 1.0));
}
ok &= expect(saw_m61_tool_named_params,
"expected LinuxCNC M61 to leave selected/current tool named parameters unchanged in nonrandom fixture");
"expected LinuxCNC M61 to update current pocket while leaving selected/current tool named parameters unchanged in nonrandom fixture");
const char dynamic_tool_length_program[] =
"G21 G90 G17\n"
@@ -5404,7 +5485,7 @@ int main() {
const char spindle_tool_info_parameter_program[] =
"G21 G90 G17\n"
"F100\n"
"O10 if [[#5400 EQ 1] AND [#5410 EQ 6] AND [#5411 EQ 12] AND [#5412 EQ 34] AND [#5413 EQ 5]]\n"
"O10 if [[#5400 EQ 1] AND [#5410 EQ 6] AND [#5411 EQ 12] AND [#5412 EQ 34] AND [#5413 EQ 5] AND [#<_current_pocket> EQ 0] AND [#<_selected_pocket> EQ -1]]\n"
"G1 X1\n"
"O10 endif\n";
events.clear();
@@ -5420,7 +5501,57 @@ int main() {
near(event.end.x, 1.0));
}
ok &= expect(saw_spindle_tool_info_parameter,
"expected LinuxCNC numbered tool info parameters for preloaded spindle tool");
"expected LinuxCNC numbered tool info parameters and pocket named parameters for preloaded spindle tool");
}
{
ScopedEnv spindle_tool_env("CNC_SIM_TOOL_IN_SPINDLE", "1");
const char m61_unload_spindle_program[] =
"G21 G90 G17\n"
"M61 Q0\n"
"O10 if [#5400 EQ 0]\n"
"G1 X1 F100\n"
"O10 endif\n"
"M30\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
m61_unload_spindle_program,
sizeof(m61_unload_spindle_program) - 1) == 0,
cnc_sim_last_error(sim));
bool saw_m61_unload_spindle = false;
for (const auto &event : events) {
saw_m61_unload_spindle = saw_m61_unload_spindle ||
(event.type == CNC_SIM_EVENT_LINEAR_FEED &&
event.line == 4 &&
near(event.end.x, 1.0));
}
ok &= expect(saw_m61_unload_spindle,
"expected LinuxCNC M61 Q0 to unload the spindle current tool number");
}
{
ScopedEnv spindle_tool_env("CNC_SIM_TOOL_IN_SPINDLE", "1");
const char t0_m6_unload_program[] =
"G21 G90 G17\n"
"T0 M6\n"
"O10 if [#5400 EQ 0]\n"
"G1 X1 F100\n"
"O10 endif\n"
"M30\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
t0_m6_unload_program,
sizeof(t0_m6_unload_program) - 1) == 0,
cnc_sim_last_error(sim));
bool saw_t0_m6_unload = false;
for (const auto &event : events) {
saw_t0_m6_unload = saw_t0_m6_unload ||
(event.type == CNC_SIM_EVENT_LINEAR_FEED &&
event.line == 4 &&
near(event.end.x, 1.0));
}
ok &= expect(saw_t0_m6_unload,
"expected LinuxCNC T0 M6 to unload the spindle current tool number");
}
const char g28_g30_numbered_parameter_program[] =
@@ -6210,7 +6341,8 @@ int main() {
const std::string ini_path = temp_dir.file("named_ini.ini");
{
std::ofstream ini(ini_path);
ini << "[SETUP]\nXPOS = 3.25\n";
ini << "[TRAJ]\nLINEAR_UNITS = inch\n"
<< "[SETUP]\nXPOS = 3.25\n";
}
{
ScopedEnv ini_env("INI_FILE_NAME", ini_path.c_str());
@@ -6236,6 +6368,25 @@ int main() {
event.line == 5 &&
event.end.y == 1.0);
}
const char metric_machine_named_parameter_program[] =
"G21 G90\n"
"F100\n"
"G1 X[2 + #<_metric_machine>]\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
metric_machine_named_parameter_program,
sizeof(metric_machine_named_parameter_program) - 1) == 0,
cnc_sim_last_error(sim));
bool saw_inch_metric_machine_named_parameter = false;
for (const auto &event : events) {
saw_inch_metric_machine_named_parameter = saw_inch_metric_machine_named_parameter ||
(event.type == CNC_SIM_EVENT_LINEAR_FEED &&
event.line == 3 &&
near(event.end.x, 2.0));
}
ok &= expect(saw_inch_metric_machine_named_parameter,
"expected #<_metric_machine> to reflect inch INI LINEAR_UNITS");
}
ok &= expect(saw_ini_named_parameter_motion, "expected #<_ini[section]key> named parameter lookup");
ok &= expect(saw_ini_named_parameter_exists_motion, "expected EXISTS for #<_ini[section]key>");
@@ -9881,6 +10032,16 @@ int main() {
sizeof(missing_named_parameter_program) - 1) != 0,
"expected undefined named parameter read to fail");
const char missing_abs_uvw_named_parameter_program[] =
"G21 G90\n"
"F100\n"
"G1 X#<_abs_u>\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
missing_abs_uvw_named_parameter_program,
sizeof(missing_abs_uvw_named_parameter_program) - 1) != 0,
"expected LinuxCNC to reject undefined _abs_u named parameter");
const char indexed_parameter_program[] =
"#2 = 3\n"
"#3 = 11\n"
@@ -10832,6 +10993,407 @@ int main() {
ok &= expect(config_rc == 0, cnc_sim_last_error(sim));
ok &= expect(rc != 0, "expected uncompiled linuxcnc-rs274 backend to fail clearly");
{
std::vector<CncSimEvent> scara_events;
CncSimHandle *scara_sim = cnc_sim_create();
cnc_sim_set_event_callback(scara_sim, collect_event, &scara_events);
const char scara_config[] = "{\"backend\":\"smoke\",\"switchkins\":\"scara\"}";
const char scara_program[] =
"M428\n"
"M429\n"
"M430\n";
ok &= expect(cnc_sim_load_config_json(scara_sim, scara_config, sizeof(scara_config) - 1) == 0,
cnc_sim_last_error(scara_sim));
ok &= expect(cnc_sim_parse_program(scara_sim, scara_program, sizeof(scara_program) - 1) == 0,
cnc_sim_last_error(scara_sim));
bool saw_scara_m428 = false;
bool saw_scara_m429 = false;
bool saw_scara_m430 = false;
for (const auto &event : scara_events) {
saw_scara_m428 = saw_scara_m428 ||
(event.type == CNC_SIM_EVENT_KINEMATICS_SWITCH &&
event.line == 1 &&
event.reserved == 0);
saw_scara_m429 = saw_scara_m429 ||
(event.type == CNC_SIM_EVENT_KINEMATICS_SWITCH &&
event.line == 2 &&
event.reserved == 1);
saw_scara_m430 = saw_scara_m430 ||
(event.type == CNC_SIM_EVENT_KINEMATICS_SWITCH &&
event.line == 3 &&
event.reserved == 2);
}
ok &= expect(saw_scara_m428, "expected scara M428 remap to select kinstype 0");
ok &= expect(saw_scara_m429, "expected scara M429 remap to select kinstype 1");
ok &= expect(saw_scara_m430, "expected scara M430 remap to select kinstype 2");
cnc_sim_destroy(scara_sim);
}
{
std::vector<CncSimEvent> table_dual_events;
CncSimHandle *table_dual_sim = cnc_sim_create();
cnc_sim_set_event_callback(table_dual_sim, collect_event, &table_dual_events);
const char table_dual_config[] = "{\"backend\":\"smoke\",\"switchkins\":\"table-dual-rotary\"}";
const char table_dual_program[] =
"M428\n"
"M429\n";
ok &= expect(cnc_sim_load_config_json(table_dual_sim,
table_dual_config,
sizeof(table_dual_config) - 1) == 0,
cnc_sim_last_error(table_dual_sim));
ok &= expect(cnc_sim_parse_program(table_dual_sim,
table_dual_program,
sizeof(table_dual_program) - 1) == 0,
cnc_sim_last_error(table_dual_sim));
bool saw_table_dual_m428 = false;
bool saw_table_dual_m429 = false;
for (const auto &event : table_dual_events) {
saw_table_dual_m428 = saw_table_dual_m428 ||
(event.type == CNC_SIM_EVENT_KINEMATICS_SWITCH &&
event.line == 1 &&
event.reserved == 1);
saw_table_dual_m429 = saw_table_dual_m429 ||
(event.type == CNC_SIM_EVENT_KINEMATICS_SWITCH &&
event.line == 2 &&
event.reserved == 0);
}
ok &= expect(saw_table_dual_m428, "expected table-dual-rotary M428 remap to select kinstype 1");
ok &= expect(saw_table_dual_m429, "expected table-dual-rotary M429 remap to select kinstype 0");
cnc_sim_destroy(table_dual_sim);
}
{
std::vector<CncSimEvent> xyzab_tdr_events;
CncSimHandle *xyzab_tdr_sim = cnc_sim_create();
cnc_sim_set_event_callback(xyzab_tdr_sim, collect_event, &xyzab_tdr_events);
const char xyzab_tdr_config[] = "{\"backend\":\"smoke\",\"switchkins\":\"sim-xyzab-tdr-kins\"}";
const char xyzab_tdr_program[] =
"M428\n"
"M429\n";
ok &= expect(cnc_sim_load_config_json(xyzab_tdr_sim,
xyzab_tdr_config,
sizeof(xyzab_tdr_config) - 1) == 0,
cnc_sim_last_error(xyzab_tdr_sim));
ok &= expect(cnc_sim_parse_program(xyzab_tdr_sim,
xyzab_tdr_program,
sizeof(xyzab_tdr_program) - 1) == 0,
cnc_sim_last_error(xyzab_tdr_sim));
bool saw_xyzab_tdr_m428 = false;
bool saw_xyzab_tdr_m429 = false;
for (const auto &event : xyzab_tdr_events) {
saw_xyzab_tdr_m428 = saw_xyzab_tdr_m428 ||
(event.type == CNC_SIM_EVENT_KINEMATICS_SWITCH &&
event.line == 1 &&
event.reserved == 1);
saw_xyzab_tdr_m429 = saw_xyzab_tdr_m429 ||
(event.type == CNC_SIM_EVENT_KINEMATICS_SWITCH &&
event.line == 2 &&
event.reserved == 0);
}
ok &= expect(saw_xyzab_tdr_m428, "expected sim-xyzab-tdr-kins M428 remap to select kinstype 1");
ok &= expect(saw_xyzab_tdr_m429, "expected sim-xyzab-tdr-kins M429 remap to select kinstype 0");
cnc_sim_destroy(xyzab_tdr_sim);
}
{
std::vector<CncSimEvent> puma_events;
CncSimHandle *puma_sim = cnc_sim_create();
cnc_sim_set_event_callback(puma_sim, collect_event, &puma_events);
const char puma_config[] = "{\"backend\":\"smoke\",\"switchkins\":\"puma\"}";
const char puma_program[] =
"M428\n"
"M429\n"
"M430\n";
ok &= expect(cnc_sim_load_config_json(puma_sim, puma_config, sizeof(puma_config) - 1) == 0,
cnc_sim_last_error(puma_sim));
ok &= expect(cnc_sim_parse_program(puma_sim, puma_program, sizeof(puma_program) - 1) == 0,
cnc_sim_last_error(puma_sim));
bool saw_puma_m428 = false;
bool saw_puma_m429 = false;
bool saw_puma_m430 = false;
for (const auto &event : puma_events) {
saw_puma_m428 = saw_puma_m428 ||
(event.type == CNC_SIM_EVENT_KINEMATICS_SWITCH &&
event.line == 1 &&
event.reserved == 0);
saw_puma_m429 = saw_puma_m429 ||
(event.type == CNC_SIM_EVENT_KINEMATICS_SWITCH &&
event.line == 2 &&
event.reserved == 1);
saw_puma_m430 = saw_puma_m430 ||
(event.type == CNC_SIM_EVENT_KINEMATICS_SWITCH &&
event.line == 3 &&
event.reserved == 2);
}
ok &= expect(saw_puma_m428, "expected puma M428 remap to select kinstype 0");
ok &= expect(saw_puma_m429, "expected puma M429 remap to select kinstype 1");
ok &= expect(saw_puma_m430, "expected puma M430 remap to select kinstype 2");
cnc_sim_destroy(puma_sim);
}
{
std::vector<CncSimEvent> melfa_events;
CncSimHandle *melfa_sim = cnc_sim_create();
cnc_sim_set_event_callback(melfa_sim, collect_event, &melfa_events);
const char melfa_config[] = "{\"backend\":\"smoke\",\"switchkins\":\"melfa-sim\"}";
const char melfa_program[] =
"M428\n"
"M429\n"
"M430\n";
ok &= expect(cnc_sim_load_config_json(melfa_sim, melfa_config, sizeof(melfa_config) - 1) == 0,
cnc_sim_last_error(melfa_sim));
ok &= expect(cnc_sim_parse_program(melfa_sim, melfa_program, sizeof(melfa_program) - 1) == 0,
cnc_sim_last_error(melfa_sim));
bool saw_melfa_m428 = false;
bool saw_melfa_m429 = false;
bool saw_melfa_m430 = false;
for (const auto &event : melfa_events) {
saw_melfa_m428 = saw_melfa_m428 ||
(event.type == CNC_SIM_EVENT_KINEMATICS_SWITCH &&
event.line == 1 &&
event.reserved == 0);
saw_melfa_m429 = saw_melfa_m429 ||
(event.type == CNC_SIM_EVENT_KINEMATICS_SWITCH &&
event.line == 2 &&
event.reserved == 1);
saw_melfa_m430 = saw_melfa_m430 ||
(event.type == CNC_SIM_EVENT_KINEMATICS_SWITCH &&
event.line == 3 &&
event.reserved == 2);
}
ok &= expect(saw_melfa_m428, "expected melfa-sim M428 remap to select kinstype 0");
ok &= expect(saw_melfa_m429, "expected melfa-sim M429 remap to select kinstype 1");
ok &= expect(saw_melfa_m430, "expected melfa-sim M430 remap to select kinstype 2");
cnc_sim_destroy(melfa_sim);
}
{
std::vector<CncSimEvent> hexapod_events;
CncSimHandle *hexapod_sim = cnc_sim_create();
cnc_sim_set_event_callback(hexapod_sim, collect_event, &hexapod_events);
const char hexapod_config[] = "{\"backend\":\"smoke\",\"remap\":\"hexapod-sim\"}";
const char hexapod_program[] =
"M428\n"
"M429\n"
"M430\n";
ok &= expect(cnc_sim_load_config_json(hexapod_sim, hexapod_config, sizeof(hexapod_config) - 1) == 0,
cnc_sim_last_error(hexapod_sim));
ok &= expect(cnc_sim_parse_program(hexapod_sim, hexapod_program, sizeof(hexapod_program) - 1) == 0,
cnc_sim_last_error(hexapod_sim));
bool saw_hexapod_m428 = false;
bool saw_hexapod_m429 = false;
bool saw_hexapod_m430 = false;
for (const auto &event : hexapod_events) {
saw_hexapod_m428 = saw_hexapod_m428 ||
(event.type == CNC_SIM_EVENT_KINEMATICS_SWITCH &&
event.line == 1 &&
event.reserved == 0);
saw_hexapod_m429 = saw_hexapod_m429 ||
(event.type == CNC_SIM_EVENT_KINEMATICS_SWITCH &&
event.line == 2 &&
event.reserved == 1);
saw_hexapod_m430 = saw_hexapod_m430 ||
(event.type == CNC_SIM_EVENT_KINEMATICS_SWITCH &&
event.line == 3 &&
event.reserved == 2);
}
ok &= expect(saw_hexapod_m428, "expected hexapod-sim M428 remap to select kinstype 0");
ok &= expect(saw_hexapod_m429, "expected hexapod-sim M429 remap to select kinstype 1");
ok &= expect(saw_hexapod_m430, "expected hexapod-sim M430 remap to select kinstype 2");
cnc_sim_destroy(hexapod_sim);
}
{
std::vector<CncSimEvent> trsrn_events;
CncSimHandle *trsrn_sim = cnc_sim_create();
cnc_sim_set_event_callback(trsrn_sim, collect_event, &trsrn_events);
const char trsrn_config[] = "{\"backend\":\"smoke\",\"switchkins\":\"xyzacb-trsrn\"}";
const char trsrn_program[] =
"M428\n"
"M429\n"
"M430\n";
ok &= expect(cnc_sim_load_config_json(trsrn_sim, trsrn_config, sizeof(trsrn_config) - 1) == 0,
cnc_sim_last_error(trsrn_sim));
ok &= expect(cnc_sim_parse_program(trsrn_sim, trsrn_program, sizeof(trsrn_program) - 1) == 0,
cnc_sim_last_error(trsrn_sim));
bool saw_trsrn_m428 = false;
bool saw_trsrn_m429 = false;
bool saw_trsrn_m430 = false;
for (const auto &event : trsrn_events) {
saw_trsrn_m428 = saw_trsrn_m428 ||
(event.type == CNC_SIM_EVENT_KINEMATICS_SWITCH &&
event.line == 1 &&
event.reserved == 0);
saw_trsrn_m429 = saw_trsrn_m429 ||
(event.type == CNC_SIM_EVENT_KINEMATICS_SWITCH &&
event.line == 2 &&
event.reserved == 1);
saw_trsrn_m430 = saw_trsrn_m430 ||
(event.type == CNC_SIM_EVENT_KINEMATICS_SWITCH &&
event.line == 3 &&
event.reserved == 2);
}
ok &= expect(saw_trsrn_m428, "expected xyzacb-trsrn M428 remap to select kinstype 0");
ok &= expect(saw_trsrn_m429, "expected xyzacb-trsrn M429 remap to select kinstype 1");
ok &= expect(saw_trsrn_m430, "expected xyzacb-trsrn M430 remap to select kinstype 2");
cnc_sim_destroy(trsrn_sim);
}
{
std::vector<CncSimEvent> machine_events;
CncSimHandle *machine_sim = cnc_sim_create();
cnc_sim_set_event_callback(machine_sim, collect_event, &machine_events);
const char machine_config[] = "{\"backend\":\"smoke\",\"machine\":\"sim-xyzac-trt-kins (switchkins)\"}";
const char machine_program[] =
"M428\n"
"M429\n"
"M430\n";
ok &= expect(cnc_sim_load_config_json(machine_sim, machine_config, sizeof(machine_config) - 1) == 0,
cnc_sim_last_error(machine_sim));
ok &= expect(cnc_sim_parse_program(machine_sim, machine_program, sizeof(machine_program) - 1) == 0,
cnc_sim_last_error(machine_sim));
ok &= expect(saw_kinematics_switch(machine_events, 1, 1),
"expected LinuxCNC xyzac-trt MACHINE config M428 remap to select kinstype 1");
ok &= expect(saw_kinematics_switch(machine_events, 2, 0),
"expected LinuxCNC xyzac-trt MACHINE config M429 remap to select kinstype 0");
ok &= expect(saw_kinematics_switch(machine_events, 3, 2),
"expected LinuxCNC xyzac-trt MACHINE config M430 remap to select kinstype 2");
cnc_sim_destroy(machine_sim);
}
{
std::vector<CncSimEvent> kinematics_events;
CncSimHandle *kinematics_sim = cnc_sim_create();
cnc_sim_set_event_callback(kinematics_sim, collect_event, &kinematics_events);
const char kinematics_config[] = "{\"backend\":\"smoke\",\"kinematics\":\"genhexkins\"}";
const char kinematics_program[] =
"M428\n"
"M429\n"
"M430\n";
ok &= expect(cnc_sim_load_config_json(kinematics_sim,
kinematics_config,
sizeof(kinematics_config) - 1) == 0,
cnc_sim_last_error(kinematics_sim));
ok &= expect(cnc_sim_parse_program(kinematics_sim,
kinematics_program,
sizeof(kinematics_program) - 1) == 0,
cnc_sim_last_error(kinematics_sim));
ok &= expect(saw_kinematics_switch(kinematics_events, 1, 0),
"expected LinuxCNC genhexkins KINEMATICS config M428 remap to select kinstype 0");
ok &= expect(saw_kinematics_switch(kinematics_events, 2, 1),
"expected LinuxCNC genhexkins KINEMATICS config M429 remap to select kinstype 1");
ok &= expect(saw_kinematics_switch(kinematics_events, 3, 2),
"expected LinuxCNC genhexkins KINEMATICS config M430 remap to select kinstype 2");
cnc_sim_destroy(kinematics_sim);
}
{
std::vector<CncSimEvent> config_path_events;
CncSimHandle *config_path_sim = cnc_sim_create();
cnc_sim_set_event_callback(config_path_sim, collect_event, &config_path_events);
const char config_path_config[] =
"{\"backend\":\"smoke\","
"\"config\":\"configs/sim/axis/vismach/5axis/table-dual-rotary/xyzab-tdr.ini\"}";
const char config_path_program[] =
"M428\n"
"M429\n";
ok &= expect(cnc_sim_load_config_json(config_path_sim,
config_path_config,
sizeof(config_path_config) - 1) == 0,
cnc_sim_last_error(config_path_sim));
ok &= expect(cnc_sim_parse_program(config_path_sim,
config_path_program,
sizeof(config_path_program) - 1) == 0,
cnc_sim_last_error(config_path_sim));
ok &= expect(saw_kinematics_switch(config_path_events, 1, 1),
"expected LinuxCNC xyzab-tdr config path M428 remap to select kinstype 1");
ok &= expect(saw_kinematics_switch(config_path_events, 2, 0),
"expected LinuxCNC xyzab-tdr config path M429 remap to select kinstype 0");
cnc_sim_destroy(config_path_sim);
}
{
struct SourceBackedConfigCase {
const char *field;
const char *value;
int m428_type;
int m429_type;
int m430_type;
};
const SourceBackedConfigCase source_cases[] = {
{"config", "configs/sim/axis/vismach/5axis/bridgemill/5axis.ini", 0, 1, 2},
{"config", "configs/sim/axis/vismach/5axis/table-dual-rotary/xyzab-tdr.ini", 1, 0, -1},
{"config", "configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini", 1, 0, 2},
{"config", "configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.ini", 1, 0, 2},
{"config",
"configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/xyzacb-trsrn_twp/xyzacb-trsrn.ini",
0,
1,
2},
{"config",
"configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/xyzbca-trsrn_twp/xyzbca-trsrn.ini",
0,
1,
2},
{"config", "configs/sim/axis/vismach/hexapod-sim/hexapod.ini", 0, 1, 2},
{"config", "configs/sim/axis/vismach/melfa-sim/melfa.ini", 0, 1, 2},
{"config", "configs/sim/axis/vismach/millturn/millturn.ini", 0, 1, -1},
{"config", "configs/sim/axis/vismach/puma/puma.ini", 0, 1, 2},
{"config", "configs/sim/axis/vismach/puma/puma_cube.ini", 0, 1, 2},
{"config", "configs/sim/axis/vismach/puma/puma560.ini", 0, 1, 2},
{"config", "configs/sim/axis/vismach/puma/puma560_uvw.ini", 0, 1, 2},
{"config", "configs/sim/axis/vismach/scara/scara.ini", 0, 1, 2},
{"config", "configs/sim/qtaxis/non-trivial/scara/scara.ini", 0, 1, 2},
{"config", "configs/sim/qtvcp_screens/non-trivial/scara/scara.ini", 0, 1, 2},
{"machine", "Sim-5Axis Bridge Mill (xyzbcw)", 0, 1, 2},
{"machine", "sim-xyzab-tdr-kins (switchkins)", 1, 0, -1},
{"machine", "sim-xyzac-trt-kins (switchkins)", 1, 0, 2},
{"machine", "sim-xyzbc-trt-kins (switchkins)", 1, 0, 2},
{"machine", "xyzacb-trsrn (switchkins)", 0, 1, 2},
{"machine", "xyzbca-trsrn (switchkins)", 0, 1, 2},
{"machine", "hexapod (switchkins)", 0, 1, 2},
{"machine", "melfa (mm)", 0, 1, 2},
{"machine", "millturn (mm)", 0, 1, -1},
{"machine", "PUMA (pumakins,switchkins)", 0, 1, 2},
{"machine", "puma_cube.ini (pumakins)", 0, 1, 2},
{"machine", "puma560 (switchkins) (inch)", 0, 1, 2},
{"machine", "SCARA (genserkins,switchkins)", 0, 1, 2},
{"kinematics", "5axiskins", 0, 1, 2},
{"kinematics", "xyzab_tdr_kins", 1, 0, -1},
{"kinematics", "xyzac-trt-kins sparm=identityfirst", 1, 0, 2},
{"kinematics", "xyzbc-trt-kins sparm=identityfirst", 1, 0, 2},
{"kinematics", "xyzacb_trsrn", 0, 1, 2},
{"kinematics", "xyzbca_trsrn", 0, 1, 2},
{"kinematics", "genhexkins", 0, 1, 2},
{"kinematics", "genserkins", 0, 1, 2},
{"kinematics", "millturn", 0, 1, -1},
{"kinematics", "pumakins", 0, 1, 2},
{"kinematics", "scarakins coordinates=xyzcab", 0, 1, 2},
{"halfile", "5axisgui.hal", 0, 1, 2},
{"halfile", "5axisgui", 0, 1, 2},
{"halfile", "kinematics.hal", 0, 1, 2},
{"hal_file", "melfa_dh.hal", 0, 1, 2},
{"hal_file", "melfa_dh", 0, 1, 2},
{"hal_file", "millturn.hal", 0, 1, -1},
{"halfile", "puma_dh.hal", 0, 1, 2},
{"halfile", "puma_dh", 0, 1, 2},
{"halfile", "puma560_dh.hal", 0, 1, 2},
{"halfile", "puma560_dh", 0, 1, 2},
};
for (const SourceBackedConfigCase &source_case : source_cases) {
const std::string json = std::string("{\"backend\":\"smoke\",\"") +
source_case.field +
"\":\"" +
source_case.value +
"\"}";
ok &= expect_switchkins_remap_config(json,
source_case.m428_type,
source_case.m429_type,
source_case.m430_type,
"expected LinuxCNC source-backed switchkins remap config");
}
}
cnc_sim_destroy(sim);
return ok ? 0 : 1;
}

View File

@@ -1,5 +1,8 @@
#include "rtcp_kinematics.h"
#include "linuxcnc_cubic_adapter.h"
#include "linuxcnc_xyzab_tdr_kins_adapter.h"
#include <cmath>
#include <iostream>
@@ -63,6 +66,48 @@ bool expect_axis_joints_near(const LinuxCncAxisJoints &actual,
return ok;
}
bool expect_genhex_joints_near(const LinuxCncGenhexJoints &actual,
const LinuxCncGenhexJoints &expected,
const char *message) {
bool ok = true;
for (int i = 0; i < 6; ++i) {
ok &= expect_near_eps(actual.strut[i], expected.strut[i], 1e-6, message);
}
return ok;
}
bool expect_pentakins_joints_near(const LinuxCncPentakinsJoints &actual,
const LinuxCncPentakinsJoints &expected,
const char *message) {
bool ok = true;
for (int i = 0; i < 5; ++i) {
ok &= expect_near_eps(actual.strut[i], expected.strut[i], 1e-6, message);
}
return ok;
}
bool expect_tripod_joints_near(const LinuxCncTripodJoints &actual,
const LinuxCncTripodJoints &expected,
const char *message) {
bool ok = true;
for (int i = 0; i < 3; ++i) {
ok &= expect_near_eps(actual.strut[i], expected.strut[i], 1e-9, message);
}
return ok;
}
bool expect_scorbot_joints_near(const LinuxCncScorbotJoints &actual,
const LinuxCncScorbotJoints &expected,
const char *message) {
bool ok = true;
ok &= expect_near_eps(actual.j0, expected.j0, 1e-9, message);
ok &= expect_near_eps(actual.j1, expected.j1, 1e-9, message);
ok &= expect_near_eps(actual.j2, expected.j2, 1e-9, message);
ok &= expect_near_eps(actual.j3, expected.j3, 1e-9, message);
ok &= expect_near_eps(actual.j4, expected.j4, 1e-9, message);
return ok;
}
} // namespace
int main() {
@@ -86,6 +131,46 @@ int main() {
axis_joints,
"identity/trivkins forward/inverse roundtrip");
LinuxCncCubicSample cubic_samples[4]{};
ok &= expect(linuxcnc_cubic_source_second_segment_samples(cubic_samples, 4),
"expected LinuxCNC cubic.c second segment sample generation");
ok &= expect_near(cubic_samples[0].x, 0.0, "cubic second segment sample0 x");
ok &= expect_near(cubic_samples[0].v, 0.0, "cubic second segment sample0 v");
ok &= expect_near(cubic_samples[0].a, 0.0, "cubic second segment sample0 a");
ok &= expect_near(cubic_samples[0].j, 10.0, "cubic second segment sample0 j");
ok &= expect_near_eps(cubic_samples[1].x, 0.026041666666666668, 1e-12, "cubic second segment sample1 x");
ok &= expect_near(cubic_samples[1].v, 0.3125, "cubic second segment sample1 v");
ok &= expect_near(cubic_samples[1].a, 2.5, "cubic second segment sample1 a");
ok &= expect_near(cubic_samples[1].j, 10.0, "cubic second segment sample1 j");
ok &= expect_near_eps(cubic_samples[2].x, 0.20833333333333334, 1e-12, "cubic second segment sample2 x");
ok &= expect_near(cubic_samples[2].v, 1.25, "cubic second segment sample2 v");
ok &= expect_near(cubic_samples[2].a, 5.0, "cubic second segment sample2 a");
ok &= expect_near_eps(cubic_samples[3].x, 0.703125, 1e-12, "cubic second segment sample3 x");
ok &= expect_near(cubic_samples[3].v, 2.8125, "cubic second segment sample3 v");
ok &= expect_near(cubic_samples[3].a, 7.5, "cubic second segment sample3 a");
ok &= expect(linuxcnc_cubic_source_rejects_invalid_configuration(),
"expected LinuxCNC cubic.c to reject invalid configuration");
axis_joints = {};
axis_joints.x = 12.0;
axis_joints.y = 4.0;
axis_joints.z = 3.0;
axis_joints.a = 10.0;
axis_joints.b = 20.0;
axis_joints.c = 30.0;
axis_joints.u = 7.0;
axis_joints.v = 8.0;
axis_joints.w = 9.0;
CncSimPose corexy_pose = linuxcnc_corexykins_forward(axis_joints);
ok &= expect_near(corexy_pose.x, 8.0, "corexykins forward x");
ok &= expect_near(corexy_pose.y, 4.0, "corexykins forward y");
ok &= expect_near(corexy_pose.z, 3.0, "corexykins forward z");
ok &= expect_near(corexy_pose.a, 10.0, "corexykins forward a");
ok &= expect_near(corexy_pose.w, 9.0, "corexykins forward w");
ok &= expect_axis_joints_near(linuxcnc_corexykins_inverse(corexy_pose),
axis_joints,
"corexykins forward/inverse roundtrip");
axis_joints = {};
axis_joints.x = 10.0;
axis_joints.y = 0.0;
@@ -104,6 +189,173 @@ int main() {
axis_joints,
"rotatekins forward/inverse roundtrip");
CncSimPose genhex_pose{};
genhex_pose.x = 1.25;
genhex_pose.y = -2.5;
genhex_pose.z = 3.75;
genhex_pose.a = 4.0;
genhex_pose.b = -5.0;
genhex_pose.c = 6.0;
const LinuxCncGenhexJoints genhex_joints = linuxcnc_genhex_inverse(genhex_pose);
CncSimPose genhex_roundtrip_pose = linuxcnc_genhex_forward(genhex_joints, genhex_pose);
ok &= expect_near_eps(genhex_roundtrip_pose.x, genhex_pose.x, 1e-6, "genhexkins forward/inverse roundtrip x");
ok &= expect_near_eps(genhex_roundtrip_pose.y, genhex_pose.y, 1e-6, "genhexkins forward/inverse roundtrip y");
ok &= expect_near_eps(genhex_roundtrip_pose.z, genhex_pose.z, 1e-6, "genhexkins forward/inverse roundtrip z");
ok &= expect_near_eps(genhex_roundtrip_pose.a, genhex_pose.a, 1e-6, "genhexkins forward/inverse roundtrip a");
ok &= expect_near_eps(genhex_roundtrip_pose.b, genhex_pose.b, 1e-6, "genhexkins forward/inverse roundtrip b");
ok &= expect_near_eps(genhex_roundtrip_pose.c, genhex_pose.c, 1e-6, "genhexkins forward/inverse roundtrip c");
ok &= expect_genhex_joints_near(linuxcnc_genhex_inverse(genhex_roundtrip_pose),
genhex_joints,
"genhexkins inverse deterministic strut lengths");
CncSimPose penta_pose{};
penta_pose.x = 1.25;
penta_pose.y = -2.5;
penta_pose.z = 3.75;
penta_pose.a = 4.0;
penta_pose.b = -5.0;
const LinuxCncPentakinsJoints penta_joints = linuxcnc_pentakins_inverse(penta_pose);
CncSimPose penta_roundtrip_pose = linuxcnc_pentakins_forward(penta_joints, penta_pose);
ok &= expect_near_eps(penta_roundtrip_pose.x, penta_pose.x, 1e-6, "pentakins forward/inverse roundtrip x");
ok &= expect_near_eps(penta_roundtrip_pose.y, penta_pose.y, 1e-6, "pentakins forward/inverse roundtrip y");
ok &= expect_near_eps(penta_roundtrip_pose.z, penta_pose.z, 1e-6, "pentakins forward/inverse roundtrip z");
ok &= expect_near_eps(penta_roundtrip_pose.a, penta_pose.a, 1e-6, "pentakins forward/inverse roundtrip a");
ok &= expect_near_eps(penta_roundtrip_pose.b, penta_pose.b, 1e-6, "pentakins forward/inverse roundtrip b");
ok &= expect_pentakins_joints_near(linuxcnc_pentakins_inverse(penta_roundtrip_pose),
penta_joints,
"pentakins inverse deterministic strut lengths");
CncSimPose tripod_pose{};
tripod_pose.x = 0.2;
tripod_pose.y = 0.3;
tripod_pose.z = 0.4;
const LinuxCncTripodJoints tripod_joints = linuxcnc_tripodkins_inverse(tripod_pose);
const CncSimPose tripod_roundtrip_pose = linuxcnc_tripodkins_forward(tripod_joints);
ok &= expect_near_eps(tripod_roundtrip_pose.x, tripod_pose.x, 1e-12, "tripodkins forward/inverse roundtrip x");
ok &= expect_near_eps(tripod_roundtrip_pose.y, tripod_pose.y, 1e-12, "tripodkins forward/inverse roundtrip y");
ok &= expect_near_eps(tripod_roundtrip_pose.z, tripod_pose.z, 1e-12, "tripodkins forward/inverse roundtrip z");
ok &= expect_near(tripod_roundtrip_pose.a, 0.0, "tripodkins forward clears a");
ok &= expect_tripod_joints_near(linuxcnc_tripodkins_inverse(tripod_roundtrip_pose),
tripod_joints,
"tripodkins inverse deterministic strut lengths");
LinuxCncScorbotJoints scorbot_joints{};
scorbot_joints.j0 = 30.0;
scorbot_joints.j1 = 20.0;
scorbot_joints.j2 = -10.0;
scorbot_joints.j3 = 7.0;
scorbot_joints.j4 = -8.0;
const CncSimPose scorbot_pose = linuxcnc_scorbot_kins_forward(scorbot_joints);
ok &= expect_near_eps(scorbot_pose.x, 382.1896396002526, 1e-9, "scorbot-kins forward x");
ok &= expect_near_eps(scorbot_pose.y, 220.65729130469182, 1e-9, "scorbot-kins forward y");
ok &= expect_near_eps(scorbot_pose.z, 177.2102044105812, 1e-9, "scorbot-kins forward z");
ok &= expect_near(scorbot_pose.a, scorbot_joints.j3, "scorbot-kins forward wrist roll");
ok &= expect_near(scorbot_pose.b, scorbot_joints.j4, "scorbot-kins forward wrist pitch");
ok &= expect_scorbot_joints_near(linuxcnc_scorbot_kins_inverse(scorbot_pose),
scorbot_joints,
"scorbot-kins forward/inverse roundtrip");
const LinuxCncLinearDeltaParameters lineardelta_parameters = linuxcnc_lineardelta_default_parameters();
ok &= expect_near(lineardelta_parameters.r, 130.25, "lineardeltakins default R");
ok &= expect_near(lineardelta_parameters.l, 269.0, "lineardeltakins default L");
CncSimPose lineardelta_pose{};
lineardelta_pose.x = 1.25;
lineardelta_pose.y = -2.5;
lineardelta_pose.z = -20.0;
lineardelta_pose.a = 4.0;
lineardelta_pose.b = 5.0;
lineardelta_pose.c = 6.0;
lineardelta_pose.u = 7.0;
lineardelta_pose.v = 8.0;
lineardelta_pose.w = 9.0;
const LinuxCncAxisJoints lineardelta_joints =
linuxcnc_lineardelta_inverse(lineardelta_pose, lineardelta_parameters);
const CncSimPose lineardelta_roundtrip_pose =
linuxcnc_lineardelta_forward(lineardelta_joints, lineardelta_parameters);
ok &= expect_near_eps(lineardelta_roundtrip_pose.x,
lineardelta_pose.x,
1e-6,
"lineardeltakins forward/inverse roundtrip x");
ok &= expect_near_eps(lineardelta_roundtrip_pose.y,
lineardelta_pose.y,
1e-6,
"lineardeltakins forward/inverse roundtrip y");
ok &= expect_near_eps(lineardelta_roundtrip_pose.z,
lineardelta_pose.z,
1e-6,
"lineardeltakins forward/inverse roundtrip z");
ok &= expect_near(lineardelta_roundtrip_pose.a, lineardelta_pose.a, "lineardeltakins pass-through a");
ok &= expect_near(lineardelta_roundtrip_pose.w, lineardelta_pose.w, "lineardeltakins pass-through w");
const LinuxCncRotaryDeltaParameters rotarydelta_parameters = linuxcnc_rotarydelta_default_parameters();
ok &= expect_near(rotarydelta_parameters.platformradius, 10.0, "rotarydeltakins default platformradius");
ok &= expect_near(rotarydelta_parameters.thighlength, 10.0, "rotarydeltakins default thighlength");
ok &= expect_near(rotarydelta_parameters.shinlength, 14.0, "rotarydeltakins default shinlength");
ok &= expect_near(rotarydelta_parameters.footradius, 6.0, "rotarydeltakins default footradius");
LinuxCncAxisJoints rotarydelta_joints{};
rotarydelta_joints.x = 20.0;
rotarydelta_joints.y = 25.0;
rotarydelta_joints.z = 30.0;
rotarydelta_joints.a = 4.0;
rotarydelta_joints.b = 5.0;
rotarydelta_joints.c = 6.0;
rotarydelta_joints.u = 7.0;
rotarydelta_joints.v = 8.0;
rotarydelta_joints.w = 9.0;
const CncSimPose rotarydelta_pose =
linuxcnc_rotarydelta_forward(rotarydelta_joints, rotarydelta_parameters);
const LinuxCncAxisJoints rotarydelta_roundtrip_joints =
linuxcnc_rotarydelta_inverse(rotarydelta_pose, rotarydelta_parameters);
const CncSimPose rotarydelta_roundtrip_pose =
linuxcnc_rotarydelta_forward(rotarydelta_roundtrip_joints, rotarydelta_parameters);
ok &= expect_near_eps(rotarydelta_roundtrip_pose.x,
rotarydelta_pose.x,
1e-6,
"rotarydeltakins forward/inverse roundtrip x");
ok &= expect_near_eps(rotarydelta_roundtrip_pose.y,
rotarydelta_pose.y,
1e-6,
"rotarydeltakins forward/inverse roundtrip y");
ok &= expect_near_eps(rotarydelta_roundtrip_pose.z,
rotarydelta_pose.z,
1e-6,
"rotarydeltakins forward/inverse roundtrip z");
ok &= expect_near(rotarydelta_roundtrip_pose.a, rotarydelta_pose.a, "rotarydeltakins pass-through a");
ok &= expect_near(rotarydelta_roundtrip_pose.w, rotarydelta_pose.w, "rotarydeltakins pass-through w");
const LinuxCncMaxkinsParameters maxkins_parameters = linuxcnc_maxkins_default_parameters();
ok &= expect_near(maxkins_parameters.pivot_length, 0.666, "maxkins default pivot-length");
ok &= expect(!maxkins_parameters.conventional_directions, "maxkins default unconventional directions");
LinuxCncAxisJoints maxkins_joints{};
maxkins_joints.x = 10.0;
maxkins_joints.y = 4.0;
maxkins_joints.z = 3.0;
maxkins_joints.a = 1.0;
maxkins_joints.b = 0.0;
maxkins_joints.c = 30.0;
maxkins_joints.u = 0.0;
maxkins_joints.v = 0.0;
maxkins_joints.w = 0.0;
const CncSimPose maxkins_pose = linuxcnc_maxkins_forward(maxkins_joints, maxkins_parameters);
const LinuxCncAxisJoints maxkins_roundtrip_joints =
linuxcnc_maxkins_inverse(maxkins_pose, maxkins_parameters);
ok &= expect_axis_joints_near(maxkins_roundtrip_joints,
maxkins_joints,
"maxkins forward/inverse roundtrip");
LinuxCncAxisJoints rosekins_joints{};
rosekins_joints.x = 5.0;
rosekins_joints.y = 2.0;
rosekins_joints.z = 45.0;
const CncSimPose rosekins_pose = linuxcnc_rosekins_forward(rosekins_joints);
ok &= expect_near_eps(rosekins_pose.x, 3.53553390593274, 1e-12, "rosekins forward x");
ok &= expect_near_eps(rosekins_pose.y, 3.53553390593274, 1e-12, "rosekins forward y");
ok &= expect_near(rosekins_pose.z, 2.0, "rosekins forward z");
const LinuxCncAxisJoints rosekins_roundtrip_joints = linuxcnc_rosekins_inverse(rosekins_pose);
ok &= expect_near_eps(rosekins_roundtrip_joints.x, rosekins_joints.x, 1e-9, "rosekins inverse radius");
ok &= expect_near(rosekins_roundtrip_joints.y, rosekins_joints.y, "rosekins inverse z joint");
ok &= expect_near_eps(rosekins_roundtrip_joints.z, rosekins_joints.z, 1e-9, "rosekins inverse theta");
LinuxCncFiveAxisJoints joints{};
joints.x = 10.0;
joints.y = 20.0;
@@ -257,6 +509,24 @@ int main() {
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");
xyzac_pose = linuxcnc_xyzac_trt_forward(joints, trt_parameters);
ok &= expect_near(xyzac_pose.a, joints.a, "xyzac-trt forward keeps configured A after xyzbc setup");
ok &= expect_near(xyzac_pose.b, 0.0, "xyzac-trt forward drops unconfigured B after xyzbc setup");
trt_parameters = linuxcnc_xyzbc_trt_default_parameters(250.0);
joints = {};
joints.x = 10.0;
joints.y = 20.0;
joints.z = 30.0;
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 after xyzac setup");
ok &= expect_near(pose.y, 20.0, "xyzbc-trt B90 C0 forward y after xyzac setup");
ok &= expect_near(pose.z, 265.0, "xyzbc-trt B90 C0 forward z after xyzac setup");
ok &= expect_near(pose.a, 0.0, "xyzbc-trt forward drops unconfigured A after xyzac setup");
ok &= expect_near(pose.b, 90.0, "xyzbc-trt forward keeps configured B after xyzac setup");
LinuxCncScaraParameters scara_parameters = linuxcnc_scara_default_parameters();
LinuxCncScaraJoints scara_joints{};
scara_joints.j0 = 0.0;
@@ -355,6 +625,32 @@ int main() {
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");
ok &= expect(!linuxcnc_xyzab_tdr_kins_switch(2),
"xyzab-tdr LinuxCNC kinematicsSwitch rejects invalid switchkins type");
bool xyzab_kinstype_is_0 = true;
bool xyzab_kinstype_is_1 = true;
ok &= expect(linuxcnc_xyzab_tdr_kins_switch_state(&xyzab_kinstype_is_0, &xyzab_kinstype_is_1),
"xyzab-tdr LinuxCNC switch state is readable after invalid switchkins type");
ok &= expect(!xyzab_kinstype_is_0,
"xyzab-tdr LinuxCNC invalid switch clears kinstype.is-0");
ok &= expect(!xyzab_kinstype_is_1,
"xyzab-tdr LinuxCNC invalid switch clears kinstype.is-1");
ok &= expect(linuxcnc_xyzab_tdr_kins_switch(1),
"xyzab-tdr LinuxCNC kinematicsSwitch restores valid switchkins type");
ok &= expect(linuxcnc_xyzab_tdr_kins_switch_state(&xyzab_kinstype_is_0, &xyzab_kinstype_is_1),
"xyzab-tdr LinuxCNC switch state is readable after type1 switch");
ok &= expect(!xyzab_kinstype_is_0,
"xyzab-tdr LinuxCNC type1 switch clears kinstype.is-0");
ok &= expect(xyzab_kinstype_is_1,
"xyzab-tdr LinuxCNC type1 switch sets kinstype.is-1");
ok &= expect(linuxcnc_xyzab_tdr_kins_switch(0),
"xyzab-tdr LinuxCNC kinematicsSwitch accepts type0 switch");
ok &= expect(linuxcnc_xyzab_tdr_kins_switch_state(&xyzab_kinstype_is_0, &xyzab_kinstype_is_1),
"xyzab-tdr LinuxCNC switch state is readable after type0 switch");
ok &= expect(xyzab_kinstype_is_0,
"xyzab-tdr LinuxCNC type0 switch sets kinstype.is-0");
ok &= expect(!xyzab_kinstype_is_1,
"xyzab-tdr LinuxCNC type0 switch clears kinstype.is-1");
LinuxCncPumaParameters puma_parameters = linuxcnc_puma_default_parameters();
LinuxCncAxisJoints puma_joints{};
@@ -377,6 +673,31 @@ int main() {
ok &= expect_near(puma_roundtrip.c, puma_joints.c, "pumakins roundtrip j5");
ok &= expect_near(puma_fflags, 0, "pumakins non-singular inverse flags");
int genser_default_iterations = 0;
LinuxCncGenserParameters genser_default_parameters =
linuxcnc_genser_default_parameters(&genser_default_iterations);
ok &= expect_near(genser_default_iterations, 100, "genserkins LinuxCNC default max iterations");
ok &= expect_near(genser_default_parameters.a[2], 300.0, "genserkins LinuxCNC default A3");
ok &= expect_near(genser_default_parameters.a[3], 50.0, "genserkins LinuxCNC default A4");
ok &= expect_near_eps(genser_default_parameters.alpha[1],
-1.5707963267948966,
1e-12,
"genserkins LinuxCNC default ALPHA2");
ok &= expect_near_eps(genser_default_parameters.alpha[3],
-1.5707963267948966,
1e-12,
"genserkins LinuxCNC default ALPHA4");
ok &= expect_near_eps(genser_default_parameters.alpha[4],
1.5707963267948966,
1e-12,
"genserkins LinuxCNC default ALPHA5");
ok &= expect_near_eps(genser_default_parameters.alpha[5],
-1.5707963267948966,
1e-12,
"genserkins LinuxCNC default ALPHA6");
ok &= expect_near(genser_default_parameters.d[2], 70.0, "genserkins LinuxCNC default D3");
ok &= expect_near(genser_default_parameters.d[3], 400.0, "genserkins LinuxCNC default D4");
LinuxCncGenserParameters genser_parameters = linuxcnc_genser_puma560_parameters();
LinuxCncAxisJoints genser_joints{};
CncSimPose genser_pose = linuxcnc_genser_forward(genser_joints, genser_parameters);
@@ -441,5 +762,16 @@ int main() {
"genserkins puma560 iterative inverse c");
ok &= expect(genser_iterations > 0, "genserkins puma560 inverse should iterate from perturbed estimate");
LinuxCncAxisJoints limited_genser_inverse{};
genser_iterations = -1;
const bool limited_genser_ok = linuxcnc_genser_inverse_checked(genser_pose,
genser_estimate,
genser_parameters,
&limited_genser_inverse,
&genser_iterations,
1);
ok &= expect(!limited_genser_ok, "genserkins puma560 inverse fails when max_iterations is too low");
ok &= expect_near(genser_iterations, 1, "genserkins failed inverse reports LinuxCNC iteration count");
return ok ? 0 : 1;
}