Table of Contents

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 nodeCarryingMc was 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 its PivotTransform differs.

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 H03 would 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 on DemoPmcAirPlane/NC/02-ED6L20.NC.)
  • RTCP using "current" — double-counts the rotary PivotTransform difference, 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

nodeCarryingMc LazyLinkedListNode<SyntaxPiece>

Returns

Vec3d

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

currentNode LazyLinkedListNode<SyntaxPiece>
mc Vec3d

Returns

Vec3d

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

node LazyLinkedListNode<SyntaxPiece>

Returns

Vec3d

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

node LazyLinkedListNode<SyntaxPiece>
axisConfig IMachineAxisConfig

Returns

DVec3d

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).

Returns null if no predecessor has ProgramXyz.
public static Vec3d FindPreviousStoredProgramXyz(LazyLinkedListNode<SyntaxPiece> node)

Parameters

node LazyLinkedListNode<SyntaxPiece>

Returns

Vec3d

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:

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

node LazyLinkedListNode<SyntaxPiece>

Returns

Vec3d

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

ncBlock JsonObject

Returns

DVec3d

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:

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

node LazyLinkedListNode<SyntaxPiece>
prevStored Vec3d

Returns

Vec3d

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

xyzSource JsonNode

JSON node containing X/Y/Z keys (e.g., Parsing root, Parsing.G28, Parsing.L).

syntaxPieceNode LazyLinkedListNode<SyntaxPiece>

Current node for lookback.

Returns

Vec3d

Absolute program coordinates, or null if no X/Y/Z found in xyzSource.