Table of Contents

Brand NC language coverage

The SoftNc pipeline is the default NC engine, and this is where most of the release went.

Siemens SINUMERIK

Real .mpf / .spf programs replay end to end, not an ISO subset. At 3.1.175 a program that declared its tool as T="NAME", shifted with SUPA, computed with R-parameters, called L-subprograms or looped with WHILE was not interpreted past that point.

  • Modal vocabulary — SUPA / G153 suppress all frames for one block; T="NAME" string tool calls with D cutting-edge offsets resolved through SiemensToolOffsetTable ($TC_DP lengths and radius plus additive wear); G70 / G71 units; path smoothing (G60x / G64x, FNORM / SOFT / FFWON / COMP* / UPATH, CYCLE832); MSG() and STOPRE; CR= and TURN= arcs. Tail comments became quote-aware, so a ; inside MSG("A;B") no longer truncates the block, and the preset stops misreading L and G74 as Fanuc-family codes.
  • Evaluation — SiemensExpressionParser feeds the shared expression engine, so Z=R63+150 and X=SIN(R10)*20 drive motion. SiemensRParameterTable holds R0–R999 as per-case project data, DEF REAL/INT declarations lower into assignments, and $P_UIFR[n,axis,TR] binds both ways to SiemensFrameTable. Any other $-variable is recorded with an unsupported note rather than dropped.
  • Five axis — SiemensProgrammableFrameSyntax simulates TRANS / ATRANS / ROT / AROT (with RPL=) and the solid-angle forms ROTS / AROTS into the tilt-transform chain in Sinumerik RPY order (ROTS / AROTS behave as ROT / AROT: at most two angles, first-named axis first); SiemensTraoriSyntax makes TRAORI a real RTCP mode, the sibling of ISO G43.4, with TRAFOOF handing the offset back; SiemensCycle800TiltSyntax decodes CYCLE800's MODE bits for all four swivel modes. SCALE / MIRROR / CROTS are recognized and reported (SiemensFrame--Unsupported), not simulated.
  • Calls — L-prefixed and named subprogram calls resolve against SubProgramFolderConfig ({name}.SPF, then .MPF, then the bare name) and inline with their P repetition count; M17 / RET pop a frame; REPEAT re-runs a labelled slice; MCALL CYCLE81/82/83/85 maps onto the shared canned-cycle machinery; PROC headers and labels are claimed whole.
  • Control flow — GOTOF / GOTOB, IF / ELSE / ENDIF, and WHILE / FOR / REPEAT-UNTIL / LOOP. Runaway programs are bounded rather than hanging the session: SiemensGotoIterationDependency caps jumps per (file, label) and SiemensLoopIterationDependency caps iterations per (file, loop-entry line); over the cap the construct warns and falls through.
  • Per-word coordinate functions — AC() / IC() / DC() / ACP() / ACN(), including on I / J / K circle centres. G90 C=IC(360/17) is one incremental index inside an absolute program. Direction resolution lives in McAbcCyclicPathSyntax: ACP() takes the [anchor, anchor+360) window, ACN() the (anchor-360, anchor] window, DC() the shortest swing, with the exact 180° tie going negative. On a machine-coordinate block the increment is a distance in the machine frame, because MachineCoordSelectSyntax is ordered ahead of IncrementalResolveSyntax in all five brand presets — Fanuc, Mazak, Syntec, Siemens and Heidenhain — so G53 X=IC(10) and SUPA Y=IC(-10) add the raw word to the previous machine position rather than being re-based through the program frame first. That order is not something a saved-file patch can express: a project saved before 3.2.21 keeps the old order for that block shape until its pipeline is re-created from the brand preset.
  • Coded positions — CAC / CIC / CDC / CACP / CACN take a 1-based indexing position number rather than a coordinate, resolved against IIndexingPositionConfig, implemented by SiemensMachineDataTable from the real machine data (the MD30500 axis assignment, the MD10910 / MD10930 position tables, the equidistant MD30501–30503 definition).
  • G74 / G75 fixed-point return is claimed as a whole block, so its dummy axis values no longer mint a rapid to the coordinates written in the block and its F never reaches the modal feedrate.
  • OEM auxiliary M-codes — the preset declares M12 / M13 / M22 / M23 and M330 / M331 note-only, so each occurrence voices DeclaredMCode--UnmodeledEffects instead of an unknown-code warning, without inventing simulated effects. A machine's own table overrides a declaration when the real effects are known.

Heidenhain

Both dialects play on one preset, HeidenhainNcRunner.

  • Klartext motion and setup — the L statement and its axis words, FMAX, M91 as a one-shot machine-coordinate move, TOOL CALL wired to tool change and spindle speed with the table height and DL. Datum handling follows TNC semantics: CYCL DEF 247 sets the preset and CYCL DEF 7 is an additive shift on top of it, composing as separate transform-chain entries instead of replacing each other, resolved against HeidenhainDatumTable. Arcs (CC pole plus C statement, DR- = CW, closed arc = full circle), RL / RR / R0 radius compensation, the M126 / M127 rotary-wrap state, M140 MB retract, and CYCL DEF 32 TOLERANCE. HeidenhainLnOrientationSyntax resolves an LN vector block into rotary axes through the same RTCP machinery the rotary-word programs use: T present plus M128 / FUNCTION TCPM keeps the tool on T; T absent plus RTCP holds the tool on the surface normal N; RTCP inactive ignores T (Orientation-Vector--IgnoredNoTcpm), as the control does. A C block never states its own centre: each in-plane component comes from the CC block's own axis word, else from the previous CC section's same axis, else from the arc's own start point. A bare CC is the one spelling that states all of them at once — it takes the last programmed position, read at the CC block, and replaces the modal centre rather than inheriting it. A centre that lands on the arc's own start point leaves the block with no radius, so it warns Arc-CircleCenter--OnStartPoint and is degraded to a linear move to the endpoint.
  • Klartext incremental axis words — the I-prefixed word (IX+20, IY-15, rotary IC+90) is the same axis word as its absolute twin, read as a distance from the last programmed position. It is per word rather than per block: L X+60 IY-10 mixes an absolute X with an incremental Y and stamps only the Y. One shared grab serves five statement heads — L, C, CC, LN and CYCL CALL POS — each writing the value under the plain axis key of its own section plus a "PositioningOverride": { "Y": "Incremental" } entry on the block root. That is the same per-word section the Siemens IC() wrapper writes, so the existing resolve consumers convert it and no second incremental mechanism exists. Coverage differs by head: L, C and LN take the whole axis tag list, rotary included, while CC and CYCL CALL POS take X / Y / Z only — a CC never positions a rotary axis, and the stamp is keyed by axis on the block root, where a rotary letter would read as a rotary word of the block. The sign is optional (L IX5), a Q reference resolves behind the prefix (L IZ+Q2), and the glued post-processed run LIX+20IY-15 parses. Before these words were read the plain grab could not see the X inside IX — its prefix guard demands a word boundary, a digit or whitespace before the tag, and I is a word character — so L X+60 IY-10 took the X and dropped the Y: a motion to the wrong place, not a missing one.
  • What an incremental word is measured from depends on the statement. An L / C / LN endpoint and a CC centre are distances from the last programmed tool position, so CC IX+0 IY+11 is a stated centre resolved against that position — never against the previous centre — and it is no longer the spelling whose coordinates the parser could not read. CYCL CALL POS has its own reference: an IX / IY / IZ word there is a distance from the coordinates the previous CYCL CALL POS programmed, which is the control's own rule behind its error 1A0-0108. An axis that no earlier call programmed has no reference: the control refuses the call, and the simulation warns HeidenhainCyclCall--IncrementalNoReference and falls back to the last programmed position. A tool move between the two calls makes the two candidate references differ, and that divergence warns HeidenhainCyclCall--IncrementalAfterMove, the control's stated reference winning. With M91 on the block the increment is a machine-frame distance, per the ordering note under Siemens above. A rotary increment is a signed traverse the wrap pass keeps verbatim — IC+270 from 75° stays 345° instead of folding into the ±180° shortest-path window, while an absolute rotary word on the same block still folds. The CYCL DEF 7 datum-shift I words are a different mechanism: CYCL DEF 7.2 IY+5 shifts by the value on top of the shift last valid, stays under prefixed keys in the cycle's own record, and writes no per-word stamp at all.
  • Q-parameters — HeidenhainExpressionParser lexes Q / QR / QL / QS, the DIV keyword of FN 4 and the prefix SQRT of FN 5, so FQ1 reaches the feedrate, L X+Q2 reaches the program XYZ and TOOL CALL SQ3 reaches the spindle speed. HeidenhainQParameterTable holds Q0–Q99 free and QR0–QR499 permanent parameters as per-case project data. Unimplemented opcodes (FN 14 / 16 / 18…) are claimed and reported rather than half-read, so an FN 18 SYSREAD target stays vacant instead of taking a fabricated value. FN 9–12 conditional jumps execute, with a (file, label)-keyed iteration cap.
  • Tilt and RTCP — PLANE (SPATIAL fully composed with SEQ / COORD ROT / TABLE ROT and STAY / MOVE / TURN positioning; VECTOR structurally captured; EULER / POINTS / RELATIV / AXIAL / PROJECTED consumed and warned with the previous tilt retained, so a PLANE AXIAL B+45 B word can never be mistaken for a rotary axis command), FUNCTION TCPM, and real M128 / M129 tool-centre-point control. A centre-referenced FUNCTION TCPM REFPNT (TIP-CENTER / CENTER-CENTER) is recorded and reported (Orientation-RefPoint--CntNotSimulated) rather than silently read as TIP-TIP.
  • Cycles and calls — CYCL DEF 2xx bodies with their Q parameters mirrored into the block assignments, cycles 200 / 232 / 251 / 252 / 253 mapped onto the shared G81 / G82 / G83 slots, CYCL CALL / CYCL CALL POS / M99 / M89 splitting call-once from modal firing, CALL LBL inlining up to LBL 0, CALL LBL n REP m as a section repeat, and CALL PGM resolved by file name. The multi-line tilde continuation form is joined at segmentation (JoinTildeContinuations, on by default).
  • Post-processed spellings — some post-processors write a whole klartext body with no separators at all, which used to yield zero motion. Glued line shapes (LX-26.3Y+43.1, FMAXM03M08, …R0FMAX) and the detached feed spelling (F 20000) now parse, with M140, M128 and PLANE MOVE widening their own F capture so a retract or feed-limit value cannot leak into the modal feedrate. BLK FORM is recorded as a brand-neutral stock declaration without replacing the project workpiece setup, and the klartext STOP word joins M00 / M01.
  • DIN/ISO dialect — % tape header, N block numbers, T + M06, absolute I / J / K arc centres with the modal pole carried forward, the ISO label family (G98 L<n> definitions and the head-anchored L<n>,<m> call mapping the comma count onto REP), G247 Q339 stamping the same datum preset as CYCL DEF 247, G54 with axis words read as a datum-shift declaration, and G70 / G71.

Fanuc and ISO common

  • Polar coordinate interpolation — G12.1 / G13.1 on the SoftNc pipeline. Before this a polar section parsed silently wrong: the X word (a diameter) and the C word (a hypothetical Cartesian axis in mm) were consumed as ordinary XYZ and rotary degrees. ProgramRxczSyntax halves X from diameter, resolves G90/G91, writes the polar and derived Cartesian positions and the machine C angle, and classifies motion into polar linear and polar arc — the latter emitting ActMcPolarSpiralContour, which keeps spiral geometry in central polar coordinates and stays continuous across ±180°. G41 / G42 compensation is resolved on the hypothetical plane, a C-axis speed clamp applies, and YA / ZB axis pairs are supported. PolarGCodeCheckSyntax scans for incompatible G codes before the mode syntaxes consume them.
  • Custom Macro B — #var assignment with range-routed stores (#1–#33 local per macro frame, #100–#499 volatile cleared on M02/M30, #500–#999 retained and persisted in the project, #3000–#3999 system-control), boolean and logical operators, IF[..]GOTO n, IF[..]THEN <stmt>, WHILE[..]DO m / END m with a bounded-loop watchdog, and position and tool-offset system variables. M98 P_ L_, M198 external call, M99 return and M99 P{seq} early return; G65 one-shot macro call with A–Z → #1–#26 argument binding, and G66 / G67 modal macro.
  • Additional work coordinate systems — G54.1 Pn, also written G54 Pn, resolves against the extended table (P1–P48). A selected row nobody has entered reports Coord-WorkOffset--AdditionalZero. G59.1–G59.9 have a provider on the Fanuc, Mazak and Syntec presets. See Work Coordinates and ISO core.
  • Fanuc / Syntec unit codes and G05. G71 is the metric spelling of G21; G70 is the inch spelling of G20 and reports Unit--InchNotSupported rather than silently switching units. G05.1 Q1 / Q0 is AICC / Nano Smoothing (recognized). Bare G05 P{n} is a different feature (HPCC) and is consumed without changing programmed coordinates, reported under the Hpcc--* ids.
  • Canned-cycle repeats take rotary words on SoftNc. The G73–G89 family expands XYZ / R / Q / F / P / K strokes, and a repeat block carrying an A / B / C word indexes the table and drills again, the way a control does: a rotary-only repeat fires the cycle, a mixed block pre-positions XYZ and the table in one simultaneous move before the strokes, and under G68.2 without RTCP the feature frame stays pinned in machine space so the hole stays put while the workpiece turns. Under G91 only the words the block itself spoke are increments. The hold that 3.2.24 placed on these programs is lifted; EnableSoftNcRunner = false is no longer needed for them.
  • A rotary word under modal G91 is an increment on SoftNc. A bare A / B / C word follows the block's modal positioning onto the same anchor + delta path as Siemens IC() and klartext IC+: G91 … C20. then C20. leaves the table at 40. A per-word directive (C=AC(90), ACP / ACN, IC()) still beats the modal state, and a spoken G91 B270. stays a +270° traverse instead of folding to −90°. A G91 G28 rotary intermediate anchors on the previous modal angle, then returns home. HardNc keeps its absolute reading of a rotary word under G91 and warns. The programmed deltas are recorded in IncrementalWords, beside the RotaryWords the block spoke.

Cross-brand

  • A shared DwellSyntax consumes G4 and G04 with the dialect held on the instance: Fanuc-family X / U seconds, P milliseconds, S spindle revolutions; the Siemens instance reads F seconds and S revolutions. Capturing the G04 spelling fixes a real defect — the un-captured spelling fell through to the flag and axis syntaxes, where a Fanuc G04 X0.5 dwell time became a ghost X motion word.
  • Machine-declared M-codes — IMCodeDeclarationConfig and MCodeEffects let a machine state what its own OEM codes do (composite spindle+coolant codes, a tool-change trigger, turret T-word semantics), and MCodeExpansionSyntax expands a declared code into the canonical ISO flags the shared consumers already understand.
  • Custom spindle M-codes drive the spindle direction through a machine-level ISpindleControlConfig; ISO M03 / M04 / M05 remain the built-in fallback. An S greater than zero with no direction ever issued assumes clockwise and emits SpindleDirection--AssumedCw, so the physics gate no longer silently produces zero mechanics for a whole file.
  • Tool changes synthesize their axis travel. ToolChangeMotionSyntax overlays the per-axis tooling position from IToolingMcConfig onto the current pose (a NaN or missing axis stays put) and stamps a one-item rapid compound motion, and ToolChangeSemantic moves behind CompoundMotionSemantic so the tooling step lands at the tooling point. Programs that retract on their own overlay to a zero-length move and emit nothing extra.
  • Per-brand pivot gates. PivotTransformationSyntax reverts to the ISO/Fanuc vocabulary (G43.4 plus the G68.x family), SiemensPivotTransformationSyntax gates TRAORI / CYCLE800, and HeidenhainPivotTransformationSyntax gates the M128 / PLANE vocabulary. All three compose the identical entry through the shared PivotTransformUtil. Exactly one brand gate belongs in a pipeline list — never register two.
  • Controller presets are writable and shipped. A controller resource file is one serialized SoftNcRunner — the whole pipeline that decides how a brand's NC code is interpreted. ControllerPresetWriter serializes the built-in brand presets (CreateBrandPreset, WriteBrandPresetFile, WriteAllBrandPresetFiles) under Resource/Controller/ with the .Controller extension, and HiNc-Resource ships all five so the load browser starts populated. The static brand properties remain the source of truth; the files are regenerable snapshots. Writing needs no XFactory registration — reading one back does, because the loader drops unregistered pipeline entries silently rather than failing the load.
  • Saved pipelines back-fill their system-wired dependencies on load. A project saved before a system-wired dependency existed never self-healed by round-tripping, because re-saving stamped a fresh API version on the same incomplete list. The SoftNcRunner XML constructor now appends the missing ones after the legacy version patches. A runner rehydrated from an older file still keeps the syntax list it was saved with, though — take the regenerated preset, or a fresh NcRunnerSuit built from it, rather than expecting an old file to grow new syntaxes.
  • Machine-coordinate and tilted-plane failures report. G53 and G53.1 record their source G-code on the parsed block and emit Coord-MachCoord--005 / --006 / --007 on paths that used to fail silently. A G68.2 tilted plane the machine cannot reach emits Coord-Tilt--001 / --002, with a tool-axis-only IK retry that avoids a spurious warning on a machine with fewer than three rotary axes.
  • Session-global sentence indexing. SyntaxPiece.SentenceIndex used to be assigned by two independent sequences, so indices collided as soon as a call was inlined mid-stream. The new SentenceIndexCounterDependency supplies every index at one chokepoint, so values are session-globally unique and strictly increasing in execution order, including nested and repeated calls.
  • Inline plays can loop and jump, and playing a file no longer holds it open. Inline NC-code plays stamp their pieces with the command title as a pseudo-path, and every control-flow re-segmentation re-read the host file by that path — the existence check always failed, so loops fell through without looping. RunNc now registers the raw lines on NcLineSourceDependency and LabelScanUtil reads memory first, disk second.
  • M02 / M30 / END PGM is a reset edge. SoftNc models program-end as the edge between that block and the next, so a concatenated multi-program file does not keep TCP, tilt, CRC or a canned cycle alive into the next program. Each modal owner writes an explicit cancel section on the successor (G49 / G69 / G40 / G80); the end-of-program block itself still runs under the modal state it executed with. HardNc still carries G43.4 across M02 — see Results that change on upgrade.