Upgrading from 3.1.175 to 3.2
This page is the long form of the 3.2 release-note entry. It exists because 3.2 is not an increment on the last release most callers hold — it is the accumulation of everything that landed after the 3.1.175 package set, delivered in one step.
Read it in order the first time. The two sections that decide whether an upgrade is a recompile or an afternoon are Breaking changes and Results that change on upgrade; everything after them is new capability you can adopt when you need it.
Important
Read the Adoption status note on the release-note page before planning around this.
The short version: 3.2 is published for review, verification is still in progress across most
areas, and NC optimization is not finished on this line — work that depends on it should stay
on 3.1, serviced as 3.1.175.<patch>.
The package line
master develops the 3.2 package line as of 2026-08-19. All ten packages — HiGeom, HiLicense,
HiDisp, Hi.WinForm, Hi.WpfPlus, HiCbtr, HiMech, HiUniNc, HiNc, HiNc-Resource — moved to 3.2
together and restarted their build counters.
Three consequences worth stating plainly:
- A 3.2 build number starts low.
3.2.6is newer than3.1.204; the two counters are not comparable, and the gap between the last 3.1 number a feed served and the first 3.2 number is expected rather than a missing upload. - The 3.1 line is closed at the 3.1.175 set and is serviced only as
3.1.175.<patch>. A reference left on 3.1.175.x receives correctness fixes for that line and none of the capability on this page. - The versions between — 3.1.176 through 3.1.204 — were never published as a release set. That is why the release note carries one 3.2 entry where it might have carried a dozen: for a caller moving off 3.1.175, they were never separate releases.
Breaking changes
In the order they will bite an upgrading host.
1. Registration, before anything else
XFactory.Generators changes from a plain Dictionary to a ConcurrentDictionary, so parallel
Reg() calls no longer corrupt the registration map. The property is public, so a caller that
declares its type explicitly stops compiling.
Carried over from 3.1.172 and still the first thing an upgrading host hits: Reg must be called once at startup, before any project XML is deserialized. Registration no longer happens by accident when a type is first touched. See XML IO.
2. The message channel
Message reporting is rebuilt on a unified model. Every notification carries a Severity, a Category and a filterable id (SimpleMessage), and arrives on one of three typed sinks: ShellProgress for session-lifecycle messages, StepDiagnosticProgress for step-anchored diagnostics, and NcDiagnosticProgress for NC-parsing diagnostics.
MixedProgress0, MultiTagMessage and MultiTagMessageUtil are removed. Every message parameter
across the API — the XFactory deserialization chain included — is retyped
from IProgress<object> to IProgress<IMessage>. Category.General is deleted,
MessageUtil becomes id-first {Category}{Severity}, and
NcDiagnostic.Text renames to Notification. See
Message Management.
3. The session surface
LocalProjectService.SessionShell is created by BeginSession() and nulled at EndSession() — it
is null outside a session and no longer lazily created. ShellProgress
is recreated per session, so hold no long-lived reference to either: subscribe once through
OnShellMessageAdded /
OnShellMessageCleared, or buffer one call's messages
with MessageCollector.
MachiningSession takes an injected IMachiningService
host in its constructor, and IMachiningService replaces PlayerCancellationToken / PausePlayer
with a single PacePlayer property.
4. The play verbs
Nc becomes the umbrella term for any playable control program, and BrandNc names the
famous-brand controller-code group as a sibling of Cl and Csv.
| Was | Is now |
|---|---|
PlayNcFile(file) — brand G-code only |
PlayNcFile(file, NcKind kind = NcKind.Auto) |
RunNcFile(file) |
RunNcFile(file, NcKind kind = NcKind.Auto) |
| the narrow brand-only file verbs | PlayBrandNcFile / RunBrandNcFile |
IControlRunner |
Hi.Numerical.INcRunner |
RunControlLines |
RunNcLines |
ControlKind |
NcKind |
IsRunningControlLines / BeginControlRunner |
IsRunningNcLines / BeginNcRunner |
the interim PlayControlFile / RunControlFile mirrors |
removed |
The same rename applies on LocalProjectService, SessionShellController and MachiningSession.
Only .cl / .cls / .clsf / .csv arguments change meaning —
DetectByPath treats those as closed extension sets and
everything else falls back to brand G-code, so an exotic brand extension can never be misrouted.
5. Renames with no shim
| Was | Is now |
|---|---|
WorkpieceService.GetRuntimeGeom / ReadRuntimeGeom / WriteRuntimeGeom / SetRuntimeGeom / ResetRuntimeGeom / IsRuntimeGeomInit / ScanRuntimeGeomInfDefect |
GetOrBuildMeshedGeom / ReadMeshedGeom / WriteMeshedGeom / SetMeshedGeom / ResetMeshedGeom / IsMeshedGeomInit / ScanMeshedGeomInfDefect |
MachiningEquipmentCollisionIndex.WorkpieceRuntimeGeomGetter |
WorkpieceMeshedGeomGetter |
IContourTray / UniformContourTray / FreeContourTray |
IFluting / UniformFluting / FreeFluting |
MillingCutter.FluteContourTray |
Fluting |
Hi.Common.ResourceUtil (the HiNc one) |
ResourceLayout |
NativeTopoStld / NativeTopoStlfr / NativeCarveTopoStl3wfr |
NativeTopoStl3d / NativeTopoStl3wfr / CarveStl |
Solid.NativeSmoothTopoStl / Sweptable.NativeTopoStl |
SmoothTopoStl3d / NativeTopoStl3d |
ITimeGetter and its Time member |
Hi.Physics.ITimecoded and Timecode |
ClStrip.DrawingRefreshing |
ClStrip.DrawingRefreshed |
CbtrPickable.CleanLinked* |
CbtrPickable.CleanAttached* |
SoftNcRunner.NcDependencyList |
PipelineNcDependencyList |
Four notes on that table.
The SessionShell script names for meshed geometry keep hidden [Obsolete] aliases so existing
player scripts still run; the service-level members do not. WorkpieceService.ResetRuntimeGeom also
drops its ClStrip parameter.
ClStrip.DrawingRefreshed was renamed because both invocations always fired after the work — the
-ing name told subscribers the opposite of when they are called. A subscriber that misses the
rename silently detaches.
Project and cutter files written before the fluting rename keep loading: each Reg() registers the
ContourTray-era XName beside the older aliases, and the cutter element reader tries Fluting, then
FluteContourTray, then FluteContourTrackTray. A cutter that nevertheless fails to resolve its
fluting machines as a plain bounding shape rather than failing loudly, so verify the load rather than
assuming it.
SoftNcRunner.PipelineNcDependencyList is the raw list; machine-config consumers read the resolved
view through GetEffectiveNcDependencyList. Legacy
<NcDependencyList> XML still loads and migrates.
6. Removals
- CSV —
CsvRunner0,LocalProjectService.EnableSoftCsvRunner, and the earlierRawCsvRunnerandCsvRowSemantic. CSV playback has one path, GeneralCsvRunner, and CsvRunner returns the CSV suit's SoftNcRunner directly. - Carriers —
IndexedSentence(wrap a bareSentencein your own ISentenceCarrier if you passed one as asourceCommand),SimpleSessionCommand, HiCbtr's[Obsolete] LsStl. The packedMixedIndexfile-line key is replaced by typed FileLineIndex comparison, so file and line positions compare by type rather than through a packed integer. - GUI-layer composition —
MachiningProjectDisplayee,IsoCoordinateEntryDisplayee,HeidenhainCoordinateEntryDisplayee,UserConfig,UserService,PlayerDivConfig. Construct LocalProjectService with theILogger-only constructor and copy the displayees from any app project —Hi.Sample.Wpf/Disp/ships them. They compose only public API (IDisplayee overLocalProjectService), so tailoring them is the point. - Managed physics kernel types — the class
FluteZData,MillingForceUtil.RuntimePack/LayerPack/AnglePack, theLayerMillingEngagementconstructor that built an engagement from a z-to-dz list (the default andBinaryReaderconstructors stay), andMillingPhysicsBrief.YieldStressMinHeight_mm. - Culture declaration —
CultureUtil.SupportedCultureNamesandCultureUtil.SetCurrentCulture(string), deleted outright with no[Obsolete]shim. What remains is English and SetCurrentCultureEn. A host that enumerated supported cultures must enumerate its own manual or resource folders instead. - Dead P/Invoke declarations — eight
gl*methods on HiDisp's publicGLclass (glFenceSync,glGetDoublev,glGetDoublei_v,glGetDoubleIndexedvEXT,glIglooInterfaceSGIX,glPNTrianglesfATI,glPNTrianglesiATI, and the already-commentedglDebugMessageCallbackAMD). They had no backing export and threwEntryPointNotFoundExceptionwhen called. NcFileListCommand— a list of NC files is just a List of Program File commands, so the dedicated type is gone. Loading a project that contains one converts it in place: each<File>entry becomes a single-file NcFileCommand (kindAuto, the same per-file extension dispatch) inside a ListCommand, and re-saving persists the converted form.- Post-Execution meshed-geometry output —
PostExecutionCommandlosesEnableWriteMeshedGeomandMeshedGeomFileTemplate(and their pre-rename…RuntimeGeom…spellings) together with theenable-write-meshed-geomandmeshed-geom-file-pathroutes. A geometry snapshot can be taken at any time-spot, unlike the run-derived outputs that command manages, so the carriers are now the placeable RecordMeshedGeomCommand and ExportMeshedGeomToStlCommand. Loading an old project with the pair enabled emitsPostExecution--MeshedGeomOutputRetired, and re-saving drops the elements.
7. Signature and shape changes
- MachiningToolHouse derives from
Dictionary<int, IMachiningTool>: SetToolId takes anintand CreateStickMillingTool returnsKeyValuePair<int, MillingTool>. Any(int)entry.Keycast stops compiling. SiemensT="name"string tool calls are unaffected — they still resolve to anintat the semantic layer. - ActualTimecode and
ActualDateTime become get-only views onto the new optional
ActualTime (StepActualTime) — their
setters are gone.
AccumulatedTimeis superseded by EndTimecode, kept as an[Obsolete]alias; step CSVs write the new header and still read the old. - PreSettingCommand becomes a legacy bundle. A saved bundle expands on load
into MachiningResolutionCommand,
MachiningMotionResolutionCommand,
CollisionDetectionCommand,
PauseOnFailureCommand and PhysicsCommand (plus a
Read-mode
RecordMeshedGeomCommand), and is never written back. Anything that located the bundle element in a saved.hincprojmust look for the split commands. ToPresentDtowire keys change with the obfuscation fix: geometry DTOs useType/Min/Max/PairZrs/Z/R/SourceFile/FileIndex/LineIndex(Vec3dkeeps lowercasex/y/z), transformer DTOs useTrans,Angle_deg,CosTheta/SinTheta,Axis,Pivot,Scale,Rotation,Translation,Step,Stack,Matrix. A front-end reading those payloads must be updated in lockstep.- defaultFontFile changes value from
"Font/WCL06.ttf"to"(embedded)". It is a publicconst, so an assembly compiled against 3.1.175 already carries the old literal and keeps passing it —Initstill accepts it — but a rebuild changes what it passes, and no font file is extracted to the working directory any more.
8. Defaults and gates that changed
- EnableSoftNcRunner defaults to
true. The SoftNc pipeline is the NC engine; HardNcRunner is the opt-out fallback for the shrinking set of features still bound to it. - EnableNativeMillingPhysics defaults to
true, and in a shipping build setting it tofalsethrowsInvalidOperationExceptionat the setter — the managed reference implementation lives only in a non-shipping assembly. Code that flipped it off for an A/B comparison now fails at configuration time. - YieldingStressRatio and
YieldingStressRatio report
NaNinstead of0when no beam section qualifies. A caller treating0as “no yielding constraint” — as both feed solvers did — must add aNaNbranch or it will passNaNinto downstream queries. - New licence feature
NcComposition(id 22). Registering any non-built-in processing unit into a SoftNcRunner pipeline, or executing an NC-embedded C# script, requires it. Degradation is silent and functional: external units are skipped for the session with oneComposition--NotLicensednaming them, an external segmenter falls back toSingleLineSegmenter, and scripts are skipped withScript--NotLicensed. An unlicensed installation therefore produces a different simulation, not an error. Calling the public API from your own application or session script needs no extra licence; composing the interpretation pipeline does. See NC Parsing Engine. - The four
SnapshotSyntaxentries in the Fanuc preset default toIsEnabled = false, so projects stop serializing enabled debug snapshots. A project saved by an earlier build keeps what it serialized until its pipeline list is refreshed from the current preset. - Server side: HiNcServer pins request localization to English, so an
Accept-Language: zh-Hantrequest falls back toen. HiNcRcl removes theHiNC:DisplayEngine:FontFileconfiguration key, which never had any effect — delete it fromappsettings. FontFile remains for a custom font. - Localization: HiMech's
MachiningStep.zh-Hant/.zh-Hansresx are deleted. Step presentation strings now come from the HiNc-Resource present catalog (catalog.en.json,catalog.zh-Hant.json,catalog.zh-Hans.json) that a host overlays; a host that ships neither loses the localized step labels it used to get for free. - Packaging is x64-only: HiDisp drops the
win-x86runtime identifier and its Sentinel payload, HiNc-Resource drops the x86 platform. - The shipped machine-tool packages are renamed. They now carry the
.defaultmarker and neutral names:MachineTool/Table-B1.defaultandMachineTool/CT-350.default. The table-type package was renamed outright — its.mt, its.general-mechand its STL headers travel with it — and the duplicated nested STL set inside the CT-350 package is deleted. A project, script or.mtthat refers to a shipped machine-tool package by its earlier path must be repointed at the name above.
Results that change on upgrade
None of the following breaks a build. All of them change what a simulation produces, so a byte-for-byte comparison against 3.1.175 output will differ — usually because 3.1.175 was wrong.
Silent wrong geometry, now fixed. Each of these produced a plausible-looking simulation of the wrong thing.
- The kinematic pivot anchor. The pivot-transform chain entry was built as
K(0)·K(abc)⁻¹, folding the whole machine-zero forward kinematic — its linear part included — into the pre-pivot anchor. That linear part encodes each axis' motion sense and tool/workpiece-side ownership, so it mirrored the program components of every workpiece-side linear axis before the IK ran. On a table-side chain whose machine-zero linear part isdiag(1,1,-1), every program Z was mirrored and a near-180° swing amplified it into metres of machine-Z error, floating the toolpath above the workpiece. The anchor is now the translation alone, matching what HardNc has always kept. Machines whose linear axes all ride the tool side are unaffected. - G68 2D coordinate rotation was a silent no-op. TiltTransformUtil
judged the active mode from the current block but always took the matrix from the previous one, so
every G68 activation past the first block had its freshly authored rotation overwritten with an
identity that then propagated. G68 rotation did nothing at all while the block's term still read
G68, and the simulation machined the unrotated pattern. Re-running an existing G68 program now gives different — correct — geometry. - A blank line reset G90/G91. A piece with no parsing section — a blank line, a comment-only line,
%, an O-number — left the next block's single-step lookback empty, and it fell back to the G90 default. A program that was incremental throughout silently flipped to absolute mid-file. Such pieces now carry the positioning section forward like every other modal syntax. - A Siemens D offset with no
$TC_DProw resolved to zero length, in silence. A whole TRAORI program machined one tool length low. The read point now falls back to the generic tool-number-keyed height and emitsSiemensToolOffset--TcdpRowMissing. - G43.4 with an unresolvable H word activated RTCP with a zero-length tool. It now reports
Comp-ToolHeight--001as a warning and keeps processing the block. An absent H stays silent — re-activating G43.4 on the modal offset id is legitimate input. - Heidenhain DIN/ISO arc centres.
I/J/Kare absolute circle centres on Heidenhain — the ISO face of the klartextCCpole — not start-to-centre offsets. Reading them incrementally turned arcs into near-full phantom circles. Fixed in all three engines: the HardNc reader (IsIjkAbsolute), the SoftNc reader (IsIjkAbsolute) and the optimizer's write-back. Also delivered on the 3.1.175.x service line. - A
K0word on a HardNc G02/G03 saturated the turn count. Under the default G17 plane a written-but-zero plane-normal word divided the axial travel by zero, the additional-turn count saturated toint.MaxValue, and one arc block became a spiral act of roughly 302 simulated years — the play appeared to hang on a single NC line. The reading now falls back to the closed-circle rule for a zero pace, matching the guard the SoftNc side already had. - Indexed-rotary programs folded the pivot into plain moves. A block with no active G68.2 or G43.4 no longer folds the kinematic pivot transform into a plain XYZ move, which is what a real controller does with a table-side rotary program.
- The radius-compensation arc transient cache landed on the wrong block. Any non-motion line between the corner and the arc orphaned the cache, so the arc lost its leading linear bridge and began at the corner intersection instead of on the offset arc.
- The G68.2 tool-axis IK fallback probed the mirrored tool axis. Both normal-only fallbacks in IsoG68p2TiltSyntax read the transposed third column instead of the third row. On the no-hint path this only skewed a warning gate, but the explicit A/B/C path seeds its hint blend from that solve, so a machine with fewer than three rotary axes composed a mirrored tilted plane.
- A Fanuc
WHILEforward jump bound to the wrongEND. Sequential loops idiomatically reuseDO 1, so a secondWHILE's falsy-condition exit bound to the first loop'sEND 1and redirected execution to a point before the secondWHILE— an unbounded loop the iteration watchdog cannot see, because it only ticks onENDreverse jumps. The jump now uses the anchored label scan the Siemens loop family already used. - Heidenhain G28 is MIRROR IMAGE, not a reference-point return. On the Heidenhain preset the
shared pipeline had been reading it as the Fanuc reference return, so a
G28 Xblock minted a phantom rapid to home while the mirror silently vanished. It is now simulated as a program-to-MC transform,ReferenceReturnSyntaxleaves the Heidenhain logic list, and klartextCYCL DEF 8records the same mirror statement so one program mirrors identically in either dialect. HardNc keeps the Fanuc reading, so the two engines are deliberately divergent on Heidenhain G28 files and any parity comparison must account for it. MathUtil.Convert_inchdmin_To_mmdsreturned mm/min, not mm/s. The body multiplied by 25.4 and never divided by 60, so a caller trusting the name got a 60× feedrate; the siblingConvert_mmdmin_To_mmdsdivides as its name demands. No shipped path called it — the CL/APT feedrate route converts inches-per-minute to mm/min itself — so this changes nothing inside the product, but a caller who had compensated for the old behaviour must remove that compensation.GetRByZ(List<PairZr>)never interpolated. The list overload looked up its ceiling node with the floor lookup, so floor equalled ceiling and the method degenerated into a step function. For a sharp cone whose inner-beam Z–R list has no node between apex and rim, that pinned the inner radius to 0 across the whole cone face and produced NaN flute vertices — the root cause of the transparent cone-tip flute that 3.1.180 addressed at the display layer. It affects every consumer of radius-by-Z interpolation, force geometry included.
Numerical results move even where nothing was renamed.
- Five-axis IK is roughly 1000× tighter. The rotary solver behind
XyzabcSolver now runs coarse→polish: the coarse stage keeps the
original dot residual so solve success and failure
semantics are unchanged, then a
[dot, cross, rr-per-axis]system polishes. The dot criterion is quadratically blind to angle error — an envelope of about 1.4e-3 rad, and the resulting tip error is that envelope times the tool length — so adding the cross term restores quadratic convergence and drops the envelope to about 1e-6 rad. Failed solves no longer pollute the implicit seed, and the measure-zero perfect-saddle case is escaped by a deterministic retry offset instead of by leftover seed pollution. - ActMcXyzLinearContour steps by euclidean tip travel instead of the
largest per-axis component, so
LinearResolution_mmnow caps actual tool-tip travel per step and a diagonal move produces up to √3× more steps at the same setting. - The SoftNc pipeline is the default engine.
- Executed
SyntaxPieces freeze to UTF-8 (below), so theirJsonObjectis a fresh read-only snapshot per call rather than a retained live graph. SyntaxPiece.SentenceIndexbecomes a session-global execution-order counter and is no longer contiguous per file.- Repeated NC diagnostics fold into per-run summaries at the run boundaries.
- HardNc tool changes fire on M06 / Heidenhain
TOOL CALLrather than on a changedTword — a bareTis magazine pre-selection, and a same-tool M06 still runs the changer cycle — and unsetHardNcEnvtooling defaults become the three-axis shape (XYZ = NaN, NaN, 0; ABC all NaN). The old defaults swung all three rotary axes home on every M06, which no post expects.
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/G153suppress all frames for one block;T="NAME"string tool calls withDcutting-edge offsets resolved through SiemensToolOffsetTable ($TC_DPlengths and radius plus additive wear);G70/G71units; path smoothing (G60x/G64x,FNORM/SOFT/FFWON/COMP*/UPATH,CYCLE832);MSG()andSTOPRE;CR=andTURN=arcs. Tail comments became quote-aware, so a;insideMSG("A;B")no longer truncates the block, and the preset stops misreadingLandG74as Fanuc-family codes. - Evaluation — SiemensExpressionParser feeds the
shared expression engine, so
Z=R63+150andX=SIN(R10)*20drive motion. SiemensRParameterTable holds R0–R999 as per-case project data,DEF REAL/INTdeclarations 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(withRPL=) into the tilt-transform chain in Sinumerik RPY order; SiemensTraoriSyntax makesTRAORIa real RTCP mode, the sibling of ISO G43.4, withTRAFOOFhanding the offset back; SiemensCycle800TiltSyntax decodesCYCLE800's MODE bits for all four swivel modes.ROTS/SCALE/MIRRORare recognized and reported, not simulated. - Calls — L-prefixed and named subprogram calls resolve against
SubProgramFolderConfig (
{name}.SPF, then.MPF, then the bare name) and inline with theirPrepetition count;M17/RETpop a frame;REPEATre-runs a labelled slice;MCALL CYCLE81/82/83/85maps onto the shared canned-cycle machinery;PROCheaders and labels are claimed whole. - Control flow —
GOTOF/GOTOB,IF/ELSE/ENDIF, andWHILE/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 onI/J/Kcircle 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. - Coded positions —
CAC/CIC/CDC/CACP/CACNtake 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/G75fixed-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 itsFnever reaches the modal feedrate.- OEM auxiliary M-codes — the preset declares
M12/M13/M22/M23andM330/M331note-only, so each occurrence voicesDeclaredMCode--UnmodeledEffectsinstead 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
Lstatement and its axis words,FMAX,M91as a one-shot machine-coordinate move,TOOL CALLwired to tool change and spindle speed with the table height andDL. Datum handling follows TNC semantics:CYCL DEF 247sets the preset andCYCL DEF 7is an additive shift on top of it, composing as separate transform-chain entries instead of replacing each other, resolved against HeidenhainDatumTable. Arcs (CCpole plusCstatement,DR-= CW, implicit centre, closed arc = full circle),RL/RR/R0radius compensation, theM126/M127rotary-wrap state,M140 MBretract, andCYCL DEF 32 TOLERANCE. - Q-parameters — HeidenhainExpressionParser lexes
Q/QR/QL/QS, theDIVkeyword of FN 4 and the prefixSQRTof FN 5, soFQ1reaches the feedrate,L X+Q2reaches the program XYZ andTOOL CALL SQ3reaches 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 anFN 18 SYSREADtarget 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 withSEQ/COORD ROT/TABLE ROTandSTAY/MOVE/TURNpositioning; VECTOR structurally captured; EULER / POINTS / RELATIV / AXIAL / PROJECTED consumed and warned with the previous tilt retained, so aPLANE AXIAL B+45B word can never be mistaken for a rotary axis command),FUNCTION TCPM, and realM128/M129tool-centre-point control. - Cycles and calls —
CYCL DEF 2xxbodies 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/M89splitting call-once from modal firing,CALL LBLinlining up toLBL 0,CALL LBL n REP mas a section repeat, andCALL PGMresolved 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, withM140,M128andPLANE MOVEwidening their ownFcapture so a retract or feed-limit value cannot leak into the modal feedrate.BLK FORMis recorded as a brand-neutral stock declaration without replacing the project workpiece setup, and the klartextSTOPword joinsM00/M01. - DIN/ISO dialect —
%tape header,Nblock numbers,T+M06, absoluteI/J/Karc centres with the modal pole carried forward, the ISO label family (G98 L<n>definitions and the head-anchoredL<n>,<m>call mapping the comma count ontoREP),G247 Q339stamping the same datum preset asCYCL DEF 247,G54with axis words read as a datum-shift declaration, andG70/G71.
Fanuc and ISO common
- Polar coordinate interpolation —
G12.1/G13.1on the SoftNc pipeline. Before this a polar section parsed silently wrong: theXword (a diameter) and theCword (a hypothetical Cartesian axis in mm) were consumed as ordinary XYZ and rotary degrees. ProgramRxczSyntax halvesXfrom 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/G42compensation 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 —
#varassignment with range-routed stores (#1–#33local per macro frame,#100–#499volatile cleared on M02/M30,#500–#999retained and persisted in the project,#3000–#3999system-control), boolean and logical operators,IF[..]GOTO n,IF[..]THEN <stmt>,WHILE[..]DO m / END mwith a bounded-loop watchdog, and position and tool-offset system variables.M98 P_ L_,M198external call,M99return andM99 P{seq}early return;G65one-shot macro call with A–Z →#1–#26argument binding, andG66/G67modal macro.
Cross-brand
- A shared
DwellSyntaxconsumesG4andG04with the dialect held on the instance: Fanuc-familyX/Useconds,Pmilliseconds,Sspindle revolutions; the Siemens instance readsFseconds andSrevolutions. Capturing theG04spelling fixes a real defect — the un-captured spelling fell through to the flag and axis syntaxes, where a FanucG04 X0.5dwell 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/M05remain the built-in fallback. AnSgreater than zero with no direction ever issued assumes clockwise and emitsSpindleDirection--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) underResource/Controller/with the.Controllerextension, 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 noXFactoryregistration — 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/--007on paths that used to fail silently. A G68.2 tilted plane the machine cannot reach emitsCoord-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.SentenceIndexused 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.
RunNcnow registers the raw lines on NcLineSourceDependency and LabelScanUtil reads memory first, disk second.
NC optimization and writeback
Important
NC optimization is not finished on the 3.2 line. What follows describes the leg as it stands,
and it is published for review, not for production. Work that depends on optimized output should
stay on the 3.1 line, serviced as 3.1.175.<patch>.
- The optimizer runs on the SoftNc pipeline by default. OptimizeToFiles keeps its signature, its script snippet and its HTTP route, but when EnableSoftNcRunner is on and the session holds played SyntaxPieceLayers, it delegates to the new OptimizeNcFiles; otherwise the frozen HardNc path runs unchanged. The new leg classifies the final SyntaxPiece layer, solves the per-step feed adjustments from milling physics, and regenerates text as anchored token edits over the verbatim source block — lines the optimizer does not touch round-trip byte-identically instead of being re-synthesized. Per-file results are retained in NcOptimizations. EnableIndividualStepAdjustmentLog drives both legs.
- Depth splition re-interpolates through a planned fragment path, with separate modal chains for
the pre-build feed and the emission endpoint, per-step machine-to-program-frame inversion, and arc
IJK recomputation including R-to-IJK conversion. Two cases where the frozen HardNc optimizer is
silently wrong are downgraded to a non-split rewrite rather than reproduced: a
G91incremental block (the old fragment rewrite always emitted absolute coordinates), and a Heidenhain klartext block (the rewrite would splice ISO-shaped I/J/K words into a block whose arc centre comes from a preceding modal block). - The compensation stage is no longer dead on the SoftNc leg. The HardNc baseline wrote the compensation into the step contexts while its output read the piece packs, so it never emitted a compensated coordinate at all. Each fragment endpoint is now offset — XYZ only, rotary words untouched — by the tool-tip deflection rotated into the leaf program frame the endpoint lives in, so a G68.2 tilted setup is compensated in the right direction. It is consumed on re-interpolated splition fragments only, and CompensationMask defaults to 0, so with no mask set the stage is a strict no-op.
- Output follows the source's decimal digits. Both legs used to write coordinates through a fixed
F4and every F word throughF2, so a program stated to three decimals could come back with a fourth — an alarm on controllers strict about their least input increment. Digit counts are now scanned per word family over the played source texts with comment spans masked, floored at 3/3/0 and capped at 9, and threaded through the whole write path. The word-suppression tolerances and the F comparison grid derive from the resolved digits instead of the old fixed literals. - Optimized NC is written back in the source file's encoding. NC play reads and optimized writes go through DetectRoundTripEncoding — BOM, then strict UTF-8, then Latin-1 — so an ANSI-family file (GBK, Big5, Shift-JIS) re-encodes to its original bytes instead of having every undecodable byte replaced. Comments in those encodings survive the round trip.
- A feed change no longer mints a one-step carrier fragment. Under an arc split that fragment is a
degenerate arc whose start and end nearly coincide, which an incremental-IJK reader — or a control
that treats begin == end as a full turn — expands into a full circle. An
Fre-statement is now handed to the feed run's next emitted fragment, the one that actually runs at that feed. - A splition fragment at the head of the stream writes only the axis words the source stated, so it
can no longer invent a
Z0.whose value under the source's silence is just the home-fallback modal. - Unparsable NC lines survive. A line the parser could not read used to be dropped from the runner's
line list, so the optimized file silently lost it.
HardNcRunnernow rebuilds a parse-failed line as an opaque no-op — the empty-text parse inherits modal state like a blank line, and the raw text is restored for writers — and the optimizer mirrors the fallback, soIFblocks,#-variable macros,G68 R#andGOTOcome through verbatim. ABuildNcLines--ParseFaileddiagnostic still reports the line, and the line stays un-simulated. The output writer is also closed in afinally, so an exception can no longer leave a half-written file locked. First delivered on the 3.1.175.x service line. - A Siemens
CYCLE800swivel counts as a macro line in the piece classifier, so it is preserved rather than treated as an optimizable motion block. - The host key
HiNC:OptCoreNumgoverns both legs again. It used to set only the legacyNcOptProc.CoreNum; once the SoftNc optimizer became the default path the key silently stopped governing anything. It now assigns both (0 = derive from the processor count). - NC text writeback regenerates NC from a program that has already been played, in two stages over
the session's finished syntax pieces: a converter turns the source piece stream into a destination
stream plus a bidirectional source↔destination map keyed by sentence index, and a reverse segmenter
serializes that stream back to lines. The data→text seam is a brand-agnostic sentence composer, first
implemented for Fanuc. A patch-mode writer performs positional token edits —
NaNdeletes a word absorbing one separator space, insertion follows conventional order, trailing zeros trim keeping the dot — and refuses to rewrite variable, bracket and keyword values rather than corrupting them, reportingWriteback-Patch--VariableValue,--KeywordValue,--CommentOnlyTextand--EditUnmatched. - A second diagnostic home. NcManipulationDiagnosticProgress
is a sibling of the play-time
NcDiagnosticProgress, dedicated to NC-rework operations — writeback conversion and optimization — so a play reset never discards manipulation results and vice versa.
Milling physics, training and measured data
- Physics runs in the native kernel. The per-step milling physics migrated into
core.dllin stages: the engagement is scan-converted natively at the substraction completion point, the force kernel is reached through the same handle with no managed marshal, and the sequential cutting-temperature and wear chain runs from a native thermal session held per (tool, cutting parameter) pack. The switch is EnableNativeMillingPhysics, surfaced runtime-only (not persisted to project XML) as EnableNativeMillingPhysics and EnableNativeMillingPhysics, and it now defaults totrue. Public entry points that used to reach the managed kernel still work: without a session pack they build an ad-hoc physics pack per live (cutting parameter, tool) pair and run natively. - MillingToolPhysicsPack is an immutable record holding one tool's
scalar derivations for one cutting-parameter set — spindle-buckle-to-tip length, observation height,
effective cutting diameter, the bending/Z-deflection pair, the simplified rake angle, the minimum
uncut chip thickness. MachiningSession owns the packs keyed by tool id
(GetToolPhysicsPack) and invalidation is explicit at
the points that know the state changed: every run-op start and the tool-change act, plus
InvalidateToolPhysicsPacks. The corresponding
MillingTool/MillingCuttermembers are now deliberately uncached pure computations. - Milling-force waveforms are reproducible again. The parallel per-step force build read lazily built scalar caches on the shared tool objects; a thread could pass a cache guard and then read a value a concurrent writer had stored in between, so two plays of the same NC exported different forces in the thin-chip window of each tooth pass. The caches were first republished as single immutable references and then removed in favour of the frozen session pack.
- Thermal gating and seeding. The sequential cutting-temperature and wear build now checks
EnablePhysics (spindle temperature deliberately
keeps running), and the tool-change thermal seeding re-arms whenever the incoming chain state has no
flute temperature list — which covers fault and cancel re-seeds, stop-then-replay residue, and
EnablePhysicsbeing switched on mid-session. The shank temperature list is seeded to the exact node count the thermal FEM builds, so trailing shank nodes no longer sit at 0 K after a tool change. - Cutter geometry is validated up front.
GetUpperBeamGeometryIssues collects upper-beam and shank
configuration problems as keyed messages — for example an extended-cylinder beam whose full length
sits below the flute height (
Cutter-UpperBeam--BelowFluteHeight), which inverts the shank solid and makes the shank thermal model unbuildable. They are reported once per tool atBeginSessionand at each tool change, instead of surfacing later as a null-reference cascade inside the thermal physics with nothing naming the beam. RakeFaceCuttingPara3dno longer throws on a six-field parameter string (the guard read the seventh element behind a>= 6check), and the published coefficient index mappings are corrected: theLocalProfileMillingPara(Vec3d, Vec3d)constructor maps (x,y,z) to (Ksr, Kst, Ksa) / (Kpr, Kpt, Kpa), and the 2d element index range is 0–3 with 0=Ksc, 1=Ksn, 2=Kpc, 3=Kpn.- Training diagnostics name their cause. The per-step warnings split into
Train-StepLuggage--Unreadable(the step luggage row could not be read back) andTrain-StepEngagement--Missing(the row is present but the engagement was never built because physics was inactive at simulation time). The gather pass counts both against the eligible steps: silent at zero, one summary warning at or below MissingEngagementAbortRatio (default 0.25), and a configuration error above it. A gather pass that produces no samples at all now reports immediately rather than throwing inside the SVD solve, separating “not one step touched the workpiece” from “touched steps whose mapped force data yielded no usable shots”. - New training knobs. EnableDesignMatrixSolver
(default false) solves the least squares on a thin QR of the design matrix instead of forming the
normal equations, which square the condition number;
DesignMatrixSvdRelativeTol is its truncation cutoff.
EnableCwePhasePairing (default false) determines each
step's rotation phase with a cutter-workpiece-engagement block-pairing detector instead of the
self-bootstrapped lead parameter, for one-flute and symmetric two-flute cutters in light radial side
cuts. ReTrainAnchorOutputScale exposes the virtual
anchor weight.
LastMillingParaTrainResult captures the outcome — kind,
sample flags, outlier ratio, success, output file, parameter name and note, correlation R, filtered
sample count, parameter XML, timestamp — so a caller reads it without re-opening the
.mpfile. - Time mapping is reworked around absolute wall-clock time.
AddTimeDataByFile accepts
DateTimewindows, stored as IFileTimeSection forms, and the project-scoped MappingAnchorDateTime — seeded set-once from the date of the first controller instant seen — converts controller timestamps onto one run-relative axis. EndTimecode replacesAccumulatedTimeas the canonical end-of-step time. - CSV timing survives midnight. Step durations derive from full date-bearing instants, so a multi-day recording no longer produces negative durations and a negative chart time axis, and a non-physical duration from a spliced recording is clamped with a validation warning instead of stalling physics evaluation.
- Wall-clock time is dense. The trio moved into one optional sub-object,
StepActualTime (
Timecode/Instant/IsInterpolated), reached through ActualTime. On CSV plays every built step is stamped: steps built from a controller row re-anchor, and the steps between extrapolate along the machine timeline and are marked interpolated, which makes the actual-time mapper window per-step exact instead of sparse-anchor scaled. Pure NC plays keep null stamps. - An empty step-shot pairing window is a data gap, not something to interpolate across. The window
builder used to expand outward to the rows bracketing the gap, silently pairing such steps with force
values that were never measured — a training run over a file whose transients had been carved out
produced a plausible correlation and a full set of coefficients derived entirely from fabricated
rows. Such a window now skips its step and one
Map-ShotGap--StepsSkippedwarning per mapping call carries the count; a window-edge row is interpolated only when its bracketing rows span at most two spindle revolutions. - “No cut / No data for step” on freshly-simulated steps is fixed. The bulk step-data readers cached the absence of rows the writer had not committed yet, so a step that had just been simulated could report no data until the program was re-run. A covered-but-missing index now drops the stale segment and re-reads.
- New end-of-play warnings, each once per session:
Play-Touch--None(the play finished without any step touching the workpiece),Tool-FluteCount--Zero(physics is on and a milling cutter resolves to zero flutes, so feed per tooth is undefined), andPlay-Physics--None(physics is on and at least one step touched the workpiece but no touched step carries a physics brief — naming the three things that gate it: a tool bound to the spindle, the spindle actually rotating, and the workpiece cutting parameter). That last state previously surfaced one process later, as a training run gathering zero samples. - Performance is collected in its own section below.
Cutter-location (CL) playback and CL-to-NC
- Replay an NX CLSF / APT-source toolpath directly.
PlayClFile reads
MSYS/FROM/GOTO/CIRCLE/RAPID/FEDRAT/SPINDL/COOLNT/TLDATA/LOAD. The parser is the NxClRunner preset — a SoftNcRunner composition — and the project holds a third runner suit ClsfRunnerSuit beside the NC and CSV suits. ATLDATArecord creates the tool geometry when the tool house has no matching id. Run-ops are first class: ClRunner,MachiningSession.PlayClFile/RunClFile. See Cutter-Location (CL) Playback. - A CL file can now drive a machine-tool chain, not only a
ClMillingDevice. When the pipeline's kinematics dependency resolves to a
live solver, ClToMcTransformSyntax inverse-solves every CLSF motion
endpoint at parse time and expresses the result in the same
ProgramToMcTransformvocabulary the NC pipeline uses — a tool-height entry from the active tool, a pivot entry anchored to the workpiece frame, and the solved rotary axes in raw degrees for the cyclic wrap tail-pass — soMcLinearand the wrap are reused unchanged. Played onto aClMillingDevicethe same file is still pure cutter-location motion, which is what you want for verifying a CAM toolpath before any post-processor is involved. - CL moves resample along the true path. GetClSteps walks
IClPath.Atfor each intermediate step rather than linearly interpolating between the path's begin and end, so a CL arc actually curves and the tool axis rotates along the path instead of through it. ClLinear gained two guards on the same path: the near-parallel begin/end normal case short-circuits instead of falling into a degenerate cross product that yielded a NaN rotation axis, and the interpolation reads its rotation delta through the lazily-built property rather than a still-null backing field. - CL tool changes teleport instead of stepping. A stepping change stamps collision detection and
volume removal at the chain's current pose, and at CL session start that pose is identity — the tool
sitting at program zero inside the workpiece. The CSV pipeline's teleporting semantic is promoted to
the shared ToolingTeleportSemantic (the old
CsvToolingTeleportSemanticelement name is kept as a load-only alias), and the firstGOTOafter aLOAD/TOOLis forced into a reposition so a new tool never sweeps a cut from the previous operation's endpoint. - Convert a played CL program into Fanuc NC files.
ConvertClToNcFiles, its HTTP action, and the
LocalProjectService/MachiningSessionentry points walk the session's final syntax-piece layer, group pieces per source file and write one NC file per source, template-substituting[NcName](defaultOutput/[NcName].nc). It requires a prior play on a machine chain — a pure-CL device leaves no machine-solved sections to serialize — and reportsConvertClToNc--NoPlayotherwise. Stage-one results are retained in NcConversions as the hook for source↔output cross-navigation. A mission can declare the writeback through EnableConvertClToNcFiles and ClToNcFileTemplate. - CL→MC hardening. Tool-offset resolution walked back to the distant
LOADblock for every motion (O(N²) on production-scale files) and is now O(1) through a modal active-tool section; the documentary program-to-Pn stamp is stamped once per run rather than walked per motion; the program-zero query no longer deep-clones the whole equipment assembly per motion block; the warned-tool-id set is run-scoped, so theClToMc--NoToolOffsetwarning is no longer suppressed on every run after the first; and MachineAxisConfig gains a publicClear()so a machine switch rebuilds the axis table instead of accumulating stale rotary axes.
Session, project and command model
- A parser and its per-case data travel as one file. NcRunnerSuit bundles a
runner with the dependency data a particular job needs, as a single file-loadable unit. The project
holds three suits — NcRunnerSuit,
CsvRunnerSuit and
ClsfRunnerSuit — and
ReadNcRunnerSuit /
WriteNcRunnerSuit switch the active parser mid-project
from a suit file. A switch attempted while a program is playing is refused with
ReadNcRunnerSuit--Refused. - One NC runner configuration is shareable across projects. Per-case data — tool offsets,
work-coordinate offsets, Siemens frames, Heidenhain datums, retained macro variables, seeded brand
parameter tables — travels as proxy placeholders inside SoftNcRunner and
resolves against the owning project's per-case list, so a controller configuration is no longer
welded to the job it was first built for. Machine-config consumers read the resolved view through
GetEffectiveNcDependencyList; legacy
<NcDependencyList>XML andNcEnv-based projects still load and migrate automatically. - HTTP guards and one envelope. RequireActiveSessionAttribute answers a
session-scoped action with HTTP 409 and an
ApiActionResult.NoActiveSession()body when no session is active, instead of the previous null-reference 500; RequireLoadedProjectAttribute does the same for the project-level controller. Both are applied at the controller level and honour opt-out markers (AllowNoActiveSessionAttribute, AllowNoLoadedProjectAttribute). Mutating actions inject a fresh MessageCollector and return the collected notifications inline in the shared ApiActionResult envelope, so a REST or AI caller sees progress, success and error messages in the response instead of only out of band. LocalProjectServiceController exposes the project-level (session-independent) surface parallel to SessionShellController. - Session commands declare themselves. CommandCatalogAttribute marks an
ISessionCommand as user-addable and places it in a
CommandCategory (Setup / Program / Optimization / Output / Flow, declaration
order = display order) with an
Ordersort key and an optional wire kind (default: the class name minus theCommandsuffix, lower-cased). CommandFieldAttribute marks a bool / int / double / string property as a directly editable scalar with an optional label, unit and physics-licence flag, so a generic editor renders and updates it without a hand-written form. A command without the catalog attribute stays loadable from project files but is not offered for creation. - Program File dispatches by kind. NcFileCommand gains an
NcKindproperty (XML elementNcKind, absent =Autofor legacy projects) and its mission label becomes “Program File”; each command resolves its own file, so a List of Program Files can mix brand NC, CL and CSV. - Two session commands saved but could never be read back.
ListCommand.Regchained every type except NcOptOptionCommand and RecordMeshedGeomCommand, so any XML round-trip of an entry holding one threwKeyNotFoundExceptionout of the XFactory generator lookup — reloading a saved project containing an NC Optimization Config command failed. Both are now chained. - Naming. Title is an optional name shown in place of the type
name, so nested command lists can be named in the mission tree.
PreSettingCommanddisplays as “General Config” andNcOptOptionCommandas “NC Optimization Config” — display strings only, so serialization and endpoints are untouched. A bare non-listPlayerCommandroot is normalized on read into a single enabled entry of the defaultListCommand. Command titles, catalog categories and field labels are now localized, and a zh-Hans resource set was created (none existed before). Default-script template keys stay untranslated on purpose — they compose the C# comments and script title written into the user's.hincproj, which travels to other machines. - Script faults are keyed errors. A
CompilationErrorExceptionor a faulted script task used to surface as an anonymous warning; ScriptCommand now reportsScriptCommand-Compile--Failedwith the full diagnostic list andScriptCommand-Run--Faultwith the exception, both at Error severity. - A fresh session re-homes the machining chain.
ResetRuntimewrote the configured XYZ home but hard-coded ABC to 0, and it only ran on project switch or pace-player reset — never before a plain Play. A freshly loaded project therefore started from whatever pose the.mthappened to serialize, while the act stream interpolated from the home seed, so the first contour swept from a pose the machine was never at and cut along the way. The re-home now reads the rotary homes from the same home configuration and also runs at BeginSession; a mid-session replay is untouched. - What a reset actually resets. ResetRuntime now also
rewinds the NC-runner session state, so the next play restarts file and line indexing from scratch
the way
PowerResetdoes, and it resets the CL device pose to identity. It no longer clobbersMachiningResolution_mm: the runtime resolution is seeded from the workpiece's initial resolution only when a project loads, so an explicit override survives a runtime reset and a workpiece swap. - Mixed runner kinds in one session are refused.
NcRunnerSessionStateremembers the runner that initialised it and RunNcLines refuses a different one withRunNcLines--RunnerMismatch— reachable now thatNcKind.Automakes mixed-kind missions a first-class flow. - Stale state on a chain or project switch. Building the coordinate converter nulls the rotary
solver when the chain is not an
IXyzabcChain, so a solver built for the old machine no longer keeps converting after a switch to a CL device; and ClearCache now also calls ClearIdealGeomCache, so loading another project stops rendering the previous project's target geometry. - Project-file operations are serialized through a zero-wait gate. A New / Load / Save / Reload
arriving while another is in progress throws ProjectFileBusyException
immediately instead of racing into a file-in-use
IOException. - AlignWorkpieceProgramZeroToIso computes in the machine-zero state. It reflects the assembly and zeroes every dynamic axis before querying displacements, so the alignment is correct even when the live machine's axes are displaced.
- RunCount counts runs started, incremented synchronously before the
run's task launches and never reset. Pairing it with
IsFinishedin one snapshot lets a polling client distinguish “the run I started has finished” from a stale Finished left by a previous run.
Geometry, rendering and native stability
- Disposing a display or geometry object still in use no longer crashes the process. A client disconnect, a meshed-geometry reset or an app shutdown could take the process down with an access violation. DispEngine and CubeTree native calls are now gated against a concurrent dispose, and disposals run serialized on a single background chain — IsDisposed reports the state and DisposeBackground enqueues a tree, or a collection of its attachments, onto that chain.
- Large geometry no longer freezes the UI. Solid builds its display topology off-thread and draws a wire bounding box with a “Loading” mark until it is ready.
- ClStrip raises a
Clearedevent afterClear, pairing with the existingPosAdded. - A P/Invoke correctness sweep reconciled the managed declarations against the real export table.
It fixed a non-existent export (
substraction_ExpandToBox3d), twovoidnatives declared as struct returns (a garbage register read), twoToStringmarshals that made the CLR free foreign memory, and a log-callback delegate nothing rooted against GC — native could call a dead thunk long after the P/Invoke returned. These are the crashes a user reports as “it crashes randomly”. - Milled step colours no longer go stale at random. Step colours are baked into the native cube-tree attachment, but the refresh only flagged the strip while the display cache was freed immediately, so a frame landing in that window rebuilt the cache from stale colours and nothing cleaned it again. The service now cleans the cache once more after the re-stamp. A paired fix stops an empty strip display window swallowing a pending recolor indefinitely.
- The dimension bar reads on any background. It was a single hard-coded near-black stroke left over from the light-host era, invisible once the front end went dark. It is now drawn as a wider dark halo under a white main stroke, with the halo pushed back whole depth steps rather than the white pass nudged forward by half a depth LSB — which intermittently lost the depth test.
- New geometry API. CarveStl is the managed face of the native exact
triangle-CSG container. Add is the boolean-union counterpart of
Substract, returning UnmanagedAddition, and AddBySweepingVolume mirrorsRemoveBySweepingVolume— the geometry-layer symmetric point for additive processes. There is no deposition step in the machining runner yet, so this is a geometry-level API only. - FreeformBottomContour is the user-editable point-list bottom contour, mirroring the side contour with the key axis switched to radius.
Performance and footprint
Every figure below is a measurement with its conditions stated, and where a change cost time to buy correctness that is said too. Read the ratios rather than the absolute times: several campaigns were run on Debug builds or on small machines, deliberately, because a paired A/B on one machine answers “did this get faster” far more reliably than an unpaired Release number on a fast one.
Milling physics in the native kernel
The per-step physics moved into core.dll in stages — engagement scan conversion, the force kernel,
then the sequential temperature and wear chain. Measured as a same-day paired A/B, managed leg
against native leg, on one circular test program:
| Per touched step | managed | native | |
|---|---|---|---|
| Engagement build | 46.0 ms | 5.03 ms | ~9× |
Force (GetMillingFoce) |
17.1 ms | 0.75 ms | ~20× |
| Temperature and wear chain | 3.97 ms | 0.24 ms | ~16× |
| Whole play | 59.3 s / 16.1 GB allocated | 40.6 s / 3.4 GB allocated | −32% wall, −79% allocation |
Allocation is where the migration bites hardest: engagement construction alone fell from about 5.2 GB to 29 MB per step.
Conditions. Debug x64,
EnablePhysicson, collision off, one force worker, a two-core / four-thread laptop, ±10% thermal-throttle noise, single paired run per leg. Absolute times are not representative of a customer machine — the ratios are the claim.
Numerical parity. Engagement, force and brief are bit-identical between the two legs on Windows. The Linux build is not bit-identical (different libm), so a cross-platform comparison should use a tolerance, not equality.
Playing a long program stays linear
NcOptOption.Equals ended on a null-propagating comparison of a dictionary that is created on
demand and is null on virtually every option, so the whole comparison collapsed to false — an
option compared unequal even to a copy of itself. Both record-on-change guards built on it were
therefore dead: the session appended an option-map entry for every played act instead of only at
change points, and the step rewrote unchanged entries. Reading the last recorded option through a
LINQ LastOrDefault over a SortedList<,> — which has no indexed fast path — then walked the whole
map each time, so the two defects together made a long play quadratic.
Measured on a 2.35-million-line Siemens program: the option map now holds 1 entry instead of one per line, and the per-100,000-line rate stays flat instead of degrading from 73 s at the start of the file to about 11 minutes by 1.9 million lines.
This applies to every runner and to sessions doing no optimization at all, because the call site
is the session-level play loop. GetHashCode drops the dictionary in the same change, since it
hashed by reference and would otherwise disagree with Equals — relevant if you use NcOptOption
as a dictionary key.
Where the time actually goes
Worth knowing before you tune anything. After the migration, on the measured workload the whole parallel physics stage is about 3.3% of wall time, while the single-worker volume subtraction is about 77% — and that subtraction is single-worker as a correctness requirement, not as an oversight. Raising the force-worker count therefore buys nothing on any machine; the bottleneck moved rather than disappearing.
What did change in the worker derivation is narrower than it sounds. An unmeasured six-core ceiling was removed, but it only ever governed the sweep workers, and only machines with nine or more logical processors see a different count; force workers are unchanged everywhere. The throughput benefit on such a machine has not been measured — the development machines are smaller — so this is a ceiling removal, not a claimed speedup.
Queue depths became fixed item budgets (120 geometry, 3840 physics) rather than scaling with the core count, because those queues bound per-item memory: uncapped, a 64-core machine would have been handed 40,960-deep physics queues. On machines with fewer than six cores this is a small increase in bounded-queue memory (from 80 / 2560), which is the honest cost of the change.
Loading a large STL workpiece
Building the topology from an STL was quadratic in triangle count — a pointer-derived hash collapsed into a handful of buckets, so lookups degenerated into linear scans. On one 935,000-triangle binary STL, topology construction was 99.6% of the entire load; reading the file off disk was 0.16% of it. With a multiplicative hash mix the build is linear:
| Triangles | before | after | worst bucket | |
|---|---|---|---|---|
| 100,000 | 8,711 ms | 1,305 ms | 6.7× | 2,245 → 21 |
| 300,000 | 89,036 ms | 4,750 ms | 18.7× | 6,593 → 23 |
Per-triangle cost is now flat (0.013 → 0.016 ms/tri across a 3× size increase), which is the real result: the cost grows with the mesh instead of with its square. Extrapolated to the full 935,000-triangle mesh that is roughly 14 minutes → 15 seconds.
Deduplication and the resulting topology are unchanged — the equality predicate was not touched, and the 300,000-triangle case produces an identical 899,997 lines before and after. Separately, the managed-to-native STL handoff dropped from three full copies of the buffer (about 86 MB each, plus around twenty doubling reallocations) to two.
Conditions. Native test harness, debug CRT — which inflates container-operation constants, so the absolute milliseconds are an upper bound. The composition breakdown and the complexity change are build-configuration independent. The full-mesh figure is an extrapolation, not a measured run.
Re-triangulating after a cut
The marching-cubes step gained a lookup table, and produces fewer triangles for the same surface:
| Tree | before | after | triangles | |
|---|---|---|---|---|
| 17 MB diagnostic | 0.63 s | 0.29 s | 2.17× | −35% |
| 30 MB demo | 1.29 s | 0.60 s | 2.15× | −35% |
| 309 MB customer part | 14.68 s | 6.51 s | 2.25× | −43% |
Scope. This lands on the rebuild burst after a cut invalidates cached geometry, not on steady-state rendering, which draws from the display cache and is unchanged.
It is also an approximation change, not purely a speedup: a non-finite cut drops its triangles, so a sub-voxel feature vanishes at that level of detail instead of being capped. That is what fixed the broken-face slabs seen on RTCP paths. The contact-loop extraction used by milling physics deliberately still uses the previous walk, so physics results are untouched.
Session memory: a long program no longer exhausts the client
A session retains every executed NC block for its whole lifetime. Once a block leaves the executing window its piece is now frozen to compact UTF-8.
Measured on a 25,018-block play: session retention 406 MB → 142 MB, about 2.9×.
The encoding itself is smaller than that ratio suggests — roughly 12 KB per line live against 1.6 KB frozen, about 12.9× — because a retained piece carries more than its JSON. The 2.9× is the figure that matters for whether a program fits in memory.
The trade is explicit: after the freeze the JsonObject getter re-parses on every call and returns a
fresh read-only snapshot, with no caching and no write-back. Code that reads the same piece
repeatedly should hold the snapshot in a local. The switch is
FreezeExecutedPieces, on by default.
Sizing a meshed workpiece
A cube tree costs roughly 3.2× its file size in RAM while loaded — a 10 GB .wct at 0.125 mm
resolution is about 95 million nodes, holding around 25 GB of tree plus 6 GB of index. Tearing down a
tree that size used to block the caller for over two minutes; disposal now runs serialized on a
background chain, so the thread that dropped it does not wait. The remaining cost is genuine work:
the live renderer must not be left showing geometry that no longer exists.
Cutter-location files at production scale
Three costs were removed from the CL-to-machine path, and on a production-scale file they are the
difference between replaying and appearing to hang: tool-offset resolution walked back to the distant
LOAD block for every motion (O(N²), now O(1) through a modal section), the documentary
program-to-Pn stamp did the same walk (now stamped once per run), and the program-zero query
deep-cloned the whole equipment assembly on every motion block (now a cached per-run matrix over the
live assembly). These are complexity changes; they have not been separately timed.
Smaller footprint
The embedded default font is handed to the display engine from memory, so an 11 MB .ttf is no
longer written into the process working directory on startup. The packages are x64-only, and
HiNc-Resource no longer ships a duplicated nested copy of the CT-350 STL set.
Things that cost more, on purpose
Five-axis inverse kinematics. Tightening the orientation envelope from about 1.4e-3 rad to about
1e-6 rad — measured maxima 1.5e-8 rad on the hot path and 2.1e-8 on teleport, a tip deviation of
0.05 µm on a 50 mm tool — costs roughly eleven solver iterations where one used to do, so a
posture-changing call went from about 23 µs to about 251 µs, and a teleport from 418 µs to 1607 µs.
Only posture-changing RTCP and arc steps pay it: three-axis programs and constant-posture segments
are exempt through the McLinear downgrade. In absolute terms a 1,432-step five-axis replay spends
about 0.36 s in the solver. (Debug build including measurement overhead, so those microseconds are an
upper bound.)
Machine-coordinate linear stepping. ActMcXyzLinearContour derives its
step count from the euclidean length of the machine XYZ delta rather than the largest single-axis
component, so LinearResolution_mm caps actual tool-tip travel per step. A diagonal move therefore
produces up to √3× more steps than before at the same setting — more work, for a sampling density
that now means what the setting says. Lower the resolution if the old step count was what you were
budgeting for.
A tuning cliff worth knowing about
MillingCycleDivisionNum saturates. Raising it past roughly 180 buys no additional training accuracy
while the cost keeps climbing: a training run that takes about four minutes at 180 takes hours at 720
and needs on the order of 100 GB of RAM to do it. The default of 36 is for ordinary simulation; raise
it for training, but not past the point where the curve flattens.
Packaging, resources and hosting
- Shipped resources carry an ownership marker. ResourceDefaultMarker introduces the
.defaultconvention: files carry the marker before the final extension (AlTiBN.default.CoatingMaterial) and machine-tool packages carry it on the folder (MachineTool/Table-B1.default/). Marked items are system territory the seeder may refresh or delete; unmarked items belong to the user and are never touched. Seed copies the shipped defaults into the admin resource root at startup — anchored on the application base directory rather than the process working directory, version-stamped so steady-state boots do no work. A save-as flow must strip the marker so a user file cannot masquerade as a shipped default. - HiNc-Resource ships more presets:
Resource/Controller/<Brand>.default.Controllerfor all five brands, andStandardForcedAir/StandardWaterSolubleCoolant/StandardOilBasedCoolantcoolant conditions, so those load browsers start populated instead of empty. - CoolantHeatCondition gains name/note, the three static presets, preset
find/match/apply, and side-file IO following the workpiece-material pattern —
CoolantHeatConditionFile externalizes
the condition to a
.CoolantHeatConditionfile. - Obfuscation-safe DTOs. Obfuscar renames compiler-generated anonymous types and strips their
constructor parameter names, so
System.Text.Jsonthrew on everyToPresentDtoserialization in released assemblies — the geometry, cutter and transformer editors all returned HTTP 500 while Debug builds were fine. Every implementation now returnsDictionary<string, object>withnameofkeys, which compile to string literals obfuscation cannot touch, and IToPresentDto documents the rule. See the wire-key list under Signature and shape changes. - The embedded default font is Noto Sans CJK TC (SIL OFL 1.1) in place of the Big5-only HanWang
WCL06, fed to the native engine from memory instead of writing an 11 MB
Font/WCL06.ttfinto the process working directory. Traditional, simplified and kana render from one face with zh-Hant glyph conventions. Explicit font paths still go through the file route. - Third-party notices ship with the two packages that actually redistribute third-party bits: HiDisp
(the native files under
runtimes/and the embedded font) and HiNc-Resource (the offline documentation site among its content files). HiLicense gained a real package description. - Dependency updates —
SQLitePCLRaw.bundle_e_sqlite3is pinned to 3.0.3 (SQLite CVE-2025-6965),Microsoft.Data.Sqliteto 10.0.9 andDapperto 2.1.79. - Small public additions — DetectRoundTripEncoding,
CompactNanOptions and named float literals in
GetDouble, CycleUpperInclusive (the
(l,u]cyclic window the existing overloads could not express), and AddAndGetIndex. IndexSegment no longer leaves an open run end when the match sits at either edge of the list.
New diagnostics you may now see
Several failure modes that used to be silent now report. These ids are searchable and filterable — they are the fastest way to find out what a run actually did.
| Id | Means |
|---|---|
Play-Touch--None |
the play finished without any step touching the workpiece |
Play-Physics--None |
physics is on and steps touched, but no touched step carries a physics brief |
Tool-FluteCount--Zero |
physics is on and a milling cutter resolves to zero flutes |
SpindleDirection--AssumedCw |
an S word greater than zero with no direction ever issued |
Composition--NotLicensed |
external pipeline units were skipped for this session |
Script--NotLicensed |
an NC-embedded C# script was skipped |
SiemensToolOffset--TcdpRowMissing |
a Siemens (T, D) pair had no $TC_DP row; the generic tool height was used |
Comp-ToolHeight--001 |
a G43.4 H word could not be resolved |
Coord-MachCoord--005 / --006 / --007 |
a G53 or G53.1 machine-coordinate move failed on a path that used to fail silently |
Coord-Tilt--001 / --002 |
a G68.2 tilted plane the machine cannot reach |
BuildNcLines--ParseFailed |
a line could not be parsed; it survives verbatim but is not simulated |
RunNcLines--RunnerMismatch |
a second runner kind was attempted inside one session |
ReadNcRunnerSuit--Refused |
a suit switch was attempted while a program was playing |
PostExecution--MeshedGeomOutputRetired |
an old project still carries the retired meshed-geom output pair |
ConvertClToNc--NoPlay |
CL-to-NC conversion ran with no prior play on a machine chain |
ClToMc--NoToolOffset |
a CL motion resolved no tool offset |
Map-ShotGap--StepsSkipped |
steps were skipped because their shot pairing window held no measured row |
Train-StepEngagement--Missing |
a step's row is present but its engagement was never built |
Train-StepLuggage--Unreadable |
a step's luggage row could not be read back |
Writeback-Patch--* |
a writeback edit refused a variable, keyword or comment-only value, or matched nothing |
HeidenhainPlane--Unsupported / SiemensFrame--Unsupported / HeidenhainCycl--Unsupported |
the construct is recognized and consumed safely, but not simulated |
DeclaredMCode--UnmodeledEffects |
a machine-declared OEM M-code occurred; no effects are simulated for it |