Skip to content

Honor [AXIS_<letter>] TYPE in the interpreter, canon and motion - #4581

Open
grandixximo wants to merge 2 commits into
LinuxCNC:masterfrom
grandixximo:axis-type
Open

grandixximo wants to merge 2 commits into
LinuxCNC:masterfrom
grandixximo:axis-type

Conversation

@grandixximo

@grandixximo grandixximo commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

The interpreter and canon decide by letter what an axis is: X Y Z U V W are lengths, A B C are angles. [AXIS_<letter>] TYPE is read by the AXIS GUI alone, so a V configured ANGULAR on a mm machine moves 25.4 times too far under G20, and an A configured LINEAR does not scale at all.

This makes both follow the INI:

  • One small table, src/emc/ini/axis_kinds.hh: TYPE per letter, the letter's default when absent, and a new [TRAJ] FEED_AXES, the axes F is measured along, X Y Z by default. X Y Z must stay LINEAR, and every FEED_AXES letter must be a LINEAR axis; anything else is a startup error. The interpreter and canon read the same table, so they cannot drift.
  • Every G20/G21 conversion of a position, an offset, a stored position (#5161, #5181, #5211, #5221 onward), a tool offset and the applied tool offset goes by type, in the interpreter and in canon.
  • The length F applies to, in the interpreter (G93) and in canon alike: the feed axes that move, else the other linear axes, else the angular axes in degrees. With the defaults that is XYZ, else UVW, else ABC, as today.
  • Motion still measures a line by letter. Canon computes the move time from its own rule and scales the rates it sends by motion's length over its own, so the move takes the time canon planned. The ratio is not computed, exactly 1, whenever both measure the same axes of one kind, which the defaults always do.
  • The max velocity slider caps the feed, a length rate, so it follows the same rule. Motion used to decide from its own view of the move (every move but a line with no X Y Z or U V W) and capped an ANGULAR V alone at the linear slider rate while a LINEAR A alone escaped it. Canon now sends per move how the slider applies: the same ratio, or 0 for a move measured in degrees, which the slider does not cap. The scale travels with the linear, circular and probe moves to tpAddLine() and tpAddCircle(), and tcPureRotaryCheck() is gone. With the defaults it is 1 on every move with X Y Z or U V W and 0 on the others, as before.
  • WRAPPED_ROTARY and LOCKING_INDEXER_JOINT apply to any ANGULAR axis, U V W included, and are ignored on a LINEAR one as they are on U V W today. The Python properties a/b/c_axis_wrapped and a/b/c_indexer_jnum stay.

Two fixes ride along: B and C moves used the linear minimum displacement where A used the angular one, so on an inch machine a B or C move of up to 25.4 CART_FUZZ degrees was dropped; and rs274 (sai) reported XYZABC whatever the INI said, so a program with U V W words could not be run through it. It now takes the axes from [TRAJ] COORDINATES.

A second commit names the axis indices AXIS_X to AXIS_W in axis_kinds.hh, so a lookup reads axis_wrapped[AXIS_U] rather than [6]. It is mechanical and can be dropped on its own.

Nothing changes by default

With every TYPE and FEED_AXES at its default the output is unchanged: all 632 programs under tests/ and nc_files/, through rs274 with no INI and with the axis_mm, axis and axis_9axis sims, give the same canon calls byte for byte before and after. (The seven probe_basic probe macros are left out: they read an uninitialised probe result and differ from run to run on master too.)

Tests

  • tests/interp/axis-type: rs274 on a mm INI with A LINEAR and V ANGULAR and wrapped: under G20 A scales and V does not, G93 feeds along A, V wraps from 350 to 370, and G10 L2 stores A in mm and V in degrees.
  • tests/axis-type: the same machine through task, canon and motion. G20 A1 V10 reaches A 25.4 mm and V 10 degrees, V alone at F3600 turns 60 degrees a second under G20, X with A feeds along X, A with V feeds along A while motion runs its V measure 8 times faster so the move takes its 1 s, and V wraps. Under the slider, V alone keeps its rate, A alone is capped, and A with V is capped along A. On master it fails at the first check, A at 1.

Not in this PR

  • The GUIs and the preview still convert an axis by its letter, so an ANGULAR V shows in length units there. That follows in its own PR once this one is merged.
  • A rotary cannot join the feed group yet: that needs a reference radius per rotary (Siemens FGREF, Fanuc parameters 1408/1465).

This is the first half of item 10 of the multiaxis work in #4374, where the kinematics also learns which axes orient the tool so a head that tilts on V is driven on V. That part needs this one first; this one needs nothing else and stands on master alone.

Comment thread src/emc/ini/axis_kinds.hh Outdated
@grandixximo
grandixximo force-pushed the axis-type branch 2 times, most recently from 8bd0982 to 04d594b Compare September 24, 2026 13:16
Comment thread src/emc/ini/axis_kinds.hh Outdated
@BsAtHome

Copy link
Copy Markdown
Contributor

Can axes X, Y or Z be angular axes? If not, is there a check to error when such axis is marked angular?

The interpreter and canon took an axis to be a length or an angle by its
letter, so an ANGULAR V moved 25.4 times too far under G20 and a LINEAR A
did not scale at all.

- src/emc/ini/axis_kinds.hh reads TYPE per letter and a new [TRAJ]
  FEED_AXES (default X Y Z), for both the interpreter and canon. X Y Z
  must be LINEAR, and the FEED_AXES letters must be LINEAR axes.
- G20/G21 converts positions, offsets, stored positions and tool offsets
  by type.
- F, G93 included, is measured along the feed axes that move, else the
  other linear axes, else the angular axes in degrees. By default that is
  XYZ, else UVW, else ABC.
- Motion still measures a line by letter, so canon scales the rates it
  sends by motion's length over its own. The ratio is 1 with the default
  types.
- The max velocity slider caps canon's length. Canon sends a per-move
  scale, 0 for a move measured in degrees, and tcPureRotaryCheck() goes.
- WRAPPED_ROTARY and LOCKING_INDEXER_JOINT apply to any ANGULAR axis.
- B and C moves use the angular minimum displacement, as A does.
- rs274 (sai) takes its axes from [TRAJ] COORDINATES.

With the default types the canon output is unchanged.
The interpreter and canon index the per axis tables and conversions by
number, 0 X to 8 W. An enum in axis_kinds.hh names them, so
axis_wrapped[AXIS_U] reads without counting.
@grandixximo

Copy link
Copy Markdown
Contributor Author

No, X Y Z have to stay lengths: arcs, cutter compensation and tool length all treat them as lengths. TYPE = ANGULAR on [AXIS_X], [AXIS_Y] or [AXIS_Z] fails the interpreter init with "[AXIS_X], [AXIS_Y] and [AXIS_Z] TYPE must be LINEAR", which shows at startup, and no program or MDI runs until it's fixed. A FEED_AXES letter that isn't a linear axis fails the same way.

Also force pushed, a few things changed since your first review:

  • Rebased on master and squashed. The B/C minimum displacement and rs274 fixes from the description are folded in now, not separate commits.
  • The max velocity slider was still deciding by letter in motion: everything but a line with no X Y Z or U V W. So an ANGULAR V alone got capped at the linear rate and a LINEAR A alone escaped it. Canon owns the feed rule, so it now sends the slider's scale with each move (motion length over canon length, 0 for a move measured in degrees), and tcPureRotaryCheck() is gone. That touches the NML move messages, the emcmot command and the tp signatures. With default types it caps exactly what it did before; tests/axis-type checks the new cases.
  • A second commit names the axis indices, AXIS_X to AXIS_W, so it reads axis_wrapped[AXIS_U] instead of [6]. Kept separate so it's easy to drop if you'd rather not.
  • The GUI and preview side is done too, but it'll be its own PR once this one is in.

Description updated to match.

@grandixximo grandixximo changed the title Honor [AXIS_<letter>] TYPE in the interpreter and canon Honor [AXIS_<letter>] TYPE in the interpreter, canon and motion Sep 24, 2026
@grandixximo

Copy link
Copy Markdown
Contributor Author

I tagged this breaking-change, not because I expect it to bite anyone, but so it lands in the 2.10 release-notes sweep.

Two config shapes change:

  • [AXIS_X|Y|Z] TYPE = ANGULAR loads today. Only AXIS reads the key (axis.py:3451) and nothing validates it, so the config starts and the interpreter treats the axis as a length regardless. With this PR the interpreter refuses it at init.
  • TYPE set against the letter on any other axis, [AXIS_V] TYPE = ANGULAR or [AXIS_A] TYPE = LINEAR, keeps loading but now scales under G20 the way the INI says, so the same program moves differently.

The population is hand-written configs only: no in-tree config sets TYPE under [AXIS_*] (they all use [JOINT_n]), and neither pncconf nor stepconf writes it. TYPE is documented for the axis section as a plain LINEAR/ANGULAR enum with no letter restriction, so the combination was reachable from the docs alone, which is the only reason I expect a config out there to exist at all.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants