Workflow: NC Optimization
This workflow describes how to generate optimized NC files from a physics-based simulation. The optimizer adjusts feed rates to keep physical quantities (spindle power, torque, thermal stress, cutting force) within specified safety limits while maximizing machining efficiency.
flowchart TD
Prereq["Prerequisites<br>(simulation with physics,<br>cutting parameters)"]
Config["Configure optimization options"]
Simulate["Run simulation"]
Output["Generate optimized NC files"]
Verify["Verify optimization results"]
Prereq --> Config --> Simulate --> Output --> Verify
1. Prerequisites
NC optimization requires a simulation environment with physics enabled and valid cutting parameters:
EnablePhysics = true;
LoadCuttingParaByFile("Material.mp");
| Prerequisite | Description |
|---|---|
| Physics enabled | EnablePhysics must be true |
| Cutting parameters | Workpiece must have loaded milling coefficients (see Workflow: Milling Force Parameter Training) |
| Valid tool definitions | Tool geometry, flute count, and material properties configured |
Note
Optimization is based on an ideal geometric model. If the workpiece is a casting or has installation errors, configure a conservatively larger workpiece geometry to prevent misidentification of cutting vs. non-cutting regions.
2. Configure Optimization Options
Feed Rate Control
| Property | Description | Default |
|---|---|---|
| OptEnableFeedrate | Enable sequential feed rate optimization | true |
| OptEnableInterpolation | Re-interpolation for smoother acceleration/deceleration | — |
| OptRapidFeed_mmdmin | Feed rate for non-cutting regions (mm/min) | — |
| OptMinFeedrate_mmdmin | Minimum cutting-region feed rate (mm/min) | — |
| EnableSourceFeedrateFloor | Floor each step's feed rate at a share of the feed rate the source program commanded on it | false |
| SourceFeedrateFloorRatio | That share, 0 to 1 |
0.8 |
| OptMaxFeedrate_mmdmin | Maximum cutting-region feed rate (mm/min) | — |
| OptMaxAcceleration_mmds2 | Acceleration/deceleration limit (mm/s²) | — |
| OptFeedrateAssignmentRatio | Re-interpolation trigger threshold | — |
The two source-feedrate options have no Opt* shortcut; a script sets them on
NcOptOption, before the program is played:
NcOptOption.EnableSourceFeedrateFloor = true;
NcOptOption.SourceFeedrateFloorRatio = 0.8;
Below 1 the physics-based constraints can still slow a peak-load step down to that share of its
source feed, which is where the tool-life gain comes from; at 1 the optimizer only speeds steps
up. See
Bounding the Slow-Down of a Proven Program.
Extended Distance
| Property | Description |
|---|---|
| OptExtendedPreDistance_mm | Pre-distance for equivalent calculation of cutting regions (mm) |
| OptExtendedPostDistance_mm | Post-distance for equivalent calculation of cutting regions (mm) |
Safety Factors (Physics-Based Constraints)
| Property | Description |
|---|---|
| OptSpindlePowerSafetyFactor | Spindle power safety factor (0 = ignore) |
| OptSpindleTorqueSafetyFactor | Spindle torque safety factor (0 = ignore) |
| OptThermalYieldSafetyFactor | Thermal yield safety factor (0 = ignore) |
| OptPreferedForce_N | Target cutting force (N) |
Note
Target value = 100% / Safety factor. For example, a safety factor of 1.5 means the physical quantity targets ~67% of the limit.
Constraint Priority
In cutting regions, constraints are applied in this order:
- Direct feed rate constraints (min/max feed rate, min/max feed per tooth from tool settings, the source-feedrate floor)
- Acceleration/deceleration constraints (
OptMaxAcceleration_mmds2) - Physics-based constraints (spindle power, torque, thermal yield, preferred force)
Where several ceilings apply, the lowest feed rate is used.
A floor above the ceiling neither fails nor lowers the feed: where the composed minimum feed per tooth exceeds the composed maximum — a minimum feed rate, a minimum feed per tooth, the cutter's minimum uncut chip thickness or the source-feedrate floor sitting above the maximum — the boundary collapses onto the minimum, and the step is solved there. The physics-based constraints do not go below a floor either: a step held up by one runs above the load its target asked for, and for the source-feedrate floor the run reports how many (NcOpt-Feed--SourceFloorOverTarget).
Script Command Example
OptEnableFeedrate = true;
OptEnableInterpolation = true;
OptRapidFeed_mmdmin = 4000;
OptMinFeedrate_mmdmin = 100;
OptMaxFeedrate_mmdmin = 4000;
OptMaxAcceleration_mmds2 = 10;
OptExtendedPreDistance_mm = 3;
OptExtendedPostDistance_mm = 2;
OptSpindlePowerSafetyFactor = 1.5;
OptSpindleTorqueSafetyFactor = 1.5;
OptThermalYieldSafetyFactor = 0;
OptPreferedForce_N = double.PositiveInfinity;
Settings Embedded in NC Code
Optimization settings can be set from a script embedded in the program's own comments, so a setting
changes at the line it applies from. The @@ marker runs the script before that block's motion, and
the comment character is the controller's own — parentheses below, on a Fanuc-family controller
(Script Commands in NC Code):
N0110 X-3.064 Y6.378 (@@OptMaxAcceleration_mmds2=10;)
N0150 G01 X-3.068 Y40.776 (@@OptMaxAcceleration_mmds2=100; OptMaxFeedrate_mmdmin=12000;)
An embedded optimization setting changes how the steps from that line on are optimized, not how they are simulated: the play records each change against the step it happened at, and OptimizeToFiles solves each step against the setting in force where that step was built.
3. Run Simulation
Configuration can be interleaved between NC files. Settings apply to the files that follow:
OptRapidFeed_mmdmin = 4000;
PlayNcFile("NC/file1.nc");
OptRapidFeed_mmdmin = 8000;
PlayNcFile("NC/file2.nc");
Note
One OptimizeToFiles covers every file played since the steps were last cleared, by
ResetRuntime or the transport's Reset. A
PowerReset, or a switch of the controller brand or preset,
after the first of those files makes it write nothing
(What the Run Reports): optimize before the reset.
Excluding Lines from Optimization
To keep one NC line at the feed rate its program commanded:
N0140 G03 X-2.66 Y38.193 I-103.796 J7.172 (@@Preserve();)
Preserve covers the line it is written on and nothing else: it releases the optimizer again at that line's end, on its own, so it takes no closing command.
A range is opened with BeginPreserve before the first line's
motion, and closed with EndPreserve after the last line's
— the @@^ marker:
N0140 G03 X-2.66 Y38.193 (@@BeginPreserve();)
N0150 G01 X-3.068 Y40.776
N0160 X-3.555 Y43.338 (@@^EndPreserve();)
EndPreserve() releases the optimizer the moment it runs, which is what the marker on its line
decides. After the block, as above, the range covers all three lines. Before the block — written
@@ — it would release the optimizer ahead of its own line's motion, and N0160 would be optimized
like any other line while N0140 and N0150 stayed preserved.
A range still open when its program's play ends — no EndPreserve() reached, or a Stop inside the
range — closes there, so the next program is optimized as its own lines say.
Warning
Do not combine UpdateNcOptOption inside the SessionStepBuilt event with NC-embedded optimization commands. This may cause undefined behavior due to parallel computation.
4. Generate Optimized NC Files
OptimizeToFiles writes the optimized NC programs:
OptimizeToFiles("Cache/Opt-[NcName]");
The [NcName] template is replaced with each input NC file name.
What the Run Reports
An optimization reports itself to the Shell tab of the Session Messages panel, as a fixed sequence of rows:
| Row | Stage |
|---|---|
Start NC optimization. |
the pass opens, and the scan of the program begins |
NC line to step segment mapped. Un-optable lines are filtered. |
the scan closed: each line matched to the steps it made, the lines that cannot be optimized left out of the solve |
Computing Optimized Feed by indivisual step.. |
the per-step feed solve |
Optimization Feedrate built. |
the feed solve closed |
Constrain feedrate By expaneded segment.. |
the extended-distance constraint |
Constrain Feedrate By Acceleration.. |
the acceleration constraint |
Build Compensation.. |
the compensation build |
Regenerate NC commands.. |
the NC text is rewritten |
File optimized: <path> |
one row per written file |
Total N files optimized. |
the pass is over |
When the session's steps were played across a reset of the NC pipeline — a script's
PowerReset, a switch of the controller brand or preset, or a
session ended (EndSession()) and begun again with its steps kept — the optimizer cannot match the
programs played since the reset to their steps, so it writes nothing, and one error row stands in
place of the whole ladder: The NC pipeline was reset while the strip held N positions …
(OptimizeNc--StripSpansPipelineReset). Optimize before the reset, or clear the steps first —
ResetRuntime in the script, or the transport's Reset — and
play the programs to optimize again. The script or mission that asked for the optimization goes on.
Other rows can join the ladder. While the program is scanned, a tool change to a tool the optimizer
cannot use is a warning: Tool Not Found: <id> at each change to a tool the tool house holds no
milling cutter for, and, once per tool, Tool <id>: its cutter carries no optimization limit; … or
Tool <id>: optimization is off in its cutter's optimization limit; …. Two errors end the pass at
the scan, with no mapping row
and straight to a green Total 0 files optimized.: NC optimization aborted: the piece SentenceIndex stream is not strictly increasing … (NcOpt-Pieces--NonMonotonic), when the program calls a
subprogram or a macro after a jump — a GOTO, or a loop going back to its head or out past its
end — and NcOpt--Exception, when the scan itself threw. During the feed solve, a step whose solve
threw is an error row,
NC optimization step <i> (<file and line>) failed; …, at most twenty of them in a run; a failed
read of the step cache is an error row too (StepLuggage-Read--Fault or StepBrief-Read--Fault,
below); and the spindle capability adds a warning (NcOpt-SpindleSpeed--Warning) once per
run for each of its two wordings, Continuous condition not set in Spindle Capability. when it has
no continuous torque or power contour, and The spindle limits and heat parameters cannot be read …
when a contour cannot be read at some steps' speed. Two warnings come just before the feed
solve's closing row: N step(s) failed while computing the optimized feed … when a step's solve
threw (see
When a Step Cannot Be Solved),
and The source-feedrate floor holds N step(s) above a load target …
(NcOpt-Feed--SourceFloorOverTarget) when the source-feedrate floor kept steps above a force or
safety target, with the count per target. With a deflection compensation on, Build Compensation..
is followed by Deflection compensation built on N of M steps; …. While Regenerate NC commands..
prepares the edits — before the first File optimized: row — a block whose text the optimizer cannot
read when it decides whether to restate an F on it, or to put the file's first F on it, adds an error
row (NcOpt-Parse--Error) each time it is asked: a block left out of the solve then keeps its text,
and an optimized block is still edited as the solve decided — its F written or its own F dropped, a
split block still cut into fragments — but the file's first F is not put on it and goes to the next
block that can take it.
A StepLuggage-Read--Fault or StepBrief-Read--Fault row means a stretch of the step cache could
not be read: the steps' recorded engagement for the first, their recorded physics for the second.
Either way the touched steps of that stretch are left at their simulated feed (still held within the
feed limits) rather than being solved without that record, so the output can be used; the stretch is
left unoptimized but for its non-cutting regions, whose feed is raised as usual. To have it
optimized, optimize again, and if the row comes back, Reset and play again to rebuild the step cache
before optimizing. With a deflection compensation on, the compensation build reads the step cache
again — a step whose feed is still its simulated one when the build runs is compensated from its
recorded physics, one whose feed the run changed (by the solve, or after it by the feed limits or the
extended-distance and acceleration constraints) from its recorded engagement — so either row can also
follow Build Compensation..: the steps of that stretch that needed the record it names then get no
compensation, and their feeds are not changed by it. A step a StepLuggage-Read--Fault in the feed
solve left at its simulated feed is therefore still compensated, unless a later constraint changed its
feed and the compensation build cannot read its recorded engagement either; one a
StepBrief-Read--Fault left there is compensated from its recorded physics only if the compensation
build can read that record, or from its recorded engagement when a later constraint changed its feed.
Three of those stages tick while they work, and they are the only instrument for telling a slow
optimization from a stopped one. The scan adds Scanning NC for optimization.. FileNo:<n>, LineNo:<m>
every thousand blocks scanned; the feed solve
adds Computing Optimized Feed by indivisual step.. FileNo:<n>, LineNo:<m> every thousand steps
solved — StepIndex:<i> instead, for a step that carries no source line — and the writer adds
Now optimizing to: FileNo.<n>, LineNo.<m> every thousand lines written. Each names the source
file and line the run has reached, so a ladder whose last row keeps advancing is a run still moving
through the program, and one that has gone quiet without reaching Total N files optimized. is
not. A stage with fewer than a thousand blocks, steps or lines to get through adds no counting row
between its first row and its last — for the scan, which has no opening row of its own, between
Start NC optimization. and the mapping row — so a short program crosses the whole ladder in
near-silence.
Stop reaches inside an optimization. The transport's Stop is tested before each block while the
program is scanned, between steps during the per-step feed solve, and before each output file and
each destination piece while files are written, so a stopped optimization halts at the next block,
step or piece rather than running to the end. The stages between are not interrupted: a Stop that
lands in the extended-distance constraint or the compensation build takes effect when that stage
ends, and one that lands in the acceleration constraint lets the compensation build after it run
too. One that lands after the compensation build — in the last constraint pass, which has no row of
its own, or while Regenerate NC commands.. prepares the edits — is seen only at the first file, so
Regenerate NC commands.. can still follow the Stop. The Shell ladder says so: an
optimization canceled. row is added before the pass ends, and a Stop during the feed solve also
closes the solve with Optimization Feed Process canceled. in place of
Optimization Feedrate built..
Important
A stopped optimization still ends on the green Total N files optimized. row a completed one ends
on. That row is not a statement that the optimization finished — read the row above it. N counts
the files the run had begun writing, so a Stop before the writing begins ends on
Total 0 files optimized., and a Stop during writing counts the file it was in the middle of,
which is left short. The files after it are not begun: an output an earlier run wrote for one of
them stays as that run left it.
An optimization that cannot write an output file — one held open by another program, or read-only —
stops there, with no closing rows: the files written before it stay. Run as a mission's command or
from a Post-Execution, the whole run ends with it, nothing below it in the mission runs, and Shell
shows The play stopped on an error: … with the reason. Run from a Script, the script ends at that
call, Shell shows Script […] faulted: …, and the mission goes on with its next command. A
per-step log (below) that cannot be opened ends the optimization the same way, earlier: right after
Computing Optimized Feed by indivisual step.., before any step is solved or any file written, with
the logs opened before it closed again. Free the file, or turn the log off.
5. Verify Optimization Results
Optimization Logs
The per-step log is written by default. Every optimization drops one .IndependentStepAdjustment.log
beside each optimized NC file that had steps to solve, named after that optimized file and written
afresh, by a run that reaches the feed solve, over the one an earlier run left there, and it records
which constraint limited each step.
EnableIndividualStepAdjustmentLog is the switch that stops it
being written:
EnableIndividualStepAdjustmentLog = false;
An optimization stopped or failed during the feed solve leaves that file short. Its buffered tail is
written out only when the feed solve runs to completion; when the pass is cut off, the lines still in
the buffer are dropped, and so is any step line held back in the ordering window waiting for a lower
step index that never arrived. Batches reach the file at most once a second, so the missing tail can cover the
last second of solving as well as the steps that were never reached. Read the log as a complete
record only for a run whose feed solve closed with Optimization Feedrate built.
Each row of the .IndependentStepAdjustment.log file opens with the source NC file and line, the step index and the cutter location, then lists the feed per tooth every active criterion allowed:
| Field | Criterion |
|---|---|
FrtBySourceFeedrateFloor_mm |
not a criterion: the source-feedrate floor as a feed per tooth, stated on every solved step where it lies above the other lower limits and so is the lower limit the criteria are solved from — its presence alone does not mean the floor held the step |
FrtByPreferedForce_mm |
target cutting force |
FrtByYieldingStressRatio_mm |
yielding stress |
FrtBySpindleTorqueRatio_mm |
spindle torque |
FrtBySpindlePowerRatio_mm |
spindle power |
FrtByThermalYieldingRatio_mm |
thermal yield |
FrtByCustom_mm(n) |
the n-th custom criterion the script registered |
FrtByReliefAngle_mm |
relief-face contact — see Relief Face Avoidance |
The step's feed per tooth is the lowest of the six criteria from FrtByPreferedForce_mm to FrtByCustom_mm(n); the relief-angle pass then runs on that value, held within the step's upper feed-per-tooth limit — a step with no load row at all is tried at that limit itself — so its row is the last word rather than one vote among the others. A criterion equal to FrtBySourceFeedrateFloor_mm was held up by the floor rather than settled by its own target. The one exception is the target cutting force while EnableDepthSplition is on, its default: a step the force target pins on the floor logs the step's own feed per tooth on the FrtByPreferedForce_mm row instead of the floor, and the step is still written at the floor.
Every criterion row except the target cutting force carries the solver's status for that criterion in brackets. Acceptable means the criterion was met at the feed the row states, without a solve: on the five load rows, the criterion's value is under its target at the upper feed limit, so the criterion does not limit the step and the row states that limit; on the relief-angle row, the relief face was clear at the feed the load criteria chose, or — on a step with no load row — at the upper feed-per-tooth limit, which the row then states. Solved means the bisection landed within the criterion's tolerance of its target (1 % of the target on the load rows). OverIteration means ten halvings did not, and the value is then the trial closest to the target among the acceptable ones — or among all of them when none is acceptable, which is the lower limit when the target is still exceeded there. A load row only reaches the bisection when its value at the upper limit is not under the target — at or over it, or not evaluable there — so an OverIteration value lies between the limits or on the lower one; a criterion that cannot be evaluated at any trial feed ends OverIteration on the upper limit instead. On the relief-angle row the tolerance is 0.0001 mm/s of relief-face colliding speed, so a bisected relief row mostly reads OverIteration: the highest trial feed at which the face was clear, or the lower limit when the face collides there too. The relief-angle row reads ∞ only when the upper feed-per-tooth limit is itself infinite — the option's maximum feed rate, its maximum feed per tooth and the cutter's maximum feed per tooth all Infinity — and the step then sets no limit of its own: the probe is tried once at that infinite feed and reads the face clear. A criterion with no row at all was switched off (its safety factor is 0, or the target cutting force is Infinity), was not evaluable for that tool, read 0 on the step as played (a step with no stored load, or no stored tool heating, is not solved for that criterion), or — on the torque and power rows — met a spindle whose limits could not be read at the step's speed; the relief-angle row is absent when the cutter's EnableLimitByReliefAngle is off. The criteria are solved one at a time and the lowest wins, which assumes that a criterion met at a feed is met at every lower feed; a value that peaks between the limits — a thin-chip step's thermal yield ratio at its minimum chip thickness — is the known exception.
Four rows replace that list rather than joining it:
| Field | Meaning |
|---|---|
FrtByUnTouched: inf |
the step cuts nothing, so no physical criterion applies and the step itself sets no limit |
FrtByNoForceBrief: inf |
the step cuts but has no force brief to solve on — its tool's cutter is not a milling cutter, or the stored brief is missing beside its luggage — so no physical criterion applies and the step itself sets no limit |
FeedrateByNoData_mmdmin: <feed> |
no luggage stored for the step — no tool or no milling coefficients on it, or the physics switched off for the play — or its stretch of the step cache could not be read (StepLuggage-Read--Fault, StepBrief-Read--Fault), so it keeps its feed |
StepFailed: <exception> |
the solve threw; the step keeps its simulated feed and is reported as an error — see When a Step Cannot Be Solved |
Embedded Log Comments
Control embedded log verbosity with EmbeddedLogMode:
| Mode | Description |
|---|---|
None |
No log comments |
SimpleLog |
StepIndex on re-interpolated lines; LineNo on last interpolated line per original line |
FullLog |
StepIndex and LineNo on all lines |
Important
The mode is read by the legacy optimization path. While
EnableSoftNcRunner is on and the session holds played
NC — the default — the optimizer writes the SimpleLog shape whatever the mode says: every
re-interpolated fragment carries its StepIndex, the last fragment of each source line adds that
line's LineNo, and a line the optimizer did not split carries no note.
The note is written in the controller's comment grammar, so the optimized file stays legal for the control that reads it. On the Fanuc family it is parenthesized:
G01 X10.0 Y20.0 F500 (src(LineNo: 140, StepIndex: 256))
On Heidenhain it is a ; comment, because a TNC reads parentheses as code:
120 L X+35 Y-11.7 R0 F500 ;src(LineNo: 140, StepIndex: 256)
On a Heidenhain project a feed word the optimizer has to insert is also placed in the element order
a TNC enforces — after the coordinate words, after the rotation direction DR+ / DR-, and after
the radius compensation RL / RR / R0. A block that read L X+10 Y+20 RL comes back as
L X+10 Y+20 RL F500.
Tracking Individual Step Constraints
To isolate which physical quantity limits each step, disable smoothing:
OptMaxAcceleration_mmds2 = double.PositiveInfinity;
OptFeedrateAssignmentRatio = 0;
OptExtendedPreDistance_mm = 0;
OptExtendedPostDistance_mm = 0;
The four smoothing settings are what this block changes. The per-step log is already on, and
EmbeddedLogMode does not reach the pipeline that runs by default — see above.
Post-Optimization Simulation Differences
Optimized feed rates produce different interpolation points, causing:
- Different simulation mesh errors
- Surface morphology changes at the surface roughness level (more pronounced at corners)
Simulated physical quantities after optimization may be slightly above target values due to these differences.
Tip
For abnormally low optimized feed rates at corners, refer to Corner Feed Rate Optimization.
Tool Breakage Solutions
If the simulation shows yielding stress ratio, max spindle torque ratio, or max spindle power ratio above 100%, consider:
- Modify the toolpath to reduce cutting width/depth
- Use HiNC optimization to adjust feed rates, bringing these ratios below 100%
For thermal edge chipping, reduce the spindle speed to allow heat dissipation.
Complete Script Example
EnablePhysics = true;
LoadCuttingParaByFile("Material.mp");
OptEnableFeedrate = true;
OptEnableInterpolation = true;
OptRapidFeed_mmdmin = 4000;
OptMinFeedrate_mmdmin = 100;
OptMaxFeedrate_mmdmin = 4000;
OptMaxAcceleration_mmds2 = 10;
OptExtendedPreDistance_mm = 3;
OptExtendedPostDistance_mm = 2;
OptSpindlePowerSafetyFactor = 1.5;
OptSpindleTorqueSafetyFactor = 1.5;
OptThermalYieldSafetyFactor = 0;
OptPreferedForce_N = double.PositiveInfinity;
PlayNcFile("NC/file1.nc");
OptimizeToFiles("Cache/Opt-[NcName]");
WriteStepFiles("Output/[NcName].step.csv");
See Also
- NC Optimization (Concepts) — theory and objectives
- Corner Feed Rate Optimization
- Workflow: Milling Force Parameter Training — prerequisite: training cutting parameters
- Workflow: Basic Machining Simulation — basic simulation setup
- SessionShell — SessionShell quick-reference