Table of Contents

Class NcOptPieceClassifier

Namespace
Hi.NcOpt
Assembly
HiMech.dll

Classifies the SoftNc final-layer SyntaxPiece stream into the INcOptPiecePack list and the per-step StepOptContext dictionary that the StepFeedSolver consumes: maps each piece to its MachiningStep index segment, runs the tool-change state machine, evaluates the opt-ability criteria and seeds preserved packs with their constraining feedrate.

public static class NcOptPieceClassifier
Inheritance
NcOptPieceClassifier
Inherited Members

Remarks

Ported from HiNc NcOptProc.CreateLinePackList + OnToolChanging (frozen HardNc baseline). The HardNc NcFlagBitArray reads are replaced by reads of the SoftNc JSON sections (ToolChange, ToolHeightCompensation, CannedCycle, MotionState/MotionEvent, CompoundMotion, RadiusCompensation, Feedrate), and the HardNc GetStepIndexSegment FileLineIndex scan is replaced by an identity map: a step names its source piece by SentenceIndex (the piece's SentenceIndex, unique per piece), the strip's order is the play order, and the packs come out in that order with CONTIGUOUS segments: every step index lands in exactly one piece's segment, so downstream step-keyed dictionaries built from the segments are hole-free over the whole step list. The sentence index is identity only, never order: a body called from an eagerly numbered slice (a label scan, a loop's second round) is numbered after the slice's remainder, and a nested run's file is numbered after the outer run's remainder it drained, so neither the layer's node order nor the strip's step order is monotonic in it (see CreatePiecePackList(IReadOnlyList<SyntaxPiece>, List<MachiningStep>, SortedList<int, NcOptOption>, MachiningToolHouse, bool, double, out Dictionary<int, StepOptContext>, NcDiagnosticProgress, IProgress<IMessage>, CancellationToken)).

Null-step tolerance mirrors the oracle exactly where the oracle has it: the HardNc step list is the raw unfiltered strip-pose projection and only the segment key selector is null-conditional (machiningStep?.SourceCommand?...) — a null entry behaves as a key-less (SentenceIndex == -1) step in the mapping. Everywhere else (opt-ability probes, distance walkers, the solver) steps are read directly, exactly as the oracle reads them; no extra null guards are added.

The preserved-pack seed precedence mirrors the HardNc chain exactly: blank/comment-only line → +Inf; else macro line (in-canned-cycle, G28 or G53.1, see NcFlagUtil.IsMacro) → the minimum RapidFeed_mmds over the seed option list (the OPTION rapid feed, not the machine rapid); else rapid (G00-modal) line → the machine rapid feedrate (the value HardNcLine.Feedrate_mmds reads on a rapid line, inherited from HardNcEnv); else the line's modal F.

P2 splition emission: an opt-able piece whose seed options enable interpolation (and which is not radius-compensating nor a multi-turn arc) is emitted as a SplitionPiecePack — its ctor seeds the per-step StepAdjustment list — mirroring the oracle's SplitionLinePack emission. A SoftNc-only guard downgrades such a piece to MonoPiecePack with one diagnostic per optimization, safer than the oracle's silent wrongness: an incremental piece — G91 modal, or any word stamped Incremental in the block-root PositioningOverride (Siemens IC(), klartext IX+20) (NcOpt--SplitionIncrementalUnsupported; the oracle has no G90/G91 handling and its fragment rewrite always emits absolute coordinates, and the fragment composer's tag patcher cannot even see the axis letter inside IX, so it would insert a second absolute word beside the surviving incremental one). A Dynamic (RTCP) ProgramToMcTransform chain is NOT a guard: the fragment engine rebuilds the Dynamic entries at each step's own ABC (GetComposedTransformAtAbc(JsonObject, vec3d_t, vec3d_t, IMachineKinematics, double)), keeping the per-step inverse valid at interpolated points — the SoftNc dual of the oracle's per-step XyzabcSolver.McToPn re-solve (HardNcLine.GetProgramXyz). The second guard is a Heidenhain klartext arc whose center comes from its own start point (NcOpt--SplitionStartPointCenterUnsupported): a C … DR± block with no CC ever given, or with a CC that left an in-plane axis unstated, fills the center from the current position (HeidenhainCircularMotionSyntax stamps StartPoint), so on a re-parse every fragment would fill it from ITS start and the split could not share one circle. The guard the code labels (d) — after (c), the plan builder's steps that are not the block's own, NcOpt--SplitionForeignStepsUnsupported — is a compound motion (NcOpt--SplitionCompoundMotionUnsupported): a block whose motion is a compound of its own items — a Heidenhain approach or departure function, whose words are a distance, an angle, a radius or an end point reached after an arc — has no single endpoint a fragment could re-emit. Before any of these, a Heidenhain APPR LT / LN / CT block is not opt-able at all, and neither is the positioning block before it whose feed its first segment runs at (the last block with a motion of its own, when that is a feed move): the segment to the auxiliary point runs at the feed in effect before the approach, the segment onto the contour at the approach's own F, and no single F word of either line carries both, so both lines are preserved with their source F. A preserved line restates its source F only when it can carry one (a simple feed-assignable line), so the walk back from the approach preserves every motion line up to the one that carries a literal F or can take it restated — a cycle call, an F-less departure or klartext arc standing between is preserved with it — and no optimized line re-emits an F before the approach; a rapid line first makes the segment rapid and preserves nothing. An LCT approach and a departure run at their own feed and are optimized as whole lines; under FMAX any of them is a rapid line, preserved as every rapid line is. Neither the brand nor klartext as such is a guard: a klartext linear L block splits like an ISO one (the fragment rewrite only touches endpoint/rotary/F words, all in the L vocabulary), and a klartext arc with a CC pole splits into fragments that keep the block's words around the unchanged CC line — the plan builder skips the I/J/K rebuild for a ModalCircleCenter arc. IsArc follows the oracle's MODAL semantics (NcFlagUtil.IsOnArcCommand, the G02/G03 Group-01 latch); the one-shot arc-data presence is carried separately as HasArcMotionEvent.

Scope notes still in effect. The Heidenhain L/LN veto of the HardNc IsRapid predicate (an L/LN line is never rapid, even under FMAX) is deliberately NOT reproduced: the SoftNc Logic bundle normalizes a klartext L block onto the shared ISO vocabulary and consumes the L marker, stamping G00 for FMAX and G01 otherwise (HeidenhainMotionModeSyntax), so no piece-level probe for "this was an L statement" survives — and porting the veto on top of that normalization would classify an FMAX rapid as a feed move, which is wrong in the SoftNc model rather than merely different. Reproducing it would require carrying the klartext statement kind into the sections; that is a deliberate open question, not an oversight. G95 feed-per-revolution is warned once per optimization (NcOpt--FeedPerRevNotSupported) while its raw value is still treated as mm/min.

Methods

CreatePiecePackList(IReadOnlyList<SyntaxPiece>, List<MachiningStep>, SortedList<int, NcOptOption>, MachiningToolHouse, bool, double, out Dictionary<int, StepOptContext>, NcDiagnosticProgress, IProgress<IMessage>, CancellationToken)

Creates the piece pack list with the step segments built, and prepares the StepOptContext seats of every step of the opt-able pieces (only those steps get an entry).

public static List<INcOptPiecePack> CreatePiecePackList(IReadOnlyList<SyntaxPiece> pieces, List<MachiningStep> machiningSteps, SortedList<int, NcOptOption> stepIndexToSeedOptionSortedList, MachiningToolHouse machiningToolHouse, bool isPhysicsComputable, double machineRapidFeedrate_mmds, out Dictionary<int, StepOptContext> stepIndexToStepOptContextDictionary, NcDiagnosticProgress conversionProgress, IProgress<IMessage> messageProgress, CancellationToken cancellationToken)

Parameters

pieces IReadOnlyList<SyntaxPiece>

Final-layer syntax pieces in the layer's order, already filtered of the init seed (pieces without a source file path). Every SentenceIndex must be unique (the identity the steps name their piece by); a repeated one aborts the classification (see returns). The order is not read as the play order: the layer holds a nested run's file behind the outer run's remainder, and the strip decides (see returns).

machiningSteps List<MachiningStep>

The raw strip-pose step projection, UNFILTERED (a strip pos without a machining step contributes a null entry — the oracle's unfiltered read), in strip order, which is the play order. A step's SentenceIndex names the piece it came from; it need not be monotonic (a body called from an eagerly numbered slice is numbered after the slice's remainder, a nested run's blocks after the outer's), and key-less -1 steps may be interspersed. Step indices used in the outputs are positions in this list, which equal StepIndex (the strip pos index) exactly because the list is unfiltered.

stepIndexToSeedOptionSortedList SortedList<int, NcOptOption>

Sorted list mapping step index to the seed NcOptOption effective from that step on (floor lookup semantics).

machiningToolHouse MachiningToolHouse

The machining tool house (int tool id keys).

isPhysicsComputable bool

Whether the physics data required by the solver exists (HardNc equivalent: millingPara != null).

machineRapidFeedrate_mmds double

The machine rapid traverse feedrate in mm/s (HardNc equivalent: HardNcEnv.RapidFeedrate_mmds), see GetMachineRapidFeedrate_mmds(MachiningSession). NaN when no rapid-feedrate config is reachable; rapid pieces then fall back per piece, see ResolveRapidFeedrate_mmds(double, List<MachiningStep>, IndexSegment).

stepIndexToStepOptContextDictionary Dictionary<int, StepOptContext>

Output: one StepOptContext per step of every opt-able piece, keyed by step index, with the ExtendedIndexSegment already fixed up to inclusive-begin / exclusive-end.

conversionProgress NcDiagnosticProgress

Optional diagnostic home for the piece-anchored messages: tool not found, the splition guard downgrades, the split-block warning and the duplicate-sentence-index error.

messageProgress IProgress<IMessage>

The message host for progress logging.

cancellationToken CancellationToken

Cancellation token to cancel the operation.

Returns

List<INcOptPiecePack>

One INcOptPiecePack per piece, in play order: a piece with steps where its first step is on the strip; a piece without steps where it began playing — the strip's count as its first act arrived (PlayStepIndex), before a piece whose first step is that count, and among such pieces in the order the blocks began (PlayOrdinal) — so a nested run's blocks sit where the script played them; a piece never played on a hosted session (no record) follows the piece before it in the layer. The segments are contiguous in that order and partition the step list: each runs from where the previous ended to the first step of the next piece that built steps (the strip's end when none follows), so the steps between that name no piece of the list — a key-less -1 step, a null entry, the init seed's, the blocks of a nested run a stop after it cut off the layers — and a split block's later steps go with the piece before them, the leading ones with the first piece, and a piece without steps has a zero-length segment where it sits. The tool-change state machine follows that order (a tool change inside a nested run's file is in force for the outer run's remainder); the G43/G44 edges are read per run as the play order tells the runs apart: a nested run that the outer's remainder follows in play order — a begin script's run, or an end script's on a block that is not the outer's last — has an id of its own: its blocks carry the modal state the pipeline gave them, the drained outer remainder's end (a program end's reset among it), so the outer's edge state waits while it plays and the outer's next block is compared with the outer's previous one, and its first block is an edge as the play's first block is; a nested run played after the outer's last block, and a top-level run played after another, have no id of their own and are read on from the block before them, as the writer carries the feed chain through them. A block whose steps another block's steps split keeps its first run of steps as its own; the rest is reported once per block as NcOpt-StepMap–BlockSplit on both channels (NC text under the shipped semantic order never plays such a shape: an embedded script runs before or after all of its block's acts; a mission script or a handler that plays a run between two acts of one block does). A splition candidate whose segment holds steps that are not its own is optimized as a whole line instead (NcOpt–SplitionForeignStepsUnsupported, once per optimization): a fragment is built per step of the segment from that step's machine coordinates, so a foreign step would end a fragment where another block's step stands. Null when canceled, or when two pieces share a SentenceIndex (a preset without the sentence-index counter replaying a subprogram): a NcOpt-Pieces–DuplicateSentenceIndex error is emitted on both the message channel and the diagnostic home and the optimization aborts gracefully (the orchestrator treats null as the cancel path).

GetEffectiveMachineRapidFeedrate_mmds(double)

The machine rapid feedrate to seed modal-F state with, in mm/s: machineRapidFeedrate_mmds when resolved (non-NaN), otherwise the 20000 mm/min sentinel — the shared default of both HardNcEnv.RapidFeedrate_mmds and IRapidFeedrateConfig. This is the SoftNc dual of the HardNc chain seed: HardNcEnv.RefNcLineOnInit seeds BOTH the rapid and the machining modal-F registers with the env rapid.

public static double GetEffectiveMachineRapidFeedrate_mmds(double machineRapidFeedrate_mmds)

Parameters

machineRapidFeedrate_mmds double

The machine rapid feedrate in mm/s; NaN when unresolved (see GetMachineRapidFeedrate_mmds(MachiningSession)).

Returns

double

GetLiteralArcTurnCount(string)

The literal L (turn count) word on an arc block's text, comment masked, or 0 when absent/non-numeric — the SoftNc stand-in for the oracle's ArcNcArg.L multi-turn splition veto (frozen NcOptProc): a value > 0 refuses re-interpolation.

public static double GetLiteralArcTurnCount(string blockText)

Parameters

blockText string

The arc block's text; null/empty reads 0.

Returns

double

Remarks

Deliberately NOT the parsed MotionEvent.AdditionalCircleNum. That field is derived, not literal: CircularMotionSyntaxUtil.IsClosedOnPlane makes it 1 for an L-less closed full circle (the renderer needs the extra turn) and a pitch helix can carry one too — arcs the oracle splits happily, because its veto reads the L word alone. Probing the source text keeps the two sides on the same criterion. Only reached under a G02/G03 modal, so a Heidenhain L motion word never lands here (and it is followed by a space, not a number, so it would read as absent anyway).

GetLiteralFeedrateTagKind(string)

public static NcTagValueKind GetLiteralFeedrateTagKind(string blockText)

Parameters

blockText string

Returns

NcTagValueKind

HasLiteralFeedrateTag(string)

Whether the block text literally carries an F word (outside comments), numeric or not — see GetLiteralFeedrateTagKind(string).

public static bool HasLiteralFeedrateTag(string blockText)

Parameters

blockText string

The block text to test.

Returns

bool

HasStartPointArcCenter(JsonObject)

Whether the piece's one-shot arc event took its own start point as the center — its MotionEvent carries CenterSource == StartPoint, the stamp HeidenhainCircularMotionSyntax writes on a klartext C block whose CC is missing or leaves an in-plane component unstated. This is the probe behind the NcOpt–SplitionStartPointCenterUnsupported guard (see class remarks); a missing event or stamp (block-word centers, the modal CC center) reads false.

public static bool HasStartPointArcCenter(JsonObject motionEvent)

Parameters

motionEvent JsonObject

The piece's MotionEvent section; may be null.

Returns

bool

IsBlankOrCommentOnly(string)

Whether the block text is blank or consists of comments only (Fanuc family comment shapes: head-% line and paren spans). Such a piece never constrains its neighbors, so its preserved seed feedrate is positive infinity.

public static bool IsBlankOrCommentOnly(string blockText)

Parameters

blockText string

Returns

bool

IsIncrementalPositioning(JsonObject)

Whether any coordinate word of the piece is incremental — its Positioning section (written on every NC block by PositioningSyntax, defaulting to absolute) carries Mode == Incremental, or its block-root PositioningOverride section stamps at least one word Incremental / CodedIncremental (Siemens IC() / CIC(), klartext IX+20; the coded value is listed for completeness — the write stage rewrites it to its plain counterpart before any piece reaches the optimizer). A missing section (e.g. a CL-sourced piece) counts as absolute. This is the probe behind the NcOpt–SplitionIncrementalUnsupported guard (see class remarks).

public static bool IsIncrementalPositioning(JsonObject json)

Parameters

json JsonObject

The piece's JSON object.

Returns

bool

IsMacroPiece(JsonObject)

public static bool IsMacroPiece(JsonObject json)

Parameters

json JsonObject

Returns

bool

IsSimpleFeedrateAssignable(string, IProgress<IMessage>)

Whether an F word can simply be inserted into (or restated on) the block: the block carries a G01/G02/G03 (or Heidenhain L/LN) token, or — excluding head-% blocks — it consists solely of axis words (X/Y/Z and A/B/C both present) with nothing else left after grabbing them.

public static bool IsSimpleFeedrateAssignable(string blockText, IProgress<IMessage> messageProgress)

Parameters

blockText string

The block text to test.

messageProgress IProgress<IMessage>

The message host for parse-error logging.

Returns

bool

Remarks

Ported from HiNc NcOptProc.IsSimpleFeedrateAssignableNcLine (frozen HardNc baseline) over BlockText, including the original's requirement that both an XYZ and an ABC word exist on the bare-coordinate branch.

ResolveRapidFeedrate_mmds(double, List<MachiningStep>, IndexSegment)

Resolves the effective rapid feedrate of a rapid (G00-modal) piece in mm/s: machineRapidFeedrate_mmds when resolved (non-NaN); otherwise the maximum played step feedrate over the piece's segment (the SoftNc player runs rapids at the configured per-axis rapid rates, so the effective step feedrate approximates the machine rapid); last resort the 20000 mm/min sentinel — the shared default of both HardNcEnv.RapidFeedrate_mmds and IRapidFeedrateConfig.

public static double ResolveRapidFeedrate_mmds(double machineRapidFeedrate_mmds, List<MachiningStep> machiningSteps, IndexSegment stepIndexSegment)

Parameters

machineRapidFeedrate_mmds double

The machine rapid feedrate in mm/s; NaN when unresolved.

machiningSteps List<MachiningStep>

All machining steps.

stepIndexSegment IndexSegment

The piece's step index segment.

Returns

double