Import LinuxCNC source tree

This commit is contained in:
Wang Dequan
2026-08-20 06:17:39 +08:00
commit 2c814634c3
9429 changed files with 10556013 additions and 0 deletions

View File

@@ -0,0 +1,15 @@
G21 ; Set units to millimeters
G64 P0.1 Q0.1 ; Tolerance 0.1mm
G90 ; Set to absolute positioning
G0 X75 Y50 ; Rapid move to start point (center X50 + radius 25)
G2 X75 Y50 I-25 J0 F5000 ; Clockwise circle to start, center is -25mm in X
G1 X50 Y75 F5000 ; Feed move reversed X and Y
G3 X50 Y75 I25 J0 F5000 ; Counter clockwise circle back, center is +25mm in X
G20 ; Set units to inches
G90 ; Set to absolute positioning
G0 X3 Y2 ; Rapid move to start point (center X2 + radius 1)
G2 X3 Y2 I-1 J0 F5000 ; Clockwise circle to start, center is -1" in X
G1 X2 Y3 F5000 ; Feed move reversed X and Y
G3 X2 Y3 I1 J0 F5000 ; Counter clockwise circle back, center is +1" in X
M30
M2

View File

@@ -0,0 +1,22 @@
# HAL file for sampling motion heading data
loadrt sampler depth=400 cfg=ssfffffffbff
# disable sampler before adding the function
setp sampler.0.enable 0
addf sampler.0 servo-thread
# Net pins to record to the sampler inputs
net line-number motion.interp.line-number => sampler.0.pin.0
net motion-type motion.interp.motion-type => sampler.0.pin.1
net feedrate motion.interp.feedrate => sampler.0.pin.2
net arc-radius motion.interp.arc-radius => sampler.0.pin.3
net arc-center-x motion.interp.arc-center-x => sampler.0.pin.4
net arc-center-y motion.interp.arc-center-y => sampler.0.pin.5
net arc-center-z motion.interp.arc-center-z => sampler.0.pin.6
net heading motion.interp.heading => sampler.0.pin.7
net normal-heading motion.interp.normal-heading => sampler.0.pin.8
net iscircle motion.interp.iscircle => sampler.0.pin.9
net x-pos-fb joint.0.pos-fb => sampler.0.pin.10
net y-pos-fb joint.1.pos-fb => sampler.0.pin.11

View File

@@ -0,0 +1 @@
Completed successfully

View File

@@ -0,0 +1,96 @@
[EMC]
DEBUG = 0
VERSION = 1.1
[DISPLAY]
DISPLAY = ../test-ui.py -ngc ../heading-test.ngc
[TASK]
TASK = milltask
CYCLE_TIME = 0.001
[RS274NGC]
PARAMETER_FILE = sim.var
[EMCMOT]
EMCMOT = motmod
BASE_PERIOD = 0
SERVO_PERIOD = 1000000
[HAL]
HALFILE = LIB:core_sim.hal
HALFILE = ../heading.hal
[TRAJ]
NO_FORCE_HOMING=1
AXES = 3
COORDINATES = X Y Z
HOME = 0 0 0
LINEAR_UNITS = inch
ANGULAR_UNITS = degree
DEFAULT_LINEAR_VELOCITY = 4
MAX_LINEAR_VELOCITY = 20
[KINS]
KINEMATICS = trivkins
JOINTS = 3
[AXIS_X]
MIN_LIMIT = -40.0
MAX_LIMIT = 40.0
MAX_VELOCITY = 20
MAX_ACCELERATION = 120
[JOINT_0]
TYPE = LINEAR
HOME = 0.000
MAX_VELOCITY = 40
MAX_ACCELERATION = 120
BACKLASH = 0.000
INPUT_SCALE = 4000
OUTPUT_SCALE = 1.000
MIN_LIMIT = -40.0
MAX_LIMIT = 40.0
FERROR = 0.050
MIN_FERROR = 0.010
HOME_SEQUENCE = 0
[AXIS_Y]
MIN_LIMIT = -40.0
MAX_LIMIT = 40.0
MAX_VELOCITY = 20
MAX_ACCELERATION = 120
[JOINT_1]
TYPE = LINEAR
HOME = 0.000
MAX_VELOCITY = 40
MAX_ACCELERATION = 120
BACKLASH = 0.000
INPUT_SCALE = 4000
OUTPUT_SCALE = 1.000
MIN_LIMIT = -40.0
MAX_LIMIT = 40.0
FERROR = 0.050
MIN_FERROR = 0.010
HOME_SEQUENCE = 0
[AXIS_Z]
MIN_LIMIT = -20.0
MAX_LIMIT = 20.0
MAX_VELOCITY = 20
MAX_ACCELERATION = 120
[JOINT_2]
TYPE = LINEAR
HOME = 0.0
MAX_VELOCITY = 20
MAX_ACCELERATION = 120
BACKLASH = 0.000
INPUT_SCALE = 4000
OUTPUT_SCALE = 1.000
MIN_LIMIT = -20.0
MAX_LIMIT = 20.0
FERROR = 0.050
MIN_FERROR = 0.010
HOME_SEQUENCE = 0

View File

@@ -0,0 +1,6 @@
#!/bin/bash
# Run the Imperial config
if linuxcnc -r heading-in.ini; then
echo "Completed successfully" > result
fi

View File

@@ -0,0 +1 @@
Completed successfully

View File

@@ -0,0 +1,96 @@
[EMC]
DEBUG = 0
VERSION = 1.1
[DISPLAY]
DISPLAY = ../test-ui.py -ngc ../heading-test.ngc
[TASK]
TASK = milltask
CYCLE_TIME = 0.001
[RS274NGC]
PARAMETER_FILE = sim.var
[EMCMOT]
EMCMOT = motmod
BASE_PERIOD = 0
SERVO_PERIOD = 1000000
[HAL]
HALFILE = LIB:core_sim.hal
HALFILE = ../heading.hal
[TRAJ]
NO_FORCE_HOMING=1
AXES = 3
COORDINATES = X Y Z
HOME = 0 0 0
LINEAR_UNITS = mm
ANGULAR_UNITS = degree
DEFAULT_LINEAR_VELOCITY = 100
MAX_LINEAR_VELOCITY = 500
[KINS]
KINEMATICS = trivkins
JOINTS = 3
[AXIS_X]
MIN_LIMIT = -1000.0
MAX_LIMIT = 1000.0
MAX_VELOCITY = 500
MAX_ACCELERATION = 3000
[JOINT_0]
TYPE = LINEAR
HOME = 0.000
MAX_VELOCITY = 1000
MAX_ACCELERATION = 3000
BACKLASH = 0.000
INPUT_SCALE = 4000
OUTPUT_SCALE = 1.000
MIN_LIMIT = -1000.0
MAX_LIMIT = 1000.0
FERROR = 0.050
MIN_FERROR = 0.010
HOME_SEQUENCE = 0
[AXIS_Y]
MIN_LIMIT = -1000.0
MAX_LIMIT = 1000.0
MAX_VELOCITY = 500
MAX_ACCELERATION = 3000
[JOINT_1]
TYPE = LINEAR
HOME = 0.000
MAX_VELOCITY = 500
MAX_ACCELERATION = 3000
BACKLASH = 0.000
INPUT_SCALE = 4000
OUTPUT_SCALE = 1.000
MIN_LIMIT = -1000.0
MAX_LIMIT = 1000.0
FERROR = 0.050
MIN_FERROR = 0.010
HOME_SEQUENCE = 0
[AXIS_Z]
MIN_LIMIT = -500.0
MAX_LIMIT = 500.0
MAX_VELOCITY = 500
MAX_ACCELERATION = 3000
[JOINT_2]
TYPE = LINEAR
HOME = 0.0
MAX_VELOCITY = 500
MAX_ACCELERATION = 3000
BACKLASH = 0.000
INPUT_SCALE = 4000
OUTPUT_SCALE = 1.000
MIN_LIMIT = -500.0
MAX_LIMIT = 500.0
FERROR = 0.050
MIN_FERROR = 0.010
HOME_SEQUENCE = 0

View File

@@ -0,0 +1,7 @@
#!/bin/bash
# Run the Metric config
if linuxcnc -r heading-mm.ini; then
echo "Completed successfully" > result
fi

253
tests/motion/heading/test-ui.py Executable file
View File

@@ -0,0 +1,253 @@
#!/usr/bin/env python3
import linuxcnc
import linuxcnc_util
import hal
import time
import sys
import os
import math
INTERPTIMEOUT = 5 # Max 5 seconds to wait for the interpreter
GCODETIMEOUT = 120 # Max 60 seconds for the gcode file to run12
#
# Sample collection routine
#
nsamples = 0
lastsample = None
lastrecnum = 0
ctr = 0
def is_arc_valid(sample, tolerance):
"""
Validates circularity, radius accuracy, and G2/G3 heading consistency.
Returns True on pass, False on failure.
"""
global ctr
# Extract Fields
motion_type = sample[1] # 20=G2, 30=G3
expected_radius = sample[3]
cx, cy = sample[4], sample[5]
heading = sample[7]
normal_heading = sample[8]
x = sample[10]
y = sample[11]
# 2. Radius Check: Distance from tool (x,y) to center (cx,cy)
actual_radius = math.hypot(x - cx, y - cy)
if abs(actual_radius - expected_radius) > tolerance:
ctr = ctr + 1
if ctr > 40:
print(f"FAIL Radius after 40 times: Expected {expected_radius}, Got {actual_radius:.4f}")
ctr = 0
return False
ctr = 0
# 3. Normal Direction Check: Normal must point from tool to center
angle_to_center = math.degrees(math.atan2(cy - y, cx - x)) % 360
norm_diff = abs(angle_to_center - (normal_heading % 360))
if norm_diff > 180: norm_diff = 360 - norm_diff
if norm_diff > tolerance:
print(f"FAIL Normal: Expected {angle_to_center:.4f}, Got {normal_heading:.4f}")
return False
# 4. Tangency/Direction Check: (Heading vs Normal)
#G2 (20): Heading should be Normal + 90
#G3 (30): Heading should be Normal - 90 (or + 270)
diff = (heading - normal_heading) % 360
if motion_type == 20: # G2
if abs(diff - 90) > tolerance:
print(f"FAIL: G2 normal heading outside of tolerance",diff)
return False
elif motion_type == 30: # G3
if abs(diff - 270) > tolerance:
print(f"FAIL: G3 normal heading outside of tolerance",diff)
return False
return True
def is_heading_valid(sample1, sample2, tolerance=0.1):
"""
Returns True if the travel direction between two samples
matches the reported heading within a given tolerance.
"""
global ctr
if sample1[0] != sample2[0]:
ctr = ctr + 1
if ctr < 5: #allow to settle on change of segment
return True
else:
ctr = 0 #Reset
# Extract coords and reported heading
x1, y1 = sample1[10], sample1[11]
x2, y2 = sample2[10], sample2[11]
reported_heading = sample2[7]
dx = x2 - x1
dy = y2 - y1
# Avoid calculation if there is no movement (prevents jitter errors)
if math.hypot(dx, dy) < 1e-9:
return True
# Calculate angle: atan2(y, x) -> degrees
calculated_heading = math.degrees(math.atan2(dy, dx))
# Normalize to 0-360 if your system doesn't use negative degrees
if calculated_heading < 0:
calculated_heading += 360
# Standardize reported_heading to same 0-360 range if needed
if reported_heading < 0:
reported_heading += 360
# Handle the "wrap-around" case (e.g., 359.9 is near 0.1)
diff = abs(calculated_heading - reported_heading)
if diff > 180:
diff = 360 - diff
return(diff <= tolerance)
def parse_sample(sample):
"""
Parses a tuple of sampler data for tests
Fields: line-number, motion-type, feedrate, arc-radius, arc-center-x, arc-center-y, arc-center-z,
heading, normal-heading, iscircle, x-pos-fb, y-pos-fb
"""
global lastsample
global lastrecnum
print("{:6d} {}".format(nsamples, sample))
if None == lastsample or sample[0] != lastsample[0]:
return True
if sample[1] not in (00,10,20,30):
print("Gcode line is not G0,G1,G2 or G3, nothing to do")
return True
if (sample[1] == 20 or sample[1] == 30):
# gcode is G2 but G3
if not is_arc_valid(sample, 0.001):
print("G2/G3 tests are invalid at record ", lastrecnum)
return False
if (lastsample[1] == sample[1] == 10): # we have at least 2 G1 records
if not is_heading_valid(lastsample, sample, tolerance=0.001):
print("G0 heading or other values does not agree with expected value at record ",lastrecnum)
return False
if (lastsample[1] == sample[1] == 0): # we have at least 2 G0 records
if not is_heading_valid(lastsample, sample, tolerance=0.001):
print("G0 heading or other values does not agree with expected value at record ",lastrecnum)
return False
lastrecnum = lastrecnum + 1
lastsample = sample
#
# Sample collection routine
#
def read_sample():
global nsamples, lastsample
sample = sampler.read()
if None == sample:
print("Error: read_sample() read None (readable={}, depth={})".format(samples.readable, sampler.depth))
sys.exit(1)
if sample != lastsample and 0 != sample[0]: # Don't handle duplicates and non-gcode
if not parse_sample(sample):
print("Test Failed")
nsamples += 1
lastsample = sample
#
# Test the command line arguments. It should have a .ngc file we can read
#
ngcfile = None
# Crudely parse command like args to find: -ngc file
for i in range(1, len(sys.argv)-1):
if "-ngc" == sys.argv[i]:
ngcfile = sys.argv[i+1]
break
if not ngcfile:
print("Missing NGC-file; run with: test-ui.py -ngc ngcfile.ngc")
sys.exit(1)
if not os.path.exists(ngcfile):
print("NGC-file '{}' does not exist".format(ngcfile))
sys.exit(1)
#
# Setup a hal component and connect to the sampler stream
#
h = hal.component("python-ui")
sampler = hal.stream(h, hal.sampler_base, "ssfffffffbff")
h.ready()
#
# connect to LinuxCNC
#
c = linuxcnc.command()
s = linuxcnc.stat()
e = linuxcnc.error_channel()
l = linuxcnc_util.LinuxCNC(command=c, status=s, error=e)
# Enable machine
c.state(linuxcnc.STATE_ESTOP_RESET)
c.state(linuxcnc.STATE_ON)
# Home all joints
c.home(-1)
c.wait_complete()
l.wait_for_home([1, 1, 1, 0, 0, 0, 0, 0, 0])
#
# Setup to run GCode program
#
c.mode(linuxcnc.MODE_AUTO)
c.program_open(ngcfile)
# Enable sampling
hal.set_p("sampler.0.enable", "1")
# Start the GCode
c.auto(linuxcnc.AUTO_RUN, 1)
# Wait for the interpreter to start running the program
start = time.time()
while time.time() - start < INTERPTIMEOUT:
s.poll()
if s.interp_state != linuxcnc.INTERP_IDLE:
print("Interpreter active...")
break
time.sleep(0.01)
if s.interp_state == linuxcnc.INTERP_IDLE:
print("Timed out starting interpreter")
sys.exit(1)
#
# Loop until the program finishes while collecting data
#
start = time.time()
s.poll()
while s.interp_state != linuxcnc.INTERP_IDLE and time.time() - start < GCODETIMEOUT:
# Continuously drain the sampler buffer
while sampler.readable:
read_sample()
time.sleep(0.01)
s.poll()
if s.interp_state != linuxcnc.INTERP_IDLE:
print("Timed out running the GCode program")
sys.exit(1)
# Disable the sampler
hal.set_p("sampler.0.enable", "0")
# Give RT time to finalize the last sample(s)
time.sleep(0.01)
# Drain any remaining samples
while sampler.readable:
read_sample()
# if we get here it all worked!
sys.exit(0)