diff --git a/wasm-port/docs/compatibility-validation.md b/wasm-port/docs/compatibility-validation.md index dcfc395..9044280 100644 --- a/wasm-port/docs/compatibility-validation.md +++ b/wasm-port/docs/compatibility-validation.md @@ -213,6 +213,7 @@ The validation fails if: | `linuxcnc_interp_init_harness` | Validates vendored LinuxCNC `Interp::init()` emits canonical initialization boundaries, reads metric/inch machine units, and synchronizes current/selected tool slots through standalone status adapters. | | `linuxcnc_indexer_harness` | Validates vendored LinuxCNC single-axis rotary indexer dispatch emits lock/unlock and motion boundaries through the standalone event sink. | | `linuxcnc_remap_hal_sync_harness` | Validates vendored LinuxCNC `M68`/`M66` execution can drive the standalone HAL adapter boundary used by 5-axis switchkins remap files, including `_hal[motion.switchkins-type]` readback. | +| `linuxcnc_5axis_remap_execute_harness` | Validates vendored LinuxCNC `REMAP` parsing plus NGC remap execution for the 5-axis `M429 -> M428 -> M430 -> M429` switchkins path in the `xyzac-trt` and `xyzbc-trt` sample machines. | | `linuxcnc_tp_api_probe` | Validates vendored LinuxCNC trajectory planner calls for linear, arc, and queued motion paths. | | `linuxcnc_kinematics_probe` | Validates vendored LinuxCNC `trivkins.c` plus `kins_util.c` initialize and perform identity forward/inverse mapping through the standalone HAL/RTAPI boundary. | | `linuxcnc_5axis_kinematics_probe` | Validates vendored LinuxCNC `5axiskins.c` through `switchkins.c`, including 5-axis forward/inverse round-trip behavior and switching to identity kinematics. | diff --git a/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_5axis_remap_execute_harness.cpp b/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_5axis_remap_execute_harness.cpp new file mode 100644 index 0000000..bd4ad5a --- /dev/null +++ b/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_5axis_remap_execute_harness.cpp @@ -0,0 +1,186 @@ +#define private public +#include "emc/rs274ngc/rs274ngc_interp.hh" +#undef private + +#include +#include +#include +#include +#include +#include + +#include "canon_event_sink.hh" +#include "emc/ini/inifile.hh" +#include "emc/rs274ngc/interp_internal.hh" +#include "linuxcnc_hal_adapter.hh" +#include "linuxcnc_tool_adapter.hh" + +#ifndef LINUXCNC_VENDOR_DIR +#error "LINUXCNC_VENDOR_DIR must point at wasm-port/vendor/linuxcnc" +#endif + +namespace { + +struct MachineCase { + const char *label; + const char *ini_rel; +}; + +std::filesystem::path vendor_path(const std::string &rel) +{ + return std::filesystem::path(LINUXCNC_VENDOR_DIR) / rel; +} + +void print_bool(const std::string &name, bool value) +{ + std::cout << name << "=" << (value ? 1 : 0) << "\n"; +} + +void seed_switchkins_hal() +{ + standalone::reset_hal_adapter(); + + hal_data_u switchkins{}; + switchkins.f = 0.0; + standalone::set_hal_value(standalone::HalValueKind::Pin, "motion.switchkins-type", + HAL_FLOAT, switchkins); + standalone::set_hal_value(standalone::HalValueKind::Pin, "motion.analog-out-03", + HAL_FLOAT, switchkins); + standalone::link_hal_pin_alias("motion.analog-out-03", "motion.switchkins-type"); +} + +void initialize_interp(Interp &interp) +{ + seed_switchkins_hal(); + standalone::reset_tool_adapter(); + standalone::reset_canon_events(); + + interp._setup.length_units = CANON_UNITS_MM; + interp._setup.distance_mode = DISTANCE_MODE::ABSOLUTE; + interp._setup.ijk_distance_mode = DISTANCE_MODE::ABSOLUTE; + interp._setup.feed_mode = FEED_MODE::UNITS_PER_MINUTE; + interp._setup.plane = CANON_PLANE::XY; + interp._setup.motion_mode = G_0; + interp._setup.percent_flag = false; + interp._setup.sequence_number = 0; + interp._setup.parameter_occurrence = 0; + interp._setup.num_spindles = 1; + interp._setup.feature_set = FEATURE_INI_VARS | FEATURE_HAL_PIN_VARS; + interp._setup.parameter_g73_peck_clearance = 1.0; + interp._setup.parameter_g83_peck_clearance = 1.0; + interp._setup.parameters[5599] = 1.0; + std::memcpy(interp._readers, Interp::default_readers, sizeof(Interp::default_readers)); + interp.init_named_parameters(); +} + +bool read_hal_named(Interp &interp, const std::string &label, const char *name, double expected) +{ + int status = 0; + double value = 0.0; + const int rc = interp.find_named_param(name, &status, &value); + std::cout << label << "_" << name << ": rc=" << rc << " found=" << status + << " value=" << value << "\n"; + return rc == INTERP_OK && status == 1 && std::fabs(value - expected) < 0.000001; +} + +bool parse_machine_remaps(Interp &interp, const MachineCase &machine) +{ + const auto ini_path = vendor_path(machine.ini_rel); + const auto machine_dir = ini_path.parent_path(); + const auto remap_dir = machine_dir / "remap_subs"; + + linuxcnc::IniFile ini(ini_path.string()); + const bool opened = static_cast(ini); + print_bool(std::string(machine.label) + "_ini_open", opened); + if (!opened) { + return false; + } + + std::snprintf(interp._setup.program_prefix, sizeof(interp._setup.program_prefix), "%s", + machine_dir.c_str()); + interp._setup.subroutines[0] = strstore(remap_dir.c_str()); + + bool ok = true; + int remap_count = 0; + int line_number = 0; + while (auto remap = ini.findString(++remap_count, "REMAP", "RS274NGC")) { + line_number = ini.lineOf(remap_count, "REMAP", "RS274NGC").second; + const int rc = interp.parse_remap(remap->c_str(), line_number); + std::cout << machine.label << "_parse_remap_" << remap_count << "=" << rc << "\n"; + ok &= rc == INTERP_OK; + } + + remap_count -= 1; + std::cout << machine.label << "_parsed_remap_count=" << remap_count << "\n"; + ok &= remap_count == 3; + print_bool(std::string(machine.label) + "_remaps_ready", ok); + return ok; +} + +std::string command_label(const char *command) +{ + std::string value = command ? command : ""; + while (!value.empty() && (value.back() == '\n' || value.back() == '\r')) { + value.pop_back(); + } + return value; +} + +bool execute_step(Interp &interp, const std::string &label, const char *command, double expected) +{ + const std::string step = command_label(command); + int rc = interp.execute(command); + std::cout << label << "_" << step << "_execute_0=" << rc << "\n"; + + int finish_count = 0; + while (rc == INTERP_EXECUTE_FINISH && finish_count < 8) { + ++finish_count; + rc = interp.execute(); + std::cout << label << "_" << step << "_execute_" << finish_count << "=" << rc << "\n"; + } + + std::cout << label << "_" << step << "_execute_finish_count=" << finish_count << "\n"; + bool ok = rc == INTERP_OK; + ok &= read_hal_named(interp, label, "_hal[motion.switchkins-type]", expected); + return ok; +} + +bool check_machine(const MachineCase &machine) +{ + Interp interp; + initialize_interp(interp); + + bool ok = parse_machine_remaps(interp, machine); + ok &= read_hal_named(interp, machine.label, "_hal[motion.switchkins-type]", 0.0); + ok &= execute_step(interp, machine.label, "M429\n", 0.0); + ok &= execute_step(interp, machine.label, "M428\n", 1.0); + ok &= execute_step(interp, machine.label, "M430\n", 2.0); + ok &= execute_step(interp, machine.label, "M429\n", 0.0); + + print_bool(std::string(machine.label) + "_linuxcnc_remap_execute_path", ok); + return ok; +} + +} // namespace + +int main() +{ + const MachineCase cases[] = { + { + "xyzac_trt", + "configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini", + }, + { + "xyzbc_trt", + "configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.ini", + }, + }; + + bool ok = true; + for (const auto &machine : cases) { + ok &= check_machine(machine); + } + + print_bool("fiveaxis_linuxcnc_remap_execute_path", ok); + return ok ? 0 : 1; +} diff --git a/wasm-port/tests/native/verify_native_probes.sh b/wasm-port/tests/native/verify_native_probes.sh index 7bcc119..6b81bda 100755 --- a/wasm-port/tests/native/verify_native_probes.sh +++ b/wasm-port/tests/native/verify_native_probes.sh @@ -163,6 +163,8 @@ check_exitcode linuxcnc_remap_parse_harness check_exitcode linuxcnc_remap_parse_harness.run check_exitcode linuxcnc_remap_hal_sync_harness check_exitcode linuxcnc_remap_hal_sync_harness.run +check_exitcode linuxcnc_5axis_remap_execute_harness +check_exitcode linuxcnc_5axis_remap_execute_harness.run check_exitcode linuxcnc_indexer_harness check_exitcode linuxcnc_indexer_harness.run check_exitcode linuxcnc_rs274_compile_probe @@ -281,6 +283,27 @@ grep -Fq "canon_has_m66_sync=1" "$REMAP_HAL_SYNC_STDOUT" grep -Fq "canon_has_m68_identity=1" "$REMAP_HAL_SYNC_STDOUT" grep -Fq "fiveaxis_remap_hal_sync_boundary=1" "$REMAP_HAL_SYNC_STDOUT" +FIVEAXIS_REMAP_EXECUTE_STDOUT="$BUILD_DIR/linuxcnc_5axis_remap_execute_harness.run.stdout.log" +grep -Fq "xyzac_trt_ini_open=1" "$FIVEAXIS_REMAP_EXECUTE_STDOUT" +grep -Fq "xyzac_trt_remaps_ready=1" "$FIVEAXIS_REMAP_EXECUTE_STDOUT" +grep -Fq "xyzac_trt_M429_execute_0=2" "$FIVEAXIS_REMAP_EXECUTE_STDOUT" +grep -Fq "xyzac_trt_M429_execute_1=0" "$FIVEAXIS_REMAP_EXECUTE_STDOUT" +grep -Fq "xyzac_trt_M428_execute_0=2" "$FIVEAXIS_REMAP_EXECUTE_STDOUT" +grep -Fq "xyzac_trt_M428_execute_1=0" "$FIVEAXIS_REMAP_EXECUTE_STDOUT" +grep -Fq "xyzac_trt_M430_execute_0=2" "$FIVEAXIS_REMAP_EXECUTE_STDOUT" +grep -Fq "xyzac_trt_M430_execute_1=0" "$FIVEAXIS_REMAP_EXECUTE_STDOUT" +grep -Fq "xyzac_trt_linuxcnc_remap_execute_path=1" "$FIVEAXIS_REMAP_EXECUTE_STDOUT" +grep -Fq "xyzbc_trt_ini_open=1" "$FIVEAXIS_REMAP_EXECUTE_STDOUT" +grep -Fq "xyzbc_trt_remaps_ready=1" "$FIVEAXIS_REMAP_EXECUTE_STDOUT" +grep -Fq "xyzbc_trt_M429_execute_0=2" "$FIVEAXIS_REMAP_EXECUTE_STDOUT" +grep -Fq "xyzbc_trt_M429_execute_1=0" "$FIVEAXIS_REMAP_EXECUTE_STDOUT" +grep -Fq "xyzbc_trt_M428_execute_0=2" "$FIVEAXIS_REMAP_EXECUTE_STDOUT" +grep -Fq "xyzbc_trt_M428_execute_1=0" "$FIVEAXIS_REMAP_EXECUTE_STDOUT" +grep -Fq "xyzbc_trt_M430_execute_0=2" "$FIVEAXIS_REMAP_EXECUTE_STDOUT" +grep -Fq "xyzbc_trt_M430_execute_1=0" "$FIVEAXIS_REMAP_EXECUTE_STDOUT" +grep -Fq "xyzbc_trt_linuxcnc_remap_execute_path=1" "$FIVEAXIS_REMAP_EXECUTE_STDOUT" +grep -Fq "fiveaxis_linuxcnc_remap_execute_path=1" "$FIVEAXIS_REMAP_EXECUTE_STDOUT" + NAMEDPARAM_STDOUT="$BUILD_DIR/linuxcnc_namedparam_harness.run.stdout.log" grep -Fq "init_named_parameters=0" "$NAMEDPARAM_STDOUT" grep -Fq "global_named_count=57" "$NAMEDPARAM_STDOUT" diff --git a/wasm-port/tools/build_native_probes.sh b/wasm-port/tools/build_native_probes.sh index e1ff432..cc91dda 100755 --- a/wasm-port/tools/build_native_probes.sh +++ b/wasm-port/tools/build_native_probes.sh @@ -667,7 +667,7 @@ INDEXER_HARNESS_SOURCES=( "$WRAP_DIR/linuxcnc_indexer_harness.cpp" ) -REMAP_PARSE_SOURCES=( +REMAP_RUNTIME_CORE_SOURCES=( "$VENDOR_DIR/src/emc/rs274ngc/modal_state.cc" "$VENDOR_DIR/src/emc/rs274ngc/interp_array.cc" "$VENDOR_DIR/src/emc/rs274ngc/interp_internal.cc" @@ -694,6 +694,10 @@ REMAP_PARSE_SOURCES=( "$WRAP_DIR/linuxcnc_tool_adapter.cpp" "$WRAP_DIR/linuxcnc_interp_minimal_runtime.cpp" "$VENDOR_DIR/src/emc/ini/inifile.cc" +) + +REMAP_PARSE_SOURCES=( + "${REMAP_RUNTIME_CORE_SOURCES[@]}" "$WRAP_DIR/linuxcnc_remap_parse_harness.cpp" ) @@ -702,6 +706,11 @@ REMAP_HAL_SYNC_SOURCES=( "$WRAP_DIR/linuxcnc_remap_hal_sync_harness.cpp" ) +FIVEAXIS_REMAP_EXECUTE_SOURCES=( + "${REMAP_RUNTIME_CORE_SOURCES[@]}" + "$WRAP_DIR/linuxcnc_5axis_remap_execute_harness.cpp" +) + build_binary_target \ linuxcnc_ini_probe \ "$BUILD_DIR/linuxcnc_ini_probe" \ @@ -1309,6 +1318,28 @@ else "$BUILD_DIR/linuxcnc_remap_hal_sync_harness.run.stderr.log" fi +build_binary_target \ + linuxcnc_5axis_remap_execute_harness \ + "$BUILD_DIR/linuxcnc_5axis_remap_execute_harness" \ + REMAP_RUNTIME_FLAGS \ + FIVEAXIS_REMAP_EXECUTE_SOURCES \ + MINIMAL_LINK_FLAGS + +if [[ "$(tr -d '[:space:]' < "$BUILD_DIR/linuxcnc_5axis_remap_execute_harness.exitcode")" == "0" ]]; then + set +e + "$BUILD_DIR/linuxcnc_5axis_remap_execute_harness" \ + >"$BUILD_DIR/linuxcnc_5axis_remap_execute_harness.run.stdout.log" \ + 2>"$BUILD_DIR/linuxcnc_5axis_remap_execute_harness.run.stderr.log" + FIVEAXIS_REMAP_EXECUTE_RUN_RC=$? + set -e + echo "$FIVEAXIS_REMAP_EXECUTE_RUN_RC" > "$BUILD_DIR/linuxcnc_5axis_remap_execute_harness.run.exitcode" +else + rm -f \ + "$BUILD_DIR/linuxcnc_5axis_remap_execute_harness.run.exitcode" \ + "$BUILD_DIR/linuxcnc_5axis_remap_execute_harness.run.stdout.log" \ + "$BUILD_DIR/linuxcnc_5axis_remap_execute_harness.run.stderr.log" +fi + build_binary_target \ linuxcnc_indexer_harness \ "$BUILD_DIR/linuxcnc_indexer_harness" \