Class ProgramXyzUtil
- Namespace
- Hi.NcParsers.LogicSyntaxs
- Assembly
- HiMech.dll
Shared utilities for ProgramXyz and MachineCoordinate lookback and resolution. Used by ProgramXyzSyntax, ReferenceReturnSyntax, and semantic resolvers that need position lookback.
Two strategies for "what's the program coordinate at a block's endpoint?" — both invert an MC value through an EndPointProgramToMcTransform chain, but they pick the chain from different nodes:
-
By current-state transform
(ComputeProgramXyzByCurrentTransform(LazyLinkedListNode<SyntaxPiece>, Vec3d)) — modal anchor is
MachineCoordinate. Re-expresses an
MC value (typically a predecessor's modal MC) into the current
block's program frame using the current block's chain. Suitable for
chain-change blocks where the spindle physically stays put while the
chain (G54 swap, G68.2 activation, G43.4 toggle, tool-height change,
...) re-anchors the program frame; mirrors legacy
HardNcLine.RebuildProgramXyzByMc. -
By corresponding-state transform
(ComputeProgramXyzByCorrespondingTransform(LazyLinkedListNode<SyntaxPiece>)) — modal
anchor is ProgramXyz. Recovers the
program coordinate that
nodeCarryingMcwas originally commanded at, by inverting that same node's own transform on its own MC. Suitable for RTCP rotary-dynamic inheritance, where the modal invariant is "tool tip in workpiece frame stays put while rotary axes turn" — the recovered Vec3d carries forward as the next rotary block's modal ProgramXyz unchanged, regardless of how itsPivotTransformdiffers.
Both strategies yield the same Vec3d when prev and current share the same chain modal state; they only diverge across chain boundaries (RTCP toggle, coord-system swap, tilt activation) and at rotary motion (PivotTransform difference). Pick the wrong one and the result lands in a stale frame:
-
Non-RTCP using "corresponding" — leaves the pre-chain-change values,
so a block emitted right after
G43.4 H03would inherit ProgramXyz still in the G49 frame and the next motion's MC.Z drifts by the introduced tool-height offset. (This was the 2026-04-25 SoftNc / HardNc divergence onDemoPmcAirPlane/NC/02-ED6L20.NC.) -
RTCP using "current" — double-counts the rotary
PivotTransformdifference, so the inherited workpiece anchor rotates by the C delta on every rotary block.
Direct callers of the two strategy helpers are rare — typically you call the dispatcher ResolveBlockProgramXyz(LazyLinkedListNode<SyntaxPiece>, Vec3d) (block's own MC vs predecessor lookback, picks strategy from IsRotaryDynamic) or GetLastProgramXyz(LazyLinkedListNode<SyntaxPiece>) (pure predecessor lookback).
public static class ProgramXyzUtil
- Inheritance
-
ProgramXyzUtil
- Inherited Members
Methods
ComputeProgramXyzByCorrespondingTransform(LazyLinkedListNode<SyntaxPiece>)
Strategy: by corresponding-state transform. Recovers the
ProgramXyz that
nodeCarryingMc was originally commanded at, by
inverting that same node's
EndPointProgramToMcTransform on
its own MachineCoordinate.
Modal invariant: ProgramXyz carries
forward (RTCP rotary modal) — the workpiece-frame anchor survives
downstream rotary motion regardless of how the next block's
PivotTransform differs, so the next rotary-dynamic block
can adopt this Vec3d unchanged as its modal ProgramXyz.
Returns null when nodeCarryingMc has no usable
MC. Called from the RTCP branch of
GetLastProgramXyz(LazyLinkedListNode<SyntaxPiece>) and from
ResolveBlockProgramXyz(LazyLinkedListNode<SyntaxPiece>, Vec3d) when the dispatched node has
its own MC.
public static Vec3d ComputeProgramXyzByCorrespondingTransform(LazyLinkedListNode<SyntaxPiece> nodeCarryingMc)
Parameters
nodeCarryingMcLazyLinkedListNode<SyntaxPiece>
Returns
ComputeProgramXyzByCurrentTransform(LazyLinkedListNode<SyntaxPiece>, Vec3d)
Strategy: by current-state transform. Re-expresses
mc into currentNode's program
frame by inverting currentNode's own
EndPointProgramToMcTransform
chain.
Modal invariant: MachineCoordinate
carries forward — between the source of mc and
currentNode, the spindle physically stays put
while the chain (G54 swap, G68.2 activation, G43.4 toggle,
tool-height change, ...) re-anchors the program frame. Result is
the program coordinate that, when transformed by
currentNode's chain, yields
mc back.
Mirrors legacy HardNcLine.RebuildProgramXyzByMc; called
from the non-RTCP branch of GetLastProgramXyz(LazyLinkedListNode<SyntaxPiece>).
public static Vec3d ComputeProgramXyzByCurrentTransform(LazyLinkedListNode<SyntaxPiece> currentNode, Vec3d mc)
Parameters
currentNodeLazyLinkedListNode<SyntaxPiece>mcVec3d
Returns
FindPreviousMc(LazyLinkedListNode<SyntaxPiece>)
Finds the most recent MachineCoordinate from previous SyntaxPiece nodes. Returns null if no previous position found.
public static Vec3d FindPreviousMc(LazyLinkedListNode<SyntaxPiece> node)
Parameters
Returns
FindPreviousMcXyzabc(LazyLinkedListNode<SyntaxPiece>, IMachineAxisConfig)
Finds the most recent MachineCoordinate XYZABC from previous nodes as DVec3d. Point = XYZ (mm), Normal = ABC (radians, converted from degrees in JSON).
XYZ is taken from the first previous block whose MC has any of X/Y/Z set (typical motion-emitting block). ABC is then backfilled per axis for axes the machine actually has: if the XYZ-carrying block lacks a particular rotary value, we continue walking back to find the last block that wrote that axis (modal rotary state). This matches NC semantics — unchanged rotary axes carry forward silently — and prevents NaN rotary deltas from stopping ClLinearMotionSemantic's duration computation in RTCP contours where the XYZ block right before the current one didn't record ABC.
axisConfig scopes the rotary-backfill to the
machine's declared rotary axes (via
GetRotaryAxes(IMachineAxisConfig)): non-rotary axes stay
NaN and skip backward walking entirely. When
axisConfig is null (callers without the
dependency — e.g. legacy tests), all three A/B/C are attempted,
matching the pre-axisConfig behaviour.
Returns null if no previous MC with XYZ is found at all. Axes that have never been set stay NaN.
public static DVec3d FindPreviousMcXyzabc(LazyLinkedListNode<SyntaxPiece> node, IMachineAxisConfig axisConfig = null)
Parameters
nodeLazyLinkedListNode<SyntaxPiece>axisConfigIMachineAxisConfig
Returns
FindPreviousStoredProgramXyz(LazyLinkedListNode<SyntaxPiece>)
Finds the most recent stored ProgramXyz from previous SyntaxPiece nodes — a raw-value lookback that returns whatever was written on disk, without MC-inversion or frame-change reconstruction.
Contrast with GetLastProgramXyz(LazyLinkedListNode<SyntaxPiece>), which reconstructs
the inherited program position as
prev.MC × inverse(transform) and is sensitive to RTCP /
chain-change boundaries. This helper is the simple parallel of
FindPreviousMc(LazyLinkedListNode<SyntaxPiece>) — use it when a caller specifically
needs "what ProgramXyz did the last block write" (e.g. the
ProgramXyzSnapshotSyntax change check).
public static Vec3d FindPreviousStoredProgramXyz(LazyLinkedListNode<SyntaxPiece> node)
Parameters
Returns
GetLastProgramXyz(LazyLinkedListNode<SyntaxPiece>)
Gets the modal ProgramXyz inherited by node from
the most recent predecessor with an
MachineCoordinate. Dispatches
between the two strategies documented on the class summary based on
node's own
IsRotaryDynamic flag:
-
IsRotaryDynamic = true(RTCP rotary modal) → ComputeProgramXyzByCorrespondingTransform(LazyLinkedListNode<SyntaxPiece>) on the predecessor (recover prev's commanded ProgramXyz; carries forward unchanged because workpiece-frame tool tip is the modal anchor). -
Otherwise (chain-change / non-RTCP) →
ComputeProgramXyzByCurrentTransform(LazyLinkedListNode<SyntaxPiece>, Vec3d) with
node's own transform on the predecessor's MC (re-express prev MC in current program frame; MC is the modal anchor while the chain re-frames around it).
When prev and current share the same chain modal state both strategies agree, so the discriminator only matters at chain boundaries / rotary motion.
Returns Zero only when no predecessor has a usable MC (i.e. the start of the program with no motion emitted).
public static Vec3d GetLastProgramXyz(LazyLinkedListNode<SyntaxPiece> node)
Parameters
Returns
ReadMcXyzabc(JsonObject)
Reads XYZABC from a MachineCoordinate section as DVec3d. Point = XYZ (mm), Normal = ABC (radians, converted from degrees in JSON). Missing axes are NaN. Returns null if the section doesn't exist or has no XYZ.
public static DVec3d ReadMcXyzabc(JsonObject ncBlock)
Parameters
ncBlockJsonObject
Returns
ResolveBlockProgramXyz(LazyLinkedListNode<SyntaxPiece>, Vec3d)
Resolves the ProgramXyz at
node's endpoint — i.e. what
ProgramXyzSnapshotSyntax would write on
node. Dispatcher; the actual inversion math runs
inside one of the two strategy helpers documented on the class
summary:
-
nodehas its own MachineCoordinate XYZ → ComputeProgramXyzByCorrespondingTransform(LazyLinkedListNode<SyntaxPiece>) onnodeitself (its own MC and own transform; the "current" / "corresponding" distinction collapses since both come from the same node). -
nodehas no own MC andprevStoredis non-null → GetLastProgramXyz(LazyLinkedListNode<SyntaxPiece>) walks back to the most recent predecessor with MC and dispatches strategy fromnode's IsRotaryDynamic flag. - Both empty → return null; callers must not fabricate a spurious origin on the very first block.
Shared by ProgramXyzSnapshotSyntax
(computing the snapshot value to write on node)
and McAbcXyzFallbackSyntax (computing
Previous's would-be snapshot
to inherit on the current rotary-dynamic block — the Logic-stage
caller cannot read prev's stored ProgramXyz because PostSyntaxs
run after the whole Logic chain finishes).
prevStored for the second use is taken from
FindPreviousStoredProgramXyz(LazyLinkedListNode<SyntaxPiece>) on
node's predecessor — the
predecessor-of-predecessor's stored ProgramXyz — only as a guard
against the spurious-origin case.
public static Vec3d ResolveBlockProgramXyz(LazyLinkedListNode<SyntaxPiece> node, Vec3d prevStored)
Parameters
nodeLazyLinkedListNode<SyntaxPiece>prevStoredVec3d
Returns
ResolveProgramXyz(JsonNode, LazyLinkedListNode<SyntaxPiece>)
Resolves X/Y/Z from a JSON section into absolute program coordinates. Fills missing axes from last program position via lookback.
public static Vec3d ResolveProgramXyz(JsonNode xyzSource, LazyLinkedListNode<SyntaxPiece> syntaxPieceNode)
Parameters
xyzSourceJsonNodeJSON node containing X/Y/Z keys (e.g.,
Parsingroot,Parsing.G28,Parsing.L).syntaxPieceNodeLazyLinkedListNode<SyntaxPiece>Current node for lookback.
Returns
- Vec3d
Absolute program coordinates, or null if no X/Y/Z found in
xyzSource.