From b40914747d7a3259a0d4207ea04f6acf9a69af83 Mon Sep 17 00:00:00 2001 From: cnc Date: Thu, 28 May 2026 18:00:53 +0800 Subject: [PATCH] =?UTF-8?q?=E6=BB=A1=E5=BA=AD=E8=8A=B3=C2=B7=E6=BA=90?= =?UTF-8?q?=E6=B5=81=E5=BD=92=E4=B8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 结论: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。 --- README.zh-CN.md | 18 +- analyze-linuxcnc-wasm-blockers.sh | 35 +- build-wasm.sh | 35 +- check-linuxcnc-inputs.sh | 21 + core/src/canon_event_sink.cpp | 122 +- core/src/canon_event_sink.h | 15 +- core/src/cnc_sim_api.cpp | 86 ++ core/src/linuxcnc_canon_bridge.cpp | 4 +- core/src/linuxcnc_rs274_backend.cpp | 2 +- core/src/rtcp_kinematics.cpp | 1356 ++++++++++++++++- core/src/rtcp_kinematics.h | 136 +- core/src/simulator_gcode_controls.cpp | 211 ++- core/src/simulator_gcode_controls.h | 4 + core/src/smoke_gcode_parser.cpp | 317 +++- core/tests/canon_event_sink_smoke.cpp | 264 +++- .../cnc_sim_api_linuxcnc_rs274_smoke.cpp | 228 ++- core/tests/cnc_sim_api_smoke.cpp | 748 ++++++++- core/tests/linuxcnc_canon_bridge_smoke.cpp | 7 +- core/tests/linuxcnc_gees_table_smoke.cpp | 79 +- core/tests/rtcp_kinematics_smoke.cpp | 436 +++++- core/tests/simulator_gcode_controls_smoke.cpp | 199 ++- docs/architecture.md | 25 +- docs/linuxcnc-porting.md | 4 +- docs/linuxcnc-source-policy.md | 32 + linuxcnc-rs274-source-files.txt | 17 +- linuxcnc-rs274-wasm-source-files.txt | 16 + list-cmake-set-plus-append-sources.sh | 45 + list-cmake-set-sources.sh | 35 + list-cnc-sim-core-sources.sh | 41 +- list-filtered-existing-paths.sh | 36 + list-linuxcnc-bridge-smoke-project-sources.sh | 24 +- list-linuxcnc-manifest-sources.sh | 73 + list-linuxcnc-native-linkflags.sh | 12 + list-linuxcnc-rs274-api-project-sources.sh | 50 +- list-linuxcnc-rs274-dump-project-sources.sh | 27 +- list-linuxcnc-source-common-cxxflags.sh | 67 + list-linuxcnc-source-files.sh | 34 +- list-linuxcnc-source-manifest-sources.sh | 70 +- list-linuxcnc-source-support-objects.sh | 29 +- list-linuxcnc-wasm-common-cxxflags.sh | 117 ++ list-linuxcnc-wasm-manifest-sources.sh | 68 +- list-linuxcnc-wasm-safe-project-sources.sh | 42 +- list-linuxcnc-wasm-safe-shims.sh | 42 +- list-project-source-set.sh | 45 + test-all-native.sh | 192 ++- test-linuxcnc-api-native.sh | 31 +- test-linuxcnc-bridge-native.sh | 29 +- test-linuxcnc-rs274-native.sh | 33 +- test-linuxcnc-source-link.sh | 44 +- test-linuxcnc-source-objects.sh | 34 +- test-linuxcnc-source-syntax.sh | 42 +- test-linuxcnc-wasm-blockers.sh | 32 +- test-linuxcnc-wasm-cmake-safe-probe.sh | 249 ++- test-linuxcnc-wasm-interp-base-link.sh | 18 +- test-linuxcnc-wasm-interp-find-link.sh | 23 +- test-linuxcnc-wasm-python-plugin-link.sh | 18 +- ...inuxcnc-wasm-rs274ngc-pre-link-blockers.sh | 82 +- test-linuxcnc-wasm-rs274ngc-pre-link.sh | 43 +- test-linuxcnc-wasm-rs274ngc-pre-object.sh | 18 +- test-linuxcnc-wasm-source-objects.sh | 36 +- test-linuxcnc-wasm-source-syntax.sh | 33 +- test-linuxcnc-wasm-tooldata-common-link.sh | 18 +- test-linuxcnc-wasm-tooldata-link.sh | 18 +- test-linuxcnc-wasm-tooldata-mmap-symbols.sh | 18 +- ...-linuxcnc-wasm-tooldata-runtime-symbols.sh | 21 +- 65 files changed, 5189 insertions(+), 1117 deletions(-) create mode 100755 check-linuxcnc-inputs.sh create mode 100644 docs/linuxcnc-source-policy.md create mode 100755 list-cmake-set-plus-append-sources.sh create mode 100755 list-cmake-set-sources.sh create mode 100755 list-filtered-existing-paths.sh create mode 100755 list-linuxcnc-manifest-sources.sh create mode 100755 list-linuxcnc-native-linkflags.sh create mode 100755 list-linuxcnc-source-common-cxxflags.sh create mode 100755 list-linuxcnc-wasm-common-cxxflags.sh create mode 100755 list-project-source-set.sh diff --git a/README.zh-CN.md b/README.zh-CN.md index 6997214..aa3175e 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -143,11 +143,19 @@ RTCP 默认关闭。开启后,原有 `rapid`、`linear-feed`、`arc-feed` 事 "toolLengths": { "7": 125 } + }, + "xyzbcTrt": { + "xRotPoint": 0, + "yRotPoint": 0, + "zRotPoint": 0, + "xOffset": -20, + "zOffset": -15, + "conventionalDirections": false } } ``` -当前 RTCP 内核是第一版头头/摆头类几何模型:编程 XYZ 视为刀尖点,刀长沿本地 `-Z`,A/B/C 按 `Rz(C) * Ry(B) * Rx(A)` 旋转。后续会继续加入机型拓扑、旋转中心偏置、刀表刀长和不同五轴构型。 +RTCP pivot 事件只在已知 LinuxCNC switchkins 类型下输出。`M428` 切换到 `XYZBC_TRT` 后,`xyzbcTrt` 对应 LinuxCNC `xyzbc-trt-kins` 的 HAL pin:`x-rot-point`、`y-rot-point`、`z-rot-point`、`x-offset`、`z-offset`、`conventional-directions`;刀长仍来自 `toolLength` / `toolLengths`。`M430` 使用 LinuxCNC `userkfuncs.c` 经 `kins_util.c` identity 运动学路径。未支持或未知的 switchkins 类型不会回退到自研 RTCP 几何。 RTCP G 码控制已开始标准化: @@ -161,11 +169,11 @@ RTCP G 码控制已开始标准化: core 已对原型中的运动学切换 M 码做标准化解析: -- `M429`:切换到三轴恒等运动学 `IDENTITY`,关闭 RTCP。 -- `M428`:恢复到原始五轴运动学,开启 RTCP。 -- `M430`:切换到 `FIVEAXIS_BC`,开启 RTCP。 +- `M429`:切换到三轴恒等运动学 `IDENTITY`。 +- `M428`:切换到 LinuxCNC `xyzbc-trt-kins` 的 XYZBC TRT 运动学。 +- `M430`:切换到 LinuxCNC 默认 `userk` 运动学;当前对齐 `userkfuncs.c` 的 identity 正/逆解。 -解析后会发出 `kinematics-switch` 事件。事件中的 `reserved` 字段表示运动学类型:`0=IDENTITY`、`1=ORIGINAL`、`2=FIVEAXIS_BC`;`feed` 字段临时表示 RTCP 状态:`0=关闭`、`1=开启`。同一行中的 `M428 M429 M430` 会按出现顺序产生三个切换事件。 +解析后会发出 `kinematics-switch` 事件。事件中的 `reserved` 字段表示运动学类型:`0=IDENTITY`、`1=XYZBC_TRT`、`2=USERK_IDENTITY`。这些 M 码对齐 LinuxCNC `remap_subs/428remap.ngc`、`429remap.ngc`、`430remap.ngc`,只设置 `motion.switchkins-type`,不隐式开启或关闭 RTCP;RTCP 状态仍由 `G43.4`、`G43.5`、`G49` 控制。同一行中的 `M428 M429 M430` 会按出现顺序产生三个切换事件。 `linuxcnc-rs274` native API backend 会在送入 LinuxCNC 解释器前拦截只包含这些切换 M 码的行,避免 LinuxCNC 去查找外部用户 M-code 文件。 diff --git a/analyze-linuxcnc-wasm-blockers.sh b/analyze-linuxcnc-wasm-blockers.sh index 39e8175..dcc84d7 100755 --- a/analyze-linuxcnc-wasm-blockers.sh +++ b/analyze-linuxcnc-wasm-blockers.sh @@ -6,15 +6,7 @@ cd "$(dirname "$0")" linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} manifest=${1:-linuxcnc-rs274-wasm-source-files.txt} -if [[ ! -f "$manifest" ]]; then - echo "missing manifest: $manifest" >&2 - exit 1 -fi - -if [[ ! -d "$linuxcnc_root" ]]; then - echo "missing LinuxCNC root: $linuxcnc_root" >&2 - exit 1 -fi +LINUXCNC_ROOT="$linuxcnc_root" ./check-linuxcnc-inputs.sh "$manifest" python_blockers=() core_python_sources=() @@ -30,9 +22,6 @@ core_native_fs_sources=() core_count=0 blocked_count=0 blocked_replacements=() -core_source_list=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_wasm_blocker_core_sources.XXXXXX.txt") -blocked_source_list=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_wasm_blocker_blocked_sources.XXXXXX.txt") -trap 'rm -f "$core_source_list" "$blocked_source_list"' EXIT blocked_replacement_for() { case "$1" in @@ -45,15 +34,19 @@ blocked_replacement_for() { esac } -./list-linuxcnc-wasm-manifest-sources.sh "$manifest" core > "$core_source_list" -./list-linuxcnc-wasm-manifest-sources.sh "$manifest" blocked > "$blocked_source_list" -core_count=$(wc -l < "$core_source_list") -blocked_count=$(wc -l < "$blocked_source_list") +core_source_output=$(./list-linuxcnc-wasm-manifest-sources.sh "$manifest" core) +mapfile -t core_sources <<<"$core_source_output" +blocked_source_output=$(./list-linuxcnc-wasm-manifest-sources.sh "$manifest" blocked) +mapfile -t blocked_sources <<<"$blocked_source_output" +core_count=${#core_sources[@]} +blocked_count=${#blocked_sources[@]} scan_source() { local group=$1 local path=$2 - full_path="$linuxcnc_root/$path" + full_path=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-files.sh \ + --missing-message "missing manifest source" \ + "$path") if [[ "$group" == "blocked" ]]; then if ! replacement=$(blocked_replacement_for "$path"); then echo "missing blocked replacement mapping: $path" >&2 @@ -111,15 +104,15 @@ scan_source() { fi } -while IFS= read -r path; do +for path in "${core_sources[@]}"; do [[ -z "$path" ]] && continue scan_source core "$path" -done < "$core_source_list" +done -while IFS= read -r path; do +for path in "${blocked_sources[@]}"; do [[ -z "$path" ]] && continue scan_source blocked "$path" -done < "$blocked_source_list" +done print_group() { local title=$1 diff --git a/build-wasm.sh b/build-wasm.sh index a6e59e7..43fb185 100755 --- a/build-wasm.sh +++ b/build-wasm.sh @@ -6,32 +6,35 @@ cd "$(dirname "$0")" linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} manifest=${1:-linuxcnc-rs274-wasm-source-files.txt} -if [[ ! -f "$manifest" ]]; then - echo "missing manifest: $manifest" >&2 - exit 1 -fi - -if [[ ! -d "$linuxcnc_root" ]]; then - echo "missing LinuxCNC root: $linuxcnc_root" >&2 - exit 1 -fi +LINUXCNC_ROOT="$linuxcnc_root" ./check-linuxcnc-inputs.sh "$manifest" manifest=$(cd "$(dirname "$manifest")" && pwd)/$(basename "$manifest") linuxcnc_root=$(cd "$linuxcnc_root" && pwd) default_manifest="$PWD/linuxcnc-rs274-wasm-source-files.txt" +count_nonempty_lines() { + local text=${1-} + local count=0 + local line + + while IFS= read -r line; do + if [[ -n "$line" ]]; then + count=$((count + 1)) + fi + done <<<"$text" + + printf '%s\n' "$count" +} + # This script writes fixed build and public artifact paths, so serialize it. exec 9>"${TMPDIR:-/tmp}/cnc_sim_build_wasm.lock" flock 9 manifest_core_count=0 manifest_blocked_count=0 -manifest_core_sources=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_wasm_manifest_core.XXXXXX.txt") -manifest_blocked_sources=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_wasm_manifest_blocked.XXXXXX.txt") -trap 'rm -f "$manifest_core_sources" "$manifest_blocked_sources"' EXIT -LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-wasm-manifest-sources.sh "$manifest" core > "$manifest_core_sources" -LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-wasm-manifest-sources.sh "$manifest" blocked > "$manifest_blocked_sources" -manifest_core_count=$(wc -l < "$manifest_core_sources") -manifest_blocked_count=$(wc -l < "$manifest_blocked_sources") +manifest_core_output=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-wasm-manifest-sources.sh "$manifest" core) +manifest_blocked_output=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-wasm-manifest-sources.sh "$manifest" blocked) +manifest_core_count=$(count_nonempty_lines "$manifest_core_output") +manifest_blocked_count=$(count_nonempty_lines "$manifest_blocked_output") if [[ "$manifest_core_count" -eq 0 || "$manifest_blocked_count" -eq 0 ]]; then echo "unexpected wasm manifest partition: core=$manifest_core_count blocked=$manifest_blocked_count" >&2 diff --git a/check-linuxcnc-inputs.sh b/check-linuxcnc-inputs.sh new file mode 100755 index 0000000..3f0c88b --- /dev/null +++ b/check-linuxcnc-inputs.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")" + +linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} +manifest=${1:-} + +if [[ "$manifest" == "--root-only" ]]; then + manifest= +fi + +if [[ -n "$manifest" && ! -f "$manifest" ]]; then + echo "missing manifest: $manifest" >&2 + exit 1 +fi + +if [[ ! -d "$linuxcnc_root" ]]; then + echo "missing LinuxCNC root: $linuxcnc_root" >&2 + exit 1 +fi diff --git a/core/src/canon_event_sink.cpp b/core/src/canon_event_sink.cpp index 6601668..74444ce 100644 --- a/core/src/canon_event_sink.cpp +++ b/core/src/canon_event_sink.cpp @@ -2,10 +2,11 @@ #include -#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 ¶meters) { + xyzbc_trt_parameters_ = parameters; + xyzbc_trt_parameters_configured_ = true; + use_xyzac_trt_ = false; +} + +void CanonEventSink::configure_xyzac_trt(const LinuxCncXyzbcTrtParameters ¶meters) { + 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#, then M66 E0 L0 to force synchronization. + set_analog_output(3, static_cast(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); } diff --git a/core/src/canon_event_sink.h b/core/src/canon_event_sink.h index 22c80d1..6f35278 100644 --- a/core/src/canon_event_sink.h +++ b/core/src/canon_event_sink.h @@ -1,6 +1,7 @@ #pragma once #include "cnc_sim_api.h" +#include "rtcp_kinematics.h" #include @@ -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 ¶meters); + void configure_xyzac_trt(const LinuxCncXyzbcTrtParameters ¶meters); + 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; diff --git a/core/src/cnc_sim_api.cpp b/core/src/cnc_sim_api.cpp index b6c74b8..ed4fd02 100644 --- a/core/src/cnc_sim_api.cpp +++ b/core/src/cnc_sim_api.cpp @@ -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 +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 +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, ¶meters.x_rot_point, "xrotpoint", "x_rot_point", "x-rot-point") || + configured; + configured = find_number_any(compact, ¶meters.y_rot_point, "yrotpoint", "y_rot_point", "y-rot-point") || + configured; + configured = find_number_any(compact, ¶meters.z_rot_point, "zrotpoint", "z_rot_point", "z-rot-point") || + configured; + configured = find_number_any(compact, ¶meters.x_offset, "xoffset", "x_offset", "x-offset") || + configured; + configured = find_number_any(compact, ¶meters.y_offset, "yoffset", "y_offset", "y-offset") || + configured; + configured = find_number_any(compact, ¶meters.z_offset, "zoffset", "z_offset", "z-offset") || + configured; + configured = contains_bool_any(compact, + ¶meters.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(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; } diff --git a/core/src/linuxcnc_canon_bridge.cpp b/core/src/linuxcnc_canon_bridge.cpp index 38f0e91..cf720b8 100644 --- a/core/src/linuxcnc_canon_bridge.cpp +++ b/core/src/linuxcnc_canon_bridge.cpp @@ -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); } diff --git a/core/src/linuxcnc_rs274_backend.cpp b/core/src/linuxcnc_rs274_backend.cpp index 957ae5a..f652a86 100644 --- a/core/src/linuxcnc_rs274_backend.cpp +++ b/core/src/linuxcnc_rs274_backend.cpp @@ -217,7 +217,7 @@ int parse_linuxcnc_rs274_backend(CanonEventSink &sink, ++line_number; cnc_sim_linuxcnc_set_current_line(line_number); std::vector 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); diff --git a/core/src/rtcp_kinematics.cpp b/core/src/rtcp_kinematics.cpp index 0d4a272..0030307 100644 --- a/core/src/rtcp_kinematics.cpp +++ b/core/src/rtcp_kinematics.cpp @@ -5,68 +5,1346 @@ namespace { constexpr double kPi = 3.141592653589793238462643383279502884; +constexpr double kRpyPitchFuzz = 1e-6; +constexpr double kGoRealEpsilon = 1e-6; +constexpr double kGoSingularEpsilon = 1.0e-15; + +struct RotationMatrix { + RtcpVector x; + RtcpVector y; + RtcpVector z; +}; + +struct HomogeneousTransform { + RotationMatrix rot; + RtcpVector tran; +}; + +struct Quaternion { + double s; + double x; + double y; + double z; +}; double radians(double degrees) { return degrees * kPi / 180.0; } -RtcpVector rotate_x(RtcpVector vector, double angle) { - const double c = std::cos(angle); - const double s = std::sin(angle); +double degrees(double radians_value) { + return radians_value * 180.0 / kPi; +} + +RtcpVector spherical_to_cartesian(double radius, double theta_deg, double phi_deg) { + const double theta = radians(theta_deg); + const double phi = radians(phi_deg); return { - vector.x, - vector.y * c - vector.z * s, - vector.y * s + vector.z * c, + radius * std::sin(phi) * std::cos(theta), + radius * std::sin(phi) * std::sin(theta), + radius * std::cos(phi), }; } -RtcpVector rotate_y(RtcpVector vector, double angle) { - const double c = std::cos(angle); - const double s = std::sin(angle); +RotationMatrix multiply_rotation(const RotationMatrix &lhs, const RotationMatrix &rhs) { + RotationMatrix result{}; + result.x.x = lhs.x.x * rhs.x.x + lhs.y.x * rhs.x.y + lhs.z.x * rhs.x.z; + result.y.x = lhs.x.x * rhs.y.x + lhs.y.x * rhs.y.y + lhs.z.x * rhs.y.z; + result.z.x = lhs.x.x * rhs.z.x + lhs.y.x * rhs.z.y + lhs.z.x * rhs.z.z; + + result.x.y = lhs.x.y * rhs.x.x + lhs.y.y * rhs.x.y + lhs.z.y * rhs.x.z; + result.y.y = lhs.x.y * rhs.y.x + lhs.y.y * rhs.y.y + lhs.z.y * rhs.y.z; + result.z.y = lhs.x.y * rhs.z.x + lhs.y.y * rhs.z.y + lhs.z.y * rhs.z.z; + + result.x.z = lhs.x.z * rhs.x.x + lhs.y.z * rhs.x.y + lhs.z.z * rhs.x.z; + result.y.z = lhs.x.z * rhs.y.x + lhs.y.z * rhs.y.y + lhs.z.z * rhs.y.z; + result.z.z = lhs.x.z * rhs.z.x + lhs.y.z * rhs.z.y + lhs.z.z * rhs.z.z; + return result; +} + +RtcpVector rotate_vector(const RotationMatrix &matrix, const RtcpVector &vector) { return { - vector.x * c + vector.z * s, - vector.y, - -vector.x * s + vector.z * c, + matrix.x.x * vector.x + matrix.y.x * vector.y + matrix.z.x * vector.z, + matrix.x.y * vector.x + matrix.y.y * vector.y + matrix.z.y * vector.z, + matrix.x.z * vector.x + matrix.y.z * vector.y + matrix.z.z * vector.z, }; } -RtcpVector rotate_z(RtcpVector vector, double angle) { - const double c = std::cos(angle); - const double s = std::sin(angle); +HomogeneousTransform multiply_transform(const HomogeneousTransform &lhs, + const HomogeneousTransform &rhs) { + HomogeneousTransform result{}; + result.rot = multiply_rotation(lhs.rot, rhs.rot); + const RtcpVector rotated = rotate_vector(lhs.rot, rhs.tran); + result.tran.x = lhs.tran.x + rotated.x; + result.tran.y = lhs.tran.y + rotated.y; + result.tran.z = lhs.tran.z + rotated.z; + return result; +} + +double square(double value) { + return value * value; +} + +bool go_small(double value) { + return std::fabs(value) < kGoRealEpsilon; +} + +RtcpVector add_vector(const RtcpVector &lhs, const RtcpVector &rhs) { return { - vector.x * c - vector.y * s, - vector.x * s + vector.y * c, - vector.z, + lhs.x + rhs.x, + lhs.y + rhs.y, + lhs.z + rhs.z, }; } +RtcpVector cross_vector(const RtcpVector &lhs, const RtcpVector &rhs) { + return { + lhs.y * rhs.z - lhs.z * rhs.y, + lhs.z * rhs.x - lhs.x * rhs.z, + lhs.x * rhs.y - lhs.y * rhs.x, + }; +} + +// Source: linuxcnc/src/libnml/posemath/_posemath.c +// Mirrors pmRpyMatConvert(). +RotationMatrix posemath_rpy_to_matrix(double roll, double pitch, double yaw) { + const double sa = std::sin(yaw); + const double sb = std::sin(pitch); + const double sg = std::sin(roll); + const double ca = std::cos(yaw); + const double cb = std::cos(pitch); + const double cg = std::cos(roll); + + RotationMatrix matrix{}; + matrix.x.x = ca * cb; + matrix.y.x = ca * sb * sg - sa * cg; + matrix.z.x = ca * sb * cg + sa * sg; + + matrix.x.y = sa * cb; + matrix.y.y = sa * sb * sg + ca * cg; + matrix.z.y = sa * sb * cg - ca * sg; + + matrix.x.z = -sb; + matrix.y.z = cb * sg; + matrix.z.z = cb * cg; + return matrix; +} + +// Source: linuxcnc/src/libnml/posemath/_posemath.c +// Mirrors pmMatRpyConvert(). +CncSimPose posemath_matrix_to_pose(const RtcpVector &tran, const RotationMatrix &matrix) { + CncSimPose pose{}; + pose.x = tran.x; + pose.y = tran.y; + pose.z = tran.z; + + const double pitch = std::atan2(-matrix.x.z, + std::sqrt(square(matrix.x.x) + square(matrix.x.y))); + double roll = 0.0; + double yaw = 0.0; + if (std::fabs(pitch - kPi / 2.0) < kRpyPitchFuzz) { + roll = std::atan2(matrix.y.x, matrix.y.y); + pose.b = 90.0; + } else if (std::fabs(pitch + kPi / 2.0) < kRpyPitchFuzz) { + roll = -std::atan2(matrix.y.x, matrix.y.y); + pose.b = -90.0; + } else { + roll = std::atan2(matrix.y.z, matrix.z.z); + yaw = std::atan2(matrix.x.y, matrix.x.x); + pose.b = degrees(pitch); + } + pose.a = degrees(roll); + pose.c = degrees(yaw); + return pose; +} + +// Source: linuxcnc/src/libnml/posemath/gomath.c +// Mirrors go_mat_quat_convert() for normalized rotation matrices. +Quaternion linuxcnc_matrix_to_quaternion(const RotationMatrix &matrix) { + Quaternion q{}; + double discr = 1.0 + matrix.x.x + matrix.y.y + matrix.z.z; + if (discr < 0.0) { + discr = 0.0; + } + q.s = 0.5 * std::sqrt(discr); + if (go_small(q.s)) { + q.s = 0.0; + discr = 1.0 + matrix.x.x - matrix.y.y - matrix.z.z; + if (discr < 0.0) { + discr = 0.0; + } + q.x = std::sqrt(discr) / 2.0; + discr = 1.0 + matrix.y.y - matrix.x.x - matrix.z.z; + if (discr < 0.0) { + discr = 0.0; + } + q.y = std::sqrt(discr) / 2.0; + discr = 1.0 + matrix.z.z - matrix.y.y - matrix.x.x; + if (discr < 0.0) { + discr = 0.0; + } + q.z = std::sqrt(discr) / 2.0; + + if (q.x > q.y && q.x > q.z) { + if (matrix.x.y < 0.0) { + q.y *= -1.0; + } + if (matrix.x.z < 0.0) { + q.z *= -1.0; + } + } else if (q.y > q.z) { + if (matrix.x.y < 0.0) { + q.x *= -1.0; + } + if (matrix.y.z < 0.0) { + q.z *= -1.0; + } + } else { + if (matrix.x.z < 0.0) { + q.x *= -1.0; + } + if (matrix.y.z < 0.0) { + q.y *= -1.0; + } + } + } else { + const double a = 4.0 * q.s; + q.x = (matrix.y.z - matrix.z.y) / a; + q.y = (matrix.z.x - matrix.x.z) / a; + q.z = (matrix.x.y - matrix.y.x) / a; + } + + const double size = std::sqrt(square(q.s) + square(q.x) + square(q.y) + square(q.z)); + if (go_small(size)) { + return {1.0, 0.0, 0.0, 0.0}; + } + const double inv_size = 1.0 / size; + if (q.s >= 0.0) { + q.s *= inv_size; + q.x *= inv_size; + q.y *= inv_size; + q.z *= inv_size; + } else { + q.s *= -inv_size; + q.x *= -inv_size; + q.y *= -inv_size; + q.z *= -inv_size; + } + return q; +} + +// Source: linuxcnc/src/libnml/posemath/gomath.c +// Mirrors go_quat_mat_convert(). +RotationMatrix linuxcnc_quaternion_to_matrix(const Quaternion &q) { + RotationMatrix matrix{}; + matrix.x.x = 1.0 - 2.0 * (square(q.y) + square(q.z)); + matrix.y.x = 2.0 * (q.x * q.y - q.z * q.s); + matrix.z.x = 2.0 * (q.z * q.x + q.y * q.s); + + matrix.x.y = 2.0 * (q.x * q.y + q.z * q.s); + matrix.y.y = 1.0 - 2.0 * (square(q.z) + square(q.x)); + matrix.z.y = 2.0 * (q.y * q.z - q.x * q.s); + + matrix.x.z = 2.0 * (q.z * q.x - q.y * q.s); + matrix.y.z = 2.0 * (q.y * q.z + q.x * q.s); + matrix.z.z = 1.0 - 2.0 * (square(q.x) + square(q.y)); + return matrix; +} + +// Source: linuxcnc/src/libnml/posemath/gomath.c +// Mirrors go_quat_inv(). +Quaternion linuxcnc_quaternion_inverse(const Quaternion &q) { + return {q.s, -q.x, -q.y, -q.z}; +} + +// Source: linuxcnc/src/libnml/posemath/gomath.c +// Mirrors go_quat_quat_mult(). +Quaternion linuxcnc_multiply_quaternion(const Quaternion &lhs, const Quaternion &rhs) { + Quaternion q{}; + q.s = lhs.s * rhs.s - lhs.x * rhs.x - lhs.y * rhs.y - lhs.z * rhs.z; + if (q.s >= 0.0) { + q.x = lhs.s * rhs.x + lhs.x * rhs.s + lhs.y * rhs.z - lhs.z * rhs.y; + q.y = lhs.s * rhs.y - lhs.x * rhs.z + lhs.y * rhs.s + lhs.z * rhs.x; + q.z = lhs.s * rhs.z + lhs.x * rhs.y - lhs.y * rhs.x + lhs.z * rhs.s; + } else { + q.s = -q.s; + q.x = -lhs.s * rhs.x - lhs.x * rhs.s - lhs.y * rhs.z + lhs.z * rhs.y; + q.y = -lhs.s * rhs.y + lhs.x * rhs.z - lhs.y * rhs.s - lhs.z * rhs.x; + q.z = -lhs.s * rhs.z - lhs.x * rhs.y + lhs.y * rhs.x - lhs.z * rhs.s; + } + return q; +} + +// Source: linuxcnc/src/libnml/posemath/gomath.c +// Mirrors go_quat_cart_mult(). +RtcpVector linuxcnc_quaternion_rotate_vector(const Quaternion &q, const RtcpVector &vector) { + const RtcpVector c{ + q.y * vector.z - q.z * vector.y, + q.z * vector.x - q.x * vector.z, + q.x * vector.y - q.y * vector.x, + }; + return { + vector.x + 2.0 * (q.s * c.x + q.y * c.z - q.z * c.y), + vector.y + 2.0 * (q.s * c.y + q.z * c.x - q.x * c.z), + vector.z + 2.0 * (q.s * c.z + q.x * c.y - q.y * c.x), + }; +} + +// Source: linuxcnc/src/libnml/posemath/gomath.c +// Mirrors go_quat_rvec_convert(). +RtcpVector linuxcnc_quaternion_to_rvec(const Quaternion &q) { + const double sh = std::sqrt(square(q.x) + square(q.y) + square(q.z)); + if (go_small(sh)) { + return {}; + } + const double mag = 2.0 * std::atan2(sh, q.s) / sh; + return { + mag * q.x, + mag * q.y, + mag * q.z, + }; +} + +// Source: linuxcnc/src/libnml/posemath/gomath.c +// Mirrors go_dh_pose_convert(). +HomogeneousTransform linuxcnc_genser_dh_transform(double a, double alpha, double d, double theta) { + const double sth = std::sin(theta); + const double cth = std::cos(theta); + const double sal = std::sin(alpha); + const double cal = std::cos(alpha); + + HomogeneousTransform transform{}; + transform.rot.x.x = cth; + transform.rot.y.x = -sth; + transform.rot.z.x = 0.0; + transform.rot.x.y = sth * cal; + transform.rot.y.y = cth * cal; + transform.rot.z.y = -sal; + transform.rot.x.z = sth * sal; + transform.rot.y.z = cth * sal; + transform.rot.z.z = cal; + transform.tran.x = a; + transform.tran.y = -sal * d; + transform.tran.z = cal * d; + return transform; +} + +HomogeneousTransform identity_transform() { + HomogeneousTransform transform{}; + transform.rot.x.x = 1.0; + transform.rot.y.y = 1.0; + transform.rot.z.z = 1.0; + return transform; +} + +HomogeneousTransform linuxcnc_genser_build_pose(const double joint_radians[6], + const LinuxCncGenserParameters ¶meters) { + HomogeneousTransform total = identity_transform(); + for (int i = 0; i < 6; ++i) { + const HomogeneousTransform link = + linuxcnc_genser_dh_transform(parameters.a[i], + parameters.alpha[i], + parameters.d[i], + joint_radians[i]); + total = multiply_transform(total, link); + } + return total; +} + +// Source: linuxcnc/src/libnml/posemath/gomath.c +// Mirrors go_pose_inv(). +HomogeneousTransform linuxcnc_pose_inverse(const HomogeneousTransform &pose) { + HomogeneousTransform inverse{}; + const Quaternion q = linuxcnc_matrix_to_quaternion(pose.rot); + const Quaternion q_inv = linuxcnc_quaternion_inverse(q); + inverse.rot = linuxcnc_quaternion_to_matrix(q_inv); + const RtcpVector rotated = linuxcnc_quaternion_rotate_vector(q_inv, pose.tran); + inverse.tran.x = -rotated.x; + inverse.tran.y = -rotated.y; + inverse.tran.z = -rotated.z; + return inverse; +} + +bool invert_6x6(const double input[6][6], double inverse[6][6]) { + double lu[6][6] = {}; + double scratchrow[6] = {}; + int index[6] = {}; + double d = 1.0; + + // Source: linuxcnc/src/libnml/posemath/gomath.c + // Mirrors ludcmp(). + for (int i = 0; i < 6; ++i) { + double big = 0.0; + for (int j = 0; j < 6; ++j) { + lu[i][j] = input[i][j]; + const double temp = std::fabs(lu[i][j]); + if (temp > big) { + big = temp; + } + } + if (big < kGoSingularEpsilon) { + return false; + } + scratchrow[i] = 1.0 / big; + } + + for (int j = 0; j < 6; ++j) { + for (int i = 0; i < j; ++i) { + double sum = lu[i][j]; + for (int k = 0; k < i; ++k) { + sum -= lu[i][k] * lu[k][j]; + } + lu[i][j] = sum; + } + double big = 0.0; + int imax = 0; + for (int i = j; i < 6; ++i) { + double sum = lu[i][j]; + for (int k = 0; k < j; ++k) { + sum -= lu[i][k] * lu[k][j]; + } + lu[i][j] = sum; + const double dum = scratchrow[i] * std::fabs(sum); + if (dum >= big) { + big = dum; + imax = i; + } + } + if (j != imax) { + for (int k = 0; k < 6; ++k) { + const double dum = lu[imax][k]; + lu[imax][k] = lu[j][k]; + lu[j][k] = dum; + } + d = -d; + scratchrow[imax] = scratchrow[j]; + } + index[j] = imax; + if (std::fabs(lu[j][j]) < kGoSingularEpsilon) { + return false; + } + if (j != 5) { + const double dum = 1.0 / lu[j][j]; + for (int i = j + 1; i < 6; ++i) { + lu[i][j] *= dum; + } + } + } + (void)d; + + // Source: linuxcnc/src/libnml/posemath/gomath.c + // Mirrors go_matrix_inv() and lubksb(). + for (int col = 0; col < 6; ++col) { + double v[6] = {}; + v[col] = 1.0; + int ii = -1; + for (int i = 0; i < 6; ++i) { + const int ip = index[i]; + double sum = v[ip]; + v[ip] = v[i]; + if (ii != -1) { + for (int j = ii; j <= i - 1; ++j) { + sum -= lu[i][j] * v[j]; + } + } else if (sum) { + ii = i; + } + v[i] = sum; + } + for (int i = 5; i >= 0; --i) { + double sum = v[i]; + for (int j = i + 1; j < 6; ++j) { + sum -= lu[i][j] * v[j]; + } + if (std::fabs(lu[i][i]) < kGoSingularEpsilon) { + return false; + } + v[i] = sum / lu[i][i]; + } + for (int row = 0; row < 6; ++row) { + inverse[row][col] = v[row]; + } + } + return true; +} + +void multiply_6x6_vector(const double matrix[6][6], const double vector[6], double output[6]) { + // Source: linuxcnc/src/libnml/posemath/gomath.c + // Mirrors go_matrix_vector_mult(). + for (int row = 0; row < 6; ++row) { + output[row] = 0.0; + for (int col = 0; col < 6; ++col) { + output[row] += matrix[row][col] * vector[col]; + } + } +} + +bool linuxcnc_genser_compute_jfwd(const double joint_radians[6], + const LinuxCncGenserParameters ¶meters, + double jfwd[6][6], + HomogeneousTransform *t_l_0) { + // Source: linuxcnc/src/emc/kinematics/genserfuncs.c + // Mirrors compute_jfwd() for six DH angular joints. + RtcpVector jv[6] = {}; + RtcpVector jw[6] = {}; + jw[0].z = 1.0; + + HomogeneousTransform pose = + linuxcnc_genser_dh_transform(parameters.a[0], + parameters.alpha[0], + parameters.d[0], + joint_radians[0]); + *t_l_0 = pose; + + for (int col = 1; col < 6; ++col) { + pose = linuxcnc_genser_dh_transform(parameters.a[col], + parameters.alpha[col], + parameters.d[col], + joint_radians[col]); + const Quaternion q = linuxcnc_matrix_to_quaternion(pose.rot); + const RotationMatrix r_i_ip1 = linuxcnc_quaternion_to_matrix(linuxcnc_quaternion_inverse(q)); + + RtcpVector scratch[6] = {}; + for (int i = 0; i < 6; ++i) { + scratch[i] = add_vector(jv[i], cross_vector(jw[i], pose.tran)); + jv[i] = rotate_vector(r_i_ip1, scratch[i]); + jw[i] = rotate_vector(r_i_ip1, jw[i]); + } + jv[col] = {}; + jw[col] = {0.0, 0.0, 1.0}; + *t_l_0 = multiply_transform(*t_l_0, pose); + } + + const RotationMatrix r_inv = t_l_0->rot; + for (int col = 0; col < 6; ++col) { + jv[col] = rotate_vector(r_inv, jv[col]); + jw[col] = rotate_vector(r_inv, jw[col]); + jfwd[0][col] = jv[col].x; + jfwd[1][col] = jv[col].y; + jfwd[2][col] = jv[col].z; + jfwd[3][col] = jw[col].x; + jfwd[4][col] = jw[col].y; + jfwd[5][col] = jw[col].z; + } + return true; +} + +bool linuxcnc_genser_compute_jinv(const double jfwd[6][6], double jinv[6][6]) { + // Source: linuxcnc/src/emc/kinematics/genserfuncs.c + // compute_jinv() uses go_matrix_inv() directly for square 6x6 Jacobians. + return invert_6x6(jfwd, jinv); +} + } // namespace -RtcpVector rtcp_rotate_abc_degrees(RtcpVector vector, double a_deg, double b_deg, double c_deg) { - vector = rotate_x(vector, radians(a_deg)); - vector = rotate_y(vector, radians(b_deg)); - vector = rotate_z(vector, radians(c_deg)); - return vector; +LinuxCncAxisJoints linuxcnc_identity_default_joints() { + return {}; } -RtcpVector rtcp_tool_vector_from_pose(const CncSimPose &pose, double tool_length) { - return rtcp_rotate_abc_degrees({0.0, 0.0, -tool_length}, pose.a, pose.b, pose.c); +// Source: linuxcnc/src/emc/kinematics/kins_util.c +// Mirrors identityKinematicsForward() with default XYZABCUVW coordinate mapping. +CncSimPose linuxcnc_identity_forward(const LinuxCncAxisJoints &joints) { + CncSimPose pose{}; + pose.x = joints.x; + pose.y = joints.y; + pose.z = joints.z; + pose.a = joints.a; + pose.b = joints.b; + pose.c = joints.c; + pose.u = joints.u; + pose.v = joints.v; + pose.w = joints.w; + return pose; } -CncSimPose rtcp_pivot_from_tool_tip(const CncSimPose &tool_tip, double tool_length) { - const RtcpVector tool = rtcp_tool_vector_from_pose(tool_tip, tool_length); - CncSimPose pivot = tool_tip; - pivot.x = tool_tip.x - tool.x; - pivot.y = tool_tip.y - tool.y; - pivot.z = tool_tip.z - tool.z; - return pivot; +// Source: linuxcnc/src/emc/kinematics/kins_util.c +// Mirrors identityKinematicsInverse() with default XYZABCUVW coordinate mapping. +LinuxCncAxisJoints linuxcnc_identity_inverse(const CncSimPose &pose) { + LinuxCncAxisJoints joints{}; + joints.x = pose.x; + joints.y = pose.y; + joints.z = pose.z; + joints.a = pose.a; + joints.b = pose.b; + joints.c = pose.c; + joints.u = pose.u; + joints.v = pose.v; + joints.w = pose.w; + return joints; } -CncSimPose rtcp_tool_tip_from_pivot(const CncSimPose &pivot, double tool_length) { - const RtcpVector tool = rtcp_tool_vector_from_pose(pivot, tool_length); - CncSimPose tool_tip = pivot; - tool_tip.x = pivot.x + tool.x; - tool_tip.y = pivot.y + tool.y; - tool_tip.z = pivot.z + tool.z; - return tool_tip; +// Source: linuxcnc/src/emc/kinematics/trivkins.c +// kinematicsForward()/kinematicsInverse() delegate to identityKinematics*(). + +// Source: linuxcnc/src/emc/kinematics/rotatekins.c +// Mirrors kinematicsForward(). +CncSimPose linuxcnc_rotatekins_forward(const LinuxCncAxisJoints &joints) { + const double c_rad = -radians(joints.c); + CncSimPose pose{}; + pose.x = joints.x * std::cos(c_rad) - joints.y * std::sin(c_rad); + pose.y = joints.x * std::sin(c_rad) + joints.y * std::cos(c_rad); + pose.z = joints.z; + pose.a = joints.a; + pose.b = joints.b; + pose.c = joints.c; + pose.u = joints.u; + pose.v = joints.v; + pose.w = joints.w; + return pose; +} + +// Source: linuxcnc/src/emc/kinematics/rotatekins.c +// Mirrors kinematicsInverse(). +LinuxCncAxisJoints linuxcnc_rotatekins_inverse(const CncSimPose &pose) { + const double c_rad = radians(pose.c); + LinuxCncAxisJoints joints{}; + joints.x = pose.x * std::cos(c_rad) - pose.y * std::sin(c_rad); + joints.y = pose.x * std::sin(c_rad) + pose.y * std::cos(c_rad); + joints.z = pose.z; + joints.a = pose.a; + joints.b = pose.b; + joints.c = pose.c; + joints.u = pose.u; + joints.v = pose.v; + joints.w = pose.w; + return joints; +} + +// Source: linuxcnc/src/emc/kinematics/5axiskins.c +// Mirrors s2r(), fiveaxis_KinematicsForward(). +CncSimPose linuxcnc_5axis_forward(const LinuxCncFiveAxisJoints &joints, double pivot_length) { + const RtcpVector radius = spherical_to_cartesian(pivot_length + joints.w, + joints.c, + 180.0 - joints.b); + CncSimPose pose{}; + pose.x = joints.x + radius.x; + pose.y = joints.y + radius.y; + pose.z = joints.z + pivot_length + radius.z; + pose.b = joints.b; + pose.c = joints.c; + pose.w = joints.w; + return pose; +} + +// Source: linuxcnc/src/emc/kinematics/5axiskins.c +// Mirrors s2r(), fiveaxis_KinematicsInverse(). +LinuxCncFiveAxisJoints linuxcnc_5axis_inverse(const CncSimPose &pose, double pivot_length) { + const RtcpVector radius = spherical_to_cartesian(pivot_length + pose.w, + pose.c, + 180.0 - pose.b); + LinuxCncFiveAxisJoints joints{}; + joints.x = pose.x - radius.x; + joints.y = pose.y - radius.y; + joints.z = pose.z - pivot_length - radius.z; + joints.b = pose.b; + joints.c = pose.c; + joints.w = pose.w; + return joints; +} + +// Source defaults: linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.ini +LinuxCncXyzbcTrtParameters linuxcnc_xyzbc_trt_default_parameters(double tool_offset) { + LinuxCncXyzbcTrtParameters parameters{}; + parameters.x_offset = -20.0; + parameters.y_offset = 0.0; + parameters.z_offset = -15.0; + parameters.tool_offset = tool_offset; + parameters.conventional_directions = false; + return parameters; +} + +// Source: linuxcnc/src/emc/kinematics/trtfuncs.c +// Mirrors xyzbcKinematicsForward(). +CncSimPose linuxcnc_xyzbc_trt_forward(const LinuxCncFiveAxisJoints &joints, + const LinuxCncXyzbcTrtParameters ¶meters) { + const double x_rot_point = parameters.x_rot_point; + const double y_rot_point = parameters.y_rot_point; + const double z_rot_point = parameters.z_rot_point; + const double dx = parameters.x_offset; + const double dz = parameters.z_offset + parameters.tool_offset; + const double b_rad = radians(joints.b); + const double c_rad = radians(joints.c); + const double con = parameters.conventional_directions ? 1.0 : -1.0; + + CncSimPose pose{}; + pose.x = +std::cos(c_rad) * std::cos(b_rad) * (joints.x - dx - x_rot_point) + - con * std::sin(c_rad) * (joints.y - y_rot_point) + + con * std::cos(c_rad) * std::sin(b_rad) * (joints.z - dz - z_rot_point) + + std::cos(c_rad) * dx + + x_rot_point; + + pose.y = +con * std::sin(c_rad) * std::cos(b_rad) * (joints.x - dx - x_rot_point) + + std::cos(c_rad) * (joints.y - y_rot_point) + + std::sin(c_rad) * std::sin(b_rad) * (joints.z - dz - z_rot_point) + + con * std::sin(c_rad) * dx + + y_rot_point; + + pose.z = -con * std::sin(b_rad) * (joints.x - dx - x_rot_point) + + std::cos(b_rad) * (joints.z - dz - z_rot_point) + + dz + + z_rot_point; + + pose.b = joints.b; + pose.c = joints.c; + return pose; +} + +// Source: linuxcnc/src/emc/kinematics/trtfuncs.c +// Mirrors xyzbcKinematicsInverse(). +LinuxCncFiveAxisJoints linuxcnc_xyzbc_trt_inverse(const CncSimPose &pose, + const LinuxCncXyzbcTrtParameters ¶meters) { + const double x_rot_point = parameters.x_rot_point; + const double y_rot_point = parameters.y_rot_point; + const double z_rot_point = parameters.z_rot_point; + const double dx = parameters.x_offset; + const double dz = parameters.z_offset + parameters.tool_offset; + const double b_rad = radians(pose.b); + const double c_rad = radians(pose.c); + const double dpx = -std::cos(b_rad) * dx + std::sin(b_rad) * dz + dx; + const double dpz = -std::sin(b_rad) * dx - std::cos(b_rad) * dz + dz; + const double con = parameters.conventional_directions ? 1.0 : -1.0; + + LinuxCncFiveAxisJoints joints{}; + joints.x = +std::cos(c_rad) * std::cos(b_rad) * (pose.x - x_rot_point) + + con * std::sin(c_rad) * std::cos(b_rad) * (pose.y - y_rot_point) + - con * std::sin(b_rad) * (pose.z - z_rot_point) + + dpx + + x_rot_point; + + joints.y = -con * std::sin(c_rad) * (pose.x - x_rot_point) + + std::cos(c_rad) * (pose.y - y_rot_point) + + y_rot_point; + + joints.z = +con * std::cos(c_rad) * std::sin(b_rad) * (pose.x - x_rot_point) + + std::sin(c_rad) * std::sin(b_rad) * (pose.y - y_rot_point) + + std::cos(b_rad) * (pose.z - z_rot_point) + + dpz + + z_rot_point; + + joints.b = pose.b; + joints.c = pose.c; + return joints; +} + +// Source: linuxcnc/src/emc/kinematics/trtfuncs.c +// Mirrors xyzacKinematicsForward(). +CncSimPose linuxcnc_xyzac_trt_forward(const LinuxCncFiveAxisJoints &joints, + const LinuxCncXyzbcTrtParameters ¶meters) { + const double x_rot_point = parameters.x_rot_point; + const double y_rot_point = parameters.y_rot_point; + const double z_rot_point = parameters.z_rot_point; + const double dy = parameters.y_offset; + const double dz = parameters.z_offset + parameters.tool_offset; + const double a_rad = radians(joints.a); + const double c_rad = radians(joints.c); + const double con = parameters.conventional_directions ? 1.0 : -1.0; + + CncSimPose pose{}; + pose.x = +std::cos(c_rad) * (joints.x - x_rot_point) + - con * std::sin(c_rad) * std::cos(a_rad) * (joints.y - dy - y_rot_point) + + std::sin(c_rad) * std::sin(a_rad) * (joints.z - dz - z_rot_point) + - con * std::sin(c_rad) * dy + + x_rot_point; + + pose.y = +con * std::sin(c_rad) * (joints.x - x_rot_point) + + std::cos(c_rad) * std::cos(a_rad) * (joints.y - dy - y_rot_point) + - con * std::cos(c_rad) * std::sin(a_rad) * (joints.z - dz - z_rot_point) + + std::cos(c_rad) * dy + + y_rot_point; + + pose.z = +con * std::sin(a_rad) * (joints.y - dy - y_rot_point) + + std::cos(a_rad) * (joints.z - dz - z_rot_point) + + dz + + z_rot_point; + + pose.a = joints.a; + pose.c = joints.c; + return pose; +} + +// Source: linuxcnc/src/emc/kinematics/trtfuncs.c +// Mirrors xyzacKinematicsInverse(). +LinuxCncFiveAxisJoints linuxcnc_xyzac_trt_inverse(const CncSimPose &pose, + const LinuxCncXyzbcTrtParameters ¶meters) { + const double x_rot_point = parameters.x_rot_point; + const double y_rot_point = parameters.y_rot_point; + const double z_rot_point = parameters.z_rot_point; + const double dy = parameters.y_offset; + const double dz = parameters.z_offset + parameters.tool_offset; + const double a_rad = radians(pose.a); + const double c_rad = radians(pose.c); + const double con = parameters.conventional_directions ? 1.0 : -1.0; + + LinuxCncFiveAxisJoints joints{}; + joints.x = +std::cos(c_rad) * (pose.x - x_rot_point) + + con * std::sin(c_rad) * (pose.y - y_rot_point) + + x_rot_point; + + joints.y = -con * std::sin(c_rad) * std::cos(a_rad) * (pose.x - x_rot_point) + + std::cos(c_rad) * std::cos(a_rad) * (pose.y - y_rot_point) + + con * std::sin(a_rad) * (pose.z - z_rot_point) + - std::cos(a_rad) * dy + - con * std::sin(a_rad) * dz + + dy + + y_rot_point; + + joints.z = +std::sin(c_rad) * std::sin(a_rad) * (pose.x - x_rot_point) + - con * std::cos(c_rad) * std::sin(a_rad) * (pose.y - y_rot_point) + + std::cos(a_rad) * (pose.z - z_rot_point) + + con * std::sin(a_rad) * dy + - std::cos(a_rad) * dz + + dz + + z_rot_point; + + joints.a = pose.a; + joints.c = pose.c; + return joints; +} + +// Source: linuxcnc/src/emc/kinematics/userkfuncs.c +// userkKinematicsForward() delegates to identityKinematicsForward(), +// implemented in linuxcnc/src/emc/kinematics/kins_util.c. +CncSimPose linuxcnc_xyzbc_userk_forward(const LinuxCncFiveAxisJoints &joints) { + CncSimPose pose{}; + pose.x = joints.x; + pose.y = joints.y; + pose.z = joints.z; + pose.b = joints.b; + pose.c = joints.c; + return pose; +} + +// Source: linuxcnc/src/emc/kinematics/userkfuncs.c +// userkKinematicsInverse() delegates to identityKinematicsInverse(), +// implemented in linuxcnc/src/emc/kinematics/kins_util.c. +LinuxCncFiveAxisJoints linuxcnc_xyzbc_userk_inverse(const CncSimPose &pose) { + LinuxCncFiveAxisJoints joints{}; + joints.x = pose.x; + joints.y = pose.y; + joints.z = pose.z; + joints.b = pose.b; + joints.c = pose.c; + return joints; +} + +// Source defaults: linuxcnc/src/emc/kinematics/scarakins.c +LinuxCncScaraParameters linuxcnc_scara_default_parameters() { + LinuxCncScaraParameters parameters{}; + parameters.d1 = 490.0; + parameters.d2 = 340.0; + parameters.d3 = 50.0; + parameters.d4 = 250.0; + parameters.d5 = 50.0; + parameters.d6 = 50.0; + return parameters; +} + +// Source: linuxcnc/src/emc/kinematics/scarakins.c +// Mirrors scaraKinematicsForward(). +CncSimPose linuxcnc_scara_forward(const LinuxCncScaraJoints &joints, + const LinuxCncScaraParameters ¶meters, + int *iflags) { + double a0 = radians(joints.j0); + double a1 = radians(joints.j1); + double a3 = radians(joints.j3); + + a1 = a1 + a0; + a3 = a3 + a1; + + CncSimPose pose{}; + pose.x = parameters.d2 * std::cos(a0) + + parameters.d4 * std::cos(a1) + + parameters.d6 * std::cos(a3); + pose.y = parameters.d2 * std::sin(a0) + + parameters.d4 * std::sin(a1) + + parameters.d6 * std::sin(a3); + pose.z = parameters.d1 + parameters.d3 - joints.j2 - parameters.d5; + pose.c = degrees(a3); + pose.a = joints.j4; + pose.b = joints.j5; + + if (iflags) { + *iflags = joints.j1 < 90.0 ? 1 : 0; + } + return pose; +} + +// Source: linuxcnc/src/emc/kinematics/scarakins.c +// Mirrors scaraKinematicsInverse(). +LinuxCncScaraJoints linuxcnc_scara_inverse(const CncSimPose &pose, + const LinuxCncScaraParameters ¶meters, + int iflags) { + const double a3 = radians(pose.c); + const double xt = pose.x - parameters.d6 * std::cos(a3); + const double yt = pose.y - parameters.d6 * std::sin(a3); + const double rsq = xt * xt + yt * yt; + double cc = (rsq - parameters.d2 * parameters.d2 - parameters.d4 * parameters.d4) / + (2.0 * parameters.d2 * parameters.d4); + if (cc < -1.0) { + cc = -1.0; + } + if (cc > 1.0) { + cc = 1.0; + } + + double q1 = std::acos(cc); + if (iflags) { + q1 = -q1; + } + + double q0 = std::atan2(yt, xt); + const double local_xt = parameters.d2 + parameters.d4 * std::cos(q1); + const double local_yt = parameters.d4 * std::sin(q1); + q0 = q0 - std::atan2(local_yt, local_xt); + + LinuxCncScaraJoints joints{}; + joints.j0 = degrees(q0); + joints.j1 = degrees(q1); + joints.j2 = parameters.d1 + parameters.d3 - parameters.d5 - pose.z; + joints.j3 = pose.c - (joints.j0 + joints.j1); + joints.j4 = pose.a; + joints.j5 = pose.b; + return joints; +} + +LinuxCncXyzabTdrParameters linuxcnc_xyzab_tdr_default_parameters(double tool_offset_z) { + LinuxCncXyzabTdrParameters parameters{}; + parameters.tool_offset_z = tool_offset_z; + return parameters; +} + +// Source: linuxcnc/src/hal/components/xyzab_tdr_kins.comp +// Mirrors kinematicsForward() switchkins type 0. +CncSimPose linuxcnc_xyzab_tdr_identity_forward(const LinuxCncAxisJoints &joints) { + CncSimPose pose{}; + pose.x = joints.x; + pose.y = joints.y; + pose.z = joints.z; + pose.a = joints.a; + pose.b = joints.b; + return pose; +} + +// Source: linuxcnc/src/hal/components/xyzab_tdr_kins.comp +// Mirrors kinematicsInverse() switchkins type 0. +LinuxCncAxisJoints linuxcnc_xyzab_tdr_identity_inverse(const CncSimPose &pose) { + LinuxCncAxisJoints joints{}; + joints.x = pose.x; + joints.y = pose.y; + joints.z = pose.z; + joints.a = pose.a; + joints.b = pose.b; + return joints; +} + +// Source: linuxcnc/src/hal/components/xyzab_tdr_kins.comp +// Mirrors kinematicsForward() switchkins type 1. +CncSimPose linuxcnc_xyzab_tdr_tcp_forward(const LinuxCncAxisJoints &joints, + const LinuxCncXyzabTdrParameters ¶meters) { + const double x_rot_point = parameters.x_rot_point; + const double y_rot_point = parameters.y_rot_point; + const double z_rot_point = parameters.z_rot_point; + const double dz = parameters.z_offset; + const double dt = parameters.tool_offset_z; + const double sa = std::sin(radians(joints.a)); + const double ca = std::cos(radians(joints.a)); + const double sb = std::sin(radians(joints.b)); + const double cb = std::cos(radians(joints.b)); + const double px = joints.x - x_rot_point; + const double py = joints.y - y_rot_point; + const double pz = joints.z - z_rot_point - dt; + + CncSimPose pose{}; + pose.x = cb * px + sb * pz + x_rot_point; + pose.y = sa * sb * px + ca * py - cb * sa * pz + sa * dz + y_rot_point; + pose.z = -ca * sb * px + sa * py + ca * cb * pz - ca * dz + z_rot_point + dz + dt; + pose.a = joints.a; + pose.b = joints.b; + return pose; +} + +// Source: linuxcnc/src/hal/components/xyzab_tdr_kins.comp +// Mirrors kinematicsInverse() switchkins type 1. +LinuxCncAxisJoints linuxcnc_xyzab_tdr_tcp_inverse(const CncSimPose &pose, + const LinuxCncXyzabTdrParameters ¶meters) { + const double x_rot_point = parameters.x_rot_point; + const double y_rot_point = parameters.y_rot_point; + const double z_rot_point = parameters.z_rot_point; + const double dx = parameters.x_offset; + const double dz = parameters.z_offset; + const double dt = parameters.tool_offset_z; + const double sa = std::sin(radians(pose.a)); + const double ca = std::cos(radians(pose.a)); + const double sb = std::sin(radians(pose.b)); + const double cb = std::cos(radians(pose.b)); + const double qx = pose.x - x_rot_point - dx; + const double qy = pose.y - y_rot_point; + const double qz = pose.z - z_rot_point - dz - dt; + + LinuxCncAxisJoints joints{}; + joints.x = cb * qx + sa * sb * qy - ca * sb * qz + cb * dx - sb * dz + x_rot_point; + joints.y = ca * qy + sa * qz + y_rot_point; + joints.z = sb * qx - sa * cb * qy + ca * cb * qz + sb * dx + cb * dz + z_rot_point + dt; + joints.a = pose.a; + joints.b = pose.b; + return joints; +} + +LinuxCncPumaParameters linuxcnc_puma_default_parameters() { + LinuxCncPumaParameters parameters{}; + parameters.a2 = 300.0; + parameters.a3 = 50.0; + parameters.d3 = 70.0; + parameters.d4 = 400.0; + parameters.d6 = 70.0; + return parameters; +} + +// Source: linuxcnc/src/emc/kinematics/pumakins.h +constexpr int kPumaShoulderRight = 0x01; +constexpr int kPumaElbowDown = 0x02; +constexpr int kPumaWristFlip = 0x04; +constexpr int kPumaSingular = 0x08; +constexpr int kPumaReach = 0x01; +constexpr double kPumaSingularFuzz = 0.000001; +constexpr double kPumaFlagFuzz = 0.000001; + +// Source: linuxcnc/src/emc/kinematics/pumakins.c +// Mirrors pumaKinematicsForward(), with posemath RPY conversion from _posemath.c. +CncSimPose linuxcnc_puma_forward(const LinuxCncAxisJoints &joints, + const LinuxCncPumaParameters ¶meters, + int *iflags) { + const double s1 = std::sin(radians(joints.x)); + const double s2 = std::sin(radians(joints.y)); + const double s3 = std::sin(radians(joints.z)); + const double s4 = std::sin(radians(joints.a)); + const double s5 = std::sin(radians(joints.b)); + const double s6 = std::sin(radians(joints.c)); + const double c1 = std::cos(radians(joints.x)); + const double c2 = std::cos(radians(joints.y)); + const double c3 = std::cos(radians(joints.z)); + const double c4 = std::cos(radians(joints.a)); + const double c5 = std::cos(radians(joints.b)); + const double c6 = std::cos(radians(joints.c)); + + const double s23 = c2 * s3 + s2 * c3; + const double c23 = c2 * c3 - s2 * s3; + + double t1 = c4 * c5 * c6 - s4 * s6; + double t2 = s23 * s5 * c6; + double t3 = s4 * c5 * c6 + c4 * s6; + double t4 = c23 * t1 - t2; + double t5 = c23 * s5 * c6; + + RotationMatrix hom_rot{}; + hom_rot.x.x = c1 * t4 + s1 * t3; + hom_rot.x.y = s1 * t4 - c1 * t3; + hom_rot.x.z = -s23 * t1 - t5; + + t1 = -c4 * c5 * s6 - s4 * c6; + t2 = s23 * s5 * s6; + t3 = c4 * c6 - s4 * c5 * s6; + t4 = c23 * t1 + t2; + t5 = c23 * s5 * s6; + + hom_rot.y.x = c1 * t4 + s1 * t3; + hom_rot.y.y = s1 * t4 - c1 * t3; + hom_rot.y.z = -s23 * t1 + t5; + + t1 = c23 * c4 * s5 + s23 * c5; + hom_rot.z.x = -c1 * t1 - s1 * s4 * s5; + hom_rot.z.y = -s1 * t1 + c1 * s4 * s5; + hom_rot.z.z = s23 * c4 * s5 - c23 * c5; + + t1 = parameters.a2 * c2 + parameters.a3 * c23 - parameters.d4 * s23; + RtcpVector tran{}; + tran.x = c1 * t1 - parameters.d3 * s1; + tran.y = s1 * t1 + parameters.d3 * c1; + tran.z = -parameters.a3 * s23 - parameters.a2 * s2 - parameters.d4 * c23; + + int flags = 0; + const double sum_sq = tran.x * tran.x + tran.y * tran.y - parameters.d3 * parameters.d3; + const double k = (sum_sq + tran.z * tran.z - parameters.a2 * parameters.a2 - + parameters.a3 * parameters.a3 - parameters.d4 * parameters.d4) / + (2.0 * parameters.a2); + if (std::fabs(radians(joints.x) - std::atan2(tran.y, tran.x) + + std::atan2(parameters.d3, -std::sqrt(sum_sq))) < kPumaFlagFuzz) { + flags |= kPumaShoulderRight; + } + if (std::fabs(radians(joints.z) - std::atan2(parameters.a3, parameters.d4) + + std::atan2(k, -std::sqrt(parameters.a3 * parameters.a3 + + parameters.d4 * parameters.d4 - k * k))) < kPumaFlagFuzz) { + flags |= kPumaElbowDown; + } + t1 = -hom_rot.z.x * s1 + hom_rot.z.y * c1; + t2 = -hom_rot.z.x * c1 * c23 - hom_rot.z.y * s1 * c23 + hom_rot.z.z * s23; + if (std::fabs(t1) < kPumaSingularFuzz && std::fabs(t2) < kPumaSingularFuzz) { + flags |= kPumaSingular; + } else if (!(std::fabs(radians(joints.a) - std::atan2(t1, t2)) < kPumaFlagFuzz)) { + flags |= kPumaWristFlip; + } + if (iflags) { + *iflags = flags; + } + + tran.x = tran.x + hom_rot.z.x * parameters.d6; + tran.y = tran.y + hom_rot.z.y * parameters.d6; + tran.z = tran.z + hom_rot.z.z * parameters.d6; + return posemath_matrix_to_pose(tran, hom_rot); +} + +// Source: linuxcnc/src/emc/kinematics/pumakins.c +// Mirrors pumaKinematicsInverse(), with posemath RPY conversion from _posemath.c. +LinuxCncAxisJoints linuxcnc_puma_inverse(const CncSimPose &pose, + const LinuxCncAxisJoints ¤t_joints, + const LinuxCncPumaParameters ¶meters, + int iflags, + int *fflags) { + if (fflags) { + *fflags = 0; + } + + RotationMatrix hom_rot = posemath_rpy_to_matrix(radians(pose.a), + radians(pose.b), + radians(pose.c)); + const double px = pose.x - parameters.d6 * hom_rot.z.x; + const double py = pose.y - parameters.d6 * hom_rot.z.y; + const double pz = pose.z - parameters.d6 * hom_rot.z.z; + + const double sum_sq = px * px + py * py - parameters.d3 * parameters.d3; + double th1 = 0.0; + if (iflags & kPumaShoulderRight) { + th1 = std::atan2(py, px) - std::atan2(parameters.d3, -std::sqrt(sum_sq)); + } else { + th1 = std::atan2(py, px) - std::atan2(parameters.d3, std::sqrt(sum_sq)); + } + const double s1 = std::sin(th1); + const double c1 = std::cos(th1); + + const double k = (sum_sq + pz * pz - parameters.a2 * parameters.a2 - + parameters.a3 * parameters.a3 - parameters.d4 * parameters.d4) / + (2.0 * parameters.a2); + double th3 = 0.0; + if (iflags & kPumaElbowDown) { + th3 = std::atan2(parameters.a3, parameters.d4) - + std::atan2(k, -std::sqrt(parameters.a3 * parameters.a3 + + parameters.d4 * parameters.d4 - k * k)); + } else { + th3 = std::atan2(parameters.a3, parameters.d4) - + std::atan2(k, std::sqrt(parameters.a3 * parameters.a3 + + parameters.d4 * parameters.d4 - k * k)); + } + + const double s3 = std::sin(th3); + const double c3 = std::cos(th3); + double t1 = (-parameters.a3 - parameters.a2 * c3) * pz + + (c1 * px + s1 * py) * (parameters.a2 * s3 - parameters.d4); + double t2 = (parameters.a2 * s3 - parameters.d4) * pz + + (parameters.a3 + parameters.a2 * c3) * (c1 * px + s1 * py); + const double t3 = pz * pz + (c1 * px + s1 * py) * (c1 * px + s1 * py); + const double th23 = std::atan2(t1, t2); + const double th2 = th23 - th3; + const double s23 = t1 / t3; + const double c23 = t2 / t3; + + t1 = -hom_rot.z.x * s1 + hom_rot.z.y * c1; + t2 = -hom_rot.z.x * c1 * c23 - hom_rot.z.y * s1 * c23 + hom_rot.z.z * s23; + double th4 = 0.0; + if (std::fabs(t1) < kPumaSingularFuzz && std::fabs(t2) < kPumaSingularFuzz) { + if (fflags) { + *fflags |= kPumaReach; + } + th4 = radians(current_joints.a); + } else { + th4 = std::atan2(t1, t2); + } + const double s4 = std::sin(th4); + const double c4 = std::cos(th4); + + const double s5 = hom_rot.z.z * (s23 * c4) - + hom_rot.z.x * (c1 * c23 * c4 + s1 * s4) - + hom_rot.z.y * (s1 * c23 * c4 - c1 * s4); + const double c5 = -hom_rot.z.x * (c1 * s23) - + hom_rot.z.y * (s1 * s23) - + hom_rot.z.z * c23; + double th5 = std::atan2(s5, c5); + + const double s6 = hom_rot.x.z * (s23 * s4) - + hom_rot.x.x * (c1 * c23 * s4 - s1 * c4) - + hom_rot.x.y * (s1 * c23 * s4 + c1 * c4); + const double c6 = hom_rot.x.x * ((c1 * c23 * c4 + s1 * s4) * c5 - c1 * s23 * s5) + + hom_rot.x.y * ((s1 * c23 * c4 - c1 * s4) * c5 - s1 * s23 * s5) - + hom_rot.x.z * (s23 * c4 * c5 + c23 * s5); + double th6 = std::atan2(s6, c6); + + if (iflags & kPumaWristFlip) { + th4 = th4 + kPi; + th5 = -th5; + th6 = th6 + kPi; + } + + LinuxCncAxisJoints joints{}; + joints.x = degrees(th1); + joints.y = degrees(th2); + joints.z = degrees(th3); + joints.a = degrees(th4); + joints.b = degrees(th5); + joints.c = degrees(th6); + return joints; +} + +LinuxCncGenserParameters linuxcnc_genser_puma560_parameters() { + LinuxCncGenserParameters parameters{}; + // Source: linuxcnc/configs/sim/axis/vismach/puma/puma560_dh.hal. + parameters.a[0] = 0.0; + parameters.a[1] = 0.0; + parameters.a[2] = 17.0; + parameters.a[3] = 0.0; + parameters.a[4] = 0.0; + parameters.a[5] = 0.0; + + parameters.alpha[0] = 0.0; + parameters.alpha[1] = 1.570796326; + parameters.alpha[2] = 0.0; + parameters.alpha[3] = 1.570796326; + parameters.alpha[4] = -1.570796326; + parameters.alpha[5] = 1.570796326; + + parameters.d[0] = 26.45; + parameters.d[1] = -5.5; + parameters.d[2] = 0.0; + parameters.d[3] = 17.05; + parameters.d[4] = 0.0; + parameters.d[5] = 2.2; + return parameters; +} + +// Source: linuxcnc/src/emc/kinematics/genserfuncs.c +// Mirrors genserKinematicsForward() -> genser_kin_fwd() -> go_link_pose_build() +// for the six angular DH joints used by the LinuxCNC puma560 M428 configuration. +CncSimPose linuxcnc_genser_forward(const LinuxCncAxisJoints &joints, + const LinuxCncGenserParameters ¶meters) { + const double raw_joints[6] = { + joints.x, + joints.y, + joints.z, + joints.a, + joints.b, + joints.c, + }; + double joint_radians[6] = {}; + for (int i = 0; i < 6; ++i) { + joint_radians[i] = radians(raw_joints[i]); + if (i != 0 && parameters.unrotate[i]) { + joint_radians[i] -= parameters.unrotate[i] * joint_radians[i - 1]; + } + } + + const HomogeneousTransform total = linuxcnc_genser_build_pose(joint_radians, parameters); + return posemath_matrix_to_pose(total.tran, total.rot); +} + +// Source: linuxcnc/src/emc/kinematics/genserfuncs.c +// Mirrors genserKinematicsInverse() for the six angular DH joints used by +// the LinuxCNC puma560 M428 configuration. +LinuxCncAxisJoints linuxcnc_genser_inverse(const CncSimPose &pose, + const LinuxCncAxisJoints &joint_estimate, + const LinuxCncGenserParameters ¶meters, + int *iterations, + int max_iterations) { + const double estimate_degrees[6] = { + joint_estimate.x, + joint_estimate.y, + joint_estimate.z, + joint_estimate.a, + joint_estimate.b, + joint_estimate.c, + }; + double jest[6] = {}; + for (int link = 0; link < 6; ++link) { + jest[link] = radians(estimate_degrees[link]); + } + + HomogeneousTransform target{}; + target.tran = {pose.x, pose.y, pose.z}; + target.rot = posemath_rpy_to_matrix(radians(pose.a), radians(pose.b), radians(pose.c)); + + int completed_iterations = 0; + for (; completed_iterations < max_iterations; ++completed_iterations) { + double jfwd[6][6] = {}; + double jinv[6][6] = {}; + HomogeneousTransform t_l_0{}; + if (!linuxcnc_genser_compute_jfwd(jest, parameters, jfwd, &t_l_0) || + !linuxcnc_genser_compute_jinv(jfwd, jinv)) { + break; + } + + const HomogeneousTransform pest = linuxcnc_genser_build_pose(jest, parameters); + const HomogeneousTransform pestinv = linuxcnc_pose_inverse(pest); + const HomogeneousTransform tdelta = multiply_transform(pestinv, target); + + const Quaternion pest_quat = linuxcnc_matrix_to_quaternion(pest.rot); + RtcpVector cart = linuxcnc_quaternion_rotate_vector(pest_quat, tdelta.tran); + double dvw[6] = {}; + dvw[0] = cart.x; + dvw[1] = cart.y; + dvw[2] = cart.z; + + const Quaternion tdelta_quat = linuxcnc_matrix_to_quaternion(tdelta.rot); + const RtcpVector rvec = linuxcnc_quaternion_to_rvec(tdelta_quat); + cart = linuxcnc_quaternion_rotate_vector(pest_quat, rvec); + dvw[3] = cart.x; + dvw[4] = cart.y; + dvw[5] = cart.z; + + double dj[6] = {}; + multiply_6x6_vector(jinv, dvw, dj); + + int smalls = 0; + for (int link = 0; link < 6; ++link) { + if (go_small(dj[link])) { + ++smalls; + } + } + if (smalls == 6) { + if (iterations) { + *iterations = completed_iterations; + } + LinuxCncAxisJoints joints{}; + double out_degrees[6] = {}; + for (int link = 0; link < 6; ++link) { + out_degrees[link] = degrees(jest[link]); + if (link != 0 && parameters.unrotate[link]) { + out_degrees[link] += parameters.unrotate[link] * out_degrees[link - 1]; + } + } + joints.x = out_degrees[0]; + joints.y = out_degrees[1]; + joints.z = out_degrees[2]; + joints.a = out_degrees[3]; + joints.b = out_degrees[4]; + joints.c = out_degrees[5]; + joints.u = pose.u; + joints.v = pose.v; + joints.w = pose.w; + return joints; + } + for (int link = 0; link < 6; ++link) { + jest[link] += dj[link]; + } + } + + if (iterations) { + *iterations = completed_iterations; + } + LinuxCncAxisJoints joints{}; + joints.x = degrees(jest[0]); + joints.y = degrees(jest[1]); + joints.z = degrees(jest[2]); + joints.a = degrees(jest[3]); + joints.b = degrees(jest[4]); + joints.c = degrees(jest[5]); + joints.u = pose.u; + joints.v = pose.v; + joints.w = pose.w; + return joints; } diff --git a/core/src/rtcp_kinematics.h b/core/src/rtcp_kinematics.h index ebfa405..232aef3 100644 --- a/core/src/rtcp_kinematics.h +++ b/core/src/rtcp_kinematics.h @@ -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 ¶meters); +LinuxCncFiveAxisJoints linuxcnc_xyzbc_trt_inverse(const CncSimPose &pose, + const LinuxCncXyzbcTrtParameters ¶meters); +CncSimPose linuxcnc_xyzac_trt_forward(const LinuxCncFiveAxisJoints &joints, + const LinuxCncXyzbcTrtParameters ¶meters); +LinuxCncFiveAxisJoints linuxcnc_xyzac_trt_inverse(const CncSimPose &pose, + const LinuxCncXyzbcTrtParameters ¶meters); +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 ¶meters, + int *iflags); +LinuxCncScaraJoints linuxcnc_scara_inverse(const CncSimPose &pose, + const LinuxCncScaraParameters ¶meters, + 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 ¶meters); +LinuxCncAxisJoints linuxcnc_xyzab_tdr_tcp_inverse(const CncSimPose &pose, + const LinuxCncXyzabTdrParameters ¶meters); + +LinuxCncPumaParameters linuxcnc_puma_default_parameters(); +CncSimPose linuxcnc_puma_forward(const LinuxCncAxisJoints &joints, + const LinuxCncPumaParameters ¶meters, + int *iflags); +LinuxCncAxisJoints linuxcnc_puma_inverse(const CncSimPose &pose, + const LinuxCncAxisJoints ¤t_joints, + const LinuxCncPumaParameters ¶meters, + int iflags, + int *fflags); + +LinuxCncGenserParameters linuxcnc_genser_puma560_parameters(); +CncSimPose linuxcnc_genser_forward(const LinuxCncAxisJoints &joints, + const LinuxCncGenserParameters ¶meters); +LinuxCncAxisJoints linuxcnc_genser_inverse(const CncSimPose &pose, + const LinuxCncAxisJoints &joint_estimate, + const LinuxCncGenserParameters ¶meters, + int *iterations, + int max_iterations = 100); diff --git a/core/src/simulator_gcode_controls.cpp b/core/src/simulator_gcode_controls.cpp index 950386e..51a2980 100644 --- a/core/src/simulator_gcode_controls.cpp +++ b/core/src/simulator_gcode_controls.cpp @@ -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(std::toupper(static_cast(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(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(std::floor(value)); + const double fraction = value - *integer; + if (fraction > 0.9999) { + *integer = static_cast(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(std::floor(scaled)); + const double fraction = scaled - parsed; + if (fraction > 0.999) { + parsed = static_cast(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(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(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(stripped[i]))) { + ++i; + } + if (stripped[i] != 'N') { + *without_n = stripped; + return true; + } + ++i; + const size_t integer_start = i; + while (std::isdigit(static_cast(stripped[i]))) { + ++i; + } + if (i == integer_start) { + return false; + } + if (stripped[i] == '.') { + ++i; + const size_t fraction_start = i; + while (std::isdigit(static_cast(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(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 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(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(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(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(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 *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 *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); } diff --git a/core/src/simulator_gcode_controls.h b/core/src/simulator_gcode_controls.h index 6572fcb..6660418 100644 --- a/core/src/simulator_gcode_controls.h +++ b/core/src/simulator_gcode_controls.h @@ -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 *actions); +bool parse_simulator_gcode_control_line(const std::string &line, + bool block_delete, + std::vector *actions); void emit_simulator_gcode_control_action(CanonEventSink &sink, const SimulatorGcodeControlAction &action, int line); diff --git a/core/src/smoke_gcode_parser.cpp b/core/src/smoke_gcode_parser.cpp index 50b3e67..a7565c4 100644 --- a/core/src/smoke_gcode_parser.cpp +++ b/core/src/smoke_gcode_parser.cpp @@ -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 &words, char letter, int *value); +bool evaluate_expression(const std::string &text, + const std::vector &call_stack, + const std::unordered_map ¶meters, + const std::unordered_map &named_parameters, + double *value, + std::string *error); template bool contains_int(const std::array &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 &call_stack, + const std::unordered_map ¶meters, + const std::unordered_map &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(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(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(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(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 seen_letters{}; + std::array seen_letter_values{}; const char *text = line.c_str(); for (size_t i = 0; text[i] != '\0';) { if (std::isspace(static_cast(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(letter - 'A'); if (strict && seen_letters[letter_index]) { - if (error) { - *error = "Multiple " + std::string(1, static_cast(std::tolower(static_cast(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(std::tolower(static_cast(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 &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 &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 &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 &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 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 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; } diff --git a/core/tests/canon_event_sink_smoke.cpp b/core/tests/canon_event_sink_smoke.cpp index 8827996..5189282 100644 --- a/core/tests/canon_event_sink_smoke.cpp +++ b/core/tests/canon_event_sink_smoke.cpp @@ -1,5 +1,6 @@ #include "canon_event_sink.h" +#include #include #include @@ -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 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 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; } - diff --git a/core/tests/cnc_sim_api_linuxcnc_rs274_smoke.cpp b/core/tests/cnc_sim_api_linuxcnc_rs274_smoke.cpp index 6db7401..392832b 100644 --- a/core/tests/cnc_sim_api_linuxcnc_rs274_smoke.cpp +++ b/core/tests/cnc_sim_api_linuxcnc_rs274_smoke.cpp @@ -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 *>(user_data); events->push_back(*event); @@ -54,6 +58,34 @@ bool saw_comment_state(const std::vector &events, } // namespace int main() { + { + std::vector 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; diff --git a/core/tests/cnc_sim_api_smoke.cpp b/core/tests/cnc_sim_api_smoke.cpp index 9b861bf..eb96bf2 100644 --- a/core/tests/cnc_sim_api_smoke.cpp +++ b/core/tests/cnc_sim_api_smoke.cpp @@ -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 *>(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 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 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" + "# = 5420\n" + "G41.1 D1\n" + "#2 = #[#]\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 #[#] 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# *>(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); diff --git a/core/tests/linuxcnc_gees_table_smoke.cpp b/core/tests/linuxcnc_gees_table_smoke.cpp index b93ff65..0d31d74 100644 --- a/core/tests/linuxcnc_gees_table_smoke.cpp +++ b/core/tests/linuxcnc_gees_table_smoke.cpp @@ -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 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(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(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"); diff --git a/core/tests/rtcp_kinematics_smoke.cpp b/core/tests/rtcp_kinematics_smoke.cpp index 75298fa..c1e3561 100644 --- a/core/tests/rtcp_kinematics_smoke.cpp +++ b/core/tests/rtcp_kinematics_smoke.cpp @@ -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; } diff --git a/core/tests/simulator_gcode_controls_smoke.cpp b/core/tests/simulator_gcode_controls_smoke.cpp index a694ce2..1de06f9 100644 --- a/core/tests/simulator_gcode_controls_smoke.cpp +++ b/core/tests/simulator_gcode_controls_smoke.cpp @@ -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"); diff --git a/docs/architecture.md b/docs/architecture.md index 1bcd1b3..5160c5a 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -18,6 +18,9 @@ The event callback emits normalized `CncSimEvent` records. JavaScript can transf ## LinuxCNC integration plan +Functional behavior must come from LinuxCNC source code, not independently +designed project code. See `docs/linuxcnc-source-policy.md`. + 1. Build a native `CanonEventSink` that implements all functions declared in `canon.hh`. 2. Link the sink with `src/emc/rs274ngc` and `src/emc/nml_intf` instead of the task controller. 3. Stub or remove Python remap support for the first browser target. @@ -58,10 +61,18 @@ Feature parity needs more than G-code parsing: ## Five-axis and RTCP -The first RTCP implementation is a geometry kernel, not the full LinuxCNC motion -controller. It treats programmed XYZ as the tool-center point, applies A/B/C -orientation to a local tool vector, and computes the compensated pivot/spindle -point needed to keep the tool tip fixed. +RTCP pivot events are emitted only for known LinuxCNC switchkins modes. These +paths must use formulas copied from LinuxCNC source code, not independently +derived geometry. The current source mapping is: + +- `M428` / `XYZBC_TRT`: `linuxcnc/src/emc/kinematics/trtfuncs.c`, + `xyzbcKinematicsForward()` and `xyzbcKinematicsInverse()` +- `M430` / `USERK_IDENTITY`: `linuxcnc/src/emc/kinematics/userkfuncs.c`, + delegating to `identityKinematicsForward()` and + `identityKinematicsInverse()` in `kins_util.c` +- The retained `5axiskins` helpers map to + `linuxcnc/src/emc/kinematics/5axiskins.c`, + `fiveaxis_KinematicsForward()` and `fiveaxis_KinematicsInverse()` RTCP is opt-in through config JSON. When enabled, the original motion events remain programmed tool-tip motion and an additional `rtcp-pivot` event is emitted @@ -74,7 +85,5 @@ Current files: - `core/src/rtcp_kinematics.cpp` - `core/tests/rtcp_kinematics_smoke.cpp` -The next integration step is to add machine configuration for rotary topology, -pivot offsets, tool length sources, and rotation order, then apply RTCP -compensation while converting LinuxCNC Canon motion events into renderable -machine/tool-tip trajectories. +The next integration step is to add more LinuxCNC source-backed kinematics for +additional switchkins or machine topologies. diff --git a/docs/linuxcnc-porting.md b/docs/linuxcnc-porting.md index 147a438..bdf4767 100644 --- a/docs/linuxcnc-porting.md +++ b/docs/linuxcnc-porting.md @@ -19,7 +19,7 @@ g++ -std=c++17 -I core/include core/src/cnc_sim_api.cpp core/tests/cnc_sim_api_s Status: in progress. -This parser only exists to test the ABI and UI before Emscripten and LinuxCNC are wired in. It currently handles: +This parser only exists to test the ABI and UI before Emscripten and LinuxCNC are wired in. It is not a production feature implementation. Do not extend it with independently designed G-code behavior; when a behavior needs to change, first locate the LinuxCNC source implementation and either route to that source-backed backend or port the corresponding source logic. It currently handles: - multiple G/M words on one line - `G0`, `G1`, `G2`, `G3` @@ -39,7 +39,7 @@ This parser only exists to test the ABI and UI before Emscripten and LinuxCNC ar - canned cycles `G73`, `G74`, `G81`, `G82`, `G83`, `G84`, `G85`, `G86`, `G87`, `G88`, `G89` with `G80`, `G98`, `G99`, `L` - coordinate offset events for `G10 L2`, `G10 L20`, `G54`-`G59.3`, `G52`, `G92`, `G92.1`, `G92.2`, and `G92.3` -It is not the production interpreter. +It is not the production interpreter. Any remaining smoke-only behavior is technical debt to replace with LinuxCNC source-backed code. ## Step 2: Canon event sink diff --git a/docs/linuxcnc-source-policy.md b/docs/linuxcnc-source-policy.md new file mode 100644 index 0000000..283d5fe --- /dev/null +++ b/docs/linuxcnc-source-policy.md @@ -0,0 +1,32 @@ +# LinuxCNC Source Policy + +The simulator must not grow independently designed functional behavior. +Functional behavior must come from LinuxCNC source code. + +Allowed project code: + +- Thin adapters between LinuxCNC source code and the simulator C API. +- Platform shims needed to compile LinuxCNC code for native tests or wasm. +- Event serialization, test fixtures, build scripts, and documentation. +- Temporary smoke scaffolding only when it is explicitly marked as temporary. + +Not allowed as final behavior: + +- Hand-written G-code interpretation semantics. +- Hand-derived kinematics, RTCP, coordinate transform, cutter compensation, canned cycle, modal, parameter, or expression behavior. +- Behavior copied from documentation, controller intuition, or examples without checking the LinuxCNC implementation. + +Required workflow for every functional change: + +1. Identify the LinuxCNC source file and function that defines the behavior. +2. Add or update a test that demonstrates the LinuxCNC behavior. +3. Port, wrap, or route to that LinuxCNC implementation. +4. Record the source file/function in code comments or nearby documentation when the mapping is not obvious. +5. Run `./test-native.sh` and `./test-linuxcnc-source-link.sh`. + +Current temporary exceptions: + +- The smoke parser exists only to exercise the public API and UI while the LinuxCNC RS274 backend is being wired in. +- RTCP pivot behavior is limited to LinuxCNC source-backed switchkins paths; unsupported switchkins types must not fall back to hand-derived geometry. + +Both exceptions should shrink over time. New functional behavior should not be added to them unless it is directly copied from, or routed to, LinuxCNC source code. diff --git a/linuxcnc-rs274-source-files.txt b/linuxcnc-rs274-source-files.txt index e393519..1a58e95 100644 --- a/linuxcnc-rs274-source-files.txt +++ b/linuxcnc-rs274-source-files.txt @@ -34,4 +34,19 @@ binding:src/emc/rs274ngc/pyparamclass.cc:Boost.Python bindings; exclude from was tooldata:src/emc/tooldata/tooldata_common.cc:shared tool table parser/format logic tooldata:src/emc/tooldata/tooldata_mmap.cc:native mmap implementation; replace for wasm tooldata:src/emc/tooldata/tooldata_db.cc:external database integration; exclude from wasm core - +header:src/emc/rs274ngc/array1.hh:LinuxCNC rs274 header tracked for full interpreter source coverage +header:src/emc/rs274ngc/boost_pyenum_macros.hh:LinuxCNC rs274 Boost.Python enum helper header +header:src/emc/rs274ngc/interp_array_types.hh:LinuxCNC rs274 block array type definitions +header:src/emc/rs274ngc/interp_base.hh:LinuxCNC rs274 base interpreter declarations +header:src/emc/rs274ngc/interp_fwd.hh:LinuxCNC rs274 forward declarations +header:src/emc/rs274ngc/interp_inspection.hh:LinuxCNC rs274 inspection declarations +header:src/emc/rs274ngc/interp_internal.hh:LinuxCNC rs274 internal interpreter declarations +header:src/emc/rs274ngc/interp_parameter_def.hh:LinuxCNC rs274 parameter definitions +header:src/emc/rs274ngc/interp_python.hh:LinuxCNC rs274 Python/remap declarations +header:src/emc/rs274ngc/interp_queue.hh:LinuxCNC rs274 queue declarations +header:src/emc/rs274ngc/modal_state.hh:LinuxCNC rs274 modal state declarations +header:src/emc/rs274ngc/paramclass.hh:LinuxCNC rs274 parameter class declarations +header:src/emc/rs274ngc/rs274ngc.hh:LinuxCNC rs274 public interface header +header:src/emc/rs274ngc/rs274ngc_interp.hh:LinuxCNC rs274 interpreter class header +header:src/emc/rs274ngc/rs274ngc_return.hh:LinuxCNC rs274 return code header +header:src/emc/rs274ngc/units.h:LinuxCNC rs274 units helper header diff --git a/linuxcnc-rs274-wasm-source-files.txt b/linuxcnc-rs274-wasm-source-files.txt index b42e38b..0773882 100644 --- a/linuxcnc-rs274-wasm-source-files.txt +++ b/linuxcnc-rs274-wasm-source-files.txt @@ -26,3 +26,19 @@ core:src/emc/rs274ngc/rs274ngc_pre.cc:Interp implementation covered by rs274ngc_ core:src/emc/tooldata/tooldata_common.cc:shared tool table parser/format logic linked with wasm-safe mmap backend core:src/emc/ini/inifile.cc:INI reader needed by rs274ngc_pre source-link probe blocked:src/emc/tooldata/tooldata_mmap.cc:native mmap implementation +header:src/emc/rs274ngc/array1.hh:LinuxCNC rs274 header tracked for full interpreter source coverage +blocked-header:src/emc/rs274ngc/boost_pyenum_macros.hh:Boost.Python helper header, tracked but not wasm-safe API surface +header:src/emc/rs274ngc/interp_array_types.hh:LinuxCNC rs274 block array type definitions +header:src/emc/rs274ngc/interp_base.hh:LinuxCNC rs274 base interpreter declarations +header:src/emc/rs274ngc/interp_fwd.hh:LinuxCNC rs274 forward declarations +header:src/emc/rs274ngc/interp_inspection.hh:LinuxCNC rs274 inspection declarations +header:src/emc/rs274ngc/interp_internal.hh:LinuxCNC rs274 internal interpreter declarations +header:src/emc/rs274ngc/interp_parameter_def.hh:LinuxCNC rs274 parameter definitions +header:src/emc/rs274ngc/interp_python.hh:LinuxCNC rs274 Python/remap declarations, compile-satisfied by wasm shims +header:src/emc/rs274ngc/interp_queue.hh:LinuxCNC rs274 queue declarations +header:src/emc/rs274ngc/modal_state.hh:LinuxCNC rs274 modal state declarations +header:src/emc/rs274ngc/paramclass.hh:LinuxCNC rs274 parameter class declarations +header:src/emc/rs274ngc/rs274ngc.hh:LinuxCNC rs274 public interface header +header:src/emc/rs274ngc/rs274ngc_interp.hh:LinuxCNC rs274 interpreter class header +header:src/emc/rs274ngc/rs274ngc_return.hh:LinuxCNC rs274 return code header +header:src/emc/rs274ngc/units.h:LinuxCNC rs274 units helper header diff --git a/list-cmake-set-plus-append-sources.sh b/list-cmake-set-plus-append-sources.sh new file mode 100755 index 0000000..8d8d298 --- /dev/null +++ b/list-cmake-set-plus-append-sources.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")" + +if [[ "$#" -lt 2 || "$#" -gt 3 ]]; then + echo "usage: $0 [relative-prefix]" >&2 + exit 1 +fi + +set_name=$1 +append_name=$2 +relative_prefix=${3:-} + +awk -v set_name="$set_name" -v append_name="$append_name" -v relative_prefix="$relative_prefix" ' + $0 ~ "^[[:space:]]*set\\(" set_name "([[:space:]]|$)" { + in_set = 1 + next + } + in_set && /^[[:space:]]*\)/ { + in_set = 0 + next + } + $0 ~ "^[[:space:]]*list\\(APPEND " append_name "([[:space:]]|$)" { + in_append = 1 + next + } + in_append && /^[[:space:]]*\)/ { + in_append = 0 + next + } + in_set || in_append { + line = $0 + sub(/^[[:space:]]*/, "", line) + sub(/[[:space:]]*$/, "", line) + if (line == "") { + next + } + sub(/^\$\{CMAKE_CURRENT_SOURCE_DIR\}\//, "core/", line) + if (line !~ /^core\// && relative_prefix != "") { + line = relative_prefix line + } + print line + } +' core/CMakeLists.txt diff --git a/list-cmake-set-sources.sh b/list-cmake-set-sources.sh new file mode 100755 index 0000000..da03b6f --- /dev/null +++ b/list-cmake-set-sources.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")" + +if [[ "$#" -lt 1 || "$#" -gt 2 ]]; then + echo "usage: $0 [relative-prefix]" >&2 + exit 1 +fi + +set_name=$1 +relative_prefix=${2:-} + +awk -v set_name="$set_name" -v relative_prefix="$relative_prefix" ' + $0 ~ "^[[:space:]]*set\\(" set_name "([[:space:]]|$)" { + in_list = 1 + next + } + in_list && /^[[:space:]]*\)/ { + exit + } + in_list { + line = $0 + sub(/^[[:space:]]*/, "", line) + sub(/[[:space:]]*$/, "", line) + if (line == "") { + next + } + sub(/^\$\{CMAKE_CURRENT_SOURCE_DIR\}\//, "core/", line) + if (line !~ /^core\// && relative_prefix != "") { + line = relative_prefix line + } + print line + } +' core/CMakeLists.txt diff --git a/list-cnc-sim-core-sources.sh b/list-cnc-sim-core-sources.sh index bc22776..d0a40f5 100755 --- a/list-cnc-sim-core-sources.sh +++ b/list-cnc-sim-core-sources.sh @@ -5,40 +5,7 @@ cd "$(dirname "$0")" filter=${1:-} -awk ' - /set\(cnc_sim_core_sources/ { - in_list = 1 - next - } - in_list && /^[[:space:]]*\)/ { - exit - } - in_list { - line = $0 - sub(/^[[:space:]]*/, "", line) - sub(/[[:space:]]*$/, "", line) - if (line == "") { - next - } - print "core/" line - } -' core/CMakeLists.txt | while IFS= read -r source; do - if [[ ! -f "$source" ]]; then - echo "missing cnc sim core source: $source" >&2 - exit 1 - fi - if [[ -n "$filter" && "$(basename "$source")" != "$filter" ]]; then - continue - fi - printf '%s\n' "$source" -done | { - found=0 - while IFS= read -r source; do - found=1 - printf '%s\n' "$source" - done - if [[ -n "$filter" && "$found" -eq 0 ]]; then - echo "missing cnc sim core source in CMake list: $filter" >&2 - exit 1 - fi -} +./list-cmake-set-sources.sh cnc_sim_core_sources core/ | ./list-filtered-existing-paths.sh \ + "$filter" \ + "missing cnc sim core source" \ + "missing cnc sim core source in CMake list" diff --git a/list-filtered-existing-paths.sh b/list-filtered-existing-paths.sh new file mode 100755 index 0000000..96357aa --- /dev/null +++ b/list-filtered-existing-paths.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")" + +if [[ "$#" -lt 3 ]]; then + echo "usage: $0 [missing-path-hint]" >&2 + exit 1 +fi + +filter=$1 +missing_path_message=$2 +missing_filter_message=$3 +missing_path_hint=${4:-} + +found=0 +while IFS= read -r path; do + [[ -z "$path" ]] && continue + if [[ ! -f "$path" ]]; then + echo "$missing_path_message: $path" >&2 + if [[ -n "$missing_path_hint" ]]; then + echo "$missing_path_hint" >&2 + fi + exit 1 + fi + if [[ -n "$filter" && "$(basename "$path")" != "$filter" ]]; then + continue + fi + found=1 + printf '%s\n' "$path" +done + +if [[ -n "$filter" && "$found" -eq 0 ]]; then + echo "$missing_filter_message: $filter" >&2 + exit 1 +fi diff --git a/list-linuxcnc-bridge-smoke-project-sources.sh b/list-linuxcnc-bridge-smoke-project-sources.sh index 19e0592..6e9ff4f 100755 --- a/list-linuxcnc-bridge-smoke-project-sources.sh +++ b/list-linuxcnc-bridge-smoke-project-sources.sh @@ -5,26 +5,4 @@ cd "$(dirname "$0")" filter=${1:-} -sources=( - core/src/canon_event_sink.cpp - core/src/linuxcnc_canon_bridge.cpp - core/src/rtcp_kinematics.cpp -) - -found=0 -for source in "${sources[@]}"; do - if [[ ! -f "$source" ]]; then - echo "missing LinuxCNC bridge smoke project source: $source" >&2 - exit 1 - fi - if [[ -n "$filter" && "$(basename "$source")" != "$filter" ]]; then - continue - fi - found=1 - printf '%s\n' "$source" -done - -if [[ -n "$filter" && "$found" -eq 0 ]]; then - echo "missing LinuxCNC bridge smoke project source in list: $filter" >&2 - exit 1 -fi +./list-project-source-set.sh linuxcnc-bridge-smoke "$filter" diff --git a/list-linuxcnc-manifest-sources.sh b/list-linuxcnc-manifest-sources.sh new file mode 100755 index 0000000..6ea8322 --- /dev/null +++ b/list-linuxcnc-manifest-sources.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")" + +manifest=${1:?manifest required} +filter_group=${2:?filter group required} +output_mode=${3:?output mode required} +allowed_groups=${4:?allowed groups required} +duplicate_groups=${5:?duplicate groups required} +missing_source_message=${6:?missing source message required} +linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} + +contains_group() { + local groups=$1 + local group=$2 + [[ ",$groups," == *",$group,"* ]] +} + +checks_duplicate_group() { + local groups=$1 + local group=$2 + [[ "$groups" == "all" ]] || contains_group "$groups" "$group" +} + +LINUXCNC_ROOT="$linuxcnc_root" ./check-linuxcnc-inputs.sh "$manifest" + +if [[ "$filter_group" != "all" ]] && ! contains_group "$allowed_groups" "$filter_group"; then + echo "unknown manifest source filter: $filter_group" >&2 + exit 1 +fi + +case "$output_mode" in + relative|full) + ;; + *) + echo "unknown manifest source output mode: $output_mode" >&2 + exit 1 + ;; +esac + +seen_sources=() +while IFS=: read -r group path note; do + case "$group" in + ""|\#*) + continue + ;; + esac + + if ! contains_group "$allowed_groups" "$group"; then + echo "unknown manifest group: $group" >&2 + exit 1 + fi + + full_path=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-files.sh \ + --missing-message "$missing_source_message" \ + "$path") + if checks_duplicate_group "$duplicate_groups" "$group"; then + if printf '%s\n' "${seen_sources[@]}" | grep -Fx -- "$path" >/dev/null; then + echo "duplicate manifest source: $path" >&2 + exit 1 + fi + seen_sources+=("$path") + fi + + if [[ "$filter_group" == "all" || "$filter_group" == "$group" ]]; then + if [[ "$output_mode" == "full" ]]; then + printf '%s\n' "$full_path" + else + printf '%s\n' "$path" + fi + fi +done < "$manifest" diff --git a/list-linuxcnc-native-linkflags.sh b/list-linuxcnc-native-linkflags.sh new file mode 100755 index 0000000..8f5a79c --- /dev/null +++ b/list-linuxcnc-native-linkflags.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")" + +linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} + +LINUXCNC_ROOT="$linuxcnc_root" ./check-linuxcnc-inputs.sh --root-only + +printf '%s\n' \ + -L "$linuxcnc_root/lib" \ + -Wl,-rpath,"$linuxcnc_root/lib" diff --git a/list-linuxcnc-rs274-api-project-sources.sh b/list-linuxcnc-rs274-api-project-sources.sh index 149d66f..cc25782 100755 --- a/list-linuxcnc-rs274-api-project-sources.sh +++ b/list-linuxcnc-rs274-api-project-sources.sh @@ -5,49 +5,7 @@ cd "$(dirname "$0")" filter=${1:-} -awk ' - /set\(cnc_sim_core_sources/ { - in_core = 1 - next - } - in_core && /^[[:space:]]*\)/ { - in_core = 0 - next - } - /list\(APPEND cnc_sim_core_sources/ { - in_backend_sources = 1 - next - } - in_backend_sources && /^[[:space:]]*\)/ { - in_backend_sources = 0 - next - } - in_core || in_backend_sources { - line = $0 - sub(/^[[:space:]]*/, "", line) - sub(/[[:space:]]*$/, "", line) - if (line == "") { - next - } - print "core/" line - } -' core/CMakeLists.txt | while IFS= read -r source; do - if [[ ! -f "$source" ]]; then - echo "missing LinuxCNC rs274 API project source: $source" >&2 - exit 1 - fi - if [[ -n "$filter" && "$(basename "$source")" != "$filter" ]]; then - continue - fi - printf '%s\n' "$source" -done | { - found=0 - while IFS= read -r source; do - found=1 - printf '%s\n' "$source" - done - if [[ -n "$filter" && "$found" -eq 0 ]]; then - echo "missing LinuxCNC rs274 API project source in CMake list: $filter" >&2 - exit 1 - fi -} +./list-cmake-set-plus-append-sources.sh cnc_sim_core_sources cnc_sim_core_sources core/ | ./list-filtered-existing-paths.sh \ + "$filter" \ + "missing LinuxCNC rs274 API project source" \ + "missing LinuxCNC rs274 API project source in CMake list" diff --git a/list-linuxcnc-rs274-dump-project-sources.sh b/list-linuxcnc-rs274-dump-project-sources.sh index 4cede5b..26a7218 100755 --- a/list-linuxcnc-rs274-dump-project-sources.sh +++ b/list-linuxcnc-rs274-dump-project-sources.sh @@ -5,29 +5,4 @@ cd "$(dirname "$0")" filter=${1:-} -sources=( - core/src/canon_event_sink.cpp - core/src/linuxcnc_canon_bridge.cpp - core/src/linuxcnc_tooldata_fixture.cpp - core/src/rtcp_kinematics.cpp - core/src/simulator_gcode_controls.cpp - core/tools/linuxcnc_rs274_dump.cpp -) - -found=0 -for source in "${sources[@]}"; do - if [[ ! -f "$source" ]]; then - echo "missing LinuxCNC rs274 dump project source: $source" >&2 - exit 1 - fi - if [[ -n "$filter" && "$(basename "$source")" != "$filter" ]]; then - continue - fi - found=1 - printf '%s\n' "$source" -done - -if [[ -n "$filter" && "$found" -eq 0 ]]; then - echo "missing LinuxCNC rs274 dump project source in list: $filter" >&2 - exit 1 -fi +./list-project-source-set.sh linuxcnc-rs274-dump "$filter" diff --git a/list-linuxcnc-source-common-cxxflags.sh b/list-linuxcnc-source-common-cxxflags.sh new file mode 100755 index 0000000..bebe13a --- /dev/null +++ b/list-linuxcnc-source-common-cxxflags.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")" + +linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} +include_python=0 +include_core_include=0 +include_core_src=0 +syntax_only=0 +emit_ulapi=1 + +while [[ "$#" -gt 0 ]]; do + case "$1" in + --python) + include_python=1 + shift + ;; + --core-include) + include_core_include=1 + shift + ;; + --core-src) + include_core_src=1 + shift + ;; + --syntax-only) + syntax_only=1 + shift + ;; + --no-ulapi) + emit_ulapi=0 + shift + ;; + --) + shift + break + ;; + *) + echo "unknown source cxxflag option: $1" >&2 + exit 1 + ;; + esac +done + +LINUXCNC_ROOT="$linuxcnc_root" ./check-linuxcnc-inputs.sh --root-only + +python_includes=() +if [[ "$include_python" -eq 1 ]]; then + python_include_output=$(python3.13-config --includes 2>/dev/null || python3-config --includes) + read -r -a python_includes <<<"$python_include_output" +fi + +printf '%s\n' \ + -std=c++17 \ + $([[ "$emit_ulapi" -eq 1 ]] && printf '%s\n' -DULAPI) \ + $([[ "$syntax_only" -eq 1 ]] && printf '%s\n' -fsyntax-only) \ + "${python_includes[@]}" \ + $([[ "$include_core_include" -eq 1 ]] && printf '%s\n' -I core/include) \ + $([[ "$include_core_src" -eq 1 ]] && printf '%s\n' -I core/src) \ + -I "$linuxcnc_root/src" \ + -I "$linuxcnc_root/src/emc" \ + -I "$linuxcnc_root/src/emc/nml_intf" \ + -I "$linuxcnc_root/src/emc/rs274ngc" \ + -I "$linuxcnc_root/src/emc/motion" \ + -I "$linuxcnc_root/src/emc/pythonplugin" \ + -I "$linuxcnc_root/include" diff --git a/list-linuxcnc-source-files.sh b/list-linuxcnc-source-files.sh index 2d3c063..db41f00 100755 --- a/list-linuxcnc-source-files.sh +++ b/list-linuxcnc-source-files.sh @@ -4,21 +4,43 @@ set -euo pipefail cd "$(dirname "$0")" linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} +missing_source_message="missing LinuxCNC source" -if [[ ! -d "$linuxcnc_root" ]]; then - echo "missing LinuxCNC root: $linuxcnc_root" >&2 - exit 1 -fi +LINUXCNC_ROOT="$linuxcnc_root" ./check-linuxcnc-inputs.sh --root-only + +while [[ "$#" -gt 0 ]]; do + case "$1" in + --missing-message) + if [[ "$#" -lt 2 ]]; then + echo "missing --missing-message value" >&2 + exit 1 + fi + missing_source_message=$2 + shift 2 + ;; + --) + shift + break + ;; + -*) + echo "unknown source file option: $1" >&2 + exit 1 + ;; + *) + break + ;; + esac +done if [[ "$#" -eq 0 ]]; then - echo "usage: $0 ..." >&2 + echo "usage: $0 [--missing-message ] ..." >&2 exit 1 fi for source in "$@"; do full_source="$linuxcnc_root/$source" if [[ ! -f "$full_source" ]]; then - echo "missing LinuxCNC source: $source" >&2 + echo "$missing_source_message: $source" >&2 exit 1 fi printf '%s\n' "$full_source" diff --git a/list-linuxcnc-source-manifest-sources.sh b/list-linuxcnc-source-manifest-sources.sh index da0cc30..d29b77e 100755 --- a/list-linuxcnc-source-manifest-sources.sh +++ b/list-linuxcnc-source-manifest-sources.sh @@ -6,67 +6,11 @@ cd "$(dirname "$0")" manifest=${1:-linuxcnc-rs274-source-files.txt} filter_group=${2:-core} output_mode=${3:-relative} -linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} -if [[ ! -f "$manifest" ]]; then - echo "missing manifest: $manifest" >&2 - exit 1 -fi - -if [[ ! -d "$linuxcnc_root" ]]; then - echo "missing LinuxCNC root: $linuxcnc_root" >&2 - exit 1 -fi - -case "$filter_group" in - core|binding|tooldata|all) - ;; - *) - echo "unknown manifest source filter: $filter_group" >&2 - exit 1 - ;; -esac - -case "$output_mode" in - relative|full) - ;; - *) - echo "unknown manifest source output mode: $output_mode" >&2 - exit 1 - ;; -esac - -seen_sources=() -while IFS=: read -r group path note; do - case "$group" in - ""|\#*) - continue - ;; - core|binding|tooldata) - ;; - *) - echo "unknown manifest group: $group" >&2 - exit 1 - ;; - esac - - if [[ ! -f "$linuxcnc_root/$path" ]]; then - echo "missing manifest file: $path" >&2 - exit 1 - fi - if [[ "$group" == "core" ]]; then - if printf '%s\n' "${seen_sources[@]}" | grep -Fx -- "$path" >/dev/null; then - echo "duplicate manifest source: $path" >&2 - exit 1 - fi - seen_sources+=("$path") - fi - - if [[ "$filter_group" == "all" || "$filter_group" == "$group" ]]; then - if [[ "$output_mode" == "full" ]]; then - printf '%s\n' "$linuxcnc_root/$path" - else - printf '%s\n' "$path" - fi - fi -done < "$manifest" +./list-linuxcnc-manifest-sources.sh \ + "$manifest" \ + "$filter_group" \ + "$output_mode" \ + core,header,binding,tooldata \ + core \ + "missing manifest file" diff --git a/list-linuxcnc-source-support-objects.sh b/list-linuxcnc-source-support-objects.sh index d660e20..53f95f5 100755 --- a/list-linuxcnc-source-support-objects.sh +++ b/list-linuxcnc-source-support-objects.sh @@ -6,10 +6,7 @@ cd "$(dirname "$0")" linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} filter=${1:-} -if [[ ! -d "$linuxcnc_root" ]]; then - echo "missing LinuxCNC root: $linuxcnc_root" >&2 - exit 1 -fi +LINUXCNC_ROOT="$linuxcnc_root" ./check-linuxcnc-inputs.sh --root-only objects=( src/objects/emc/rs274ngc/interpmodule.o @@ -24,22 +21,10 @@ objects=( src/objects/libnml/nml/stat_msg.o ) -found=0 for object in "${objects[@]}"; do - full_object="$linuxcnc_root/$object" - if [[ ! -f "$full_object" ]]; then - echo "missing LinuxCNC support object: $full_object" >&2 - echo "build LinuxCNC first or set LINUXCNC_ROOT to a built tree" >&2 - exit 1 - fi - if [[ -n "$filter" && "$(basename "$object")" != "$filter" ]]; then - continue - fi - found=1 - printf '%s\n' "$full_object" -done - -if [[ -n "$filter" && "$found" -eq 0 ]]; then - echo "missing LinuxCNC support object in list: $filter" >&2 - exit 1 -fi + printf '%s\n' "$linuxcnc_root/$object" +done | ./list-filtered-existing-paths.sh \ + "$filter" \ + "missing LinuxCNC support object" \ + "missing LinuxCNC support object in list" \ + "build LinuxCNC first or set LINUXCNC_ROOT to a built tree" diff --git a/list-linuxcnc-wasm-common-cxxflags.sh b/list-linuxcnc-wasm-common-cxxflags.sh new file mode 100755 index 0000000..2a108c8 --- /dev/null +++ b/list-linuxcnc-wasm-common-cxxflags.sh @@ -0,0 +1,117 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")" + +linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} +std_flag=-std=c++17 +include_core_include=0 +include_core_src=0 +include_tooldata=0 +include_wctype=0 +syntax_only=0 +function_sections=0 +data_sections=0 +profile=base + +while [[ "$#" -gt 0 ]]; do + case "$1" in + --profile) + if [[ "$#" -lt 2 ]]; then + echo "missing --profile value" >&2 + exit 1 + fi + profile=$2 + shift 2 + ;; + --std) + if [[ "$#" -lt 2 ]]; then + echo "missing --std value" >&2 + exit 1 + fi + std_flag=-std="$2" + shift 2 + ;; + --core-include) + include_core_include=1 + shift + ;; + --core-src) + include_core_src=1 + shift + ;; + --tooldata-include) + include_tooldata=1 + shift + ;; + --wctype) + include_wctype=1 + shift + ;; + --syntax-only) + syntax_only=1 + shift + ;; + --function-sections) + function_sections=1 + shift + ;; + --data-sections) + data_sections=1 + shift + ;; + --) + shift + break + ;; + *) + echo "unknown wasm cxxflag option: $1" >&2 + exit 1 + ;; + esac +done + +case "$profile" in + base|core20|core20-sections) + ;; + *) + echo "unknown wasm cxxflag profile: $profile" >&2 + exit 1 + ;; +esac + +case "$profile" in + core20|core20-sections) + std_flag=-std=c++20 + include_core_include=1 + include_core_src=1 + include_wctype=1 + ;; +esac + +case "$profile" in + core20-sections) + function_sections=1 + data_sections=1 + ;; +esac + +LINUXCNC_ROOT="$linuxcnc_root" ./check-linuxcnc-inputs.sh --root-only + +printf '%s\n' \ + "$std_flag" \ + -DULAPI \ + $([[ "$syntax_only" -eq 1 ]] && printf '%s\n' -fsyntax-only) \ + $([[ "$include_wctype" -eq 1 ]] && printf '%s\n' -include wctype.h) \ + $([[ "$function_sections" -eq 1 ]] && printf '%s\n' -ffunction-sections) \ + $([[ "$data_sections" -eq 1 ]] && printf '%s\n' -fdata-sections) \ + $([[ "$include_core_include" -eq 1 ]] && printf '%s\n' -I "$(pwd)/core/include") \ + $([[ "$include_core_src" -eq 1 ]] && printf '%s\n' -I "$(pwd)/core/src") \ + $([[ "$include_tooldata" -eq 1 ]] && printf '%s\n' -I "$linuxcnc_root/src/emc/tooldata") \ + -I "$(pwd)/core/wasm_shims" \ + -I "$linuxcnc_root/src" \ + -I "$linuxcnc_root/src/emc" \ + -I "$linuxcnc_root/src/emc/nml_intf" \ + -I "$linuxcnc_root/src/emc/rs274ngc" \ + -I "$linuxcnc_root/src/emc/motion" \ + -I "$linuxcnc_root/include" diff --git a/list-linuxcnc-wasm-manifest-sources.sh b/list-linuxcnc-wasm-manifest-sources.sh index c89404c..7802239 100755 --- a/list-linuxcnc-wasm-manifest-sources.sh +++ b/list-linuxcnc-wasm-manifest-sources.sh @@ -6,65 +6,11 @@ cd "$(dirname "$0")" manifest=${1:-linuxcnc-rs274-wasm-source-files.txt} filter_group=${2:-core} output_mode=${3:-relative} -linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} -if [[ ! -f "$manifest" ]]; then - echo "missing manifest: $manifest" >&2 - exit 1 -fi - -if [[ ! -d "$linuxcnc_root" ]]; then - echo "missing LinuxCNC root: $linuxcnc_root" >&2 - exit 1 -fi - -case "$filter_group" in - core|blocked|all) - ;; - *) - echo "unknown manifest source filter: $filter_group" >&2 - exit 1 - ;; -esac - -case "$output_mode" in - relative|full) - ;; - *) - echo "unknown manifest source output mode: $output_mode" >&2 - exit 1 - ;; -esac - -seen_sources=() -while IFS=: read -r group path note; do - case "$group" in - ""|\#*) - continue - ;; - core|blocked) - ;; - *) - echo "unknown manifest group: $group" >&2 - exit 1 - ;; - esac - - if [[ ! -f "$linuxcnc_root/$path" ]]; then - echo "missing manifest source: $path" >&2 - exit 1 - fi - if printf '%s\n' "${seen_sources[@]}" | grep -Fx -- "$path" >/dev/null; then - echo "duplicate manifest source: $path" >&2 - exit 1 - fi - seen_sources+=("$path") - - if [[ "$filter_group" == "all" || "$filter_group" == "$group" ]]; then - if [[ "$output_mode" == "full" ]]; then - printf '%s\n' "$linuxcnc_root/$path" - else - printf '%s\n' "$path" - fi - fi -done < "$manifest" +./list-linuxcnc-manifest-sources.sh \ + "$manifest" \ + "$filter_group" \ + "$output_mode" \ + core,header,blocked,blocked-header \ + all \ + "missing manifest source" diff --git a/list-linuxcnc-wasm-safe-project-sources.sh b/list-linuxcnc-wasm-safe-project-sources.sh index f12fefa..5d8ca84 100755 --- a/list-linuxcnc-wasm-safe-project-sources.sh +++ b/list-linuxcnc-wasm-safe-project-sources.sh @@ -5,41 +5,7 @@ cd "$(dirname "$0")" filter=${1:-} -awk ' - /set\(linuxcnc_rs274_wasm_safe_probe_project_sources/ { - in_list = 1 - next - } - in_list && /^[[:space:]]*\)/ { - exit - } - in_list { - line = $0 - sub(/^[[:space:]]*/, "", line) - sub(/[[:space:]]*$/, "", line) - if (line == "") { - next - } - sub(/^\$\{CMAKE_CURRENT_SOURCE_DIR\}\//, "core/", line) - print line - } -' core/CMakeLists.txt | while IFS= read -r source; do - if [[ ! -f "$source" ]]; then - echo "missing wasm-safe project source: $source" >&2 - exit 1 - fi - if [[ -n "$filter" && "$(basename "$source")" != "$filter" ]]; then - continue - fi - printf '%s\n' "$source" -done | { - found=0 - while IFS= read -r source; do - found=1 - printf '%s\n' "$source" - done - if [[ -n "$filter" && "$found" -eq 0 ]]; then - echo "missing wasm-safe project source in CMake list: $filter" >&2 - exit 1 - fi -} +./list-cmake-set-sources.sh linuxcnc_rs274_wasm_safe_probe_project_sources | ./list-filtered-existing-paths.sh \ + "$filter" \ + "missing wasm-safe project source" \ + "missing wasm-safe project source in CMake list" diff --git a/list-linuxcnc-wasm-safe-shims.sh b/list-linuxcnc-wasm-safe-shims.sh index 5feaf06..14950a5 100755 --- a/list-linuxcnc-wasm-safe-shims.sh +++ b/list-linuxcnc-wasm-safe-shims.sh @@ -5,41 +5,7 @@ cd "$(dirname "$0")" filter=${1:-} -awk ' - /set\(linuxcnc_wasm_safe_probe_shims/ { - in_list = 1 - next - } - in_list && /^[[:space:]]*\)/ { - exit - } - in_list { - line = $0 - sub(/^[[:space:]]*/, "", line) - sub(/[[:space:]]*$/, "", line) - if (line == "") { - next - } - sub(/^\$\{CMAKE_CURRENT_SOURCE_DIR\}\//, "core/", line) - print line - } -' core/CMakeLists.txt | while IFS= read -r source; do - if [[ ! -f "$source" ]]; then - echo "missing wasm-safe shim source: $source" >&2 - exit 1 - fi - if [[ -n "$filter" && "$(basename "$source")" != "$filter" ]]; then - continue - fi - printf '%s\n' "$source" -done | { - found=0 - while IFS= read -r source; do - found=1 - printf '%s\n' "$source" - done - if [[ -n "$filter" && "$found" -eq 0 ]]; then - echo "missing wasm-safe shim in CMake list: $filter" >&2 - exit 1 - fi -} +./list-cmake-set-sources.sh linuxcnc_wasm_safe_probe_shims | ./list-filtered-existing-paths.sh \ + "$filter" \ + "missing wasm-safe shim source" \ + "missing wasm-safe shim in CMake list" diff --git a/list-project-source-set.sh b/list-project-source-set.sh new file mode 100755 index 0000000..7d068ba --- /dev/null +++ b/list-project-source-set.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")" + +if [[ "$#" -lt 1 || "$#" -gt 2 ]]; then + echo "usage: $0 [filter]" >&2 + exit 1 +fi + +source_set=$1 +filter=${2:-} + +case "$source_set" in + linuxcnc-rs274-dump) + sources=( + core/src/canon_event_sink.cpp + core/src/linuxcnc_canon_bridge.cpp + core/src/linuxcnc_tooldata_fixture.cpp + core/src/rtcp_kinematics.cpp + core/src/simulator_gcode_controls.cpp + core/tools/linuxcnc_rs274_dump.cpp + ) + missing_source_message="missing LinuxCNC rs274 dump project source" + missing_filter_message="missing LinuxCNC rs274 dump project source in list" + ;; + linuxcnc-bridge-smoke) + sources=( + core/src/canon_event_sink.cpp + core/src/linuxcnc_canon_bridge.cpp + core/src/rtcp_kinematics.cpp + ) + missing_source_message="missing LinuxCNC bridge smoke project source" + missing_filter_message="missing LinuxCNC bridge smoke project source in list" + ;; + *) + echo "unknown project source set: $source_set" >&2 + exit 1 + ;; +esac + +printf '%s\n' "${sources[@]}" | ./list-filtered-existing-paths.sh \ + "$filter" \ + "$missing_source_message" \ + "$missing_filter_message" diff --git a/test-all-native.sh b/test-all-native.sh index 5004fca..202c634 100755 --- a/test-all-native.sh +++ b/test-all-native.sh @@ -8,12 +8,128 @@ linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} missing_root=/tmp/does-not-exist-linuxcnc missing_root_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_native_missing_root.XXXXXX.log") +missing_manifest=${TMPDIR:-/tmp}/cnc_sim_missing_native_manifest.txt +missing_manifest_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_native_manifest.XXXXXX.log") +unknown_group_manifest=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_native_unknown_manifest_group.XXXXXX.txt") +unknown_group_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_native_unknown_manifest_group.XXXXXX.log") +duplicate_manifest=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_native_duplicate_manifest.XXXXXX.txt") +duplicate_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_native_duplicate_manifest.XXXXXX.log") +unknown_filter_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_native_unknown_manifest_filter.XXXXXX.log") +unknown_output_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_native_unknown_manifest_output.XXXXXX.log") +missing_dump_source_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_rs274_dump_project_source.XXXXXX.log") +missing_api_source_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_rs274_api_project_source.XXXXXX.log") +missing_core_source_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_core_source.XXXXXX.log") +missing_bridge_source_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_bridge_smoke_project_source.XXXXXX.log") +missing_support_object_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_linuxcnc_support_object.XXXXXX.log") +missing_source_manifest=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_native_syntax_missing_source_manifest.XXXXXX.txt") +missing_source_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_native_syntax_missing_source.XXXXXX.log") + +cleanup_native_test_temps() { + rm -f \ + "$missing_root_log" \ + "$missing_manifest" \ + "$missing_manifest_log" \ + "$unknown_group_manifest" \ + "$unknown_group_log" \ + "$duplicate_manifest" \ + "$duplicate_log" \ + "$unknown_filter_log" \ + "$unknown_output_log" \ + "$missing_dump_source_log" \ + "$missing_api_source_log" \ + "$missing_core_source_log" \ + "$missing_bridge_source_log" \ + "$missing_support_object_log" \ + "$missing_source_manifest" \ + "$missing_source_log" +} +trap cleanup_native_test_temps EXIT + +grep -Fx 'trap cleanup_native_test_temps EXIT' test-all-native.sh >/dev/null +if ! awk ' + /cleanup_native_test_temps\(\)/ { in_cleanup = 1 } + in_cleanup && index($0, "\"$missing_manifest\"") { found_missing_manifest = 1 } + in_cleanup && /^}/ { in_cleanup = 0 } + END { exit !found_missing_manifest } +' test-all-native.sh; then + echo "native aggregate test cleanup no longer removes fixed missing-manifest path" >&2 + exit 1 +fi +for forbidden_manual_cleanup in \ + 'rm -f "$missing_root_log"' \ + 'rm -f "$missing_manifest_log"' \ + 'rm -f "$unknown_group_manifest" "$unknown_group_log"' \ + 'rm -f "$duplicate_manifest" "$duplicate_log"' \ + 'rm -f "$unknown_filter_log"' \ + 'rm -f "$unknown_output_log"' \ + 'rm -f "$missing_dump_source_log"' \ + 'rm -f "$missing_api_source_log"' \ + 'rm -f "$missing_core_source_log"' \ + 'rm -f "$missing_bridge_source_log"' \ + 'rm -f "$missing_support_object_log"' \ + 'rm -f "$missing_source_manifest" "$missing_source_log"'; do + if grep -Fx "$forbidden_manual_cleanup" test-all-native.sh >/dev/null; then + echo "native aggregate test still relies on manual cleanup: $forbidden_manual_cleanup" >&2 + exit 1 + fi +done grep -F 'exec 9>"${TMPDIR:-/tmp}/cnc_sim_rs274_source_link.lock"' test-linuxcnc-source-link.sh >/dev/null grep -F "flock 9" test-linuxcnc-source-link.sh >/dev/null -grep -F "missing LinuxCNC support object:" list-linuxcnc-source-support-objects.sh >/dev/null +grep -F "missing LinuxCNC support object" list-linuxcnc-source-support-objects.sh >/dev/null +grep -F 'echo "$missing_path_message: $path"' list-filtered-existing-paths.sh >/dev/null grep -F "build LinuxCNC first or set LINUXCNC_ROOT to a built tree" list-linuxcnc-source-support-objects.sh >/dev/null +native_link_flags=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-native-linkflags.sh) +grep -Fx -- "-L" <<<"$native_link_flags" >/dev/null +grep -Fx -- "$linuxcnc_root/lib" <<<"$native_link_flags" >/dev/null +grep -Fx -- "-Wl,-rpath,$linuxcnc_root/lib" <<<"$native_link_flags" >/dev/null grep -F 'exec 9>"${TMPDIR:-/tmp}/cnc_sim_linuxcnc_rs274_native.lock"' test-linuxcnc-rs274-native.sh >/dev/null grep -F "flock 9" test-linuxcnc-rs274-native.sh >/dev/null +for build_dir_probe_script in \ + test-linuxcnc-source-objects.sh \ + test-linuxcnc-source-link.sh \ + test-linuxcnc-bridge-native.sh \ + test-linuxcnc-rs274-native.sh \ + test-linuxcnc-api-native.sh; do + if ! awk ' + index($0, "trap '\''rm -rf \"$build_dir\"'\'' EXIT") { trap_line = NR } + index($0, "./check-linuxcnc-inputs.sh") && !check_line { check_line = NR } + END { exit !(trap_line && check_line && trap_line < check_line) } + ' "$build_dir_probe_script"; then + echo "$build_dir_probe_script installs build_dir cleanup after input validation" >&2 + exit 1 + fi +done +grep -F 'python_include_output=$(python3.13-config --includes 2>/dev/null || python3-config --includes)' list-linuxcnc-source-common-cxxflags.sh >/dev/null +if grep -F 'read -r -a python_includes <<<"$(' list-linuxcnc-source-common-cxxflags.sh >/dev/null; then + echo "source common cxxflags still hides python-config failures behind read" >&2 + exit 1 +fi +forbidden_common_flags_process_substitution='readarray -t common_flags < <(' +forbidden_link_flags_process_substitution='readarray -t link_flags < <(' +forbidden_source_common_flags_helper='./list-linuxcnc-source-common-cxxflags.sh' +forbidden_native_link_flags_helper='./list-linuxcnc-native-linkflags.sh' +if grep -RInF "${forbidden_common_flags_process_substitution}LINUXCNC_ROOT=\"\$linuxcnc_root\" ${forbidden_source_common_flags_helper}" test-linuxcnc-*.sh >/dev/null; then + echo "native probes still read source common flags through process substitution" >&2 + grep -RInF "${forbidden_common_flags_process_substitution}LINUXCNC_ROOT=\"\$linuxcnc_root\" ${forbidden_source_common_flags_helper}" test-linuxcnc-*.sh >&2 + exit 1 +fi +if grep -RInF "${forbidden_link_flags_process_substitution}LINUXCNC_ROOT=\"\$linuxcnc_root\" ${forbidden_native_link_flags_helper}" test-linuxcnc-*.sh >/dev/null; then + echo "native probes still read link flags through process substitution" >&2 + grep -RInF "${forbidden_link_flags_process_substitution}LINUXCNC_ROOT=\"\$linuxcnc_root\" ${forbidden_native_link_flags_helper}" test-linuxcnc-*.sh >&2 + exit 1 +fi +syntax_only_source_common_flags=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-common-cxxflags.sh --syntax-only) +grep -Fx -- "-fsyntax-only" <<<"$syntax_only_source_common_flags" >/dev/null +core_include_source_common_flags=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-common-cxxflags.sh --core-include) +grep -Fx -- "-I" <<<"$core_include_source_common_flags" >/dev/null +grep -Fx -- "core/include" <<<"$core_include_source_common_flags" >/dev/null +python_source_common_flags=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-common-cxxflags.sh --python) +grep -F "include/python" <<<"$python_source_common_flags" >/dev/null +no_ulapi_source_common_flags=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-common-cxxflags.sh --no-ulapi) +if grep -Fx -- "-DULAPI" <<<"$no_ulapi_source_common_flags" >/dev/null; then + echo "source common cxxflags emitted ULAPI with --no-ulapi" >&2 + exit 1 +fi grep -F "CNC_SIM_ENABLE_LINUXCNC_RS274_BACKEND is native-only; it links LinuxCNC librs274" core/CMakeLists.txt >/dev/null grep -F "Set CNC_SIM_LINUXCNC_ROOT to the LinuxCNC source root" core/CMakeLists.txt >/dev/null grep -F "find_package(Python3 REQUIRED COMPONENTS Development)" core/CMakeLists.txt >/dev/null @@ -34,6 +150,11 @@ grep -F './list-linuxcnc-rs274-api-project-sources.sh > "$project_source_list"' grep -F './list-cnc-sim-core-sources.sh > "$core_source_list"' test-native.sh >/dev/null grep -F './list-linuxcnc-bridge-smoke-project-sources.sh > "$project_source_list"' test-linuxcnc-bridge-native.sh >/dev/null grep -F 'LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-support-objects.sh > "$support_object_list"' test-linuxcnc-source-link.sh >/dev/null +grep -F 'trap '\''rm -f "$source_list" "$missing_source_manifest" "$missing_source_log"'\'' EXIT' test-linuxcnc-source-syntax.sh >/dev/null +if grep -F 'rm -f "$missing_source_manifest" "$missing_source_log"' test-linuxcnc-source-syntax.sh >/dev/null; then + echo "linuxcnc source syntax probe still relies on manual cleanup for missing-source temporaries" >&2 + exit 1 +fi for script in \ test-linuxcnc-source-syntax.sh \ test-linuxcnc-source-objects.sh \ @@ -51,10 +172,7 @@ for script in \ exit 1 fi done -rm -f "$missing_root_log" -missing_manifest=${TMPDIR:-/tmp}/cnc_sim_missing_native_manifest.txt -missing_manifest_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_native_manifest.XXXXXX.log") rm -f "$missing_manifest" for script in \ list-linuxcnc-source-manifest-sources.sh \ @@ -71,10 +189,7 @@ for script in \ exit 1 fi done -rm -f "$missing_manifest_log" -unknown_group_manifest=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_native_unknown_manifest_group.XXXXXX.txt") -unknown_group_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_native_unknown_manifest_group.XXXXXX.log") printf 'bogus:src/emc/rs274ngc/interp_arc.cc:test unknown group\n' >"$unknown_group_manifest" for script in \ list-linuxcnc-source-manifest-sources.sh \ @@ -91,10 +206,7 @@ for script in \ exit 1 fi done -rm -f "$unknown_group_manifest" "$unknown_group_log" -duplicate_manifest=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_native_duplicate_manifest.XXXXXX.txt") -duplicate_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_native_duplicate_manifest.XXXXXX.log") printf 'core:src/emc/rs274ngc/interp_arc.cc:test duplicate source\ncore:src/emc/rs274ngc/interp_arc.cc:test duplicate source\n' >"$duplicate_manifest" for script in \ list-linuxcnc-source-manifest-sources.sh \ @@ -111,16 +223,20 @@ for script in \ exit 1 fi done -rm -f "$duplicate_manifest" "$duplicate_log" ./list-linuxcnc-source-manifest-sources.sh "$manifest" core >/dev/null -grep -Fx "src/emc/rs274ngc/interp_arc.cc" \ - < <(./list-linuxcnc-source-manifest-sources.sh "$manifest" core) >/dev/null -grep -Fx "src/emc/rs274ngc/interpmodule.cc" \ - < <(./list-linuxcnc-source-manifest-sources.sh "$manifest" binding) >/dev/null -grep -Fx "src/emc/tooldata/tooldata_common.cc" \ - < <(./list-linuxcnc-source-manifest-sources.sh "$manifest" tooldata) >/dev/null -unknown_filter_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_native_unknown_manifest_filter.XXXXXX.log") +native_manifest_core=$(./list-linuxcnc-source-manifest-sources.sh "$manifest" core) +grep -Fx "src/emc/rs274ngc/interp_arc.cc" <<<"$native_manifest_core" >/dev/null +native_manifest_binding=$(./list-linuxcnc-source-manifest-sources.sh "$manifest" binding) +grep -Fx "src/emc/rs274ngc/interpmodule.cc" <<<"$native_manifest_binding" >/dev/null +native_manifest_tooldata=$(./list-linuxcnc-source-manifest-sources.sh "$manifest" tooldata) +grep -Fx "src/emc/tooldata/tooldata_common.cc" <<<"$native_manifest_tooldata" >/dev/null +native_manifest_header=$(./list-linuxcnc-source-manifest-sources.sh "$manifest" header) +grep -Fx "src/emc/rs274ngc/rs274ngc_interp.hh" <<<"$native_manifest_header" >/dev/null +if grep -E '\.(hh|h)$' <<<"$native_manifest_core" >/dev/null; then + echo "native manifest core compile group includes headers" >&2 + exit 1 +fi if ./list-linuxcnc-source-manifest-sources.sh "$manifest" bogus >"$unknown_filter_log" 2>&1; then echo "native manifest source lister accepted unknown filter" >&2 exit 1 @@ -130,8 +246,6 @@ if ! grep -F "unknown manifest source filter: bogus" "$unknown_filter_log" >/dev sed -n '1,20p' "$unknown_filter_log" >&2 exit 1 fi -rm -f "$unknown_filter_log" -unknown_output_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_native_unknown_manifest_output.XXXXXX.log") if ./list-linuxcnc-source-manifest-sources.sh "$manifest" core bogus >"$unknown_output_log" 2>&1; then echo "native manifest source lister accepted unknown output mode" >&2 exit 1 @@ -141,14 +255,12 @@ if ! grep -F "unknown manifest source output mode: bogus" "$unknown_output_log" sed -n '1,20p' "$unknown_output_log" >&2 exit 1 fi -rm -f "$unknown_output_log" -grep -Fx "$linuxcnc_root/src/emc/rs274ngc/interp_arc.cc" \ - < <(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-manifest-sources.sh "$manifest" core full) >/dev/null +native_manifest_core_full=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-manifest-sources.sh "$manifest" core full) +grep -Fx "$linuxcnc_root/src/emc/rs274ngc/interp_arc.cc" <<<"$native_manifest_core_full" >/dev/null ./list-linuxcnc-rs274-dump-project-sources.sh >/dev/null -grep -Fx "core/tools/linuxcnc_rs274_dump.cpp" \ - < <(./list-linuxcnc-rs274-dump-project-sources.sh linuxcnc_rs274_dump.cpp) >/dev/null -missing_dump_source_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_rs274_dump_project_source.XXXXXX.log") +rs274_dump_source=$(./list-linuxcnc-rs274-dump-project-sources.sh linuxcnc_rs274_dump.cpp) +grep -Fx "core/tools/linuxcnc_rs274_dump.cpp" <<<"$rs274_dump_source" >/dev/null if ./list-linuxcnc-rs274-dump-project-sources.sh does_not_exist.cc >"$missing_dump_source_log" 2>&1; then echo "rs274 dump project source lister accepted missing source filter" >&2 exit 1 @@ -158,12 +270,10 @@ if ! grep -F "missing LinuxCNC rs274 dump project source in list: does_not_exist sed -n '1,20p' "$missing_dump_source_log" >&2 exit 1 fi -rm -f "$missing_dump_source_log" ./list-linuxcnc-rs274-api-project-sources.sh >/dev/null -grep -Fx "core/src/linuxcnc_rs274_backend.cpp" \ - < <(./list-linuxcnc-rs274-api-project-sources.sh linuxcnc_rs274_backend.cpp) >/dev/null -missing_api_source_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_rs274_api_project_source.XXXXXX.log") +rs274_api_source=$(./list-linuxcnc-rs274-api-project-sources.sh linuxcnc_rs274_backend.cpp) +grep -Fx "core/src/linuxcnc_rs274_backend.cpp" <<<"$rs274_api_source" >/dev/null if ./list-linuxcnc-rs274-api-project-sources.sh does_not_exist.cc >"$missing_api_source_log" 2>&1; then echo "rs274 API project source lister accepted missing source filter" >&2 exit 1 @@ -173,12 +283,10 @@ if ! grep -F "missing LinuxCNC rs274 API project source in CMake list: does_not_ sed -n '1,20p' "$missing_api_source_log" >&2 exit 1 fi -rm -f "$missing_api_source_log" ./list-cnc-sim-core-sources.sh >/dev/null -grep -Fx "core/src/smoke_gcode_parser.cpp" \ - < <(./list-cnc-sim-core-sources.sh smoke_gcode_parser.cpp) >/dev/null -missing_core_source_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_core_source.XXXXXX.log") +cnc_sim_core_source=$(./list-cnc-sim-core-sources.sh smoke_gcode_parser.cpp) +grep -Fx "core/src/smoke_gcode_parser.cpp" <<<"$cnc_sim_core_source" >/dev/null if ./list-cnc-sim-core-sources.sh does_not_exist.cc >"$missing_core_source_log" 2>&1; then echo "cnc sim core source lister accepted missing source filter" >&2 exit 1 @@ -188,12 +296,10 @@ if ! grep -F "missing cnc sim core source in CMake list: does_not_exist.cc" "$mi sed -n '1,20p' "$missing_core_source_log" >&2 exit 1 fi -rm -f "$missing_core_source_log" ./list-linuxcnc-bridge-smoke-project-sources.sh >/dev/null -grep -Fx "core/src/linuxcnc_canon_bridge.cpp" \ - < <(./list-linuxcnc-bridge-smoke-project-sources.sh linuxcnc_canon_bridge.cpp) >/dev/null -missing_bridge_source_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_bridge_smoke_project_source.XXXXXX.log") +bridge_smoke_source=$(./list-linuxcnc-bridge-smoke-project-sources.sh linuxcnc_canon_bridge.cpp) +grep -Fx "core/src/linuxcnc_canon_bridge.cpp" <<<"$bridge_smoke_source" >/dev/null if ./list-linuxcnc-bridge-smoke-project-sources.sh does_not_exist.cc >"$missing_bridge_source_log" 2>&1; then echo "LinuxCNC bridge smoke project source lister accepted missing source filter" >&2 exit 1 @@ -203,12 +309,10 @@ if ! grep -F "missing LinuxCNC bridge smoke project source in list: does_not_exi sed -n '1,20p' "$missing_bridge_source_log" >&2 exit 1 fi -rm -f "$missing_bridge_source_log" LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-support-objects.sh >/dev/null -grep -F "/src/objects/emc/rs274ngc/interpmodule.o" \ - < <(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-support-objects.sh interpmodule.o) >/dev/null -missing_support_object_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_linuxcnc_support_object.XXXXXX.log") +interpmodule_support_object=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-support-objects.sh interpmodule.o) +grep -F "/src/objects/emc/rs274ngc/interpmodule.o" <<<"$interpmodule_support_object" >/dev/null if LINUXCNC_ROOT="$missing_root" ./list-linuxcnc-source-support-objects.sh >"$missing_support_object_log" 2>&1; then echo "LinuxCNC support object lister accepted missing LinuxCNC root: $missing_root" >&2 exit 1 @@ -227,10 +331,7 @@ if ! grep -F "missing LinuxCNC support object in list: does_not_exist.o" "$missi sed -n '1,20p' "$missing_support_object_log" >&2 exit 1 fi -rm -f "$missing_support_object_log" -missing_source_manifest=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_native_syntax_missing_source_manifest.XXXXXX.txt") -missing_source_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_native_syntax_missing_source.XXXXXX.log") printf 'core:src/emc/rs274ngc/does_not_exist.cc:test missing source\n' >"$missing_source_manifest" if ./test-linuxcnc-source-syntax.sh "$missing_source_manifest" >"$missing_source_log" 2>&1; then echo "test-linuxcnc-source-syntax.sh accepted missing manifest file" >&2 @@ -241,7 +342,6 @@ if ! grep -F "missing manifest file: src/emc/rs274ngc/does_not_exist.cc" "$missi sed -n '1,20p' "$missing_source_log" >&2 exit 1 fi -rm -f "$missing_source_manifest" "$missing_source_log" ./test-native.sh ./test-linuxcnc-source-syntax.sh "$manifest" diff --git a/test-linuxcnc-api-native.sh b/test-linuxcnc-api-native.sh index 752e68c..9f59ed2 100755 --- a/test-linuxcnc-api-native.sh +++ b/test-linuxcnc-api-native.sh @@ -7,12 +7,8 @@ linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} cxx=${CXX:-g++} build_dir=$(mktemp -d "${TMPDIR:-/tmp}/cnc_sim_native.XXXXXX") -if [[ ! -d "$linuxcnc_root" ]]; then - echo "missing LinuxCNC root: $linuxcnc_root" >&2 - exit 1 -fi - trap 'rm -rf "$build_dir"' EXIT +LINUXCNC_ROOT="$linuxcnc_root" ./check-linuxcnc-inputs.sh --root-only var_file="$build_dir/rs274ngc-api.var" cp "$linuxcnc_root/tests/halui/jogging/sim.var" "$var_file" @@ -20,21 +16,22 @@ project_source_list="$build_dir/linuxcnc_rs274_api_project_sources.txt" ./list-linuxcnc-rs274-api-project-sources.sh > "$project_source_list" mapfile -t project_sources < "$project_source_list" -"$cxx" -std=c++17 \ +common_flag_output=$( + LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-common-cxxflags.sh \ + --no-ulapi \ + --python \ + --core-include \ + --core-src +) +mapfile -t common_flags <<<"$common_flag_output" +link_flag_output=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-native-linkflags.sh) +mapfile -t link_flags <<<"$link_flag_output" + +"$cxx" "${common_flags[@]}" \ -DCNC_SIM_ENABLE_LINUXCNC_RS274_BACKEND \ - $(python3.13-config --includes 2>/dev/null || python3-config --includes) \ - -I core/include \ - -I core/src \ - -I "$linuxcnc_root/src" \ - -I "$linuxcnc_root/src/emc" \ - -I "$linuxcnc_root/src/emc/nml_intf" \ - -I "$linuxcnc_root/src/emc/rs274ngc" \ - -I "$linuxcnc_root/src/emc/motion" \ - -I "$linuxcnc_root/include" \ "${project_sources[@]}" \ core/tests/cnc_sim_api_linuxcnc_rs274_smoke.cpp \ - -L "$linuxcnc_root/lib" \ - -Wl,-rpath,"$linuxcnc_root/lib" \ + "${link_flags[@]}" \ -lrs274 \ -ltooldata \ -lpython3.13 \ diff --git a/test-linuxcnc-bridge-native.sh b/test-linuxcnc-bridge-native.sh index ba51e08..352b109 100755 --- a/test-linuxcnc-bridge-native.sh +++ b/test-linuxcnc-bridge-native.sh @@ -7,29 +7,26 @@ linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} cxx=${CXX:-g++} build_dir=$(mktemp -d "${TMPDIR:-/tmp}/cnc_sim_native.XXXXXX") -if [[ ! -d "$linuxcnc_root" ]]; then - echo "missing LinuxCNC root: $linuxcnc_root" >&2 - exit 1 -fi - trap 'rm -rf "$build_dir"' EXIT +LINUXCNC_ROOT="$linuxcnc_root" ./check-linuxcnc-inputs.sh --root-only project_source_list="$build_dir/linuxcnc_bridge_smoke_project_sources.txt" ./list-linuxcnc-bridge-smoke-project-sources.sh > "$project_source_list" mapfile -t project_sources < "$project_source_list" -"$cxx" -std=c++17 \ - -I core/include \ - -I core/src \ - -I "$linuxcnc_root/src/emc" \ - -I "$linuxcnc_root/src/emc/nml_intf" \ - -I "$linuxcnc_root/src/emc/rs274ngc" \ - -I "$linuxcnc_root/src/emc/motion" \ - -I "$linuxcnc_root/src" \ - -I "$linuxcnc_root/include" \ +common_flag_output=$( + LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-common-cxxflags.sh \ + --no-ulapi \ + --core-include \ + --core-src +) +mapfile -t common_flags <<<"$common_flag_output" +link_flag_output=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-native-linkflags.sh) +mapfile -t link_flags <<<"$link_flag_output" + +"$cxx" "${common_flags[@]}" \ "${project_sources[@]}" \ core/tests/linuxcnc_canon_bridge_smoke.cpp \ - -L "$linuxcnc_root/lib" \ - -Wl,-rpath,"$linuxcnc_root/lib" \ + "${link_flags[@]}" \ -ltooldata \ -o "$build_dir/linuxcnc_canon_bridge_smoke" diff --git a/test-linuxcnc-rs274-native.sh b/test-linuxcnc-rs274-native.sh index 6ddf096..e619b0c 100755 --- a/test-linuxcnc-rs274-native.sh +++ b/test-linuxcnc-rs274-native.sh @@ -7,12 +7,8 @@ linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} cxx=${CXX:-g++} build_dir=$(mktemp -d "${TMPDIR:-/tmp}/cnc_sim_native.XXXXXX") -if [[ ! -d "$linuxcnc_root" ]]; then - echo "missing LinuxCNC root: $linuxcnc_root" >&2 - exit 1 -fi - trap 'rm -rf "$build_dir"' EXIT +LINUXCNC_ROOT="$linuxcnc_root" ./check-linuxcnc-inputs.sh --root-only # The LinuxCNC rs274 backend path can bus-error when two dumps run concurrently. exec 9>"${TMPDIR:-/tmp}/cnc_sim_linuxcnc_rs274_native.lock" flock 9 @@ -29,19 +25,20 @@ project_source_list="$build_dir/linuxcnc_rs274_dump_project_sources.txt" ./list-linuxcnc-rs274-dump-project-sources.sh > "$project_source_list" mapfile -t project_sources < "$project_source_list" -"$cxx" -std=c++17 \ - $(python3.13-config --includes 2>/dev/null || python3-config --includes) \ - -I core/include \ - -I core/src \ - -I "$linuxcnc_root/src" \ - -I "$linuxcnc_root/src/emc" \ - -I "$linuxcnc_root/src/emc/nml_intf" \ - -I "$linuxcnc_root/src/emc/rs274ngc" \ - -I "$linuxcnc_root/src/emc/motion" \ - -I "$linuxcnc_root/include" \ +common_flag_output=$( + LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-common-cxxflags.sh \ + --no-ulapi \ + --python \ + --core-include \ + --core-src +) +mapfile -t common_flags <<<"$common_flag_output" +link_flag_output=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-native-linkflags.sh) +mapfile -t link_flags <<<"$link_flag_output" + +"$cxx" "${common_flags[@]}" \ "${project_sources[@]}" \ - -L "$linuxcnc_root/lib" \ - -Wl,-rpath,"$linuxcnc_root/lib" \ + "${link_flags[@]}" \ -lrs274 \ -ltooldata \ -lpython3.13 \ @@ -126,7 +123,7 @@ check(OUTPUT_DIR / "cnc_sim_linuxcnc_basic_mill.json", required_motion | {"tool- controls = load("cnc_sim_linuxcnc_rtcp_controls.json") kin = [event for event in controls if event["type"] == "kinematics-switch"] -if [(event["reserved"], event["feed"]) for event in kin] != [(1, 1), (0, 0), (2, 1)]: +if [event["reserved"] for event in kin] != [1, 0, 2]: raise SystemExit("unexpected kinematics switch sequence") rtcp = [event for event in controls if event["type"] == "rtcp-state"] if [(event["line"], event["tool"], event["feed"]) for event in rtcp] != [(3, 0, 0), (4, 7, 1)]: diff --git a/test-linuxcnc-source-link.sh b/test-linuxcnc-source-link.sh index 5b2be97..07db175 100755 --- a/test-linuxcnc-source-link.sh +++ b/test-linuxcnc-source-link.sh @@ -6,43 +6,26 @@ cd "$(dirname "$0")" linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} cxx=${CXX:-g++} build_dir=$(mktemp -d "${TMPDIR:-/tmp}/cnc_sim_rs274_source_link.XXXXXX") -python_includes=$(python3.13-config --includes 2>/dev/null || python3-config --includes) manifest=${1:-linuxcnc-rs274-source-files.txt} -if [[ ! -f "$manifest" ]]; then - echo "missing manifest: $manifest" >&2 - exit 1 -fi - -if [[ ! -d "$linuxcnc_root" ]]; then - echo "missing LinuxCNC root: $linuxcnc_root" >&2 - exit 1 -fi - trap 'rm -rf "$build_dir"' EXIT +LINUXCNC_ROOT="$linuxcnc_root" ./check-linuxcnc-inputs.sh "$manifest" # The LinuxCNC source-linked dump path is not concurrency-safe under parallel execution. exec 9>"${TMPDIR:-/tmp}/cnc_sim_rs274_source_link.lock" flock 9 -common_flags=( - -std=c++17 - -DULAPI - $python_includes - -I core/include - -I core/src - -I "$linuxcnc_root/src" - -I "$linuxcnc_root/src/emc" - -I "$linuxcnc_root/src/emc/nml_intf" - -I "$linuxcnc_root/src/emc/rs274ngc" - -I "$linuxcnc_root/src/emc/motion" - -I "$linuxcnc_root/src/emc/pythonplugin" - -I "$linuxcnc_root/include" +common_flag_output=$( + LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-common-cxxflags.sh \ + --python \ + --core-include \ + --core-src ) +mapfile -t common_flags <<<"$common_flag_output" +link_flag_output=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-native-linkflags.sh) +mapfile -t link_flags <<<"$link_flag_output" -linuxcnc_sources=() -source_list="$build_dir/linuxcnc_core_sources.txt" -LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-manifest-sources.sh "$manifest" core full > "$source_list" -mapfile -t linuxcnc_sources < "$source_list" +source_output=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-manifest-sources.sh "$manifest" core full) +mapfile -t linuxcnc_sources <<<"$source_output" objects=() linuxcnc_index=0 @@ -73,8 +56,7 @@ LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-support-objects.sh > "$sup mapfile -t support_objects < "$support_object_list" "$cxx" "${objects[@]}" "${support_objects[@]}" \ - -L "$linuxcnc_root/lib" \ - -Wl,-rpath,"$linuxcnc_root/lib" \ + "${link_flags[@]}" \ -llinuxcnc-uspace-posix \ -llinuxcncini \ -llinuxcnchal \ @@ -812,7 +794,7 @@ if not any( controls = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_rtcp_controls.json").read_text()) kin = [event for event in controls if event["type"] == "kinematics-switch"] -if [(event["reserved"], event["feed"]) for event in kin] != [(1, 1), (0, 0), (2, 1)]: +if [event["reserved"] for event in kin] != [1, 0, 2]: raise SystemExit("unexpected source-linked kinematics switch sequence") rtcp = [event for event in controls if event["type"] == "rtcp-state"] if [(event["line"], event["tool"], event["feed"]) for event in rtcp] != [(3, 0, 0), (4, 7, 1)]: diff --git a/test-linuxcnc-source-objects.sh b/test-linuxcnc-source-objects.sh index 4416240..36ad42a 100755 --- a/test-linuxcnc-source-objects.sh +++ b/test-linuxcnc-source-objects.sh @@ -6,43 +6,21 @@ cd "$(dirname "$0")" linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} cxx=${CXX:-g++} build_dir=$(mktemp -d "${TMPDIR:-/tmp}/cnc_sim_rs274_objects.XXXXXX") -python_includes=$(python3.13-config --includes 2>/dev/null || python3-config --includes) manifest=${1:-linuxcnc-rs274-source-files.txt} -if [[ ! -f "$manifest" ]]; then - echo "missing manifest: $manifest" >&2 - exit 1 -fi - -if [[ ! -d "$linuxcnc_root" ]]; then - echo "missing LinuxCNC root: $linuxcnc_root" >&2 - exit 1 -fi - trap 'rm -rf "$build_dir"' EXIT +LINUXCNC_ROOT="$linuxcnc_root" ./check-linuxcnc-inputs.sh "$manifest" -common_flags=( - -std=c++17 - -DULAPI - -I "$linuxcnc_root/src" - -I "$linuxcnc_root/src/emc" - -I "$linuxcnc_root/src/emc/nml_intf" - -I "$linuxcnc_root/src/emc/rs274ngc" - -I "$linuxcnc_root/src/emc/motion" - -I "$linuxcnc_root/src/emc/pythonplugin" - -I "$linuxcnc_root/include" -) +common_flag_output=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-common-cxxflags.sh --python) +mapfile -t common_flags <<<"$common_flag_output" -sources=() -source_list="$build_dir/linuxcnc_core_sources.txt" -LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-manifest-sources.sh "$manifest" core full > "$source_list" -mapfile -t sources < "$source_list" +source_output=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-manifest-sources.sh "$manifest" core full) +mapfile -t sources <<<"$source_output" source_index=0 for source in "${sources[@]}"; do obj="$build_dir/linuxcnc_${source_index}.o" - # shellcheck disable=SC2086 - "$cxx" "${common_flags[@]}" $python_includes -c "$source" -o "$obj" + "$cxx" "${common_flags[@]}" -c "$source" -o "$obj" source_index=$((source_index + 1)) done diff --git a/test-linuxcnc-source-syntax.sh b/test-linuxcnc-source-syntax.sh index 8ffbcc7..8db95e0 100755 --- a/test-linuxcnc-source-syntax.sh +++ b/test-linuxcnc-source-syntax.sh @@ -5,25 +5,21 @@ cd "$(dirname "$0")" linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} cxx=${CXX:-g++} -python_includes=$(python3.13-config --includes 2>/dev/null || python3-config --includes) manifest=${1:-linuxcnc-rs274-source-files.txt} -if [[ ! -f "$manifest" ]]; then - echo "missing manifest: $manifest" >&2 - exit 1 -fi - -if [[ ! -d "$linuxcnc_root" ]]; then - echo "missing LinuxCNC root: $linuxcnc_root" >&2 - exit 1 -fi - -source_list=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_linuxcnc_source_syntax_sources.XXXXXX.txt") -trap 'rm -f "$source_list"' EXIT -LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-manifest-sources.sh "$manifest" all full > "$source_list" +LINUXCNC_ROOT="$linuxcnc_root" ./check-linuxcnc-inputs.sh "$manifest" missing_source_manifest=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_rs274_manifest_source.XXXXXX.txt") missing_source_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_rs274_manifest_source.XXXXXX.log") +source_list=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_linuxcnc_source_syntax_sources.XXXXXX.txt") +trap 'rm -f "$source_list" "$missing_source_manifest" "$missing_source_log"' EXIT +grep -Fx 'trap '\''rm -f "$source_list" "$missing_source_manifest" "$missing_source_log"'\'' EXIT' test-linuxcnc-source-syntax.sh >/dev/null +if awk '/^rm -f "\$missing_source_manifest" "\$missing_source_log"$/{ found = 1 } END { exit !found }' test-linuxcnc-source-syntax.sh >/dev/null; then + echo "linuxcnc source syntax probe still relies on manual cleanup for missing-source temporaries" >&2 + exit 1 +fi +LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-manifest-sources.sh "$manifest" all full > "$source_list" + printf 'core:src/emc/rs274ngc/does_not_exist.cc:test missing source\n' >"$missing_source_manifest" if ./test-linuxcnc-source-objects.sh "$missing_source_manifest" >"$missing_source_log" 2>&1; then echo "linuxcnc source object probe accepted missing manifest file" >&2 @@ -43,25 +39,13 @@ if ! grep -F "missing manifest file: src/emc/rs274ngc/does_not_exist.cc" "$missi sed -n '1,20p' "$missing_source_log" >&2 exit 1 fi -rm -f "$missing_source_manifest" "$missing_source_log" -common_flags=( - -std=c++17 - -DULAPI - -fsyntax-only - -I "$linuxcnc_root/src" - -I "$linuxcnc_root/src/emc" - -I "$linuxcnc_root/src/emc/nml_intf" - -I "$linuxcnc_root/src/emc/rs274ngc" - -I "$linuxcnc_root/src/emc/motion" - -I "$linuxcnc_root/src/emc/pythonplugin" - -I "$linuxcnc_root/include" -) +common_flag_output=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-common-cxxflags.sh --python --syntax-only) +mapfile -t common_flags <<<"$common_flag_output" while IFS= read -r source; do [[ -z "$source" ]] && continue - # shellcheck disable=SC2086 - "$cxx" "${common_flags[@]}" $python_includes "$source" + "$cxx" "${common_flags[@]}" "$source" done < "$source_list" echo "linuxcnc rs274 source syntax probe passed" diff --git a/test-linuxcnc-wasm-blockers.sh b/test-linuxcnc-wasm-blockers.sh index f66441d..9a301ae 100755 --- a/test-linuxcnc-wasm-blockers.sh +++ b/test-linuxcnc-wasm-blockers.sh @@ -7,6 +7,26 @@ manifest=${1:-linuxcnc-rs274-wasm-source-files.txt} missing_manifest=${TMPDIR:-/tmp}/cnc_sim_missing_wasm_blocker_manifest.txt missing_manifest_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_wasm_blocker_manifest.XXXXXX.log") +missing_root_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_wasm_blocker_missing_root.XXXXXX.log") +missing_source_manifest=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_wasm_blocker_manifest_source.XXXXXX.txt") +missing_source_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_wasm_blocker_manifest_source.XXXXXX.log") +duplicate_manifest=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_duplicate_wasm_blocker_manifest.XXXXXX.txt") +duplicate_manifest_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_duplicate_wasm_blocker_manifest.XXXXXX.log") +missing_replacement_manifest=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_wasm_blocked_replacement.XXXXXX.txt") +missing_replacement_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_wasm_blocked_replacement.XXXXXX.log") +trap 'rm -f "$missing_manifest" "$missing_manifest_log" "$missing_root_log" "$missing_source_manifest" "$missing_source_log" "$duplicate_manifest" "$duplicate_manifest_log" "$missing_replacement_manifest" "$missing_replacement_log"' EXIT +if ! awk ' + /^trap '\''rm -f "\$missing_manifest" "\$missing_manifest_log" "\$missing_root_log" "\$missing_source_manifest" "\$missing_source_log" "\$duplicate_manifest" "\$duplicate_manifest_log" "\$missing_replacement_manifest" "\$missing_replacement_log"'\'' EXIT$/ { found_trap = 1 } + END { exit !found_trap } +' test-linuxcnc-wasm-blockers.sh; then + echo "wasm blocker scan cleanup trap no longer covers fixed blocker temporaries" >&2 + exit 1 +fi +if awk '/^rm -f "\$missing_source_manifest" "\$missing_source_log"$/{ found = 1 } END { exit !found }' test-linuxcnc-wasm-blockers.sh >/dev/null; then + echo "wasm blocker scan still relies on manual cleanup for missing-source temporaries" >&2 + exit 1 +fi + rm -f "$missing_manifest" if ./analyze-linuxcnc-wasm-blockers.sh "$missing_manifest" >"$missing_manifest_log" 2>&1; then echo "wasm blocker analyzer accepted missing manifest: $missing_manifest" >&2 @@ -17,10 +37,8 @@ if ! grep -F "missing manifest: $missing_manifest" "$missing_manifest_log" >/dev sed -n '1,20p' "$missing_manifest_log" >&2 exit 1 fi -rm -f "$missing_manifest_log" missing_root=/tmp/does-not-exist-linuxcnc -missing_root_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_wasm_blocker_missing_root.XXXXXX.log") if LINUXCNC_ROOT="$missing_root" ./analyze-linuxcnc-wasm-blockers.sh "$manifest" >"$missing_root_log" 2>&1; then echo "wasm blocker analyzer accepted missing LinuxCNC root" >&2 exit 1 @@ -30,12 +48,9 @@ if ! grep -F "missing LinuxCNC root: $missing_root" "$missing_root_log" >/dev/nu sed -n '1,20p' "$missing_root_log" >&2 exit 1 fi -rm -f "$missing_root_log" output=$(./analyze-linuxcnc-wasm-blockers.sh "$manifest") -missing_source_manifest=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_wasm_blocker_manifest_source.XXXXXX.txt") -missing_source_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_wasm_blocker_manifest_source.XXXXXX.log") printf 'blocked:src/emc/tooldata/does_not_exist.cc:test missing blocked source\n' >"$missing_source_manifest" if ./analyze-linuxcnc-wasm-blockers.sh "$missing_source_manifest" >"$missing_source_log" 2>&1; then echo "wasm blocker analyzer accepted missing manifest source" >&2 @@ -46,10 +61,7 @@ if ! grep -F "missing manifest source: src/emc/tooldata/does_not_exist.cc" "$mis sed -n '1,20p' "$missing_source_log" >&2 exit 1 fi -rm -f "$missing_source_manifest" "$missing_source_log" -duplicate_manifest=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_duplicate_wasm_blocker_manifest.XXXXXX.txt") -duplicate_manifest_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_duplicate_wasm_blocker_manifest.XXXXXX.log") cat >"$duplicate_manifest" <<'EOF' core:src/emc/rs274ngc/interp_arc.cc:duplicate core entry core:src/emc/rs274ngc/interp_arc.cc:duplicate core entry @@ -64,10 +76,7 @@ if ! grep -F "duplicate manifest source: src/emc/rs274ngc/interp_arc.cc" "$dupli sed -n '1,20p' "$duplicate_manifest_log" >&2 exit 1 fi -rm -f "$duplicate_manifest" "$duplicate_manifest_log" -missing_replacement_manifest=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_wasm_blocked_replacement.XXXXXX.txt") -missing_replacement_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_wasm_blocked_replacement.XXXXXX.log") cat >"$missing_replacement_manifest" <<'EOF' core:src/emc/rs274ngc/interp_arc.cc:core entry blocked:src/emc/tooldata/tooldata_db.cc:blocked entry without wasm replacement @@ -81,7 +90,6 @@ if ! grep -F "missing blocked replacement mapping: src/emc/tooldata/tooldata_db. sed -n '1,20p' "$missing_replacement_log" >&2 exit 1 fi -rm -f "$missing_replacement_manifest" "$missing_replacement_log" grep -F "[manifest]" <<<"$output" >/dev/null grep -F "core=25" <<<"$(sed -n '/^\[manifest\]/,/^$/p' <<<"$output")" >/dev/null diff --git a/test-linuxcnc-wasm-cmake-safe-probe.sh b/test-linuxcnc-wasm-cmake-safe-probe.sh index 48ceb7e..4bd0084 100755 --- a/test-linuxcnc-wasm-cmake-safe-probe.sh +++ b/test-linuxcnc-wasm-cmake-safe-probe.sh @@ -6,20 +6,61 @@ cd "$(dirname "$0")" linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} build_dir=$(mktemp -d "${TMPDIR:-/tmp}/cnc_sim_linuxcnc_wasm_cmake_safe_probe.XXXXXX") manifest=${1:-linuxcnc-rs274-wasm-source-files.txt} - -if [[ ! -f "$manifest" ]]; then - echo "missing manifest: $manifest" >&2 - exit 1 -fi - -if [[ ! -d "$linuxcnc_root" ]]; then - echo "missing LinuxCNC root: $linuxcnc_root" >&2 - exit 1 -fi -trap 'rm -rf "$build_dir"' EXIT - missing_manifest=${TMPDIR:-/tmp}/cnc_sim_missing_build_wasm_manifest.txt missing_manifest_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_build_wasm_manifest.XXXXXX.log") +missing_root=/tmp/does-not-exist-linuxcnc +missing_root_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_build_wasm_missing_root.XXXXXX.log") + +cleanup_wasm_cmake_probe_temps() { + local temp_path + for temp_path in \ + "$build_dir" \ + "$missing_manifest" \ + "$missing_manifest_log" \ + "$missing_root_log" \ + "${unknown_manifest_filter_log:-}" \ + "${unknown_manifest_output_log:-}" \ + "${missing_shim_log:-}" \ + "${missing_project_source_log:-}" \ + "${missing_wasm_script_manifest:-}" \ + "${missing_wasm_script_manifest_log:-}" \ + "${missing_source_root:-}" \ + "${missing_source_log:-}" \ + "${missing_interp_find_root:-}" \ + "${missing_interp_find_log:-}" \ + "${missing_tooldata_runtime_root:-}" \ + "${missing_tooldata_runtime_log:-}" \ + "${unknown_group_manifest:-}" \ + "${unknown_group_log:-}" \ + "${blocker_scan_log:-}" \ + "${blocker_report_file:-}" \ + "${missing_source_manifest:-}" \ + "${empty_partition_log:-}" \ + "${empty_core_manifest:-}" \ + "${empty_blocked_manifest:-}" \ + "${bad_default_manifest_dir:-}" \ + "${bad_default_manifest_log:-}" \ + "${duplicate_manifest:-}" \ + "${duplicate_manifest_log:-}" \ + "${comment_manifest:-}" \ + "${comment_manifest_log:-}" \ + "${duplicate_source_manifest:-}" \ + "${duplicate_source_log:-}" \ + "${duplicate_syntax_manifest:-}" \ + "${duplicate_syntax_log:-}" \ + "${duplicate_pre_blocker_manifest:-}" \ + "${duplicate_pre_blocker_log:-}" \ + "${duplicate_pre_link_manifest:-}" \ + "${duplicate_pre_link_log:-}"; do + if [[ -n "$temp_path" ]]; then + rm -rf "$temp_path" + fi + done +} +trap cleanup_wasm_cmake_probe_temps EXIT + +LINUXCNC_ROOT="$linuxcnc_root" ./check-linuxcnc-inputs.sh "$manifest" + rm -f "$missing_manifest" if ./build-wasm.sh "$missing_manifest" >"$missing_manifest_log" 2>&1; then echo "build-wasm accepted missing manifest: $missing_manifest" >&2 @@ -30,10 +71,7 @@ if ! grep -F "missing manifest: $missing_manifest" "$missing_manifest_log" >/dev sed -n '1,20p' "$missing_manifest_log" >&2 exit 1 fi -rm -f "$missing_manifest_log" -missing_root=/tmp/does-not-exist-linuxcnc -missing_root_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_build_wasm_missing_root.XXXXXX.log") if LINUXCNC_ROOT="$missing_root" ./build-wasm.sh >"$missing_root_log" 2>&1; then echo "build-wasm accepted missing LinuxCNC root: $missing_root" >&2 exit 1 @@ -59,21 +97,135 @@ grep -F -- './test-linuxcnc-wasm-python-plugin-link.sh' build-wasm.sh >/dev/null grep -F -- './test-linuxcnc-wasm-rs274ngc-pre-object.sh' build-wasm.sh >/dev/null grep -F -- './test-linuxcnc-wasm-rs274ngc-pre-link-blockers.sh "$manifest"' build-wasm.sh >/dev/null grep -F -- './test-linuxcnc-wasm-rs274ngc-pre-link.sh "$manifest"' build-wasm.sh >/dev/null +if grep -F '} > "$report_file" || true' test-linuxcnc-wasm-rs274ngc-pre-link-blockers.sh >/dev/null; then + echo "rs274ngc_pre blocker scan still hides report pipeline failures with || true" >&2 + exit 1 +fi +grep -Fx 'keep_report_file=0' test-linuxcnc-wasm-rs274ngc-pre-link-blockers.sh >/dev/null +grep -Fx 'trap cleanup_rs274ngc_pre_blocker_temps EXIT' test-linuxcnc-wasm-rs274ngc-pre-link-blockers.sh >/dev/null +grep -Fx 'keep_report_file=1' test-linuxcnc-wasm-rs274ngc-pre-link-blockers.sh >/dev/null +if ! awk ' + index($0, "if [[ \"$keep_report_file\" -eq 0 && -n \"${report_file:-}\" ]]; then") { cleanup_line = NR } + index($0, "keep_report_file=1") { keep_line = NR } + index($0, "echo \"wrote $report_file\"") { wrote_line = NR } + END { exit !(cleanup_line && keep_line && wrote_line && cleanup_line < keep_line && keep_line < wrote_line) } +' test-linuxcnc-wasm-rs274ngc-pre-link-blockers.sh; then + echo "rs274ngc_pre blocker scan does not clean failed reports before preserving successful ones" >&2 + exit 1 +fi +if ! awk ' + index($0, "LINUXCNC_ROOT=\"$linuxcnc_root\" ./check-linuxcnc-inputs.sh \"$manifest\"") { check_line = NR } + index($0, "report_file=$(mktemp ") { report_line = NR } + END { exit !(check_line && report_line && check_line < report_line) } +' test-linuxcnc-wasm-rs274ngc-pre-link-blockers.sh; then + echo "rs274ngc_pre blocker scan creates its report before input validation" >&2 + exit 1 +fi +for build_dir_probe_script in \ + test-linuxcnc-wasm-interp-base-link.sh \ + test-linuxcnc-wasm-interp-find-link.sh \ + test-linuxcnc-wasm-python-plugin-link.sh \ + test-linuxcnc-wasm-rs274ngc-pre-link-blockers.sh \ + test-linuxcnc-wasm-rs274ngc-pre-link.sh \ + test-linuxcnc-wasm-rs274ngc-pre-object.sh \ + test-linuxcnc-wasm-source-objects.sh \ + test-linuxcnc-wasm-tooldata-common-link.sh \ + test-linuxcnc-wasm-tooldata-link.sh \ + test-linuxcnc-wasm-tooldata-mmap-symbols.sh \ + test-linuxcnc-wasm-tooldata-runtime-symbols.sh; do + if ! awk ' + $0 ~ /^trap .* EXIT$/ { trap_line = NR } + index($0, "./check-linuxcnc-inputs.sh") && !check_line { check_line = NR } + END { exit !(trap_line && check_line && trap_line < check_line) } + ' "$build_dir_probe_script"; then + echo "$build_dir_probe_script installs build_dir cleanup after input validation" >&2 + exit 1 + fi +done +grep -F 'trap '\''rm -f "$missing_manifest" "$missing_manifest_log" "$missing_root_log" "$missing_source_manifest" "$missing_source_log" "$duplicate_manifest" "$duplicate_manifest_log" "$missing_replacement_manifest" "$missing_replacement_log"'\'' EXIT' test-linuxcnc-wasm-blockers.sh >/dev/null +if grep -F 'rm -f "$missing_source_manifest" "$missing_source_log"' test-linuxcnc-wasm-blockers.sh >/dev/null; then + echo "wasm blocker scan still relies on manual cleanup for missing-source temporaries" >&2 + exit 1 +fi +grep -Fx 'trap cleanup_wasm_cmake_probe_temps EXIT' test-linuxcnc-wasm-cmake-safe-probe.sh >/dev/null +if ! awk ' + /cleanup_wasm_cmake_probe_temps\(\)/ { in_cleanup = 1 } + in_cleanup && index($0, "\"$missing_manifest\"") { found_missing_manifest = 1 } + in_cleanup && index($0, "\"${missing_wasm_script_manifest:-}\"") { found_missing_script_manifest = 1 } + in_cleanup && /^}/ { in_cleanup = 0 } + END { exit !(found_missing_manifest && found_missing_script_manifest) } +' test-linuxcnc-wasm-cmake-safe-probe.sh; then + echo "wasm CMake safe probe cleanup no longer removes fixed missing-manifest paths" >&2 + exit 1 +fi +for forbidden_manual_cleanup in \ + 'rm -f "$missing_manifest_log"' \ + 'rm -f "$unknown_manifest_filter_log"' \ + 'rm -f "$unknown_manifest_output_log"' \ + 'rm -f "$missing_shim_log"' \ + 'rm -f "$missing_project_source_log"' \ + 'rm -f "$missing_wasm_script_manifest_log"' \ + 'rm -rf "$missing_source_root"' \ + 'rm -f "$missing_source_log"' \ + 'rm -rf "$missing_interp_find_root"' \ + 'rm -f "$missing_interp_find_log"' \ + 'rm -rf "$missing_tooldata_runtime_root"' \ + 'rm -f "$missing_tooldata_runtime_log"' \ + 'rm -f "$unknown_group_manifest" "$unknown_group_log"' \ + 'rm -f "$blocker_report_file" "$blocker_scan_log"' \ + 'rm -f "$missing_source_manifest" "$missing_source_log"' \ + 'rm -f "$empty_core_manifest" "$empty_blocked_manifest" "$empty_partition_log"' \ + 'rm -rf "$bad_default_manifest_dir"' \ + 'rm -f "$bad_default_manifest_log"' \ + 'rm -f "$duplicate_manifest" "$duplicate_manifest_log"' \ + 'rm -f "$comment_manifest" "$comment_manifest_log"' \ + 'rm -f "$duplicate_source_manifest" "$duplicate_source_log"' \ + 'rm -f "$duplicate_syntax_manifest" "$duplicate_syntax_log"' \ + 'rm -f "$duplicate_pre_blocker_manifest" "$duplicate_pre_blocker_log"' \ + 'rm -f "$duplicate_pre_link_manifest" "$duplicate_pre_link_log"'; do + if grep -Fx "$forbidden_manual_cleanup" test-linuxcnc-wasm-cmake-safe-probe.sh >/dev/null; then + echo "wasm CMake safe probe still relies on manual cleanup: $forbidden_manual_cleanup" >&2 + exit 1 + fi +done +forbidden_common_flags_process_substitution='readarray -t common_flags < <(' +forbidden_common_flags_helper='./list-linuxcnc-wasm-common-cxxflags.sh' +if grep -RInF "${forbidden_common_flags_process_substitution}LINUXCNC_ROOT=\"\$linuxcnc_root\" ${forbidden_common_flags_helper}" test-linuxcnc-wasm-*.sh >/dev/null; then + echo "wasm probes still read common flags through process substitution" >&2 + grep -RInF "${forbidden_common_flags_process_substitution}LINUXCNC_ROOT=\"\$linuxcnc_root\" ${forbidden_common_flags_helper}" test-linuxcnc-wasm-*.sh >&2 + exit 1 +fi grep -F -- '-DCNC_SIM_LINUXCNC_WASM_SOURCE_MANIFEST="$manifest"' build-wasm.sh >/dev/null grep -F -- 'if ! command -v emcmake >/dev/null 2>&1; then' build-wasm.sh >/dev/null grep -F "Emscripten is required. Install/activate emsdk so emcmake and emcc are in PATH." build-wasm.sh >/dev/null grep -F -- 'emcmake cmake -S core -B build/wasm \' build-wasm.sh >/dev/null grep -F -- 'default_manifest="$PWD/linuxcnc-rs274-wasm-source-files.txt"' build-wasm.sh >/dev/null -grep -F -- 'LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-wasm-manifest-sources.sh "$manifest" core > "$manifest_core_sources"' build-wasm.sh >/dev/null -grep -F -- 'LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-wasm-manifest-sources.sh "$manifest" blocked > "$manifest_blocked_sources"' build-wasm.sh >/dev/null +grep -F -- 'manifest_core_output=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-wasm-manifest-sources.sh "$manifest" core)' build-wasm.sh >/dev/null +grep -F -- 'manifest_blocked_output=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-wasm-manifest-sources.sh "$manifest" blocked)' build-wasm.sh >/dev/null +grep -F -- 'count_nonempty_lines() {' build-wasm.sh >/dev/null +grep -F -- 'manifest_core_count=$(count_nonempty_lines "$manifest_core_output")' build-wasm.sh >/dev/null +grep -F -- 'manifest_blocked_count=$(count_nonempty_lines "$manifest_blocked_output")' build-wasm.sh >/dev/null +if grep -F 'grep -c . <<<"$manifest_core_output" || true' build-wasm.sh >/dev/null; then + echo "build-wasm still counts manifest output through grep -c with || true" >&2 + exit 1 +fi grep -F -- 'if [[ "$manifest" == "$default_manifest" ]] && [[ "$manifest_core_count" -ne 25 || "$manifest_blocked_count" -ne 1 ]]; then' build-wasm.sh >/dev/null +grep -Fx 'LINUXCNC_ROOT="$linuxcnc_root" ./check-linuxcnc-inputs.sh "$manifest"' test-linuxcnc-wasm-cmake-safe-probe.sh >/dev/null ./list-linuxcnc-wasm-safe-shims.sh >/dev/null ./list-linuxcnc-wasm-safe-project-sources.sh >/dev/null ./list-linuxcnc-wasm-manifest-sources.sh "$manifest" core >/dev/null -grep -Fx "src/emc/rs274ngc/interp_arc.cc" \ - < <(./list-linuxcnc-wasm-manifest-sources.sh "$manifest" core) >/dev/null -grep -Fx "src/emc/tooldata/tooldata_mmap.cc" \ - < <(./list-linuxcnc-wasm-manifest-sources.sh "$manifest" blocked) >/dev/null +wasm_manifest_core=$(./list-linuxcnc-wasm-manifest-sources.sh "$manifest" core) +grep -Fx "src/emc/rs274ngc/interp_arc.cc" <<<"$wasm_manifest_core" >/dev/null +if grep -E '\.(hh|h)$' <<<"$wasm_manifest_core" >/dev/null; then + echo "wasm manifest core compile group includes headers" >&2 + exit 1 +fi +wasm_manifest_header=$(./list-linuxcnc-wasm-manifest-sources.sh "$manifest" header) +grep -Fx "src/emc/rs274ngc/rs274ngc_interp.hh" <<<"$wasm_manifest_header" >/dev/null +wasm_manifest_blocked=$(./list-linuxcnc-wasm-manifest-sources.sh "$manifest" blocked) +grep -Fx "src/emc/tooldata/tooldata_mmap.cc" <<<"$wasm_manifest_blocked" >/dev/null +wasm_manifest_blocked_header=$(./list-linuxcnc-wasm-manifest-sources.sh "$manifest" blocked-header) +grep -Fx "src/emc/rs274ngc/boost_pyenum_macros.hh" <<<"$wasm_manifest_blocked_header" >/dev/null unknown_manifest_filter_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_unknown_wasm_manifest_filter.XXXXXX.log") if ./list-linuxcnc-wasm-manifest-sources.sh "$manifest" bogus >"$unknown_manifest_filter_log" 2>&1; then echo "wasm manifest source lister accepted unknown filter" >&2 @@ -84,7 +236,6 @@ if ! grep -F "unknown manifest source filter: bogus" "$unknown_manifest_filter_l sed -n '1,20p' "$unknown_manifest_filter_log" >&2 exit 1 fi -rm -f "$unknown_manifest_filter_log" unknown_manifest_output_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_unknown_wasm_manifest_output.XXXXXX.log") if ./list-linuxcnc-wasm-manifest-sources.sh "$manifest" core bogus >"$unknown_manifest_output_log" 2>&1; then echo "wasm manifest source lister accepted unknown output mode" >&2 @@ -95,20 +246,20 @@ if ! grep -F "unknown manifest source output mode: bogus" "$unknown_manifest_out sed -n '1,20p' "$unknown_manifest_output_log" >&2 exit 1 fi -rm -f "$unknown_manifest_output_log" -grep -Fx "$linuxcnc_root/src/emc/rs274ngc/interp_arc.cc" \ - < <(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-wasm-manifest-sources.sh "$manifest" core full) >/dev/null -grep -Fx "core/wasm_shims/dlfcn.cc" < <(./list-linuxcnc-wasm-safe-shims.sh dlfcn.cc) >/dev/null -grep -Fx "core/wasm_shims/pythonplugin/python_plugin.cc" \ - < <(./list-linuxcnc-wasm-safe-shims.sh python_plugin.cc) >/dev/null -grep -Fx "core/wasm_shims/rtapi_compat.cc" \ - < <(./list-linuxcnc-wasm-safe-shims.sh rtapi_compat.cc) >/dev/null -grep -Fx "core/wasm_shims/tooldata/tooldata_mmap_backend.cc" \ - < <(./list-linuxcnc-wasm-safe-shims.sh tooldata_mmap_backend.cc) >/dev/null -grep -Fx "core/wasm_shims/tooldata/tooldata_runtime_stubs.cc" \ - < <(./list-linuxcnc-wasm-safe-shims.sh tooldata_runtime_stubs.cc) >/dev/null -grep -Fx "core/src/linuxcnc_canon_bridge.cpp" \ - < <(./list-linuxcnc-wasm-safe-project-sources.sh linuxcnc_canon_bridge.cpp) >/dev/null +wasm_manifest_core_full=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-wasm-manifest-sources.sh "$manifest" core full) +grep -Fx "$linuxcnc_root/src/emc/rs274ngc/interp_arc.cc" <<<"$wasm_manifest_core_full" >/dev/null +dlfcn_shim=$(./list-linuxcnc-wasm-safe-shims.sh dlfcn.cc) +grep -Fx "core/wasm_shims/dlfcn.cc" <<<"$dlfcn_shim" >/dev/null +python_plugin_shim=$(./list-linuxcnc-wasm-safe-shims.sh python_plugin.cc) +grep -Fx "core/wasm_shims/pythonplugin/python_plugin.cc" <<<"$python_plugin_shim" >/dev/null +rtapi_compat_shim=$(./list-linuxcnc-wasm-safe-shims.sh rtapi_compat.cc) +grep -Fx "core/wasm_shims/rtapi_compat.cc" <<<"$rtapi_compat_shim" >/dev/null +tooldata_mmap_backend_shim=$(./list-linuxcnc-wasm-safe-shims.sh tooldata_mmap_backend.cc) +grep -Fx "core/wasm_shims/tooldata/tooldata_mmap_backend.cc" <<<"$tooldata_mmap_backend_shim" >/dev/null +tooldata_runtime_stubs_shim=$(./list-linuxcnc-wasm-safe-shims.sh tooldata_runtime_stubs.cc) +grep -Fx "core/wasm_shims/tooldata/tooldata_runtime_stubs.cc" <<<"$tooldata_runtime_stubs_shim" >/dev/null +wasm_bridge_source=$(./list-linuxcnc-wasm-safe-project-sources.sh linuxcnc_canon_bridge.cpp) +grep -Fx "core/src/linuxcnc_canon_bridge.cpp" <<<"$wasm_bridge_source" >/dev/null missing_shim_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_wasm_safe_shim.XXXXXX.log") if ./list-linuxcnc-wasm-safe-shims.sh does_not_exist.cc >"$missing_shim_log" 2>&1; then echo "wasm-safe shim lister accepted missing shim filter" >&2 @@ -119,7 +270,6 @@ if ! grep -F "missing wasm-safe shim in CMake list: does_not_exist.cc" "$missing sed -n '1,20p' "$missing_shim_log" >&2 exit 1 fi -rm -f "$missing_shim_log" missing_project_source_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_wasm_safe_project_source.XXXXXX.log") if ./list-linuxcnc-wasm-safe-project-sources.sh does_not_exist.cc >"$missing_project_source_log" 2>&1; then echo "wasm-safe project source lister accepted missing source filter" >&2 @@ -130,8 +280,6 @@ if ! grep -F "missing wasm-safe project source in CMake list: does_not_exist.cc" sed -n '1,20p' "$missing_project_source_log" >&2 exit 1 fi -rm -f "$missing_project_source_log" -rm -f "$missing_root_log" missing_wasm_script_manifest=${TMPDIR:-/tmp}/cnc_sim_missing_wasm_script_manifest.txt missing_wasm_script_manifest_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_wasm_script_manifest.XXXXXX.log") @@ -152,7 +300,6 @@ for script in \ exit 1 fi done -rm -f "$missing_wasm_script_manifest_log" missing_root_scripts=( test-linuxcnc-wasm-interp-base-link.sh @@ -179,7 +326,6 @@ for script in "${missing_root_scripts[@]}"; do exit 1 fi done -rm -f "$missing_root_log" missing_source_root=$(mktemp -d "${TMPDIR:-/tmp}/cnc_sim_missing_linuxcnc_source_root.XXXXXX") missing_source_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_linuxcnc_source.XXXXXX.log") @@ -207,8 +353,6 @@ for script_and_path in "${fixed_source_scripts[@]}"; do exit 1 fi done -rm -rf "$missing_source_root" -rm -f "$missing_source_log" missing_interp_find_root=$(mktemp -d "${TMPDIR:-/tmp}/cnc_sim_missing_interp_find_source_root.XXXXXX") missing_interp_find_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_interp_find_source.XXXXXX.log") @@ -223,8 +367,6 @@ if ! grep -F "missing LinuxCNC source: src/emc/rs274ngc/interp_find.cc" "$missin sed -n '1,20p' "$missing_interp_find_log" >&2 exit 1 fi -rm -rf "$missing_interp_find_root" -rm -f "$missing_interp_find_log" missing_tooldata_runtime_root=$(mktemp -d "${TMPDIR:-/tmp}/cnc_sim_missing_tooldata_runtime_source_root.XXXXXX") missing_tooldata_runtime_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_tooldata_runtime_source.XXXXXX.log") @@ -239,8 +381,6 @@ if ! grep -F "missing LinuxCNC source: src/emc/tooldata/tooldata_nml.cc" "$missi sed -n '1,20p' "$missing_tooldata_runtime_log" >&2 exit 1 fi -rm -rf "$missing_tooldata_runtime_root" -rm -f "$missing_tooldata_runtime_log" unknown_group_manifest=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_unknown_wasm_manifest_group.XXXXXX.txt") unknown_group_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_unknown_wasm_manifest_group.XXXXXX.log") @@ -270,7 +410,6 @@ for script in \ exit 1 fi done -rm -f "$unknown_group_manifest" "$unknown_group_log" blocker_scan_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_wasm_blocker_scan.XXXXXX.log") ./test-linuxcnc-wasm-rs274ngc-pre-link-blockers.sh >"$blocker_scan_log" 2>&1 @@ -280,7 +419,6 @@ if [[ -z "$blocker_report_file" || ! -f "$blocker_report_file" ]]; then sed -n '1,20p' "$blocker_scan_log" >&2 exit 1 fi -rm -f "$blocker_report_file" "$blocker_scan_log" missing_source_manifest=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_wasm_manifest_source.XXXXXX.txt") missing_source_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_wasm_manifest_source.XXXXXX.log") @@ -330,7 +468,6 @@ if ! grep -F "missing manifest source: src/emc/rs274ngc/does_not_exist.cc" "$mis sed -n '1,20p' "$missing_source_log" >&2 exit 1 fi -rm -f "$missing_source_manifest" "$missing_source_log" empty_partition_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_empty_wasm_manifest_partition.XXXXXX.log") empty_core_manifest=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_empty_wasm_manifest_core.XXXXXX.txt") @@ -355,12 +492,14 @@ if ! grep -F "unexpected wasm manifest partition: core=1 blocked=0" "$empty_part sed -n '1,20p' "$empty_partition_log" >&2 exit 1 fi -rm -f "$empty_core_manifest" "$empty_blocked_manifest" "$empty_partition_log" bad_default_manifest_dir=$(mktemp -d "${TMPDIR:-/tmp}/cnc_sim_bad_default_wasm_manifest.XXXXXX") bad_default_manifest_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_bad_default_wasm_manifest.XXXXXX.log") linuxcnc_root_abs=$(cd "$linuxcnc_root" && pwd) cp build-wasm.sh "$bad_default_manifest_dir/build-wasm.sh" +cp check-linuxcnc-inputs.sh "$bad_default_manifest_dir/check-linuxcnc-inputs.sh" +cp list-linuxcnc-source-files.sh "$bad_default_manifest_dir/list-linuxcnc-source-files.sh" +cp list-linuxcnc-manifest-sources.sh "$bad_default_manifest_dir/list-linuxcnc-manifest-sources.sh" cp list-linuxcnc-wasm-manifest-sources.sh "$bad_default_manifest_dir/list-linuxcnc-wasm-manifest-sources.sh" printf 'core:src/emc/rs274ngc/interp_arc.cc:test bad default core count\nblocked:src/emc/tooldata/tooldata_mmap.cc:test bad default blocked count\n' \ >"$bad_default_manifest_dir/linuxcnc-rs274-wasm-source-files.txt" @@ -373,8 +512,6 @@ if ! grep -F "unexpected wasm manifest partition: core=1 blocked=1" "$bad_defaul sed -n '1,20p' "$bad_default_manifest_log" >&2 exit 1 fi -rm -rf "$bad_default_manifest_dir" -rm -f "$bad_default_manifest_log" duplicate_manifest=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_duplicate_wasm_manifest.XXXXXX.txt") duplicate_manifest_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_duplicate_wasm_manifest.XXXXXX.log") @@ -392,7 +529,6 @@ if ! grep -F "duplicate manifest source: src/emc/rs274ngc/interp_arc.cc" "$dupli sed -n '1,20p' "$duplicate_manifest_log" >&2 exit 1 fi -rm -f "$duplicate_manifest" "$duplicate_manifest_log" comment_manifest=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_comment_wasm_manifest.XXXXXX.txt") comment_manifest_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_comment_wasm_manifest.XXXXXX.log") @@ -410,7 +546,6 @@ if grep -F "duplicate manifest source" "$comment_manifest_log" >/dev/null; then sed -n '1,20p' "$comment_manifest_log" >&2 exit 1 fi -rm -f "$comment_manifest" "$comment_manifest_log" duplicate_source_manifest=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_duplicate_wasm_source_manifest.XXXXXX.txt") duplicate_source_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_duplicate_wasm_source_manifest.XXXXXX.log") @@ -428,7 +563,6 @@ if ! grep -F "duplicate manifest source: src/emc/rs274ngc/interp_arc.cc" "$dupli sed -n '1,20p' "$duplicate_source_log" >&2 exit 1 fi -rm -f "$duplicate_source_manifest" "$duplicate_source_log" duplicate_syntax_manifest=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_duplicate_wasm_syntax_manifest.XXXXXX.txt") duplicate_syntax_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_duplicate_wasm_syntax_manifest.XXXXXX.log") @@ -446,7 +580,6 @@ if ! grep -F "duplicate manifest source: src/emc/rs274ngc/interp_arc.cc" "$dupli sed -n '1,20p' "$duplicate_syntax_log" >&2 exit 1 fi -rm -f "$duplicate_syntax_manifest" "$duplicate_syntax_log" duplicate_pre_blocker_manifest=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_duplicate_wasm_pre_blocker_manifest.XXXXXX.txt") duplicate_pre_blocker_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_duplicate_wasm_pre_blocker_manifest.XXXXXX.log") @@ -464,7 +597,6 @@ if ! grep -F "duplicate manifest source: src/emc/rs274ngc/interp_arc.cc" "$dupli sed -n '1,20p' "$duplicate_pre_blocker_log" >&2 exit 1 fi -rm -f "$duplicate_pre_blocker_manifest" "$duplicate_pre_blocker_log" duplicate_pre_link_manifest=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_duplicate_wasm_pre_link_manifest.XXXXXX.txt") duplicate_pre_link_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_duplicate_wasm_pre_link_manifest.XXXXXX.log") @@ -482,7 +614,6 @@ if ! grep -F "duplicate manifest source: src/emc/rs274ngc/interp_arc.cc" "$dupli sed -n '1,20p' "$duplicate_pre_link_log" >&2 exit 1 fi -rm -f "$duplicate_pre_link_manifest" "$duplicate_pre_link_log" linuxcnc_root=$(cd "$linuxcnc_root" && pwd) default_manifest="$PWD/linuxcnc-rs274-wasm-source-files.txt" diff --git a/test-linuxcnc-wasm-interp-base-link.sh b/test-linuxcnc-wasm-interp-base-link.sh index d41acbf..c214feb 100755 --- a/test-linuxcnc-wasm-interp-base-link.sh +++ b/test-linuxcnc-wasm-interp-base-link.sh @@ -7,26 +7,14 @@ linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} cxx=${CXX:-g++} build_dir=$(mktemp -d "${TMPDIR:-/tmp}/cnc_sim_linuxcnc_wasm_interp_base_link.XXXXXX") -if [[ ! -d "$linuxcnc_root" ]]; then - echo "missing LinuxCNC root: $linuxcnc_root" >&2 - exit 1 -fi trap 'rm -rf "$build_dir"' EXIT +LINUXCNC_ROOT="$linuxcnc_root" ./check-linuxcnc-inputs.sh --root-only dlfcn_shim=$(./list-linuxcnc-wasm-safe-shims.sh dlfcn.cc) interp_base_source=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-files.sh src/emc/rs274ngc/interp_base.cc) -common_flags=( - -std=c++17 - -DULAPI - -I "$(pwd)/core/wasm_shims" - -I "$linuxcnc_root/src" - -I "$linuxcnc_root/src/emc" - -I "$linuxcnc_root/src/emc/nml_intf" - -I "$linuxcnc_root/src/emc/rs274ngc" - -I "$linuxcnc_root/src/emc/motion" - -I "$linuxcnc_root/include" -) +common_flag_output=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-wasm-common-cxxflags.sh) +mapfile -t common_flags <<<"$common_flag_output" "$cxx" "${common_flags[@]}" \ "$dlfcn_shim" \ diff --git a/test-linuxcnc-wasm-interp-find-link.sh b/test-linuxcnc-wasm-interp-find-link.sh index b76f2b0..bc2ea8c 100755 --- a/test-linuxcnc-wasm-interp-find-link.sh +++ b/test-linuxcnc-wasm-interp-find-link.sh @@ -7,34 +7,21 @@ linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} cxx=${CXX:-g++} build_dir=$(mktemp -d "${TMPDIR:-/tmp}/cnc_sim_linuxcnc_wasm_interp_find_link.XXXXXX") -if [[ ! -d "$linuxcnc_root" ]]; then - echo "missing LinuxCNC root: $linuxcnc_root" >&2 - exit 1 -fi trap 'rm -rf "$build_dir"' EXIT +LINUXCNC_ROOT="$linuxcnc_root" ./check-linuxcnc-inputs.sh --root-only tooldata_mmap_backend_shim=$(./list-linuxcnc-wasm-safe-shims.sh tooldata_mmap_backend.cc) tooldata_runtime_stubs_shim=$(./list-linuxcnc-wasm-safe-shims.sh tooldata_runtime_stubs.cc) rtapi_compat_shim=$(./list-linuxcnc-wasm-safe-shims.sh rtapi_compat.cc) -readarray -t linuxcnc_sources < <(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-files.sh \ +linuxcnc_source_output=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-files.sh \ src/emc/tooldata/tooldata_common.cc \ src/emc/rs274ngc/interp_find.cc) +mapfile -t linuxcnc_sources <<<"$linuxcnc_source_output" tooldata_common_source=${linuxcnc_sources[0]} interp_find_source=${linuxcnc_sources[1]} -common_flags=( - -std=c++17 - -DULAPI - -ffunction-sections - -fdata-sections - -I "$(pwd)/core/wasm_shims" - -I "$linuxcnc_root/src" - -I "$linuxcnc_root/src/emc" - -I "$linuxcnc_root/src/emc/nml_intf" - -I "$linuxcnc_root/src/emc/rs274ngc" - -I "$linuxcnc_root/src/emc/motion" - -I "$linuxcnc_root/include" -) +common_flag_output=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-wasm-common-cxxflags.sh --function-sections --data-sections) +mapfile -t common_flags <<<"$common_flag_output" "$cxx" "${common_flags[@]}" \ -c "$tooldata_common_source" \ diff --git a/test-linuxcnc-wasm-python-plugin-link.sh b/test-linuxcnc-wasm-python-plugin-link.sh index ad0fa43..764e2df 100755 --- a/test-linuxcnc-wasm-python-plugin-link.sh +++ b/test-linuxcnc-wasm-python-plugin-link.sh @@ -7,25 +7,13 @@ linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} cxx=${CXX:-g++} build_dir=$(mktemp -d "${TMPDIR:-/tmp}/cnc_sim_linuxcnc_wasm_python_plugin_link.XXXXXX") -if [[ ! -d "$linuxcnc_root" ]]; then - echo "missing LinuxCNC root: $linuxcnc_root" >&2 - exit 1 -fi trap 'rm -rf "$build_dir"' EXIT +LINUXCNC_ROOT="$linuxcnc_root" ./check-linuxcnc-inputs.sh --root-only python_plugin_shim=$(./list-linuxcnc-wasm-safe-shims.sh python_plugin.cc) -common_flags=( - -std=c++17 - -DULAPI - -I "$(pwd)/core/wasm_shims" - -I "$linuxcnc_root/src" - -I "$linuxcnc_root/src/emc" - -I "$linuxcnc_root/src/emc/nml_intf" - -I "$linuxcnc_root/src/emc/rs274ngc" - -I "$linuxcnc_root/src/emc/motion" - -I "$linuxcnc_root/include" -) +common_flag_output=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-wasm-common-cxxflags.sh) +mapfile -t common_flags <<<"$common_flag_output" "$cxx" "${common_flags[@]}" \ "$python_plugin_shim" \ diff --git a/test-linuxcnc-wasm-rs274ngc-pre-link-blockers.sh b/test-linuxcnc-wasm-rs274ngc-pre-link-blockers.sh index c66d7a1..8dd0534 100755 --- a/test-linuxcnc-wasm-rs274ngc-pre-link-blockers.sh +++ b/test-linuxcnc-wasm-rs274ngc-pre-link-blockers.sh @@ -6,48 +6,52 @@ cd "$(dirname "$0")" linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} cxx=${CXX:-g++} build_dir=$(mktemp -d "${TMPDIR:-/tmp}/cnc_sim_linuxcnc_wasm_rs274ngc_pre_link_blockers.XXXXXX") -report_file=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_linuxcnc_wasm_rs274ngc_pre_undefined.XXXXXX.txt") manifest=${1:-linuxcnc-rs274-wasm-source-files.txt} +keep_report_file=0 -if [[ ! -f "$manifest" ]]; then - echo "missing manifest: $manifest" >&2 +cleanup_rs274ngc_pre_blocker_temps() { + rm -rf "$build_dir" + if [[ "$keep_report_file" -eq 0 && -n "${report_file:-}" ]]; then + rm -f "$report_file" + fi +} +trap cleanup_rs274ngc_pre_blocker_temps EXIT +grep -Fx 'trap cleanup_rs274ngc_pre_blocker_temps EXIT' test-linuxcnc-wasm-rs274ngc-pre-link-blockers.sh >/dev/null +if awk '/^} > "\$report_file" \|\| true$/{ found = 1 } END { exit !found }' test-linuxcnc-wasm-rs274ngc-pre-link-blockers.sh >/dev/null; then + echo "rs274ngc_pre blocker scan still hides report pipeline failures with || true" >&2 exit 1 fi - -if [[ ! -d "$linuxcnc_root" ]]; then - echo "missing LinuxCNC root: $linuxcnc_root" >&2 +if ! awk ' + index($0, "LINUXCNC_ROOT=\"$linuxcnc_root\" ./check-linuxcnc-inputs.sh \"$manifest\"") { check_line = NR } + index($0, "report_file=$(mktemp ") { report_line = NR } + END { exit !(check_line && report_line && check_line < report_line) } +' test-linuxcnc-wasm-rs274ngc-pre-link-blockers.sh; then + echo "rs274ngc_pre blocker scan creates its report before input validation" >&2 exit 1 fi -trap 'rm -rf "$build_dir"' EXIT +if ! awk ' + index($0, "if [[ \"$keep_report_file\" -eq 0 && -n \"${report_file:-}\" ]]; then") { cleanup_line = NR } + index($0, "keep_report_file=1") { keep_line = NR } + index($0, "echo \"wrote $report_file\"") { wrote_line = NR } + END { exit !(cleanup_line && keep_line && wrote_line && cleanup_line < keep_line && keep_line < wrote_line) } +' test-linuxcnc-wasm-rs274ngc-pre-link-blockers.sh; then + echo "rs274ngc_pre blocker scan does not clean failed reports before preserving successful ones" >&2 + exit 1 +fi +LINUXCNC_ROOT="$linuxcnc_root" ./check-linuxcnc-inputs.sh "$manifest" +report_file=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_linuxcnc_wasm_rs274ngc_pre_undefined.XXXXXX.txt") -common_flags=( - -std=c++20 - -DULAPI - -ffunction-sections - -fdata-sections - -include wctype.h - -I "$(pwd)/core/include" - -I "$(pwd)/core/wasm_shims" - -I "$linuxcnc_root/src" - -I "$linuxcnc_root/src/emc" - -I "$linuxcnc_root/src/emc/nml_intf" - -I "$linuxcnc_root/src/emc/rs274ngc" - -I "$linuxcnc_root/src/emc/motion" - -I "$linuxcnc_root/include" -) +common_flag_output=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-wasm-common-cxxflags.sh --profile core20-sections) +mapfile -t common_flags <<<"$common_flag_output" -shim_list="$build_dir/wasm_safe_shims.txt" -./list-linuxcnc-wasm-safe-shims.sh > "$shim_list" -mapfile -t shim_sources < "$shim_list" +shim_output=$(./list-linuxcnc-wasm-safe-shims.sh) +mapfile -t shim_sources <<<"$shim_output" -project_source_list="$build_dir/wasm_safe_project_sources.txt" -./list-linuxcnc-wasm-safe-project-sources.sh > "$project_source_list" -mapfile -t project_sources < "$project_source_list" +project_source_output=$(./list-linuxcnc-wasm-safe-project-sources.sh) +mapfile -t project_sources <<<"$project_source_output" -linuxcnc_sources=() -source_list="$build_dir/wasm_core_sources.txt" -LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-wasm-manifest-sources.sh "$manifest" core full > "$source_list" -mapfile -t linuxcnc_sources < "$source_list" +source_output=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-wasm-manifest-sources.sh "$manifest" core full) +mapfile -t linuxcnc_sources <<<"$source_output" shim_index=0 for source in "${shim_sources[@]}"; do @@ -87,11 +91,14 @@ nm --defined-only "$build_dir"/*.o \ { comm -23 "$undefined_file" "$defined_file" \ - | grep -Ev '^(_GLOBAL_OFFSET_TABLE_|_Unwind_Resume)$' \ - | grep -Ev '^(__assert_fail|__cxa_|__divdc3|__dso_handle|__errno_location|__gxx_personality_v0|__isoc23_|__muldc3|__stack_chk_fail)' \ - | grep -Ev '^(abort|access|acos|asin|atan2|atof|atoi|basename|cabs|calloc|ceil|close|closedir|copysign|cos|cosl|exit|exp|fclose|fdatasync|fdopen|feof|fflush|fgetc|fgets|fileno|floor|fmod|fopen|fprintf|fputs|free|freelocale|fseek|fstat|ftell|fwrite|getcwd|getenv|getpid|gettimeofday|hypot|islower|isprint|isspace|isupper|isxdigit|link|localtime|log|lstat|malloc|memchr|memcmp|memcpy|memmove|memset|mkstemp|nearbyint|newlocale|open|opendir|perror|pow|printf|pthread_mutex_lock|pthread_mutex_unlock|putc|puts|read|readdir|realpath|realloc|remove|rename|setlocale|sin|sinl|snprintf|sprintf|sqrt|sscanf|stat|stderr|stdout|strcasecmp|strcat|strchr|strcmp|strcpy|strdup|strerror|strlen|strncasecmp|strncat|strncmp|strncpy|strrchr|strspn|strstr|strtod|strtok_r|strtol|strtoul|tan|tanl|tolower|toupper|towlower|unlink|uselocale|vfprintf|vsnprintf|wordexp|wordfree)$' \ - | grep -Ev '^(_ZN3fmt|_ZSt|_ZNSt|_ZNKSt|_ZNKRSt|_ZNS|_ZTI|_ZTS|_ZTV|_Zdl|_Znwm|_Znam|_Zda|_ZdaPv|_ZdaPvm|_ZdlPvm|_ZTv|_ZTh)' -} > "$report_file" || true + | awk ' + $0 ~ /^(_GLOBAL_OFFSET_TABLE_|_Unwind_Resume)$/ { next } + $0 ~ /^(__assert_fail|__cxa_|__divdc3|__dso_handle|__errno_location|__gxx_personality_v0|__isoc23_|__muldc3|__stack_chk_fail)/ { next } + $0 ~ /^(abort|access|acos|asin|atan2|atof|atoi|basename|cabs|calloc|ceil|close|closedir|copysign|cos|cosl|exit|exp|fclose|fdatasync|fdopen|feof|fflush|fgetc|fgets|fileno|floor|fmod|fopen|fprintf|fputs|free|freelocale|fseek|fstat|ftell|fwrite|getcwd|getenv|getpid|gettimeofday|hypot|islower|isprint|isspace|isupper|isxdigit|link|localtime|log|lstat|malloc|memchr|memcmp|memcpy|memmove|memset|mkstemp|nearbyint|newlocale|open|opendir|perror|pow|printf|pthread_mutex_lock|pthread_mutex_unlock|putc|puts|read|readdir|realpath|realloc|remove|rename|setlocale|sin|sinl|snprintf|sprintf|sqrt|sscanf|stat|stderr|stdout|strcasecmp|strcat|strchr|strcmp|strcpy|strdup|strerror|strlen|strncasecmp|strncat|strncmp|strncpy|strrchr|strspn|strstr|strtod|strtok_r|strtol|strtoul|tan|tanl|tolower|toupper|towlower|unlink|uselocale|vfprintf|vsnprintf|wordexp|wordfree)$/ { next } + $0 ~ /^(_ZN3fmt|_ZSt|_ZNSt|_ZNKSt|_ZNKRSt|_ZNS|_ZTI|_ZTS|_ZTV|_Zdl|_Znwm|_Znam|_Zda|_ZdaPv|_ZdaPvm|_ZdlPvm|_ZTv|_ZTh)/ { next } + { print } + ' +} > "$report_file" if grep -F "_ZN6Interp13read_commentEPcPiP12block_structPd" "$report_file" >/dev/null; then echo "unexpected unresolved read_comment after interp_read.cc" >&2 @@ -139,4 +146,5 @@ if [[ -s "$report_file" ]]; then fi echo "linuxcnc wasm rs274ngc_pre link blocker scan passed" +keep_report_file=1 echo "wrote $report_file" diff --git a/test-linuxcnc-wasm-rs274ngc-pre-link.sh b/test-linuxcnc-wasm-rs274ngc-pre-link.sh index e4122bb..30390fe 100755 --- a/test-linuxcnc-wasm-rs274ngc-pre-link.sh +++ b/test-linuxcnc-wasm-rs274ngc-pre-link.sh @@ -8,45 +8,20 @@ cxx=${CXX:-g++} build_dir=$(mktemp -d "${TMPDIR:-/tmp}/cnc_sim_linuxcnc_wasm_rs274ngc_pre_link.XXXXXX") manifest=${1:-linuxcnc-rs274-wasm-source-files.txt} -if [[ ! -f "$manifest" ]]; then - echo "missing manifest: $manifest" >&2 - exit 1 -fi - -if [[ ! -d "$linuxcnc_root" ]]; then - echo "missing LinuxCNC root: $linuxcnc_root" >&2 - exit 1 -fi trap 'rm -rf "$build_dir"' EXIT +LINUXCNC_ROOT="$linuxcnc_root" ./check-linuxcnc-inputs.sh "$manifest" -common_flags=( - -std=c++20 - -DULAPI - -ffunction-sections - -fdata-sections - -I "$(pwd)/core/src" - -include wctype.h - -I "$(pwd)/core/include" - -I "$(pwd)/core/wasm_shims" - -I "$linuxcnc_root/src" - -I "$linuxcnc_root/src/emc" - -I "$linuxcnc_root/src/emc/nml_intf" - -I "$linuxcnc_root/src/emc/rs274ngc" - -I "$linuxcnc_root/src/emc/motion" - -I "$linuxcnc_root/include" -) +common_flag_output=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-wasm-common-cxxflags.sh --profile core20-sections) +mapfile -t common_flags <<<"$common_flag_output" -shim_list="$build_dir/wasm_safe_shims.txt" -./list-linuxcnc-wasm-safe-shims.sh > "$shim_list" -mapfile -t sources < "$shim_list" -project_source_list="$build_dir/wasm_safe_project_sources.txt" -./list-linuxcnc-wasm-safe-project-sources.sh > "$project_source_list" -mapfile -t project_sources < "$project_source_list" +shim_output=$(./list-linuxcnc-wasm-safe-shims.sh) +mapfile -t sources <<<"$shim_output" +project_source_output=$(./list-linuxcnc-wasm-safe-project-sources.sh) +mapfile -t project_sources <<<"$project_source_output" sources+=("${project_sources[@]}") -source_list="$build_dir/wasm_core_sources.txt" -LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-wasm-manifest-sources.sh "$manifest" core full > "$source_list" -mapfile -t linuxcnc_sources < "$source_list" +source_output=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-wasm-manifest-sources.sh "$manifest" core full) +mapfile -t linuxcnc_sources <<<"$source_output" for source in "${linuxcnc_sources[@]}"; do sources+=("$source") done diff --git a/test-linuxcnc-wasm-rs274ngc-pre-object.sh b/test-linuxcnc-wasm-rs274ngc-pre-object.sh index 0f38382..465b505 100755 --- a/test-linuxcnc-wasm-rs274ngc-pre-object.sh +++ b/test-linuxcnc-wasm-rs274ngc-pre-object.sh @@ -7,24 +7,12 @@ linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} cxx=${CXX:-g++} build_dir=$(mktemp -d "${TMPDIR:-/tmp}/cnc_sim_linuxcnc_wasm_rs274ngc_pre_object.XXXXXX") -if [[ ! -d "$linuxcnc_root" ]]; then - echo "missing LinuxCNC root: $linuxcnc_root" >&2 - exit 1 -fi trap 'rm -rf "$build_dir"' EXIT +LINUXCNC_ROOT="$linuxcnc_root" ./check-linuxcnc-inputs.sh --root-only rs274ngc_pre_source=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-files.sh src/emc/rs274ngc/rs274ngc_pre.cc) -common_flags=( - -std=c++17 - -DULAPI - -I "$(pwd)/core/wasm_shims" - -I "$linuxcnc_root/src" - -I "$linuxcnc_root/src/emc" - -I "$linuxcnc_root/src/emc/nml_intf" - -I "$linuxcnc_root/src/emc/rs274ngc" - -I "$linuxcnc_root/src/emc/motion" - -I "$linuxcnc_root/include" -) +common_flag_output=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-wasm-common-cxxflags.sh) +mapfile -t common_flags <<<"$common_flag_output" "$cxx" "${common_flags[@]}" \ -c "$rs274ngc_pre_source" \ diff --git a/test-linuxcnc-wasm-source-objects.sh b/test-linuxcnc-wasm-source-objects.sh index 029057b..7439653 100755 --- a/test-linuxcnc-wasm-source-objects.sh +++ b/test-linuxcnc-wasm-source-objects.sh @@ -8,39 +8,17 @@ manifest=${1:-linuxcnc-rs274-wasm-source-files.txt} cxx=${CXX:-g++} build_dir=$(mktemp -d "${TMPDIR:-/tmp}/cnc_sim_linuxcnc_wasm_safe_probe.XXXXXX") -if [[ ! -f "$manifest" ]]; then - echo "missing manifest: $manifest" >&2 - exit 1 -fi - -if [[ ! -d "$linuxcnc_root" ]]; then - echo "missing LinuxCNC root: $linuxcnc_root" >&2 - exit 1 -fi trap 'rm -rf "$build_dir"' EXIT +LINUXCNC_ROOT="$linuxcnc_root" ./check-linuxcnc-inputs.sh "$manifest" -shim_list="$build_dir/wasm_safe_shims.txt" -./list-linuxcnc-wasm-safe-shims.sh > "$shim_list" -mapfile -t shim_sources < "$shim_list" +shim_output=$(./list-linuxcnc-wasm-safe-shims.sh) +mapfile -t shim_sources <<<"$shim_output" -common_flags=( - -std=c++20 - -DULAPI - -include wctype.h - -I "$(pwd)/core/include" - -I "$(pwd)/core/src" - -I "$(pwd)/core/wasm_shims" - -I "$linuxcnc_root/src" - -I "$linuxcnc_root/src/emc" - -I "$linuxcnc_root/src/emc/nml_intf" - -I "$linuxcnc_root/src/emc/rs274ngc" - -I "$linuxcnc_root/src/emc/motion" - -I "$linuxcnc_root/include" -) +common_flag_output=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-wasm-common-cxxflags.sh --profile core20) +mapfile -t common_flags <<<"$common_flag_output" -source_list="$build_dir/wasm_core_sources.txt" -LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-wasm-manifest-sources.sh "$manifest" core full > "$source_list" -mapfile -t sources < "$source_list" +source_output=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-wasm-manifest-sources.sh "$manifest" core full) +mapfile -t sources <<<"$source_output" shim_index=0 for source in "${shim_sources[@]}"; do diff --git a/test-linuxcnc-wasm-source-syntax.sh b/test-linuxcnc-wasm-source-syntax.sh index f701574..fed6c89 100755 --- a/test-linuxcnc-wasm-source-syntax.sh +++ b/test-linuxcnc-wasm-source-syntax.sh @@ -7,36 +7,13 @@ linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} manifest=${1:-linuxcnc-rs274-wasm-source-files.txt} cxx=${CXX:-g++} -if [[ ! -f "$manifest" ]]; then - echo "missing manifest: $manifest" >&2 - exit 1 -fi +LINUXCNC_ROOT="$linuxcnc_root" ./check-linuxcnc-inputs.sh "$manifest" -if [[ ! -d "$linuxcnc_root" ]]; then - echo "missing LinuxCNC root: $linuxcnc_root" >&2 - exit 1 -fi +common_flag_output=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-wasm-common-cxxflags.sh --profile core20 --syntax-only) +mapfile -t common_flags <<<"$common_flag_output" -common_flags=( - -std=c++20 - -DULAPI - -fsyntax-only - -include wctype.h - -I "$(pwd)/core/include" - -I "$(pwd)/core/src" - -I "$(pwd)/core/wasm_shims" - -I "$linuxcnc_root/src" - -I "$linuxcnc_root/src/emc" - -I "$linuxcnc_root/src/emc/nml_intf" - -I "$linuxcnc_root/src/emc/rs274ngc" - -I "$linuxcnc_root/src/emc/motion" - -I "$linuxcnc_root/include" -) - -source_list=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_linuxcnc_wasm_source_syntax_sources.XXXXXX.txt") -trap 'rm -f "$source_list"' EXIT -LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-wasm-manifest-sources.sh "$manifest" core full > "$source_list" -mapfile -t sources < "$source_list" +source_output=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-wasm-manifest-sources.sh "$manifest" core full) +mapfile -t sources <<<"$source_output" for source in "${sources[@]}"; do "$cxx" "${common_flags[@]}" "$source" diff --git a/test-linuxcnc-wasm-tooldata-common-link.sh b/test-linuxcnc-wasm-tooldata-common-link.sh index 4ae81f2..1086d24 100755 --- a/test-linuxcnc-wasm-tooldata-common-link.sh +++ b/test-linuxcnc-wasm-tooldata-common-link.sh @@ -7,27 +7,15 @@ linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} cxx=${CXX:-g++} build_dir=$(mktemp -d "${TMPDIR:-/tmp}/cnc_sim_linuxcnc_wasm_tooldata_common_link.XXXXXX") -if [[ ! -d "$linuxcnc_root" ]]; then - echo "missing LinuxCNC root: $linuxcnc_root" >&2 - exit 1 -fi trap 'rm -rf "$build_dir"' EXIT +LINUXCNC_ROOT="$linuxcnc_root" ./check-linuxcnc-inputs.sh --root-only tooldata_mmap_backend_shim=$(./list-linuxcnc-wasm-safe-shims.sh tooldata_mmap_backend.cc) tooldata_runtime_stubs_shim=$(./list-linuxcnc-wasm-safe-shims.sh tooldata_runtime_stubs.cc) tooldata_common_source=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-files.sh src/emc/tooldata/tooldata_common.cc) -common_flags=( - -std=c++17 - -DULAPI - -I "$linuxcnc_root/src/emc/tooldata" - -I "$linuxcnc_root/src" - -I "$linuxcnc_root/src/emc" - -I "$linuxcnc_root/src/emc/nml_intf" - -I "$linuxcnc_root/src/emc/rs274ngc" - -I "$linuxcnc_root/src/emc/motion" - -I "$linuxcnc_root/include" -) +common_flag_output=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-wasm-common-cxxflags.sh --tooldata-include) +mapfile -t common_flags <<<"$common_flag_output" "$cxx" "${common_flags[@]}" \ "$tooldata_common_source" \ diff --git a/test-linuxcnc-wasm-tooldata-link.sh b/test-linuxcnc-wasm-tooldata-link.sh index 7f7af57..66ca632 100755 --- a/test-linuxcnc-wasm-tooldata-link.sh +++ b/test-linuxcnc-wasm-tooldata-link.sh @@ -7,27 +7,15 @@ linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} cxx=${CXX:-g++} build_dir=$(mktemp -d "${TMPDIR:-/tmp}/cnc_sim_linuxcnc_wasm_tooldata_link.XXXXXX") -if [[ ! -d "$linuxcnc_root" ]]; then - echo "missing LinuxCNC root: $linuxcnc_root" >&2 - exit 1 -fi trap 'rm -rf "$build_dir"' EXIT +LINUXCNC_ROOT="$linuxcnc_root" ./check-linuxcnc-inputs.sh --root-only tooldata_mmap_backend_shim=$(./list-linuxcnc-wasm-safe-shims.sh tooldata_mmap_backend.cc) tooldata_runtime_stubs_shim=$(./list-linuxcnc-wasm-safe-shims.sh tooldata_runtime_stubs.cc) tooldata_common_source=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-files.sh src/emc/tooldata/tooldata_common.cc) -common_flags=( - -std=c++17 - -DULAPI - -I "$(pwd)/core/wasm_shims" - -I "$linuxcnc_root/src" - -I "$linuxcnc_root/src/emc" - -I "$linuxcnc_root/src/emc/nml_intf" - -I "$linuxcnc_root/src/emc/rs274ngc" - -I "$linuxcnc_root/src/emc/motion" - -I "$linuxcnc_root/include" -) +common_flag_output=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-wasm-common-cxxflags.sh) +mapfile -t common_flags <<<"$common_flag_output" "$cxx" "${common_flags[@]}" \ "$tooldata_common_source" \ diff --git a/test-linuxcnc-wasm-tooldata-mmap-symbols.sh b/test-linuxcnc-wasm-tooldata-mmap-symbols.sh index fdf9269..bd8650e 100755 --- a/test-linuxcnc-wasm-tooldata-mmap-symbols.sh +++ b/test-linuxcnc-wasm-tooldata-mmap-symbols.sh @@ -7,26 +7,14 @@ linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} cxx=${CXX:-g++} build_dir=$(mktemp -d "${TMPDIR:-/tmp}/cnc_sim_linuxcnc_wasm_tooldata_mmap_symbols.XXXXXX") -if [[ ! -d "$linuxcnc_root" ]]; then - echo "missing LinuxCNC root: $linuxcnc_root" >&2 - exit 1 -fi trap 'rm -rf "$build_dir"' EXIT +LINUXCNC_ROOT="$linuxcnc_root" ./check-linuxcnc-inputs.sh --root-only tooldata_mmap_backend_shim=$(./list-linuxcnc-wasm-safe-shims.sh tooldata_mmap_backend.cc) tooldata_mmap_source=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-files.sh src/emc/tooldata/tooldata_mmap.cc) -common_flags=( - -std=c++17 - -DULAPI - -I "$(pwd)/core/wasm_shims" - -I "$linuxcnc_root/src" - -I "$linuxcnc_root/src/emc" - -I "$linuxcnc_root/src/emc/nml_intf" - -I "$linuxcnc_root/src/emc/rs274ngc" - -I "$linuxcnc_root/src/emc/motion" - -I "$linuxcnc_root/include" -) +common_flag_output=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-wasm-common-cxxflags.sh) +mapfile -t common_flags <<<"$common_flag_output" "$cxx" "${common_flags[@]}" \ -c "$tooldata_mmap_backend_shim" \ diff --git a/test-linuxcnc-wasm-tooldata-runtime-symbols.sh b/test-linuxcnc-wasm-tooldata-runtime-symbols.sh index 7f37d72..1b5f70e 100755 --- a/test-linuxcnc-wasm-tooldata-runtime-symbols.sh +++ b/test-linuxcnc-wasm-tooldata-runtime-symbols.sh @@ -7,31 +7,20 @@ linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} cxx=${CXX:-g++} build_dir=$(mktemp -d "${TMPDIR:-/tmp}/cnc_sim_linuxcnc_wasm_tooldata_runtime_symbols.XXXXXX") -if [[ ! -d "$linuxcnc_root" ]]; then - echo "missing LinuxCNC root: $linuxcnc_root" >&2 - exit 1 -fi trap 'rm -rf "$build_dir"' EXIT +LINUXCNC_ROOT="$linuxcnc_root" ./check-linuxcnc-inputs.sh --root-only tooldata_mmap_backend_shim=$(./list-linuxcnc-wasm-safe-shims.sh tooldata_mmap_backend.cc) tooldata_runtime_stubs_shim=$(./list-linuxcnc-wasm-safe-shims.sh tooldata_runtime_stubs.cc) -readarray -t linuxcnc_sources < <(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-files.sh \ +linuxcnc_source_output=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-files.sh \ src/emc/tooldata/tooldata_db.cc \ src/emc/tooldata/tooldata_nml.cc) +mapfile -t linuxcnc_sources <<<"$linuxcnc_source_output" tooldata_db_source=${linuxcnc_sources[0]} tooldata_nml_source=${linuxcnc_sources[1]} -common_flags=( - -std=c++17 - -DULAPI - -I "$(pwd)/core/wasm_shims" - -I "$linuxcnc_root/src" - -I "$linuxcnc_root/src/emc" - -I "$linuxcnc_root/src/emc/nml_intf" - -I "$linuxcnc_root/src/emc/rs274ngc" - -I "$linuxcnc_root/src/emc/motion" - -I "$linuxcnc_root/include" -) +common_flag_output=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-wasm-common-cxxflags.sh) +mapfile -t common_flags <<<"$common_flag_output" "$cxx" "${common_flags[@]}" \ -c "$tooldata_runtime_stubs_shim" \