Skip to content

Commit aaa4b35

Browse files
authored
Merge pull request #4481 from alex-pres/pr-render-preview-cpp
optimization: Build the G-code preview in C++
2 parents bc3248e + dad6bc1 commit aaa4b35

30 files changed

Lines changed: 7484 additions & 2504 deletions

docs/src/getting-started/updating-linuxcnc.adoc

Lines changed: 79 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -264,11 +264,12 @@ and the `get_*`/`is_*` callback contract). Move any custom drawing onto those
264264
supported entry points, or draw with your own modern-OpenGL code.
265265
====
266266

267-
The immediate-mode drawing helpers used by the old renderer remain available for
268-
compatibility (`linuxcnc.draw_lines`, `linuxcnc.line9`, `linuxcnc.draw_dwells`,
269-
`linuxcnc.positionlogger.call()`); they are unused by the in-tree GUIs, which
270-
bake geometry to VBOs and upload the backplot from `positionlogger.points()`,
271-
but still work for out-of-tree tools under a legacy/compatibility context.
267+
The immediate-mode drawing helpers of the old renderer (`linuxcnc.draw_lines`,
268+
`linuxcnc.line9`, `linuxcnc.draw_dwells`, `linuxcnc.positionlogger.call()`) are
269+
retired. They keep their names, signatures and argument checking so that
270+
out-of-tree callers still import and run, but they draw nothing and raise a
271+
`DeprecationWarning` on first use. The in-tree GUIs bake geometry to VBOs and
272+
upload the backplot from `positionlogger.points()`.
272273

273274
=== Notes for integrators and driver authors
274275

@@ -298,6 +299,74 @@ but still work for out-of-tree tools under a legacy/compatibility context.
298299
drawing; only its camera consumes the (now GL-free) explicit matrices from
299300
`glnav`.
300301

302+
=== The program is built in C++ during the parse
303+
304+
`gcode.parse` no longer drives the preview through per-move Python callbacks.
305+
For a canon that subclasses `gcode.RendererCanon` - `rs274.glcanon.GLCanon` is
306+
one, so every in-tree preview is - the whole program is built in C++
307+
(`GCodeRenderer`, `src/emc/rs274ngc/gcode_renderer.{hh,cc}`): the
308+
g92/rotation/g5x transform, arc segmentation, rigid taps, `(AXIS,hide)`
309+
suppression, the vertices per drawn plane, the extents, the path lengths and
310+
the dwell and tool-change records. The finished program is handed over once,
311+
at the end of the parse, as a `gcode.PreviewGeometry` through the canon's
312+
`adopt_geometry()`. A parse reads two more things off such a canon:
313+
`program_geometry` (the GEOMETRY strings and the rotation offsets) and
314+
`arcdivision`, which defaults to 64 and is read once at parse start. Every
315+
parse starts from a zero transform with nothing drawn; where the machine stands
316+
arrives as the caller's initcode (a `G53 G0` per axis), which the
317+
leading-traverse drop repositions on rather than draws. A `RendererCanon`
318+
subclass without a callable `adopt_geometry` is a `TypeError` from
319+
`gcode.parse`, not a silent fall back to callbacks.
320+
321+
The per-event canon protocol is unchanged for every other canon:
322+
`rs274.interpret.PrintCanon`, the interpreter tests and out-of-tree users of
323+
`gcode.parse` still receive `straight_feed`, `arc_feed`, `next_line` and the
324+
rest exactly as before, and `rs274.interpret.Translated` /
325+
`ArcsToSegmentsMixin` remain for them. The `gcode` module itself was rewritten
326+
on pybind11; its functions keep their names and signatures. One behaviour
327+
change: `gcode.linecode()` snapshots the running parse, and raises
328+
`ValueError` when no parse is in progress.
329+
330+
[WARNING]
331+
.BREAKING: out-of-tree canons that subclass `rs274.glcanon.GLCanon`
332+
====
333+
On a rendered parse the interpreter forwards only `next_line` (on the handful
334+
of lines that still forward, not once per line), `comment`, `message`,
335+
`change_tool`, `check_abort`, the `get_*` queries and `parameter_file`.
336+
Consequently:
337+
338+
* *Overrides of the per-move methods are never called.* `straight_traverse`,
339+
`straight_feed`, `straight_probe`, `arc_feed`, `straight_arcsegments`,
340+
`rigid_tap`, `dwell`, `user_defined_function`, `set_g5x_offset`,
341+
`set_g92_offset`, `set_xy_rotation`, `tool_offset`, `set_plane`,
342+
`select_plane`, `set_feed_rate` and `set_spindle_rate` no longer exist on
343+
`GLCanon`, and a subclass that defines them is not called back. Read the
344+
finished program from `canon.program_geometry` instead.
345+
* *`next_line` is not a per-line tick.* A progress bar overrides
346+
`renderer_progress(lineno)`, which fires on the parser's 100 ms tick and
347+
before each forwarded callback. AXIS and QtVCP show `(AXIS,notify)` /
348+
`(PREVIEW,notify)` messages by checking once more after `load_preview`
349+
returns, since no `next_line` follows the comment.
350+
* *Parse-state attributes are gone.* `lo`, `first_move`, `xo`..`wo`,
351+
`suppress`, `in_arc`, `plane`, `feedrate`, `g5x_index`, `g5x_offset_*`,
352+
`g92_offset_*`, `rotation_xy`, `rotation_sin`, `rotation_cos` and
353+
`rotate_and_translate()`. `GLCanon` no longer mixes in `Translated` or
354+
`ArcsToSegmentsMixin`; the renderer keeps its own copy of the offsets, the
355+
rotation, the plane and the feed rate and forwards none of them. Nothing in
356+
the tree reads them - the DROs read the status channel.
357+
* *The per-move lists are gone.* `traverse`, `feed`, `arcfeed`, `moves`,
358+
`move_cats` and `preview_zero_rxy` raise `AttributeError` on read, naming
359+
the replacement: the program record's `positions()`/`lines`/`kinds`,
360+
`g0_length`/`g1_length`/`run_time()` and `extents_zero_rxy`.
361+
* *`tool_list` and `dwells` fill at the end of the parse* (in
362+
`adopt_geometry`) rather than growing during it. `dwells` keeps its column
363+
order and raw machine coordinates.
364+
* *Still honoured:* `arcdivision` (set from `[DISPLAY]ARCDIVISION`), the
365+
`comment` vocabulary (`stop`, `notify`, the foam Z levels; `hide`/`show` are
366+
counted in C++ from the same text), and `change_tool`, which the interpreter
367+
still needs for a G43 after an M6.
368+
====
369+
301370
=== How the preview is put together
302371

303372
The drawing itself lives in `lib/python/rs274/glcanon_scene.py`, in four tiers.
@@ -337,18 +406,18 @@ the offset frame the offsets progressively build.
337406
Parts read a `FrameContext` - an explicit, enumerated list of machine, view and
338407
renderer state, built once per frame by `GlCanonDraw` - rather than the widget
339408
itself. That is what lets them be tested without a window: build a context by
340-
hand, call `part.draw(ctx)`, and assert on the vertices it emitted. See
341-
`tests/glcanon-scene/`.
409+
hand, call `part.draw(ctx)`, and assert on the vertices it emitted.
342410

343411
Click-to-select is not a part - it draws nothing to the screen. `Picker` renders
344412
the *same* program geometry into an offscreen framebuffer with line numbers
345413
encoded as colour and resolves the nearest hit; `GlCanonDraw.select(x, y)`
346414
delegates to it. It shares one `ProgramGeometry` with the drawing part, so the
347415
pickable geometry and the drawn geometry cannot drift apart.
348416

349-
Setting `GLCANON_SCENE_DEBUG=1` logs which parts the scene drew and which it
350-
skipped whenever that split changes, and reports depth/blend state a part left
351-
behind.
417+
Setting `GLCANON_DEBUG=1`, the preview's one verbosity switch, raises the
418+
`rs274` logger to DEBUG, checks `glGetError` after each pass, logs which parts
419+
the scene drew and which it skipped whenever that split changes, and reports
420+
depth/blend state a part left behind.
352421

353422

354423
== New HAL components

0 commit comments

Comments
 (0)