Skip to content

feat(rs274ngc): MAX_UNWIND_TURNS auto rotary G0 rebase#3990

Draft
grandixximo wants to merge 1 commit into
LinuxCNC:masterfrom
grandixximo:auto-rotary-rebase
Draft

feat(rs274ngc): MAX_UNWIND_TURNS auto rotary G0 rebase#3990
grandixximo wants to merge 1 commit into
LinuxCNC:masterfrom
grandixximo:auto-rotary-rebase

Conversation

@grandixximo

Copy link
Copy Markdown
Contributor

Draft for testing and feedback. Targets the rotational-cutting use case from #3902 where G1 must honor literal multi-turn absolute targets (helical winding, spiral carving) but a terminal G0 should not physically unwind.

Mechanism

New INI flag [AXIS_<L>] MAX_UNWIND_TURNS = N. On a G0 absolute move with the rotary word, if the user-frame delta exceeds N full turns, the interpreter shifts a per-axis hidden user-frame offset so the motor stays put while the user-frame position jumps to the programmed target. Subsequent absolute moves use the rebased frame.

The motion-side traj.position remains accumulated. Only an interp-only offset moves. Stepgens, encoders, and PID see no discontinuity, sidestepping the motor_offset injection problem @andypugh raised in #3902. G53 explicitly bypasses (literal machine coords). Incremental (G91) is unaffected.

Trace

[AXIS_A] MAX_UNWIND_TURNS = 10

state: AA=0, offset=0, user=0
G0 A0 X0 Y5 Z1     ; no trigger (|delta|=0)
G1 Y0
G1 X20 A28800      ; literal target = 28800, +28800 (80 turn cut)
                   ; AA=28800, offset=0, user=28800
G0 X0 A0           ; |delta_turns|=80 > 10, rebase: offset=28800
                   ; machine_target=28800, motor delta=0 (no unwind)
                   ; AA=28800, offset=28800, user=0

#5423/#5424/#5425 and _a/_b/_c named params report user-frame.

Trade-offs / caveats

  • Absolute machine-frame position drifts by one rebase per run. MAX_LIMIT eventually reached on very long runs. Trade is necessary because in-program joint rebase is not safely doable (per @andypugh's analysis).
  • Trivkins 1:1 axis-to-joint only. Coupled kinematics (5-axis TCP, gantry rotary) unsupported (not validated).
  • Run-from-line not handled (skipped blocks would not trigger rebases).
  • Offset persists across M2/M30 (clean display for back-to-back runs). The first G0 of the next program self-heals after re-home/estop/power-cycle because the unwind delta exceeds the threshold.
  • Mutex with WRAPPED_ROTARY, startup warning if both set.

Mapping to other controls

Closest precedent is Hurco M31 (explicit, programmer-placed). This implementation auto-triggers on G0 above a threshold; to my knowledge no commercial control automates this but the structural reasons (mostly TCP coupling) are addressable via the trivkins gate.

Status

Draft, seeking feedback and test results from @djdelorie's helical-carving setup before promoting. Build clean.

@grandixximo

Copy link
Copy Markdown
Contributor Author

@djdelorie did you have time to check this is what you wanted?

@djdelorie

djdelorie commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

I haven't, but amusingly, I was just doing a helical carve this morning. I'll bump up its priority.
Edit: and aborted one of the jobs and had to wait through a long unwind...

@djdelorie

Copy link
Copy Markdown
Contributor

So I had time to do a bit of testing today, and noticed some (expected?) weird display results with G0 A where was NOT zero. Example:

home A
g0 a3620 -> moves a little, display reads "A:20" (wrong display) (#<_a> is 3620)
g1 a3630 -> moves to A:30

g1 a3630
g0 a-360 -> moves to A:3600 (display bug)
g1 a3630 -> moves to A:7590 (#<_a> is 3630)

home A again
g1 a3630 -> moves to A:30 (not 3630!) This is a motion, not display, bug.

I think that having the display and the internal coordinates (#<_a>) not matching is going to be too confusing to use, but the motion bug is a real bug - it would have cut the wrong path.
In trying to reproduce it, I got this:

(this was all with the WCS A offsets set to zero)

Also, a G0A0 followed by a home-A then G1A0 did a full unwind.

@grandixximo

Copy link
Copy Markdown
Contributor Author

what did you set [AXIS_A] MAX_UNWIND_TURNS to?

@grandixximo

Copy link
Copy Markdown
Contributor Author

are you using axis UI?

@grandixximo

Copy link
Copy Markdown
Contributor Author

are you ok if I were to leverage G92? like set G92 automatically? because we are basically recreating it just for this scope.

@djdelorie

Copy link
Copy Markdown
Contributor

10, Axis UI, and I think it would make more sense to tweak the G59 (or current WCS) offsets than the G92 ones, because that's what everyone ends up doing with the G10 L20 trick anyway.

@grandixximo grandixximo force-pushed the auto-rotary-rebase branch from 219c501 to bf7aa25 Compare June 8, 2026 02:39
@grandixximo

Copy link
Copy Markdown
Contributor Author

@djdelorie I reworked this to ride the G92 offset instead of a private interp offset. Two things change for the better:

  • The DRO and #<_a>/#5423 now both report the value commanded in the g-code. After G1 A28800 then a terminal G0 A0, both read 0 while the motor stays put. The display/named-parameter split you saw is gone.
  • The stale-offset-after-home bug is fixed. On a re-home or estop reset the interpreter detects the frame re-anchor and clears the unwind offset, so home A followed by G1 A3630 cuts the full path again instead of landing short.

On your suggestion to use the WCS (G10 L20) offset instead of G92: I kept it on G92 deliberately. The unwind is a machine-frame fact that has to survive WCS switches, and G92 is the single offset applied on top of whatever WCS is active, whereas a G5x offset is per-WCS and would be dropped by a G55 mid-program. G92 is also the conventionally-volatile offset (there is already DISABLE_G92_PERSISTENCE), so it avoids polluting the part zero you set with G10 L20.

A few notes on the approach:

  • On an axis with MAX_UNWIND_TURNS set, the feature reserves G92 for that axis, so do not set G92 manually there. Other axes are untouched, use G92 freely.
  • A G92.1 in the program clears the accumulated unwind; the next qualifying G0 just rebases again.
  • Still gated to trivkins 1:1 axis-to-joint mapping.

Rebased on current master and pushed. Could you retest your helical-carve cases, especially the home/abort sequences that gave the wrong path before? Thanks.

@grandixximo grandixximo force-pushed the auto-rotary-rebase branch 2 times, most recently from 4a75185 to a49fda0 Compare June 8, 2026 03:31
Add [AXIS_<L>] MAX_UNWIND_TURNS = N. On a G0 absolute move with the
rotary word, if the programmed (work-frame) delta exceeds N full turns,
fold the whole turns into the axis (G92) offset so the motor takes the
shortest path (within +/- 180 deg) to the target's angular position
while the work-frame position still reaches the commanded target. The
motion-side accumulated position is unchanged, so stepgens, encoders and
PID see no discontinuity.

Riding the existing G92 plumbing means the DRO, #<_a> and #5423 all
report the programmed value with no special-casing; the accumulated
turns appear as a G92 offset (#5214-#5216).

On re-home or estop reset the interpreter resynchronizes and detects the
frame re-anchor (a >180 deg work-frame jump on a managed axis with a
live offset); it collapses the stale offset back into the work frame and
re-emits the G92 so canon agrees, so a fresh home starts clean.

Gated to trivkins 1:1 axis-to-joint mapping; coupled kinematics (5-axis
TCP, gantry rotary) unsupported. Mutually exclusive with WRAPPED_ROTARY
(startup warning if both set on the same axis). Default 0 (disabled).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants