Skip to content

Multiaxis kinematics and limits - #4374

Draft
grandixximo wants to merge 61 commits into
LinuxCNC:masterfrom
grandixximo:multiaxis-kins-limits
Draft

grandixximo wants to merge 61 commits into
LinuxCNC:masterfrom
grandixximo:multiaxis-kins-limits

Conversation

@grandixximo

@grandixximo grandixximo commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Draft. Where the multiaxis kinematics work is going, open early so it can be argued with. As pieces are agreed I split them into small PRs, rebase this, and carry on, so nothing stalls behind it.

Plan, in order:

  1. Switchkins: one implementation, used by .comp, .c, in tree and out of tree. The registration half merged as switchkins: allow more than three kinematics types #4372; the rest is in this branch.

  2. Small fixes that stand on their own: 5axiskins gets a .tool-offset pin like every other module, maxkins gets one or a doc note saying it has none, and G53 stops ignoring the rotary tool offset on non-wrapped axes.

  3. The kinematics interface learns to answer two more questions: where the tool points, and how joint motion maps to world motion. One entry point each, both weak, both defaulting to today's behaviour, and a tool frame convention written down so every module answers the same way rather than each author choosing one.

  4. Those answers, and forward and inverse alongside them, become pure functions of what the caller passes in: joint values, a parameter block carrying the tool and the kinematics type, and no state kept inside the module. Today the maths reads its geometry from HAL pins and keeps scratch in haldata, so the only way to call it from anywhere else is to write a second copy of it. One implementation, parameters supplied at the call site.

  5. Two stages the offset chain was missing. Both came out of the discussion below, both wanted 3 and 4 first, and both are in this branch now.

    A frame composed on top of the active coordinate system, with its own cancel, so the blocks in between are programmed in the tilted plane while G54 itself is untouched. Fanuc spells this G68.2, and that is the spelling here: G68.2 in its four forms, G68.3 reading the plane off the tool, G68.4 composing onto the active plane, G69 cancelling. In tree it is a stage in rotate_and_offset_pos() plus modal group 9. Nothing is rewritten, so nothing needs restoring: any abort cancels it, and on_abort_with_twp_reset.ngc is gone with the rest of the remap.

    A move whose target is defined but whose path is left to the joints: run the inverse once at the endpoint, then interpolate the joints to reach it, the slowest setting the pace. That is how the commercial controls cross a singularity or flip a head without changing coordinate system. In tree it is one segment type in the planner, JOINT_TRAVERSE and JOINT_FEED in canon, and three non-modal codes on G0 and G1, like G53, each stripping one more layer of interpretation from the destination: G53.4 to a program point, offsets and plane applied, the form a post writes; G53.5 to the machine frame, the carriage or a robot's flange in machine coordinates with the tool left out, in program units and with no offset, the form for parking and tool change; G53.7 to joint values, J2=-5 sending joint 2 to -5 in the joint's own units with nothing converted, the form that works on every machine. Which kinematics type is the machine frame the module declares with a flag, KINSTYPE_MACHINE, next to KINSTYPE_IDENTITY and KINSTYPE_PRIMARY: the carriage on a mill, the flange in the base frame on a robot (ABB tool0, KUKA $NULLFRAME), where pumakins and genserkins flag their arm type. G13.1 and G49 cancel to it, so a robot stays on its arm kinematics and only G12.1 P1 selects raw joints. A module that declares nothing has its identity type stand in, and there a letter carries a unit class and a joint does not, so G53.5 is refused where the letters name joints of the other kind, which [JOINT_n] TYPE declares. With G1 the move takes the time the straight move would at F. G53.1, G53.3 and G53.6 orient the tool to the plane, and G53.2 solves the same orientation without moving, publishing the pose on the read-only parameters #<_orient_x> to #<_orient_c> and #<_orient_valid>, mirrored on #5071 to #5080, so a program can read the answer and use it in a move of its own. P picks the solution nearest first and Q says whether the joints that carry the work take part; the module's tool frame inverse answers, so no config carries the formula, and the two trsrn configs run their demos with no remap left.

    Between them these are the reason not to add a set of offsets per kinematics type. Controls do let you add coordinate systems, Fanuc G54.1 P1 to P48, Siemens G505 to G599, and none of them ties a set to the active kinematics or transformation: selection is explicit in the program, or it belongs to a channel. More coordinate systems is a fair request on its own; binding them to the kinstype is the workaround for these two absences.

  6. Limits taken from the joints instead of from static per-axis INI values. Joint limits are the physical fact. The region they imply in axis space depends on the tool and on where the rotaries are, and is not a box, which is why a box in the INI cannot describe it and config authors have to pick a conservative one and lose travel. Built now, in three parts, each with a test that was red before it.

    Travel is the [JOINT_n] limits, every move, every kinematics type. The [AXIS_L] box is the machine frame envelope: the carriage, or the flange, in machine coordinates, the frame that does not move when the head tilts or a tool is loaded, which is how Siemens and Fanuc read axis limits, machine-axis space after the transformation. Every move is checked against the box where its joints put the machine frame, G0 and G53.5 alike, so a tool centre point move whose tip lies past the box with the carriage inside it is accepted and a G53.5 to a carriage position past the box is refused. No box on tip coordinates: the tip is not the carriage, and a tip fence would be a feature of its own. One box, one meaning, so nothing needs resetting at a switch, which retires the setp ini.L.min_limit the docs prescribed and the puma's M129 workaround. A module that declares no machine frame keeps the box on the world in force, as today. The one behaviour change for an existing switchkins config that declares only its identity: under TCP the box moves from the tip to the carriage.

    Velocity and acceleration come off the Jacobian. Canon samples every segment through the module's Jacobian ahead of motion, on the kinematics type and the tool offset the program is under, and lowers its velocity and acceleration to what the slowest joint can give within its own limits: the rate of each joint along the tangent, the share the changing rate takes at that speed (what a carriage on a circle feels as centripetal), and the bulge between samples so a peak between them is covered. Lines and arcs, traverse and feed, probe and rigid tap. A turn of a tilted head under the tool centre point, which swings the carriage through a circle the head's own limit never mentions, runs at the carriage's pace instead of tripping the carriage's following error; a full circle of the tip with C turning a revolution under it rides both. World jogs get the same in motion, every servo cycle, from the Jacobian at the joints the machine stands in, the planners capped so no joint is asked for more than its limits and the jog keeping its direction. [AXIS_L] MAX_VELOCITY and MAX_ACCELERATION keep meaning the world of the type in force; the joint cap sits on top. A module that runs only in realtime, one without the parameter block form, caps nothing, as before. A pole, where a joint would have to move at any speed for the path to advance, gives an operator message and the move crawls rather than being dropped, since a dropped segment sends the next one along another path; refusing it ahead of time is item 7.

    The same query finishes a repair the tool orientation codes already needed. Solutions are now unwrapped onto the nearest turn of the current pose before ranking, so a C at 180 asked for a pose at C -90 goes to C 270, 90 degrees away, instead of turning 270 degrees the long way. What ranking cannot do yet is prefer the closest pose that stays inside the joint's limits: near a limit the nearest turn may be one the joint cannot reach, and ranking closest-within-limits has to know that joint's range, which this item now supplies and the ranking does not read yet.

  7. The joint limit check moves earlier and samples inside segments. It exists already, in inRange(), but only at move endpoints and only once the block has reached motion, so a program that will overtravel says so partway into the cut rather than before it starts. With 4 in place the same check runs at load time, against the tool and the kinematics type each block will actually use. A verify button in a GUI is then a presentation of that, not a subsystem of its own.

    Two things this needs pinned before it is written. The interpreter has no seam for the query: all 66 GET_EXTERNAL_* accessors take at most an index, an axis, a spindle, a pocket, and never geometry, so one taking a segment is a new shape for that interface. And a load-time answer can go stale, since the interpreter runs ahead and motion.switchkins-type is a HAL_IN pin, so until 9 lands the type a block is checked against is not necessarily the type it runs under.

    Where a module cannot answer, cheaply or at all, the check skips rather than refuses. Refusing a reachable program leaves the user no recourse; missing an unreachable one is still caught by inRange() at runtime. That asymmetry decides every ambiguous case in 6 and 7.

    Checking a program that switches type raises a question the tree currently leaves implicit: whether a type's coordinates are geometric. Most are, and for those a scan is one stream of world points with only the inverse changing at the switch. identity is not, since world becomes the joints, so a datum in millimetres lands on a rotary joint in degrees; nothing notices, which is why switchkins.adoc asks the operator to clear the offsets before switching.

    The joint-space syntax that question wanted is G53.7 above: joint values by joint number, no offsets, the joint limits directly. Whether a type should also declare its space for the scan stays open.

  8. Per-mode restrictions. G28, G30, G53, G92, G52 and M6 do not mean the same thing in every kinematics type, and today each one quietly does whatever the module happens to leave it doing. Refusing outright beats guessing, and with 4 in place the control knows enough to decide rather than to be told. The first of them is in: G53, G28 and G30 keep meaning the world of the kinematics in force, which on a table-side kinematics turns with the table, and G53.5, G28.5 and G30.5 are the machine frame forms; a configuration that wants the machine codes to mean that frame and nothing else sets MACHINE_MOVES_NEED_MACHINE_FRAME and has the world forms refused off the machine frame type.

  9. Retire motion.switchkins-type, once the grace period has run. Which release that is has still to be decided.

G12.1 and G13.1 are #4414, split out of this branch and standing on their own.

On tool length specifically: the offset is a nine-axis pose from the interpreter, canon adds it into the coordinate, and each rotary kinematics takes the Z component back through a HAL pin and re-applies it along the real tool direction. That round trip is exact when the config wires the pin, and silently wrong otherwise: a missing net costs the full tool length, and an X or Y offset is applied in the rotating work frame while the tool is bolted to the machine. The mechanism works; it has no owner.

Why 4 is worth the trouble: every other way of answering "will this program overtravel" builds a second model of the machine, in the verification package or in the post, and the two descriptions drift apart. If the kinematics module can be called with parameters the caller chooses, LinuxCNC can check a program against the same code that will run it, with no second model to keep in sync. That is not available to anyone whose kinematics is locked inside the control.

In this branch now

Items 1, 3, 4, 5 and 6, the first of 8, in that order:

  • Switchkins is one implementation: the dispatch splits from rtapi_app_main(), the four comps call it from EXTRA_SETUP(), switchkinscomp.comp is the out-of-tree template, and the implementation installs as source. G12.1, G13.1: select the kinematics type from G-code #4414 (G12.1 and G13.1) sits at the bottom.
  • Item 3: every module reports its work frame and its tool frame, 5axiskins included, which reported none until now and so refused every orientation code, the tool frame inverse and the Jacobian are shared code with closed forms registered where a module has one, and the conventions chapter fixes frames, signs, tool X and the zero pose. kinematics: let a module report where the tool points #4455 sits in the middle.
  • Item 4: a module is a parameter block and a table of pure functions; kinematics_userspace/ loads a module outside RT and binds to the RT instance's live pins; tests/kins-params checks that each module answers the same outside RT.
  • Item 5: the frame, the point-to-point moves, the orientation codes and G68.3, as above, with the interpreter evaluating the module ahead of motion. The inverse ahead of motion runs to a fixed point, because a module may read the joints it is handed and one pass from a stale seed answers for the wrong rotary angles. In motion a point-to-point segment keeps the last forward solution and clears a status flag when the forward fails inside it rather than parking the reported position at zero, and an external offset waits while a point-to-point segment is queued, since its joints were solved without it. Three repairs the work turned up: G68.3 no longer leaks its axis words into a stray traverse, the python preview applies SET_G68_FRAME in program units like every other length canon call, and orientation answers from the module are unwrapped onto the nearest turn of the current pose before nearest-first ranking. The bridgemill sim runs its sphere demo five ways, one per method: G53.2 with a fused orient-and-travel move, G53.6 orient-then-move, real quill drilling through G53.5 and G53.7, a G12.1 kinematics switch, and the historical W-as-tool-length program kept with the quill drawing locked, which reveals that the old trick only ever worked because the quill was not drawn.
  • After item 5: a tool offset change under a tilt keeps the joints where they are and reads the programmed point back from them, so G43 with the head tilted moves the point, not the machine, and the interpreter predicts the same through the loader. G43.5 gives the tool axis as a vector, I J K on G0 and G1. The machine frame is a declared kinematics type, G53.5, G28.5 and G30.5 move in it, G13.1 and G49 cancel to it, and the robots declare their arm type as such, so on a puma G53.5 Z50 lifts the flange along base Z holding the rest.
  • Item 6: the [AXIS_L] box read on the machine frame for every move, the segment cap in canon, the jog cap in motion, as above. kinslimits prints the cap canon reads for a move next to the Jacobian.

User visible

Fourteen G-codes, all of them non-modal except the plane itself, which has its own cancel:

code what it does
G68.2 define a tilted work plane on top of the active coordinate system, by Euler angles, by angles about fixed axes, by three points or by two vectors
G68.3 define one from where the tool points now
G68.4 compose one onto the plane already active
G69 cancel the plane
G53.1 turn the rotaries until the tool is normal to the plane, the linear joints staying where they are
G53.2 the same, solved without moving; the pose is published on parameters for the program to use
G53.3 the same, and take the tool to an X Y Z given in the plane
G53.6 the same, holding the tool centre point where it is
G53.4 point-to-point to a destination in program coordinates, offsets and plane applied
G53.5 point-to-point to the machine frame, the carriage or the flange in machine coordinates with the tool left out, in program units and with no offset
G53.7 point-to-point to joint values, J2=-5 naming joint 2 in its own units
G28.5, G30.5 the machine frame forms of G28 and G30, through the same stored positions
G43.5 the tool axis as a vector, I J K on G0 and G1, the rotaries solved by the module

P on the orientation codes picks among the poses that reach the plane, nearest first, and Q1 frees the joints that carry the work to take part. The point-to-point codes are modifiers of G0 and G1, and with G1 the move takes the time the straight move would at the programmed feed. J<n>=<value> is a new word form, read only where G53.7 uses it.

Alongside them:

  • stat.g68_offset, stat.g68_rotation and stat.g68_active in the python interface; halui's relative pins and the AXIS DRO apply the plane.
  • #<_orient_x> to #<_orient_c> and #<_orient_valid>, mirrored on #5071 to #5080, hold the pose G53.2 last solved; read-only, and reading one before the first G53.2 is an error.
  • The four components gain the coordinates= module parameter; millturn loses its in and out example pins.
  • The two trsrn_twp configs run on the native codes: no remap, no python, no twp-status pin. Their python maths is the oracle in tests/kins-twp.
  • One new INI option, [RS274NGC] MACHINE_MOVES_NEED_MACHINE_FRAME, off by default: G53, G28, G30, G28.1 and G30.1 refused whenever the kinematics in force is not the machine frame type.
  • [AXIS_L] MIN_LIMIT and MAX_LIMIT are the machine frame box on a module that declares its machine frame; the velocity and acceleration limits keep their meaning and the joint cap sits on top. A switch no longer needs the limits reset to keep the joints safe; it may still want the velocity limits reset where a letter changes unit at the switch, which the puma's M128 and M129 do and nothing else.
  • A module declares which of its types is the identity, the primary and the machine frame, .identity, .primary and .machine in the ops table or switchkinsDeclare() the old way; motion.kins-type and the switchkins-type N declared: line at load say what it declared.
  • The preview draws the tilted work planes a program defines, the one in effect highlighted. Suggested by Sigma1912.
  • No existing pin or G-code changes name. Configs using the .tool-offset net keep working; the tool comes from motion now and the net is only read until motion has spoken.

Left for later

  • Item 7, the joint limit check ahead of motion and inside segments, and with it the refusal of a pole that today only crawls with a message.
  • The orientation codes ranking the closest pose that stays inside the joint limits.
  • The quick reference and the qtvcp MDI help do not list the new codes yet.

Testing

tests/kins-params, tests/kins-jacobian, tests/kins-frames, tests/tool-frame, tests/kins-tool-offset, tests/kins-twp, tests/twp-native, tests/ptp-iterative, tests/ptp-machine-frame and tests/kins-limits cover the new code; tests/kins-limits holds the three tests that were red before item 6, the swing of a 400 mm pivot under G0 C180 and under an arc, the box read on the carriage instead of the tip, and the same swing as a world jog, each joint following its command through limit3 at 20 percent over its own limits the way a drive does, so a command over the limits falls behind and trips the following error; the last runs the xyzacb nutating head through the plane, the orientation codes and the point-to-point moves against the python maths, and checks that every point-to-point move keeps its joints on one line in joint space, all together, so a wrong interpolation fails three of its checks. tests/ptp-robot puts pumakins through the same codes, where G53.5 is refused and G53.7 is the form that works. The four comp sim configs give identical positions to master through the same MDI sequence, in every kinematics type.

Testers wanted

Everything above runs in the sims, and the sims follow their commands exactly. What I cannot see from here is a real five axis machine or a real robot, where the joint cap either keeps a drive inside its following error window or it does not. If you run a 5axiskins, xyzac-trt, xyzbc-trt, maxkins or trsrn config, or a pumakins or genserkins arm, a session on this branch would tell me a lot:

  • A G0 or G1 that turns a rotary under TCP with the head tilted, at full rapid, and whether it slows to the carriage's pace or trips a following error. Same with a world jog of the rotary in teleop mode.
  • Whether the [AXIS_L] box now reads right on your machine: a tip past the box with the carriage inside accepted, G53.5 past the box refused, and any move you expected to be accepted that is refused, with the line and the message.
  • On a robot, G53.5 along a base axis, and whether G13.1 and G49 leave you where you expect.
  • A config on an out-of-tree kinematics module: whether it still loads and whether motion.kins-type says what it declared.
  • Anything slower than it was: a long program with short segments on an iterative kinematics is where the per-segment sampling would show.

EMCCANON_KINS_DEBUG=1 in the environment makes canon print the cap it read for every segment on stderr, and kinslimits prints it for one move from the command line.

@grandixximo
grandixximo force-pushed the multiaxis-kins-limits branch 2 times, most recently from 9e1c9ed to 773c3e9 Compare August 12, 2026 07:22
Comment thread src/emc/kinematics/switchkins.h Outdated
Comment thread src/emc/motion/motion.h
Comment thread src/emc/nml_intf/emc_nml.hh Outdated
@grandixximo
grandixximo force-pushed the multiaxis-kins-limits branch from 773c3e9 to 34ab07b Compare August 12, 2026 08:34
Comment thread docs/src/man/man9/motion.9.adoc Outdated
Comment thread src/emc/kinematics/switchkins.c Outdated
@BsAtHome

Copy link
Copy Markdown
Contributor

What is the migration path for people having their own out-of-tree kinematics switch component?

@grandixximo

grandixximo commented Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

What is the migration path for people having their own out-of-tree kinematics switch component?

Their module needs no source change. G12.1 and G13.1 reach a module through the same kinematicsSwitch() the pin does, so a component that is switchable today is driven by G12.1 today without knowing G12.1 exists.

I checked rather than assumed. I built an out-of-tree component that deliberately does not use switchkins.h or switchkins.c: its own switchkins type, its own kinematicsSwitch(), its own kinstype pins, the shape somebody would have written before there was a supported way to reach the in-tree code. Against this branch:

G12.1 P1   kins-type=1, its type 1 kinematics active
G13.1      kins-type=0
G12.1 P7   its own BAD VALUE, motion stays on 0

with no source change, and no deprecation warning, since the pin is never written.

The config keeps working as well. The pin is deprecated, not gone, so an existing M68/M66 config runs as before with one warning per session. Moving off it is a config edit, not code:

M68 E3 Q1 / M66 E0 L0    ->   G12.1 P1
M68 E3 Q0 / M66 E0 L0    ->   G13.1

and the [HAL] net from the analog output goes with them. The sim configs in this PR are worked examples, halui buttons and remapped M-codes included.

One group does have something to do, and it is not the one you asked about: anybody keeping a copy of switchkins.c in their own tree. Their current build is unaffected, but rtapi_app_main() has moved out to switchkins_main.c, so next time they re-sync that copy they either take that file too or call switchkinsInit() themselves. They also no longer need the copy, since switchkins.h is installed now and switchkinscomp.comp shows both routes.

Announcing it is worth raising at a weekly meeting. Nothing is removed in this PR.

Comment thread docs/src/gcode/g-code.adoc Outdated
Comment thread src/emc/kinematics/5axiskins.c Outdated
Comment thread src/hal/components/switchkinscomp.comp
@Sigma1912

Copy link
Copy Markdown
Contributor

Just a thought:
Work offsets in one kinematics are usually not applicable in another one. So when using switchable kinematics I have often found it would be nice to have different sets of G54..G59 offsets.
Maybe a parameter offset set by the 'motion.kins-type' value?

@grandixximo

Copy link
Copy Markdown
Contributor Author

That matches what switchkins.adoc already warns about: coordinate system offsets are described as generally applicable only to the type 0 startup kinematics, with advice to reset them or update them before switching. So the problem is real, and today it rests on the operator remembering.

Can I ask what you switch between, and whether you cut in both types? I am trying to tell two situations apart. If both types leave the axis letters describing the same physical point, then your G54 values are still correct after the switch, and what is missing is having a different one become active. If one of the types is identity, the stored numbers stop describing anything, since X becomes joint 0 and may not even carry the same units, and then a different set does not help either; the values want clearing.

Which of these is it for you: does the zero point of the part sit somewhere different once you switch, or is the part zero the same and you simply want a different one of G54 to G59.3 selected?

On the mechanism, do you mean an association living in the INI, a coordinate system named per kinematics type? There is nothing like that today. Or do you mean it read at runtime? kins-type is a status pin, so it reports rather than commands, but you could net it into a motion analog input and branch on it with M66 in a sub, which would work now. I am unsure whether that belongs in the core or stays the sort of thing an integrator wires up, and your answer would tell me which problem I am actually looking at.

@Sigma1912

Copy link
Copy Markdown
Contributor

Which of these is it for you: does the zero point of the part sit somewhere different once you switch, or is the part zero the same and you simply want a different one of G54 to G59.3 selected?

Here are two examples, the tool may cut in all kinematic modes.

Example1: Serial manipulator arm
https://github.com/LinuxCNC/linuxcnc/tree/master/configs/sim/axis/vismach/melfa-sim
To overcome singularities in industrial robots we need to be able to switch between joint and cartesian mode inside a gcode program. Here the values as well as the units change from one kinematic mode the other.

Example2: Tilted work plane
https://github.com/LinuxCNC/linuxcnc/tree/master/configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating
The switch to TOOL/TWP mode requires a transformation of the offset values from one coordinate frame to another (rotated AND translated). The new work offset (that describes the same point on the work piece) has thus different values that must be activated after the switch. The original offsets must be restored after switching back.

Currently I have two ways of dealing with this:

  1. Keep the offsets in separate WCS systems (eg use G54 for one and G59 for the other mode). Switching from one to the other is easily handled in the custom M command used to switch the kinematics

  2. Stash the offset values in parameters (so they can be restored when switching back) and then overwrite the old ones.
    This can also be handled in the custom M command but requires much thought to ensure that the correct offset values are restored in all circumstances. Generally I VERY much dislike to alter values in the offset table in the background. There is a lot that can go wrong (eg in case of an abort)

On the mechanism, do you mean an association living in the INI, a coordinate system named per kinematics type? There is nothing like that today. Or do you mean it read at runtime? kins-type is a status pin, so it reports rather than commands, but you could net it into a motion analog input and branch on it with M66 in a sub, which would work now.

Maybe a different set of G54...G59.3 values. One set for each kins-type.
I have also thought about having G54...G58 for kins-type 0, G54.1 ... G58.1 for kins-type 1, G54.2 ... G58.2 for kins-type 2 and so on (for backwards compatibility G59..g59.3 maybe the same for all)

There are also some other things to think about when working in different kinematic modes ( eg G28 , G30, G53, G92/G52, M6) it might be useful to have way of blocking certain Gcodes / Axiswords in a given mode.
I have made a patch that allows blocking certain axis letters at runtime using a magic-comment. (Just as an illustration, not suggesting to use it)
ca9c334

@grandixximo

Copy link
Copy Markdown
Contributor Author

Both examples are clear and they moved my view: I had been assuming identity was mostly a jogging mode, and you cut in all modes. I looked at how the commercial controls handle both cases, and in each of them the closest thing we could do in LinuxCNC turns out not to be an offset set.

For the singularity case, the target stays in Cartesian coordinates and only the interpolation changes: a move that runs the inverse once at the endpoint and then interpolates the joints linearly to reach it. Coordinates and units never change, so the offsets keep applying and there is nothing to switch. In tree that is a new move type from canon into the trajectory planner, and the existing KINEMATICS_INVERSE_FLAGS already carry enough to pin which solution branch it lands on. G-code would need a way to ask for it.

For the tilted plane, an extra stage in the offset chain rather than new values in it: a frame composed on top of the active G54, with its own cancel, so the blocks in between are programmed in the tilted plane while G54 itself is untouched. That is a stage in rotate_and_offset_pos() plus the modal state to go with it. Nothing is rewritten, so nothing needs restoring and on_abort_with_twp_reset.ngc has nothing left to do. Orienting the tool normal to the plane, your G53.1, does need the kinematics to say where the tool points and to solve for the rotaries, which is item 3 above.

On having more of them: that part has plenty of precedent and I would be happy to look at it separately. Fanuc adds G54.1 P1 to P48, or to P300 on the 30i, and Siemens adds G505 to G599. What I cannot find anywhere is a control that ties a set to the active kinematics or transformation. Selection is always explicit in the program, or it belongs to a channel or path. So more coordinate systems yes, but I would rather not bind them to the kinstype.

Which leaves me reading one set per kinstype as paying for the two absences above rather than as the thing you actually want. You have run this in production and I have not, so I would rather hear where that is wrong. Would a composing frame cover your case 2, or would it miss something?

Your other point stands either way. Per-mode restrictions on G28, G30, G53, G92 and M6 look right to me, and refusing outright beats guessing.

@Sigma1912

Copy link
Copy Markdown
Contributor

Nice that you have an overview of what other controllers are doing. Makes more sense adopting something that is established rather than coming up with something quirky that falls on our feet somewhere down the road.
My big deficit (or at least one of them) is that I often have zero experience how things work
on commercial machines so I end up reinventing the wheel and often do it in a slightly odd manner because I don't really know what I'm doing. :)

Which leaves me reading one set per kinstype as paying for the two absences above rather than as the thing you actually want. You have run this in production and I have not, so I would rather hear where that is wrong. Would a composing frame cover your case 2, or would it miss something?

That is how I started out, an offset built into the kinematics. The reason I moved away from that was that in it's current form LinuxCNC is totally unaware of what is going on in a custom kinematics.
So if we can handle things like G53 etc. internally then I'm probably happy with not having different sets of offsets.

Basically what I'd like is to NOT have to tamper directly with the values in the offset table.

@grandixximo

Copy link
Copy Markdown
Contributor Author

That is the same conclusion from the other side, and it names the dependency. A composing frame is only worth having if motion knows the frame is there; otherwise G53 and G28 keep meaning whatever each module happens to do with them, and we are back to the operator remembering. That is items 3 and 4 above, which is why they come before anything in the offset chain.

What "aware" has to mean concretely: the module answers where the tool points and how joint motion maps to world motion, both as plain functions of the joint values and a parameter block, with no state hidden in HAL pins. Once the control can call the kinematics rather than only drive it, G53 resolves in machine axis space, the per-mode restrictions you listed get checked rather than guessed, and a frame composes on top of G54 with nothing rewriting the table.

So no per-kinstype offset sets, nothing tampering with the table in the background, and the tilted plane becomes a frame that cancels. Your two examples are the cases I will hold it to.

@Sigma1912

Copy link
Copy Markdown
Contributor

Sounds great!

@grandixximo
grandixximo force-pushed the multiaxis-kins-limits branch 2 times, most recently from ffd07b8 to fea53f7 Compare August 19, 2026 09:51
@grandixximo

Copy link
Copy Markdown
Contributor Author

One more thing the tool round trip does not cover, found while giving 5axiskins and maxkins the .tool-offset pin every other rotary module has.

Both modules add the W coordinate to the pivot to tip distance, so W is already a tool-length route: G43.1 W5 followed by a block that commands W0 puts the kinematics on the right radius and lands on the same joint solution as a Z-column offset does. It is easy to miss, because G43.1 W5 on its own changes nothing. convert_tool_length_offset() pushes w_current down by the offset, canon adds it back, and a block with no W word hands the module zero. Command W once and it is correct from there on.

So on these two modules the tool length has three spellings: the W column with W commanded, the sum2 into pivot-length that the bridge mill sim uses, and now a .tool-offset pin. They all compute the same radius and they all add, so using two double counts. None of them is written down anywhere, which is the actual problem.

This is the same shape as the rest of items 3 and 4. Each module infers the tool from whatever it happens to be handed, every config picks a different one of the three, and nobody owns which is meant. Once the tool arrives as a parameter at the call site there is one answer instead of three.

@Sigma1912

Copy link
Copy Markdown
Contributor

I have a set of kinematics that probably cover most 5 axis machine (tool side rotation, work side rotation and mixed) including nutating joints and tilted work plane. I have long wonder if I should add those to the collection:
https://github.com/Sigma1912/LinuxCNC_Demo_Configs/tree/main/5axis-twp

@grandixximo

Copy link
Copy Markdown
Contributor Author

Yes, and I would take the kinematics first.

The nutating ones are what items 3 and 4 need most. A nutating head is where "where does the tool point" stops being obvious, so a weak default every module answers is only worth having if something in tree can break it. Today the only nutating configs are the two trsrn ones, which are yours already.

On the TWP remap I would rather you decide with item 5 in front of you. I am proposing to move that mechanism into rotate_and_offset_pos() as a frame that composes on top of G54 and cancels, which would leave the Python doing less or nothing. That does not make the configs useless, the opposite: they are the executable spec for what the frame has to do, and the baseline for checking whether a C version matches. But adding them as tree configs while I am proposing to replace the mechanism should be a decision rather than a side effect. I would rather have them in than not, and you know the ground better than I do.

One request if the kins come in: could they take the tool length through a .tool-offset pin the way the trt modules do? I have just been through this on 5axiskins and maxkins and found three spellings of the same quantity with none of them written down. Three more modules each picking their own makes that worse, and settling it now is cheap.

@Sigma1912

Copy link
Copy Markdown
Contributor

I'm not in hurry it's been sitting here for a while now.
I have no problem making adjustments to make it fit better.

@grandixximo
grandixximo force-pushed the multiaxis-kins-limits branch 2 times, most recently from d32027c to 87c645b Compare August 22, 2026 08:00
@grandixximo

grandixximo commented Aug 23, 2026

Copy link
Copy Markdown
Contributor Author

I had a go at the split on the two trsrn configs in tree, to see how it lands: https://github.com/grandixximo/linuxcnc/tree/twp-remap-split

Your generic remap.py plus a remap_funcs_twp.py beside each config. Your two snrtr files carry exactly the two branches the tree had inline, xyzacb-snrtr is the (C, A) maths and xyzbca-snrtr is the (C, B) maths, so it was mostly substitution. Adapted for the tree: ini read through linuxcnc.ini, kinematics switched with G12.1, and kins_set_values converts the two joint angles to degrees because the modules in tree read those pins in degrees. Pin names unchanged, so neither .comp is touched.

I ran both configs through eight orientations under G53.1 P0/P1/P2, G53.3, G53.6 and G68.3 before and after, and it looks right: nothing that worked stopped working, and P1, P2, G53.6 and G68.3 now do something where they used to fail or silently no-op.

Three things I hit in your files:

xyzbca-snrtr has the wrong default tool-x. Both files aim for the same thing in the comment, the oriented x-vector parallel to the machine xy-plane. Tool x is the first column of Rp*Rs*Rz(tc), so that is entry [2][0] == 0, and since Rp is Rz only the bottom row of Rs enters:

bottom row [-Sv*Ss, t, s]  ->  tc = atan2(Sv*Ss, t)
bottom row [t, Sv*Ss, s]   ->  tc = atan2(-t, Sv*Ss)

xyzacb-snrtr has the second row and the second formula and is right. xyzbca-snrtr has the first row and the second formula, so it orients tool y horizontal instead. Looks copied from the sibling. Invisible in use, which is presumably why it survived: the tool still points where it was told to, only x and y in the tilted plane swap, so G68.3 R starts a quarter turn off. I used the tree's form in the port.

xyzacb_snrtr_kins.comp declares hal_float_t *virtual_rot; twice, lines 34 and 38, so it does not compile.

xyzacb-snrtr/remap_funcs_twp.py, kins_calc_primary, vertical tool branch sets theta_1 = 0 and then returns the empty list, so any G53.n to vertical fails. xyzbca-snrtr says return [0] there. I took return [0].

The commit is authored by me at the moment and the code is yours, so I would set you as author before it goes anywhere. Happy either way: take the branch and send it yourself, or tell me it looks right and I will.

@Sigma1912

Copy link
Copy Markdown
Contributor

Thanks for looking through this. I'll need to have a good look and report back.

@Sigma1912

Copy link
Copy Markdown
Contributor

I have found a problem with G13.1/G12.1 when either is in a 'on_abort' call.

Testing with configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/xyzbca-trsrn_twp

This config has [RS274NGC]ON_ABORT_COMMAND = o <on_abort_with_twp_reset> call

I have noticed strange behavior with the remapped gcodes after issuing MDI commands that trigger this routine. For example, it looks like the g682 function gets stuck on yield INTERP_EXECUTE_FINISH

You can get an indication of the issue if you uncomment the first line in the on_abort sub:

o<on_abort_with_twp_reset> sub
(msg, on_abort START)    ; <<<< UNCOMMENT this line so the msg shows on start
M68 E2 Q0       ; reset twp-state to 'undefined' (0)
G13.1           ; back to identity kins
G64 P0.01       ; reset the toolpath tolerance as this sometimes gets set to zero on estop events
G54             ; switch to G54
(msg, on_abort END)
o<on_abort_with_twp_reset> endsub
M2

Now start the config, clear estop and switch the machine on which calls the on_abort routine. Note though that it does apparently NOT reach the end since we don't get the message on the last line:

on abort

Further testing has shown that having either a G13.1 or G12.1 P0 in the on_abort routine triggers the problem while the former M68 E3 Q0 does not.

@grandixximo
grandixximo force-pushed the multiaxis-kins-limits branch from 87c645b to 5bf0103 Compare August 23, 2026 14:11
A segment whose endpoint is Cartesian but whose interpolation is in joint space: the inverse runs once, at the endpoint, when the move is queued, and the planner runs every joint from where the queue ends to there together, the slowest setting the pace. Its length is the joint space distance with the tightest per-joint velocity, acceleration and jerk limits scaled onto it. Nothing blends into or out of it. While it runs the servo thread takes the joints from the planner and reports the tool from the forward kinematics; a forward that fails, as an iterating one can at the singularity the move is there to cross, leaves the last solved position and carte_pos_cmd_ok cleared. This is how a program crosses a singularity or flips a head without leaving its coordinate system.

The move is a rapid with the rapid override or a feed that takes a given time with the feed override; the endpoint can also be given as joints, whose world position motion finds with the forward. Canon JOINT_TRAVERSE and JOINT_FEED, NML EMC_TRAJ_JOINT_MOVE and EMCMOT_SET_JOINT_LINE carry it; the preview draws the straight line between its ends. An external offset cannot ride on a joint segment: the queue refuses the segment while one is applied, and a request arriving while one is queued waits until the last joint segment is done. A queue reset forgets the joint-space bookkeeping with the queue.
@grandixximo
grandixximo force-pushed the multiaxis-kins-limits branch 3 times, most recently from 6beca2f to 803a5ad Compare September 19, 2026 06:27
… G53.1 to G53.6, and G68.3

The interpreter evaluates the kinematics module ahead of motion through the loader in kinematics_userspace, opened on first use on a HAL component of its own, so G53.n can ask the tool frame inverse where the rotaries go, G68.3 can read the tool direction, and the point-to-point codes can tell where their joints put the tool.

G53.4, G53.5 and G53.7 are point-to-point moves, non-modal modifiers of G0 and G1 like G53: the path is interpolated in joint space, only the endpoint is defined. G53.4 takes a program point through the offsets. G53.5 reads the axis letters as a point in the machine frame with the orientation left out, XYZ the carriage in machine coordinates and the rotary letters the rotary joints, no offset applied: the world of the type the module flags KINSTYPE_MACHINE, the identity type where the slides line up with the frame, so there a letter names the joints the module's identity mapping gives it, and on a module with a slanted or composite slide the point goes through that module's own machine frame maths, a letter not given holding its machine coordinate. Where that type is a plain identity it is refused on a machine whose letters name joints of the other unit class, as a serial robot's X; a module that declares no types is read as letters = joints. G53.7 takes joint values as J<n>=<value> words, a form the lexer reads when the J value is followed by an equals sign, the joint's own position in its own units, which works on every machine. With G1 the move takes the time the straight move would at the programmed feed. The inverse ahead of motion is run to a fixed point, because a module may read the joints it is handed.

G53.1 moves the rotaries to the plane's normal with the linear joints where they are; G53.6 does the same holding the tool centre point, a Cartesian move; G53.3 X Y Z goes to a point in the plane with the tool oriented. P picks among the poses: nearest by default, P1 and P2 by the sign of the secondary rotary, the one whose axis the other carries, found by turning each rotary through the module's tool frame, Heidenhain's SEQ+ and SEQ-. Q says whether the joints that carry the work take part: Q0 holds them, COORD ROT, falling back to everything free; Q1 frees them, TABLE ROT. G68.3 takes the plane from the tool, Z the tool axis as the joints have it, X the default tool X of the conventions chapter.

tests/twp-native runs the xyzacb nutating head through all of it against the python maths in tests/kins-twp; tests/ptp-robot puts pumakins through the point-to-point codes; tests/ptp-machine-frame builds a module with a slanted Y slide out of tree and checks G53.5 holds machine X across a Y move, which the slides would not. Reversing the joint interpolation fails three checks in twp-native.
The two trsrn_twp configs drop the python remap of G68.2, G68.3, G68.4, G69, G53.1, G53.3 and G53.6, its ngc wrappers, the abort handler that unwound it and the twp-status analog pin: the interpreter does all of it now, with nothing to restore on abort. The helper component that feeds the vismach model reads the plane from status. The demos select the TCP kinematics up front, since the orientation codes need it, and put kinematics 0 back at the end; square.ngc loses its G52, which a plane refuses.
Every plane in the frame test was one rotation of ninety degrees about a single axis, written four ways. With two angles zero, composing about the frame as it turns and about the fixed axes give the same matrix, so the difference between G68.2 P0 and P1, the whole reason they are separate codes, went unobserved. One plane at 25, 40 and -15 degrees instead, still written four ways, with the points and vectors derived from it; G68.4 and R take odd angles too, and G53 inside the plane lands somewhere that is not a permutation of the axes. Caught now: P0 composing the way P1 does, R composed before the plane rotation, the three-point form taking Y as X cross Z.
The chapter and the G-code reference described the two angle forms but never said whose conventions they are: P0 is Heidenhain's PLANE EULER, P1 its PLANE SPATIAL, the numbering is Fanuc's, and Fanuc's fifth form is not implemented. Two differences from the control they borrow from are stated: P1 and P2 refuse where a control offering SEQ falls back to the nearer pose, and P names a pose only where a rotary turns the tool, so a machine whose rotaries all carry the work has only the nearest form; the G53.1 section read as though a tilting table had a pose to name.
A point does not name one joint set: a robot wrist reaches it again with the forearm turned half a revolution, and both ends of a point-to-point move read the joints back from the point. The interpreter keeps its seed while it still explains where the machine is, instead of inverting the point every time and reporting joints the machine is not standing in, which had the next G53.7 refused on a joint limit. Motion holds the joints a joint segment ended on while the planner stays there, instead of inverting again the next cycle and undoing the move. tests/ptp-robot drives a wrist joint through zero and back and checks the joints not named stay put.
The interpreter works out the joints behind a point by inverting it, and
had nothing to start the first inverse from but zeros.  A module that
answers the inverse by iterating cannot be started anywhere: genserkins
takes the Jacobian at the seed, and a serial arm with every joint at zero
stands in a singular pose, so on a robot every G53.5 and G53.7 was refused
with "the kinematics cannot invert the current position".

Canon now reports the joint positions, and the interpreter falls back on
them when its own seed no longer explains where the machine is.  They also
name the arm the machine is standing in, which the inverse of a point
cannot: a robot reaches the same point again with the elbow the other way
up.
The preview has a machine to ask, through the status buffer it already
reads for the tool table and the offsets, so it can answer for the joints
as well.  Without them every inverse started from zeros, which previewed a
point-to-point move from a pose the machine is not standing in, and on a
serial arm could not be inverted at all.

The one method on the mixin covers AXIS, gremlin and the Qt screens, which
all take their canon from it.
pumakins answers the inverse in closed form and never looks at the joints
it is handed, so tests/ptp-robot says nothing about the seed.  genserkins
takes the Jacobian at those joints: the machine here homes well away from
all zeros, so the seed has to come from where the machine stands, and all
zeros is the singular pose it cannot come from at all.

The same program goes through the interpreter and through the preview, and
the two have to agree on where it ends up.  Then the arm is parked in the
singular pose, where the module reports through the HAL library it prints
with: the preview refuses, rather than the process going down.
The inverse took the whole Newton step the Jacobian asked for, and the Jacobian holds only near the estimate: an endpoint far from the seed asked for many radians, the arm landed somewhere unrelated and converged on a pose reached through whole turns. Seeded at 90 -90 0 0 90 -17.7 and asked for X450 Y-200 Z150 it answered joint 0 at -3263.962, the right pose nine turns out, which the joint limits refuse on a move the arm could make. Cap the step at GENSER_MAX_ANGLE_STEP and scale the whole vector so its direction survives, measured on the rotary links; a servo cycle asks for far less and is untouched.
The joint limit check inverted a move's endpoint from the joints the machine stands in. The reading runs far ahead, so every endpoint of a short program was checked from the starting pose, and an iterating inverse answers nearest whatever it is handed: park a robot at J4=-90, run a program that takes it to J4=90 and asks for a point, and the point is refused because it was inverted from the parked pose with the other wrist. Seed from the end of the queue instead: a joint segment knows its own end and the rest carry the answer the last endpoint came out with; with nothing queued the machine is the reference. The joint interpolated path already worked this way and shares the helper.
G53.2 asks where the rotaries have to go for the tool to be normal to the active plane, like G53.1, and moves nothing: it publishes the pose on #<_orient_x> and kin and on #5071 to #5079, with #<_orient_valid> and #5080 saying a pose is held, so the program reaches it with a move of its own, for instance one G0 naming the linear and rotary words together, a single Cartesian move under TCP. Heidenhain's STAY. Reading the parameters before the first G53.2 is an error.

Two fixes found on the way: a G68.3 block with axis words and a modal G0 emitted a stray move; and the solver's answers in (-180, 180] are unwrapped onto the turn nearest the present rotary position before the nearest-first ranking, or a rotary crossing 180 swings the long way round.
The old 5axisgui.ngc "drilled" a sphere with W words, W standing in for a tool length: the W axis of 5axiskins never drove a physical joint, the stroke folds into the XYZ slides, and the model's drawn quill hid that by cancelling the very motion that makes a W word work. The quill and its drawing lock go, so a W word shows the slides plunging, and 5axisgui.ngc keeps its name with comments saying what the controller cannot see. The sphere is then drilled two honest ways: g532-fused-orient-move.ngc, where G53.2 solves each plane and one fused G0 turns the head and travels to the hole, and g536-orient-then-move.ngc, where G53.6 reorients about the tip and a separate move travels in the plane. The demos and the M428 remap select the TCP kinematics with G12.1 P0, since on 5axiskins G13.1 lands on the identity. The AXIS GEOMETRY drops W: under TCP a W word never moves the tip, so W in the live plot only ever lied.
A module that applies the tool length itself reads it from motion, so a G43 with the head tilted moves the programmed point, not the joints; motion, task and the interpreter each did something else. Motion re-reads the point from the joints under the new tool, as after a kinematics switch, takes the external offsets off, moves the planner onto it and latches the joint hold there. The interpreter predicts the same point through the loader, from the joints the machine stands on, or for a G43.4 that switches on the same line from the joints read in the old kinematics before the switch, evaluating with the tool offset the program is under rather than the one motion has reached; the point rides along with the offset on EMC_TRAJ_SET_OFFSET, and motion aborts when its own answer differs. Under the identity, and where the module cannot be evaluated, the interpreter shifts by the offset difference as before and motion aborts only if the point moved. Task issues the offset once the queue has drained, so no queue busting is needed.

tests/kins-switch applies G43.4 and a zero length in a tilted pose and after a point-to-point move: the joints hold, the point moves by the tilt term, and a move of nothing from the interpreter's point goes nowhere.
G43.5 is G43.4 with one thing more: while it is in effect a G0 or G1 line may give the tool axis as I J K in place of rotary words, the form of a five-axis program written for the tool rather than the machine, Fanuc's tool center point control type 2. The rotaries are solved through the module's tool frame inverse, the solver G53.1 uses, split out as orient_solve(): every orienting joint free, the pose nearest the present one, so a path of vectors runs continuous; on the pole of the primary rotary the free joint stays. The pose enters the move as program rotary words through machine_pose_to_program(), so rotary offsets are right by construction and the move interpolates like any other. The vector is read in the coordinate system the line's X Y Z are in, the tilted plane where one is active, the machine's on a G53 line. A direction is never incremental, so G91 applies to the axis words only, and I J K alone turns the tool in place. No configuration names the rotaries: the same program orients a head, a table and a robot.

Refused: a vector with a rotary word, a zero vector, the identity kinematics, a vector on a G53.5 or G53.7 line. Left out: Fanuc's interpolation of the vector between lines, which needs the kinematics inside the planner. The semantics follow the G43.5 greatEndian built for xyzab_tdr_kins on the fork, including that the vector is absolute under G91; this is the generic form.

tests/kins-switch covers the vector against rotary words, G91, the pole, a rotary offset and the refusals; docs in g-code.adoc.
@grandixximo

grandixximo commented Sep 19, 2026

Copy link
Copy Markdown
Contributor Author

A note from a Discord conversation with David (Sigma1912) that changed the shape of G53.5 on this branch.

David asked whether G53.5 would hold up on his multiaxis_millturn config, a mill-turn with a composite Y made by two slides that do not line up with the machine frame. It would not have: G53.5 read the axis letters as slides, so Y10 there would have meant joint 1, not machine Y, and his type 0 is not the joints at all but a real transform he uses as G53 without the orientation, M428 then G53 G0 X500 Y300 Z900 in his tool change.

G53.5 is now a point in the machine frame with the tool left out, XYZ the pivot in machine coordinates and the rotary letters as the machine frame kinematics reports them, through whichever type the module flags as its machine frame: a third flag next to identity and primary, .machine = 1 on the ops table entry or switchkinsDeclare(0, KINSTYPE_MACHINE) on the old style. A module that declares none has its identity type stand in, so every in-tree machine tool module behaves as before; identity stays a separate flag because motion and the planner skip the maths on it. There, once type 0 is flagged, G53.5 G0 Y10 puts machine Y at 10 whatever mix of joints 0 and 1 makes it, and a letter left out holds its machine coordinate rather than a joint. G28.5 and G30.5 read their stored position the same way, and G13.1 and G49 cancel to that type.

I took it one step further to see whether the same reading holds on a robot. It does: pumakins and genserkins flag their arm kinematics as the machine frame as well as the working transform, since the world they report is the flange in the base frame with no tool in the maths, ABB's tool0 and KUKA's $NULLFRAME. G53.5 Z on a robot moves the flange along base Z holding everything else, interpolated in joint space, and G13.1 and G49 leave a robot on its arm kinematics. The three roles line up with Siemens' MCS, BCS after TRAANG, and TRAORI.

tests/ptp-machine-frame builds a slanted-slide module out of tree and checks it, tests/ptp-robot does the robot on pumakins.

…MACHINE_MOVES_NEED_MACHINE_FRAME

G53, G28 and G30 move in the world of the kinematics in force with the offsets removed, and on a table-side kinematics that world turns with the table: G53 X0 Y0 Z0 under TCP with the table at A10 takes the tool tip to the turned origin, which the slides reach elsewhere. That is what the existing switchkins configs rely on, so it stays; the G53 section says so now, and points at G53.5 for the machine frame.

G28.5 and G30.5 are to G28 and G30 what G53.5 is to G53: the stored position is read in the machine frame with the orientation left out, the way G53.5 reads its words, the slides on a machine whose slides line up with its frame, and the move is a joint-space traverse there, with no offset, tool length or work plane. Axis words on the line are a machine frame waypoint, and then only those letters go to their stored values, as G28 does with its words. The stored position has to be saved on the machine frame type, where the world is that frame.

[RS274NGC] MACHINE_MOVES_NEED_MACHINE_FRAME, off by default, refuses G53, G28, G30, G28.1 and G30.1 whenever the kinematics in force is not the machine frame type, for a configuration that wants the machine codes to mean that frame and nothing else. The decision is the flags the module declares for the selected type, with the machine's kinematics type as the fallback for a module that switches nothing; a plain identity type that is not the machine frame is refused like any other.

The letter mapping and the joint move come out of convert_ptp_joints into slide_joints and ptp_joint_move, shared by the three codes. The twp-native test stores a position on the identity, checks G28.5 and G30.5 with the head tilted under TCP, the waypoint included, sets the flag and checks what it refuses under TCP and accepts on the identity. The ptp-machine-frame test does the same on the slanted module, where G28.5 with the head tilted lands the carriage, and the flag refuses the codes under the plain identity too. The waypoint check wants the joint to reach its turn, and a status snapshot per task cycle misses a turn by up to 9e-3 mm at the test's deceleration, which failed on a loaded runner: the twp-native test now reads the path of every move from a sampler on the servo thread, logged by halsampler, where the turn falls inside one cycle.
A robot's working transform reports the flange in the base frame, ABB's tool0 and KUKA's $NULLFRAME: the tool is applied on top by the interpreter, not by the maths, so the type G43.4 switches to is also the machine with the tool left out, the frame G13.1 and G49 cancel to and G53.5 moves in. pumakins and genserkins declare that one type both primary and machine. G13.1 and G49 leave a robot on its arm kinematics, where a robot controller leaves it, and the identity type, joints as axes, is reached by number; G53.5 moves the flange along a base axis and holds the rest, the orientation included, interpolated in joint space, and G28.5 and G30.5 with it. A machine frame type does not read the tool offset in the parameter block, so a module whose working transform does read it declares a type without it.

The machine frame is described as the frame with the tool left out rather than with the orientation left out, since on a robot the rotary letters are the flange angles the module reports, not joints; the G13.1, G49 and G43.4 descriptions say machine frame where they said identity. tests/ptp-robot checks that G53.5 moves the flange from the arm kinematics and from the identity alike, and that G13.1, G43.4 and G49 all leave the robot on the arm kinematics, in place of the refusal it checked before.
…dentity

Two tests that fail today and define item 6 of the multiaxis plan: travel is the joint limits, on every move and every kinematics type, and the [AXIS_L] box is the machine frame envelope, the carriage, not the tool tip.

tests/kins-limits/swing: 5axiskins with a 400 mm pivot, the head laid over to B90 by a joint move, then G0 C180 with the tool tip held. The carriage draws a half circle of radius 400 to keep the tip still, and the planner runs C at C's own limits, so the slides are asked for 1396 mm/s^2 against their 800 and would be asked for 419 mm/s against their 200. The joints follow their commands through limit3 at 20 percent over each joint's own limits, the way a drive or a step generator follows only as fast as it can, so the command a joint cannot follow is a real following error: joint 1 trips it 84 cycles in. The test asserts no following error, the endpoint reached, and every joint's commanded velocity and acceleration within its own INI limit at every servo cycle, read from a sampler. The slowest joint has to set the pace; how fast the tip goes is not asserted.

tests/kins-limits/box: joints reaching 1000, the box at 500, a 250 pivot and a 50 tool with the head at B90 so that the tip sits 300 from the carriage. Under the tool centre point kinematics a move with the tip past the box and the carriage inside it must be accepted, one with the tip inside and the carriage past the box refused, G53.5 to a carriage past the box refused and to one inside it accepted whatever the tip does; a carriage past the joint limit is refused whatever the box says. Today the box is checked on the world of the kinematics in force, the tip, and never on a joint-target move, so three of the five go the wrong way.
…cs is in force

The position limits in [AXIS_L] were checked on the world of the kinematics in force, so under a tool centre point kinematics they boxed the tool tip, which moves with the tool length and the head, and a joint-target move never saw them at all. A box on the tip refuses moves the machine can make and lets through moves it cannot, which is why switchkins.adoc told configurations to setp ini.L.min_limit at every switch and the puma sims shipped M128 to M130 to do it.

The box is now the machine frame envelope: the carriage, or a robot's flange, in machine coordinates, the frame that does not move when a head tilts or a tool is loaded. kinematicsMachineFrame() runs the forward of the type flagged KINSTYPE_MACHINE on a set of joints without switching to it, and motion's inRange() takes every endpoint through it after the inverse, Cartesian and joint interpolated moves alike, so G0 and G53.5 to the same carriage position get the same answer. A module that declares no machine frame type keeps the box on the world of the type in force, as before; motion links the new entry point weakly so an older module still loads. The travel itself is the joint limits, checked on every move as they were.

The puma M-codes keep swapping the velocity and acceleration limits per type and leave the box alone, and M128 restores max_velocity rather than a pin that never existed. tests/kins-limits/box goes green: under the tool centre point a move with the tip past the box and the carriage inside is accepted, one with the tip inside and the carriage past the box is refused, and G53.5 gets the box too.
A segment is planned in world coordinates against the [AXIS_L] velocity and acceleration limits, which say nothing about the joints once the kinematics is not the identity: a turn of a tilted head under the tool centre point swings the carriage through a circle the head's own limit never mentions, and the carriage trips its following error.

Every segment is now also sampled through the kinematics module's Jacobian in canon, ahead of motion, on the kinematics type and the tool offset the program is under, and its velocity and acceleration are lowered to what the slowest joint can give within its own [JOINT_n] limits. motion_planning/segment_cap.cc does the reading: at each sample the rate of every joint per unit of path parameter along the local tangent; between samples how that rate changes, charged against the acceleration budget at the capped velocity (what a carriage on a circle feels as centripetal, at most half the budget, the velocity lowered where it would take more); from three samples in a row how much the rate can bulge between two of them, so a peak falling between samples is covered. Lines are sampled from canon.endPoint to the target, traverse, feed, probe, rigid tap and the chained segments of the naive cam alike, through getStraightVelocity() and getStraightAcceleration(); arcs are sampled on the posemath circle ARC_FEED builds, in ARC_FEED itself.

Canon owns its non-RT context: the module and joint count from [KINS] in the INI, the type tracked through SELECT_KINS_TYPE and dropped to the machine's whenever the interpreter synchs, the tool from the offset canon applies, the joints at the start of a segment kept from the last one while they still explain the point, else the machine's, else the inverse iterated to a fixed point. The identity, as a module or as the type in force, loads nothing. A module that runs only in realtime caps nothing, as before. Where the module cannot answer at a sample, or a joint would have to move at any speed at all (a pole), an operator message says so and the segment goes through capped rather than dropped, since a dropped segment sends the next one along another path.

tests/kins-limits/swing goes green, and gains a second phase: the tip draws a full circle at a feed it could keep on its own while C turns another revolution, so the carriage rides the small circle and the big swing together. kinslimits prints the same cap for a move after its own per-sample listing. emcJointGetMaxVelocity() and emcJointGetMaxAcceleration() read back what iniJoint() set. The switchkins chapter's limit section says what the velocity and acceleration limits now mean and when a switch still wants them reset; the [AXIS_L] entry in the INI chapter points to it.
A world jog is planned per axis against the [AXIS_L] limits, which say nothing about the joints once the kinematics is not the identity: a jog of C under the tool centre point with the head laid over swings the carriage through a circle, and the carriage trips its following error a fraction of a degree in. The segment cap in canon cannot help, a jog never passes canon.

Each servo cycle in teleop mode, before the axis planners run, teleop_joint_cap() reads the module's Jacobian at the joints the machine stands in (weak, so an old module without one keeps today's behaviour, and nothing is read on the identity) and, for the jog the planners were asked for, the rate every joint would move at per unit of it and how that rate has changed since the last cycle, what a carriage on a circle feels as centripetal. The planners are then capped so that no joint is asked for more than its own limits: the velocity by the joint velocity limits, read a stopping distance ahead since the rate keeps growing while the planners slow down, and by the acceleration budget the changing rate takes at that velocity, at most half of it; the acceleration by what the budget leaves. Every active axis scales by the same factor, so a jog of two axes keeps its direction. The jog's own request is kept apart from the planner's caps in the axis, teleop_vel_req and teleop_acc_req, and axis_teleop_request() and axis_teleop_cap() are the two calls the cap needs from axis.c.

tests/kins-limits/jog is the swing test's machine in teleop mode: a continuous jog of C at the axis's full rate stopped past 150 degrees, an incremental jog back by 90, an incremental jog of X with the head still laid over, every joint following through limit3 at 20 percent over its limits; red before this (joint 1 asked for 1396 mm/s^2 against 800, following error at C 0.4), green with it, the carriage held at its limits and the jog of C running faster where the carriage rate is small and slowing as it grows. The switchkins and INI chapters say that a jog is bounded the same way as a move.
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.

4 participants