Skip to content

gcode: estimate how long a program takes during the parse - #4554

Open
alex-pres wants to merge 1 commit into
LinuxCNC:masterfrom
alex-pres:pr-time-estimate-cpp
Open

alex-pres wants to merge 1 commit into
LinuxCNC:masterfrom
alex-pres:pr-time-estimate-cpp

Conversation

@alex-pres

@alex-pres alex-pres commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Proposal Add a C++ TimeEstimator alongside GCodeRenderer so the parse reports a run-time estimate, and show that estimate in the GUIs while a program runs.

Estimation is done during gcode/preview rendering parse so it is almost free.

It is supposed to replace program execution progress calculated by feed length [ used by gmoccapy ] .

out.mp4

Assumptions made

Motion model

  • Not a trajectory planner. A 32-move look-ahead (matching TP_DEFAULT_QUEUE_SIZE) over trapezoid profiles, not TP's actual blending. Moves beyond 32 ahead cannot influence the current one.
  • Junction rule stands in for G64. Junction speed is min(v_peak) * max(0, cos θ) — collinear passes at full speed, a right angle stops. G61 vs G64, the blend tolerance, and naive-cam are not distinguished; every program is timed as if blended by that one cosine.
  • One triple decides length and direction (getStraightVelocity's rule): XYZ if it moves, else UVW, else ABC. A move mixing linear and rotary is capped by all nine axes; a pure one only by its own kind.
  • Arc speed is capped at sqrt(0.866 · a · r) (BLEND_ACC_RATIO_NORMAL), and arcs enter as their drawn segments, so segment chord length sets the junction angles.
  • If No acceleration limit passed ⇒ instant to cruise speed. An axis with MAX_VELOCITY but no MAX_ACCELERATION contributes no ramp time at all.
  • [TRAJ] velocity/acceleration caps apply only to cartesian moves, never to rotary-only ones.
  • Rapids use the per-axis caps; if no axis in the move states one, they fall back to the current F rate read as units/min.

Nominal 100%

Overrides, adaptive feed, feed hold, and M0/M1/M66 waits are not modelled. M1xx costs zero time — it only drains the queue to a stop. The number is what the program would take untouched.

Specific commands

Case Assumption Flag
Probe (G38) timed at the full commanded length; where it trips is unknowable at parse time probe-full-length
G96 with no D arrives as 1e29 and is treated as unbounded rpm css-unbounded
CSS feed move split into ≤32 collinear pieces at a 1.25 radius ratio, each timed at its midpoint rpm
G95/G33/G76 with S=0 move is skipped entirely, contributing no time sync-no-spindle
Any move with no derivable velocity skipped, not treated as infinite zero-feed
Rigid tap down at pitch · rpm, retract at pitch · rpm · retract_scale, full stop each way
M6 costs [DISPLAY]TOOL_CHANGE_SECONDS, default 0 — not an ini standard, so most configs get a free tool change
G4 dwell exactly its P seconds, with a stop either side
Tool-offset change free; the chain point moves frames with no time passing

Spindle dynamics are absent throughout: a commanded S is in effect immediately, with no spin-up and no wait-for-at-speed.

Table and GUI

  • Calculation results passed with with 1 second precision [ not less ]
  • One sample per second at most (TIME_SAMPLE_RESOLUTION), which bounds the table by runtime rather than line count. Samples are exact — thinning drops rows, it never averages.
  • The table is in emission order, so a subroutine or O-loop puts one line in several brackets; a pair running backwards is read as a loop boundary, not a stretch of program.
  • Which occurrence you're on is picked by wall-clock elapsed since the run started — which assumes the run began at the top of the file and has tracked the estimate. Run-from-line or a long override excursion picks the wrong occurrence in a looping program (remaining is still computed from the line, so only the choice among duplicates is affected).
  • at_line clamps: before the first sample = 0, past the last = total.
  • Readouts pass through a 2 s hysteresis (steady_seconds) so per-sample interpolation noise doesn't flicker the number.
  • The estimate is opt-in: a canon gets one only by subclassing gcode.TimeEstimateCanon and supplying machine_limits.

Total time is shifted while program executes [ if necessary - for example tool change takes long or program paused ] . After program stop it reverts back to calculated value.

Add a C++ TimeEstimator alongside GCodeRenderer so the parse reports a
run-time estimate, and show that estimate in the GUIs while a program runs.
@grandixximo

Copy link
Copy Markdown
Contributor

If you had an error, or you stopped midway, users might want the progress time of when that happened trough the process to be visible, instead of resetting back to the estimate? Just looked at the video resetting back, not sure what happens for real will test tomorrow...

@alex-pres

alex-pres commented Sep 17, 2026

Copy link
Copy Markdown
Contributor Author

If you had an error, or you stopped midway, users might want the progress time of when that happened trough the process to be visible, instead of resetting back to the estimate? Just looked at the video resetting back, not sure what happens for real will test tomorrow...

When program executes it simply adds calculated seconds of remaining steps to time that already passed.

After program finished it resets back to calculated value [ exactly like you loaded the same program again ] . I believe that is correct because if program was paused for something, tool change take long time, or if user used feed-override 10%, we cannot expect it to happen on next run.

It is possible to display difference of last run to estimate but it will overload UI with info.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants