Files
cnc_wams/wasm-port/tools/verify_native_linuxcnc_fixture_baseline.sh
wangdequan 40f8103c94 按建议继续完成后续工作
结论:upstream rs274 side-by-side 基线已从简单 parser/conversion fixture 扩展到 motion、offset、feed-control、comment/logging、numbered parameter、spindle orient、file finish 和 tool reload 等稳定 canonical 事件;完整 native 验证已通过。
2026-06-07 22:44:17 +08:00

448 lines
11 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
UPSTREAM_RS274="$ROOT_DIR/../linuxcnc/bin/rs274"
GCODE_FIXTURE_DIR="$ROOT_DIR/tests/fixtures/gcode"
CANON_FIXTURE_DIR="$ROOT_DIR/tests/fixtures/canon"
FIXTURES=(
minimal_linear
comment_logging
coordinate_offsets
cutter_comp_motion
feed_control_modes
g53_machine_coordinates
length_units
plane_selection
modal_incremental
numbered_params
percent_file_finish
position_params
spindle_orient
tool_reload
)
if [[ ! -x "$UPSTREAM_RS274" ]]; then
echo "missing upstream rs274 executable: $UPSTREAM_RS274" >&2
exit 1
fi
TMP_DIR="$(mktemp -d)"
trap 'rm -rf "$TMP_DIR"' EXIT
normalize_standalone_events() {
awk '
function norm(value) {
return sprintf("%.12g", value + 0)
}
/^canon_event=/ {
line = $0
sub(/^canon_event=/, "", line)
if (line ~ /^COMMENT: /) {
sub(/^COMMENT: /, "", line)
print "COMMENT|" line
next
}
if (line ~ /^MESSAGE: /) {
sub(/^MESSAGE: /, "", line)
print "MESSAGE|" line
next
}
if (line ~ /^LOG(OPEN|APPEND)?: /) {
op = line
sub(/:.*/, "", op)
sub(/^[^:]+: /, "", line)
print op "|" line
next
}
if (line == "LOGCLOSE" || line == "FINISH" || line == "RELOAD_TOOLDATA") {
print line
next
}
if (line ~ /^SELECT_PLANE plane=/) {
value = line
sub(/^SELECT_PLANE plane=/, "", value)
if (value == "1") {
print "SELECT_PLANE|XY"
} else if (value == "2") {
print "SELECT_PLANE|YZ"
} else if (value == "3") {
print "SELECT_PLANE|XZ"
}
next
}
if (line ~ /^USE_LENGTH_UNITS units=/) {
value = line
sub(/^USE_LENGTH_UNITS units=/, "", value)
if (value == "1") {
print "USE_LENGTH_UNITS|INCHES"
} else if (value == "2") {
print "USE_LENGTH_UNITS|MM"
}
next
}
if (line ~ /^SET_G5X_OFFSET index=/) {
split(line, fields, /[[:space:]]+/)
for (i in fields) {
split(fields[i], pair, "=")
values[pair[1]] = pair[2]
}
print "SET_G5X_OFFSET|" values["index"] "|" norm(values["x"]) "|" \
norm(values["y"]) "|" norm(values["z"]) "|" norm(values["a"]) "|" \
norm(values["b"]) "|" norm(values["c"])
delete values
next
}
if (line ~ /^SET_G92_OFFSET /) {
split(line, fields, /[[:space:]]+/)
for (i in fields) {
split(fields[i], pair, "=")
values[pair[1]] = pair[2]
}
print "SET_G92_OFFSET|" norm(values["x"]) "|" norm(values["y"]) "|" \
norm(values["z"]) "|" norm(values["a"]) "|" norm(values["b"]) "|" \
norm(values["c"])
delete values
next
}
if (line ~ /^SET_XY_ROTATION rotation=/) {
value = line
sub(/^SET_XY_ROTATION rotation=/, "", value)
print "SET_XY_ROTATION|" norm(value)
next
}
if (line ~ /^SET_FEED_RATE rate=/) {
value = line
sub(/^SET_FEED_RATE rate=/, "", value)
print "SET_FEED_RATE|" norm(value)
next
}
if (line ~ /^SET_FEED_MODE /) {
split(line, fields, /[[:space:]]+/)
for (i in fields) {
split(fields[i], pair, "=")
values[pair[1]] = pair[2]
}
print "SET_FEED_MODE|" values["spindle"] "|" values["mode"]
delete values
next
}
if (line ~ /^SET_MOTION_CONTROL_MODE /) {
split(line, fields, /[[:space:]]+/)
for (i in fields) {
split(fields[i], pair, "=")
values[pair[1]] = pair[2]
}
print "SET_MOTION_CONTROL_MODE|" values["mode"] "|" norm(values["tolerance"])
delete values
next
}
if (line ~ /^SET_NAIVECAM_TOLERANCE tolerance=/) {
value = line
sub(/^SET_NAIVECAM_TOLERANCE tolerance=/, "", value)
print "SET_NAIVECAM_TOLERANCE|" norm(value)
next
}
if (line ~ /^DWELL seconds=/) {
value = line
sub(/^DWELL seconds=/, "", value)
print "DWELL|" norm(value)
next
}
if (line ~ /^ORIENT_SPINDLE /) {
split(line, fields, /[[:space:]]+/)
for (i in fields) {
split(fields[i], pair, "=")
values[pair[1]] = pair[2]
}
print "ORIENT_SPINDLE|" values["spindle"] "|" norm(values["orientation"]) "|" values["mode"]
delete values
next
}
if (line ~ /^WAIT_SPINDLE_ORIENT_COMPLETE /) {
split(line, fields, /[[:space:]]+/)
for (i in fields) {
split(fields[i], pair, "=")
values[pair[1]] = pair[2]
}
print "WAIT_SPINDLE_ORIENT_COMPLETE|" values["spindle"] "|" norm(values["timeout"])
delete values
next
}
if (line ~ /^STRAIGHT_(TRAVERSE|FEED) /) {
split(line, fields, /[[:space:]]+/)
for (i in fields) {
split(fields[i], pair, "=")
values[pair[1]] = pair[2]
}
print fields[1] "|" norm(values["x"]) "|" norm(values["y"]) "|" \
norm(values["z"]) "|" norm(values["a"]) "|" norm(values["b"]) "|" \
norm(values["c"])
delete values
next
}
}
'
}
normalize_upstream_rs274_output() {
awk '
function norm(value) {
return sprintf("%.12g", value + 0)
}
function trim(value) {
sub(/^[[:space:]]+/, "", value)
sub(/[[:space:]]+$/, "", value)
return value
}
function args_of(call, args) {
args = call
sub(/^[^(]+\(/, "", args)
sub(/\).*$/, "", args)
return args
}
/^[[:space:]]*[0-9]+[[:space:]]+N\.\.\.\.\./ {
line = $0
sub(/^[[:space:]]*[0-9]+[[:space:]]+N\.\.\.\.\.[[:space:]]+/, "", line)
if (line == "ON_RESET()") {
seen_reset = 1
next
}
if (!seen_reset) {
next
}
if (line ~ /^COMMENT\("/) {
value = args_of(line)
sub(/^"/, "", value)
sub(/"$/, "", value)
print "COMMENT|" value
next
}
if (line ~ /^MESSAGE\("/) {
value = args_of(line)
sub(/^"/, "", value)
sub(/"$/, "", value)
print "MESSAGE|" value
next
}
if (line ~ /^LOG(OPEN|APPEND)?\("/) {
op = line
sub(/\(.*/, "", op)
value = args_of(line)
sub(/^"/, "", value)
sub(/"$/, "", value)
print op "|" value
next
}
if (line == "LOGCLOSE()" || line == "FINISH()" || line == "RELOAD_TOOLDATA()") {
value = line
sub(/\(\)$/, "", value)
print value
next
}
if (line ~ /^SELECT_PLANE\(CANON_PLANE_/) {
value = line
sub(/^SELECT_PLANE\(CANON_PLANE_/, "", value)
sub(/\)$/, "", value)
print "SELECT_PLANE|" value
next
}
if (line ~ /^USE_LENGTH_UNITS\(CANON_UNITS_/) {
value = line
sub(/^USE_LENGTH_UNITS\(CANON_UNITS_/, "", value)
sub(/\)$/, "", value)
print "USE_LENGTH_UNITS|" value
next
}
if (line ~ /^SET_G5X_OFFSET\(/) {
args = args_of(line)
split(args, values, ",")
print "SET_G5X_OFFSET|" trim(values[1]) "|" norm(values[2]) "|" norm(values[3]) "|" \
norm(values[4]) "|" norm(values[5]) "|" norm(values[6]) "|" norm(values[7])
next
}
if (line ~ /^SET_G92_OFFSET\(/) {
args = args_of(line)
split(args, values, ",")
print "SET_G92_OFFSET|" norm(values[1]) "|" norm(values[2]) "|" norm(values[3]) "|" \
norm(values[4]) "|" norm(values[5]) "|" norm(values[6])
next
}
if (line ~ /^SET_XY_ROTATION\(/) {
args = args_of(line)
print "SET_XY_ROTATION|" norm(args)
next
}
if (line ~ /^SET_FEED_RATE\(/) {
args = args_of(line)
print "SET_FEED_RATE|" norm(args)
next
}
if (line ~ /^SET_FEED_MODE\(/) {
args = args_of(line)
split(args, values, ",")
print "SET_FEED_MODE|" trim(values[1]) "|" trim(values[2])
next
}
if (line ~ /^SET_MOTION_CONTROL_MODE\(/) {
args = args_of(line)
split(args, values, ",")
mode_name = trim(values[1])
if (mode_name == "CANON_EXACT_STOP") {
mode = 1
} else if (mode_name == "CANON_EXACT_PATH") {
mode = 2
} else if (mode_name == "CANON_CONTINUOUS") {
mode = 3
} else {
next
}
print "SET_MOTION_CONTROL_MODE|" mode "|" norm(values[2])
next
}
if (line ~ /^SET_NAIVECAM_TOLERANCE\(/) {
args = args_of(line)
print "SET_NAIVECAM_TOLERANCE|" norm(args)
next
}
if (line ~ /^DWELL\(/) {
args = args_of(line)
print "DWELL|" norm(args)
next
}
if (line ~ /^ORIENT_SPINDLE\(/) {
args = args_of(line)
split(args, values, ",")
print "ORIENT_SPINDLE|" trim(values[1]) "|" norm(values[2]) "|" trim(values[3])
next
}
if (line ~ /^SPINDLE\.[0-9]+\.WAIT_ORIENT_COMPLETE\(/) {
spindle = line
sub(/^SPINDLE\./, "", spindle)
sub(/\.WAIT_ORIENT_COMPLETE.*/, "", spindle)
args = args_of(line)
print "WAIT_SPINDLE_ORIENT_COMPLETE|" spindle "|" norm(args)
next
}
if (line ~ /^STRAIGHT_(TRAVERSE|FEED)\(/) {
op = line
sub(/\(.*/, "", op)
args = args_of(line)
split(args, values, ",")
print op "|" norm(values[1]) "|" norm(values[2]) "|" norm(values[3]) "|" \
norm(values[4]) "|" norm(values[5]) "|" norm(values[6])
next
}
}
'
}
filter_to_expected_event_keys() {
local expected_norm="$1"
awk '
NR == FNR {
key = $0
sub(/\|.*/, "", key)
keys[key] = 1
next
}
{
key = $0
sub(/\|.*/, "", key)
if (key in keys) {
print
}
}
' "$expected_norm" -
}
for name in "${FIXTURES[@]}"; do
gcode_file="$GCODE_FIXTURE_DIR/$name.ngc"
expected_file="$CANON_FIXTURE_DIR/$name.events"
native_raw="$TMP_DIR/$name.rs274.raw"
native_norm="$TMP_DIR/$name.rs274.normalized"
native_filtered="$TMP_DIR/$name.rs274.filtered"
expected_norm="$TMP_DIR/$name.expected.normalized"
if [[ ! -f "$gcode_file" ]]; then
echo "missing G-code fixture: $gcode_file" >&2
exit 1
fi
if [[ ! -f "$expected_file" ]]; then
echo "missing canonical fixture: $expected_file" >&2
exit 1
fi
set +e
"$UPSTREAM_RS274" -g "$gcode_file" > "$native_raw" 2>&1
rs274_rc=$?
set -e
if [[ "$rs274_rc" != "0" ]] && ! grep -Fq "File ended with no percent sign (%) or program end (M2)" "$native_raw"; then
echo "upstream rs274 failed for fixture: $name" >&2
sed -n '1,160p' "$native_raw" >&2
exit 1
fi
normalize_upstream_rs274_output < "$native_raw" > "$native_norm"
normalize_standalone_events < "$expected_file" > "$expected_norm"
filter_to_expected_event_keys "$expected_norm" < "$native_norm" > "$native_filtered"
if [[ ! -s "$native_filtered" ]]; then
echo "no normalized upstream rs274 events for fixture: $name" >&2
sed -n '1,160p' "$native_raw" >&2
exit 1
fi
if ! diff -u "$expected_norm" "$native_filtered"; then
echo "native LinuxCNC rs274 baseline mismatch for fixture: $name" >&2
exit 1
fi
done
echo "native LinuxCNC fixture baseline validation complete"