Table of Contents

Class SoftNcRunner

Namespace
Hi.NcParsers
Assembly
HiMech.dll

Configurable NC Runner.

public class SoftNcRunner : INcRunner, IMakeXmlSource
Inheritance
SoftNcRunner
Implements
Inherited Members
Extension Methods

Constructors

SoftNcRunner()

Creates an empty runner; populate the pipeline lists before use.

public SoftNcRunner()

SoftNcRunner(XElement, string, string, IProgress<IMessage>, object[])

Reconstructs a SoftNcRunner from a project XML element. Each pipeline list (PipelineNcDependencyList, Segmenter, NcInitializationList, NcSyntaxList, NcSemanticList) is rehydrated via XFactory, then the legacy back-fills run against the version the element was written by (the owning project's stamp, else the element's own ApiVersionAttributeName stamp, else “unstamped” — see Hi.NcParsers.SoftNcRunner.ApplyLegacyVersionPatches(Hi.Common.XmlUtils.ProjectApiVersion)).

public SoftNcRunner(XElement src, string baseDirectory, string relFile, IProgress<IMessage> progress, object[] res = null)

Parameters

src XElement

XML element previously produced by MakeXmlSource(string, string, bool).

baseDirectory string

Project base directory for resolving relative paths.

relFile string

Project file path relative to baseDirectory.

progress IProgress<IMessage>

Diagnostic sink propagated to nested factories.

res object[]

Optional ambient resources (e.g. the owning project's ProjectApiVersion, used when the element carries no stamp of its own).

Fields

ApiVersionAttributeName

XML attribute name of the API version stamp on a serialized runner (<SoftNcRunner ApiVersion=“3.2.17.0”>) — the same name and the same version line a project file carries on its root.

public const string ApiVersionAttributeName = "ApiVersion"

Field Value

string

Properties

ApiVersion

The API version MakeXmlSource(string, string, bool) stamps on the runner element, or null to write no stamp. The version line is the project file's ApiVersion — the assembly that owns the project format assigns this once at registration time (the legacy-patch gates in Hi.NcParsers.SoftNcRunner.ApplyLegacyVersionPatches(Hi.Common.XmlUtils.ProjectApiVersion) are keyed to that line, not to this library's own version, which runs ahead of it). A runner serialized by a host that never assigns it (the shipped preset generator, tests) carries no stamp and reloads as ProjectApiVersion-less, i.e. older than every gate.

public static Version ApiVersion { get; set; }

Property Value

Version

FanucNcRunner

Brand preset for Fanuc — composes a SoftNcRunner whose NcSyntaxList comes from DefaultSyntaxList.

public static SoftNcRunner FanucNcRunner { get; }

Property Value

SoftNcRunner

HeidenhainNcRunner

Brand preset for Heidenhain — composes a SoftNcRunner whose NcSyntaxList comes from DefaultSyntaxList.

public static SoftNcRunner HeidenhainNcRunner { get; }

Property Value

SoftNcRunner

MazakNcRunner

Brand preset for Mazak — composes a SoftNcRunner whose NcSyntaxList comes from DefaultSyntaxList.

public static SoftNcRunner MazakNcRunner { get; }

Property Value

SoftNcRunner

NcInitializationList

Seeds the initial SyntaxPiece JSON state (e.g. home position, defaults).

public List<INcInitializer> NcInitializationList { get; set; }

Property Value

List<INcInitializer>

NcSemanticList

Final-stage semantics that turn the last syntax layer into SourcedActEntry records consumed by the runtime.

public List<INcSemantic> NcSemanticList { get; set; }

Property Value

List<INcSemantic>

NcSyntaxList

Ordered syntax pipeline. Each entry consumes the previous layer's SyntaxPiece stream and emits the next layer.

public List<INcSyntax> NcSyntaxList { get; set; }

Property Value

List<INcSyntax>

PipelineNcDependencyList

External configuration providers consumed by syntaxes/semantics (machine axes, tool offsets, coordinate tables, block-skip flags, etc.). May contain INcDependencyProxy placeholders — see GetEffectiveNcDependencyList().

public List<INcDependency> PipelineNcDependencyList { get; set; }

Property Value

List<INcDependency>

Segmenter

Splits raw NC text into Sentence blocks.

public ISegmenter Segmenter { get; set; }

Property Value

ISegmenter

SiemensNcRunner

Brand preset for Siemens — composes a SoftNcRunner whose NcSyntaxList comes from DefaultSyntaxList.

public static SoftNcRunner SiemensNcRunner { get; }

Property Value

SoftNcRunner

SyntecNcRunner

Brand preset for Syntec — composes a SoftNcRunner whose NcSyntaxList comes from DefaultSyntaxList.

public static SoftNcRunner SyntecNcRunner { get; }

Property Value

SoftNcRunner

XName

XML element name for Generators registration.

public static string XName { get; }

Property Value

string

Methods

ConfigureByMachiningChain(IMachiningChain)

Configures this SoftNcRunner to match the kinematic chain of a machine tool. Registers every axis present on the chain into the IMachineAxisConfig in PipelineNcDependencyList (linear or rotary, derived from whether the chain's transformer implements IDynamicRotation). When the chain carries any rotary axis, a NcKinematicsDependency is appended so that downstream syntaxes / semantics (e.g. G53.1, G68.2, McLinearMotionSemantic) can resolve orientation; its KinematicsProvider is intentionally left null — the owning project service wires it up after the solver instance is available (see LocalProjectService.BuildCoordinateConverter).

The walk overwrites only the axis type, which always follows the chain; every other value it only adds: an axis's reference position (IHomeMcConfig) and, on a ControllerParameterTableBase, a rotary axis's rapid rate are seeded — 0 and the table's DefaultRotaryRapidRate_degdmin — only where the table holds no row for that axis, a stored 0 included. A value the project holds, written through SetHomePosition(string, double) or SetRotaryAxisRapidRate_degdmin(string, double), as a raw row of the table, or by the legacy MaxRotarySpeedABC_rpm of PopulateLegacyMachineConfig(XElement, List<INcDependency>), survives every rewire. Until 2026-10-28 every walk re-stamped a chain rotary axis of a brand parameter table to 0 deg and 36000 deg/min through ConfigureRotaryAxis(string, double, double), which still writes its arguments unconditionally when called directly.

The axis / home machine config is read from GetEffectiveNcDependencyList(), so a proxied parameter table resolves to the host's materialized instance — call this only after the proxies' host is wired. The NcKinematicsDependency, by contrast, is a runner-level pipeline dependency and is appended to the runner's own PipelineNcDependencyList below.

public void ConfigureByMachiningChain(IMachiningChain chain)

Parameters

chain IMachiningChain

The machining chain whose axes and kinematics the runner should match. No-op when null.

EnumerateSnapshotSyntaxs()

Enumerates every SnapshotSyntax reachable from NcSyntaxList, including those nested inside any top-level BundleSyntax's inner list. Yields in pipeline-execution order: each top-level slot in turn, and within a bundle slot the bundle's SyntaxList order.

public IEnumerable<SnapshotSyntax> EnumerateSnapshotSyntaxs()

Returns

IEnumerable<SnapshotSyntax>

FromLegacyNcEnvXml(XElement)

Creates a SoftNcRunner brand preset from legacy HardNcEnv XML, picked by the CncBrand element. The returned runner carries proxy placeholders for both per-case data and the (Fanuc-family) parameter table, so machine config and per-case values are not populated here — the owning project wires the proxies to its host, then calls PopulateLegacyMachineConfig(XElement, List<INcDependency>), ConfigureByMachiningChain(IMachiningChain), and PopulateLegacyPerCaseData(XElement, List<INcDependency>) against the resolved host tables. Remove this region when HardNcEnv is fully replaced.

public static SoftNcRunner FromLegacyNcEnvXml(XElement ncEnvXml)

Parameters

ncEnvXml XElement

The inner XML element of the legacy HardNcEnv.

Returns

SoftNcRunner

An unconfigured brand-preset SoftNcRunner.

GetEffectiveNcDependencyList()

Resolves every INcDependencyProxy in PipelineNcDependencyList to the concrete dependency it stands in for (via GetNcDependency()), leaving every non-proxy entry in place. Order is preserved, so downstream OfType<T>().FirstOrDefault() lookups behave exactly as for a proxy-free runner; entries a proxy resolves to null are dropped.

Proxies must have their host wired (InitNcDependencyHost(INcDependencyListHost)) before this is called. RunNcLines(string, IEnumerable<string>, MachiningSession, StepDiagnosticProgress, NcDiagnosticProgress, CancellationToken) resolves the list once per session and caches it on NcRunnerSessionState, so each proxy maps to a single stable instance for the whole session and every pipeline stage sees the same dependencies.

public List<INcDependency> GetEffectiveNcDependencyList()

Returns

List<INcDependency>

GetSourcedActEntrysFromNode(LazyLinkedListNode<SyntaxPiece>, NcDiagnosticProgress, List<INcDependency>, CancellationToken)

Drives the NcSemanticList over the post-syntax SyntaxPiece stream starting at startNode, yielding a SourcedActEntry for each emitted IAct (or a single null-act entry when a semantic produces no acts but still updates the source SyntaxPiece). Every piece that stands for a block of a source file is bracketed by an ActLineBegin entry before its first semantic and an ActLineEnd entry after its last — see HasSourceLine(SyntaxPiece).

public IEnumerable<SourcedActEntry> GetSourcedActEntrysFromNode(LazyLinkedListNode<SyntaxPiece> startNode, NcDiagnosticProgress ncDiagnosticProgress, List<INcDependency> ncDependencyList, CancellationToken cancellationToken)

Parameters

startNode LazyLinkedListNode<SyntaxPiece>

First node to evaluate; iteration walks Next.

ncDiagnosticProgress NcDiagnosticProgress

Sink for semantic exceptions.

ncDependencyList List<INcDependency>

Proxy-resolved dependency list (see GetEffectiveNcDependencyList()) handed to each semantic's Resolve.

cancellationToken CancellationToken

Checked between blocks: after the block's last entry (its ActLineEnd) and before the next block is pulled from this layer, so the block in progress is finished and no further block of this layer is played. A look-ahead stage may already have built the following blocks in the layers below; RunNcLines(string, IEnumerable<string>, MachiningSession, StepDiagnosticProgress, NcDiagnosticProgress, CancellationToken) cuts them back when its run ends.

Returns

IEnumerable<SourcedActEntry>

Remarks

Walks to the end of the layer, across the files of every run played since startNode; a run's own enumeration (RunNcLines(string, IEnumerable<string>, MachiningSession, StepDiagnosticProgress, NcDiagnosticProgress, CancellationToken)) ends at its own last block instead.

MakeXmlSource(string, string, bool)

Creates an XML representation of the object. This method may also generate additional resources such as related files.

public XElement MakeXmlSource(string baseDirectory, string relFile, bool exhibitionOnly)

Parameters

baseDirectory string

The base directory for resolving relative paths

relFile string

The relative file path for the XML source

exhibitionOnly bool

if true, the extended file creation is suppressed.

Returns

XElement

An XML element representing the object's state

Remarks

For the demand of easy moving source folder (especially project folder) without configuration file path corruption, the relative file path is applied. The baseDirectory is typically the folder at the nearest configuration file folder. Since the folder can be moving with the configuration file.

PopulateLegacyMachineConfig(XElement, List<INcDependency>)

Populates legacy machine config — home position, tooling pose/time, rapid rates, and stroke limits — from a legacy HardNcEnv XML element onto targetDeps. Split out of FromLegacyNcEnvXml(XElement) so it runs against the proxy-resolved host list (GetEffectiveNcDependencyList()) after the parameter-table proxy has materialized its host table — the Fanuc-family parameter table (which implements IHomeMcConfig / IRapidFeedrateConfig / IStrokeLimitConfig) is no longer a concrete entry on the runner. No-op for configs with no matching provider.

public static void PopulateLegacyMachineConfig(XElement ncEnvXml, List<INcDependency> targetDeps)

Parameters

ncEnvXml XElement

The inner XML element of the legacy HardNcEnv.

targetDeps List<INcDependency>

The proxy-resolved dependency list holding the machine-config providers.

PopulateLegacyPerCaseData(XElement, List<INcDependency>)

Populates the legacy per-case tables — tool offsets, work coordinate offsets, and (Heidenhain) datum preset/shift tables — from a legacy HardNcEnv XML element into targetDeps. Split out of FromLegacyNcEnvXml(XElement) so the per-case values land on the owning project's PerCaseNcDependencyList (where its proxies have materialized the real tables), not on the shared runner. No-op when targetDeps has no matching table.

public static void PopulateLegacyPerCaseData(XElement ncEnvXml, List<INcDependency> targetDeps)

Parameters

ncEnvXml XElement

The inner XML element of the legacy HardNcEnv.

targetDeps List<INcDependency>

The host dependency list holding the materialized per-case tables.

PopulateLegacyStrokeLimitsWhereUnset(XElement, List<INcDependency>)

Fills the stroke limits a proxy-era project never wrote onto its parameter table from the legacy HardNcEnv boxes (StrokeLimitXyz_mm / StrokeLimitAbc_deg), one axis at a time and only where the table carries neither end. The boxes are what the machine-limits editor and the stroke-limit-* endpoints wrote until 2026-09-14, while every SoftNc reader — the per-block StrokeLimitCheckSemantic, the per-step check and the tilt solves' travel preference — reads the table: a project rebuilt through that API (CHEM20180926) declared its travel and had no check at all. Non-finite ends are skipped (unbounded). Explicit table rows always win. No-op without a table or a legacy element.

public static List<string> PopulateLegacyStrokeLimitsWhereUnset(XElement ncEnvXml, List<INcDependency> targetDeps)

Parameters

ncEnvXml XElement

The inner XML element of the legacy HardNcEnv.

targetDeps List<INcDependency>

The proxy-resolved dependency list holding the parameter table.

Returns

List<string>

The axes that were filled, in X…C order; empty when nothing changed.

Reg(XFactory)

Registers SoftNcRunner and chains Reg() on every pipeline component the runner may deserialize from XML — dependencies, initializers, segmenters, parsing/logic/evaluation/post-logic/inspection syntaxes, and semantics. Idempotent; safe to call from any number of boot paths.

public static void Reg(XFactory factory = null)

Parameters

factory XFactory

RunNcLines(string, IEnumerable<string>, MachiningSession, StepDiagnosticProgress, NcDiagnosticProgress, CancellationToken)

Runs raw NC program lines and yields source sentence and Act pairs.

public IEnumerable<SourcedActEntry> RunNcLines(string relFilePath, IEnumerable<string> lines, MachiningSession machiningSession, StepDiagnosticProgress stepDiagnosticProgress, NcDiagnosticProgress ncDiagnosticProgress, CancellationToken cancellationToken)

Parameters

relFilePath string

The relative path of the NC program file

lines IEnumerable<string>

The enumerable collection of NC program lines

machiningSession MachiningSession

Session-scoped state shared across multiple RunNcLines(string, IEnumerable<string>, MachiningSession, StepDiagnosticProgress, NcDiagnosticProgress, CancellationToken) calls (e.g. lazy-initialized pipeline state, file-index counter).

stepDiagnosticProgress StepDiagnosticProgress

Step-anchored IMessage-channel sink.

ncDiagnosticProgress NcDiagnosticProgress

NC-pipeline diagnostic sink, threaded in as an input so the runner stays host-agnostic (the caller injects it; the production caller passes the service-scoped instance, standalone tests pass their own).

cancellationToken CancellationToken

A cancelled token ends the enumeration between blocks: when the runner observes the cancellation, the block whose entries are being yielded is finished (its last entry is its ActLineEnd), no further block is read or played, and a run that follows in the same session plays none of the stopped program's remaining blocks or pending bodies: it starts from the stopped block's modal state, while the stopped program's call and loop stacks end with it – unless a hosted MachiningSession starts the run that follows a stop from HiNC's program-end reset state (modelled on a controller's RESET; ProgramEndSyntax lists what it cancels, keeps and where each controller differs) at the position the machine stopped at (RunNcLines(INcRunner, string, IEnumerable<string>, CancellationToken)): the SoftNc runner does so, the HardNc runner keeps the boundary above.

Returns

IEnumerable<SourcedActEntry>

Enumerable of source sentence and Act pairs

Remarks

Every pipeline stage and the semantic loop observe the cancellation token between blocks — after the block they handed up (the semantic loop: after the block's ActLineEnd) and before they pull the next — so the enumeration ends however deep an inlined subprogram or macro expansion stands. The token reaches the inlined sources through the pipeline's CancellationTokenDependency (assigned for the run and put back when it ends; a dependency list without one gets one for the session): a subprogram or macro body ends before its next piece and reads no further repetition, and a section repeat (SiemensRepeatSyntax, Heidenhain CALL LBL n REP m) ends between repetitions. A label scan (LabelScanUtil) is one pass over one file and runs to its end.

When the enumeration ends before the program's last block — cancelled, or disposed by its consumer (foreach and LINQ dispose; an enumerator taken by hand and dropped without Dispose releases nothing) — every pipeline layer's pending source is released: the stopped program's remainder, its pending inlined bodies and any queued repeat or jump slices are disposed, never built or played, and every layer ends at the block the run ended on (a layer below an IExpandingNcSyntax stage holds the pieces that stage expanded, not the stopped one, and is left as it is). A run that follows in the same session appends behind it and starts from that block's modal state, a G66 modal macro call included; the stopped program's call and loop stacks end with it: the first block of the next program takes no CallStack, WhileFrames or SiemensLoopFrames section from the stopped block -- unless a hosted session starts the run that follows a stop from HiNC's program-end reset state (modelled on a controller's RESET; ProgramEndSyntax lists what it cancels, keeps and where each controller differs), at the pose the machine stopped at (RunNcLines(INcRunner, string, IEnumerable<string>, CancellationToken)). Cancelled before the run starts, a fresh session yields the init seed's entries and nothing of the file, and a session that has played yields nothing.

A nested run -- a script's PlayNc inside this play, on the same session -- drains this run's remainder through the top layer and appends its file behind it. This run's enumeration ends at its own last block, where the next node starts the nested file (Hi.NcParsers.Syntaxs.SyntaxPiece.StartsRun), and plays none of the nested blocks. Completed, it leaves them on the chains, and the run that follows appends behind the nested file's last block. Stopped after the nested run -- on the block whose script started it or on one of its remaining blocks; cancelled, or disposed -- it cuts the drained remainder and, behind it, the nested run's played blocks off every layer, so the run that follows appends behind the stopped block and starts from its state. A nested run that ended short released its own chains and its played blocks stay, as a completed one's; one abandoned undisposed is released at the outer run's end, which cuts back to the outer's last block.

SetAllSnapshotSyntaxEnabled(bool)

Sets IsEnabled on every SnapshotSyntax reachable from NcSyntaxList (including those nested inside top-level BundleSyntax) to isEnabled. Callers that need finer control (per-section toggling, instance inspection) should iterate EnumerateSnapshotSyntaxs() directly.

public void SetAllSnapshotSyntaxEnabled(bool isEnabled)

Parameters

isEnabled bool