Class NcOptPieceClassifier
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
piecesIReadOnlyList<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).
machiningStepsList<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
-1steps 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.stepIndexToSeedOptionSortedListSortedList<int, NcOptOption>Sorted list mapping step index to the seed NcOptOption effective from that step on (floor lookup semantics).
machiningToolHouseMachiningToolHouseThe machining tool house (int tool id keys).
isPhysicsComputableboolWhether the physics data required by the solver exists (HardNc equivalent:
millingPara != null).machineRapidFeedrate_mmdsdoubleThe 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).stepIndexToStepOptContextDictionaryDictionary<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.
conversionProgressNcDiagnosticProgressOptional diagnostic home for the piece-anchored messages: tool not found, the splition guard downgrades, the split-block warning and the duplicate-sentence-index error.
messageProgressIProgress<IMessage>The message host for progress logging.
cancellationTokenCancellationTokenCancellation 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
-1step, 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 asNcOpt-StepMap–BlockSpliton 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): aNcOpt-Pieces–DuplicateSentenceIndexerror 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_mmdsdoubleThe machine rapid feedrate in mm/s; NaN when unresolved (see GetMachineRapidFeedrate_mmds(MachiningSession)).
Returns
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
blockTextstringThe arc block's text; null/empty reads 0.
Returns
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
blockTextstring
Returns
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
blockTextstringThe block text to test.
Returns
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
motionEventJsonObjectThe piece's
MotionEventsection; may be null.
Returns
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
blockTextstring
Returns
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
jsonJsonObjectThe piece's JSON object.
Returns
IsMacroPiece(JsonObject)
public static bool IsMacroPiece(JsonObject json)
Parameters
jsonJsonObject
Returns
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
blockTextstringThe block text to test.
messageProgressIProgress<IMessage>The message host for parse-error logging.
Returns
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_mmdsdoubleThe machine rapid feedrate in mm/s; NaN when unresolved.
machiningStepsList<MachiningStep>All machining steps.
stepIndexSegmentIndexSegmentThe piece's step index segment.