mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-11-08 23:41:09 +00:00
The light's frustum ends at the highest visible layer, so its near plane cut through parts above the range (a bunny's ears) during the cut-face stencil count: only their far faces were counted, the count read them as open and the bottom cap cast an ear-shaped shadow on the bed. Clamp depth during the count so every face is counted.
148 lines
11 KiB
Markdown
148 lines
11 KiB
Markdown
# G-code preview while dragging
|
|
|
|
The sliced preview draws every toolpath segment of the plate as an instanced box. On a large
|
|
plate that is tens of millions of segments, and the frame is GPU-bound: the cost is the number of
|
|
instances drawn, not anything the CPU does per frame. Dragging the camera over such a plate cannot
|
|
keep up. The `preview_reduced_detail_mode` preference (*Graphics > G-code Preview*, off by
|
|
default) lets the preview draw less while the user drags and put the full toolpaths back when they
|
|
let go.
|
|
|
|
| Preference | Values | Effect |
|
|
|---|---|---|
|
|
| `preview_reduced_detail_mode` | `off`, `solid`, `shell` | what is drawn while dragging |
|
|
|
|
libvgcode (`src/libvgcode`) builds and binds the reduced toolpath set, `GCodeViewer` maps the
|
|
preference onto it and draws the solid model, and `GLCanvas3D` decides when the user is dragging.
|
|
The OpenGL ES path keeps a single set and ignores the preference.
|
|
|
|
## Two sets, one walk
|
|
|
|
`ViewerImpl::update_enabled_entities()` walks the visible vertex range once and fills two segment
|
|
index buffers side by side: the **full** set and the **reduced** set (segments and options).
|
|
Building them together is what makes switching free: starting or ending a drag is a buffer
|
|
binding, never a rebuild. A change of mode does rebuild. Nothing is built while the mode
|
|
is off, and the reduced buffers are then uploaded empty so that the last set does not stay
|
|
allocated.
|
|
|
|
Whatever the mode leaves out, the layers at the bottom and top of the visible range are kept whole:
|
|
they are the faces the range cuts open, and the top is what the user is looking at. An end layer
|
|
with no infill of its own does not close the print there: infill combination prints the sparse
|
|
infill of a few layers in one, leaving the others walls alone, and a support layer with no object
|
|
layer at its height gets a layer of its own. Each end therefore runs from the range's end layer to
|
|
the nearest layer that closes the print, at most eight layers deep
|
|
(`Layers::get_whole_end_layers()`), and the `Viewer` reports the two bands so that the solid
|
|
model's cut can sit at their edges. A layer closes the print when every object printed on it has
|
|
infill on it: the G-code labels its moves with their object, and `LayersClosedByInfill` in the
|
|
wrapper that feeds libvgcode tallies walls and infill per object and layer while converting the
|
|
moves (`GCodeInputData::layers_closed`). Two objects on one plate rarely combine their infill on
|
|
the same layers, and one object's sparse infill on a layer says nothing about another's, which is
|
|
why the judgement is per object. Without that list, as for a preview built from the `Print`, the
|
|
layer table judges each layer from its roles as a whole: the nearest layer with sparse infill, and
|
|
only when none is within reach, as on a print with none, the nearest layer with any infill.
|
|
|
|
The realistic view's shadow-caster pass draws whichever set is bound, so the key of the static
|
|
shadow map includes whether the reduced set is: a map built during a drag is rebuilt on release.
|
|
In the solid model mode the shells, cut at the solid's heights, cast into the same map through
|
|
`GCodeViewer::render_solid_model_casters()`, with their cut faces: without them an open body lets
|
|
the light through, onto the bed through a band cut at both ends or onto its own far wall past the
|
|
end band's lines. The cut faces are counted and drawn as the visible pass draws them (below), which
|
|
takes a stencil the map has not got, so they go into a depth and stencil target of the viewer's,
|
|
the map's size, and are copied into the map by the `depth_copy` shader where nearer than what it
|
|
holds. The solid samples the map back under the light
|
|
the canvas passed on, so a body shades the plate, its neighbours and the end-band toolpaths as the
|
|
toolpaths it stands in for would, and takes their shadows in turn.
|
|
With the mode off the reduced flag is never raised, and a drag changes nothing in the preview.
|
|
|
|
### Modes
|
|
|
|
- `EndLayersOnly` (`solid` in the preference) keeps only the end bands, the supports and the
|
|
travels.
|
|
`GCodeViewer` then draws the sliced objects and the prime tower as opaque solids, see below.
|
|
- `ShellOnly` (`shell`) keeps every role but the sparse infill, internal solid infill, internal
|
|
bridge infill and gap fill, which lie under the skins or between the walls. Walls, top and
|
|
bottom surfaces, bridges, supports and the prime tower are drawn whole, so from outside the
|
|
print looks as it does at rest. The roles come from
|
|
the slicer, so the classification is exact wherever the slicer's is; what it cannot express is
|
|
the inside of the prime tower and of a support, which share one role with their outside and are
|
|
kept whole. A profile with no top or bottom shell layers, or no walls, leaves the infill on the
|
|
surface, so `GCodeViewer` turns the hiding off for such a print when it loads it, from the
|
|
print's default region settings; per-object overrides are not consulted.
|
|
|
|
## The solid model
|
|
|
|
The preview already loads the sliced objects as shells for its translucent ghost.
|
|
`GCodeViewer::render_solid_model()` draws those shells opaque with the
|
|
`gouraud` shader, whose z range cuts them to the visible layer range: from the top of the bottom
|
|
band to the bottom of the top band, since those layers are drawn as toolpaths and a face of the
|
|
model in either plane would flicker against them. The translucent ghost is
|
|
drawn first, as in every other mode, so the part above the cut stays visible while dragging. The
|
|
cut itself is capped, one cap of one body at a time: a pass counts, per pixel and with the depth
|
|
test off, the body's back faces less its front faces into the stencil buffer, with the body cut
|
|
at that cap alone and whole at its other end, which is zero where the ray misses the body or
|
|
crosses it whole and one where it leaves through the opening, that is, where the cap plane lies
|
|
inside the body; a quad in that cut plane, in the cap's colour darkened, is drawn where it is not
|
|
zero. The count runs with depth clamping on, since the light's frustum ends at the highest
|
|
visible layer: a part above the range, cut by its near plane, would keep only its far faces and
|
|
count as open, and cast a cap-shaped shadow of itself. Counting both cuts in one pass would see an open tube: a ray through both openings as closed
|
|
and one in through a wall and out through the other opening as open, with the cap drawn in the
|
|
air. Between the lines of a sparse layer on the cut, that quad is what shows, rather than the
|
|
first layer or the bed far below moving with the camera. The toolpath layers of the reduced set are
|
|
drawn afterwards and cap the cut with what was really printed there; the band's reach to a layer
|
|
with infill is what puts real infill lines, not a bare cap, inside the walls. The shells hold only the objects, so while this mode is on the prime tower is added from its sliced
|
|
mesh, positioned as the print placed it. It is added or removed on its own when the mode changes,
|
|
without reloading the objects, keeps its opaque color so that it never appears among the
|
|
translucent shells, and stays out of their bounding box. Supports have no mesh, so the reduced set
|
|
keeps their toolpaths whole on every layer, as the shell mode does, and the objects stand on them
|
|
as they do at rest; the travels, which the solid does not stand for either, are kept with them when
|
|
the legend shows them. `libvgcode::keep_in_reduced_set()` holds the rule for both modes. `load_shells()` drops every non-model-part volume, so a negative volume is not
|
|
cut out.
|
|
|
|
The solid stands in for the toolpaths, so it wears their colours. In the views that colour by
|
|
filament (Summary, Filament, Tool) each body keeps the filament colour its shell was loaded with.
|
|
In every other view `GCodeViewer::update_solid_model_colors()` samples the toolpaths through
|
|
`Viewer::get_vertex_color()`: the body takes the outer wall's colour over the layers the solid
|
|
stands for, each cap the infill's in the layer it is cut under, sparse infill before solid, and
|
|
the prime tower its own role's. One vertex stands for the role, chosen by
|
|
`Viewer::find_extrusion_vertex()`: in a view that grades the toolpaths by a value, the
|
|
length-weighted median of that value over the role's extrusions in up to eight layers spread over
|
|
the interval, so that neither the acceleration ramp a wall starts on nor one layer with the fan
|
|
turned up sets the colour of the whole body; in any other view the first extrusion found. The
|
|
colours are resolved when a drag starts and when the view type or the bands change, not per frame.
|
|
|
|
A plate whose shells are not loaded keeps drawing toolpaths, since the solid model would leave
|
|
only the end bands. So does a print whose layers do not rise in height from one to the next, as
|
|
when it is printed object by object: the cut is a single height range for the whole plate, which
|
|
the ends of the layer range then do not bound. And so does a belt printer's: its layers are planes
|
|
tilted at the belt angle, with heights in the G-code's frame, which no horizontal cut follows.
|
|
|
|
A section view cuts the solid by its plane as it cuts the toolpaths, and the section face is one
|
|
more cap, counted on the body cut by that plane alone and drawn between the two heights.
|
|
|
|
The solid is drawn opaque whatever the filaments' alpha: a translucent filament shows its toolpaths
|
|
inside through the walls at rest, and the solid has no inside to show.
|
|
|
|
## Deciding that the user is dragging
|
|
|
|
`GLCanvas3D::_update_preview_interaction()` runs at the top of every preview frame, before the
|
|
canvas decides whether to reuse its cached scene, so that the switch lands in that frame. Dragging
|
|
is `GLCanvas3D::is_user_interacting()`, the same answer the scene cache reads: the camera, the
|
|
navigator, a gizmo, the rectangle selection or either slider being held, or the camera still
|
|
turning after a click on a navigator face, which the navigator animates over the frames that
|
|
follow with no button held. A slider reports this from
|
|
ImGui's active id rather than its dirty flag, which is raised and consumed inside one frame. A
|
|
wheel step has no duration, so it holds the reduced set for a 150 ms settle time instead, and the
|
|
frame that restores the toolpaths is scheduled for when that time runs out, since the render timer
|
|
only wakes the idle loop. A drag cut short by focus or capture loss is ended explicitly, and a
|
|
button release wakes the idle loop, because on some platforms nothing else would until the next
|
|
input.
|
|
|
|
## Reused scene frames
|
|
|
|
`GLCanvas3D` keeps its last scene pass for frames that only rebuild the overlay (`SceneCache`). Its
|
|
key covers the canvas size, the camera and hover state, not what the toolpath sets draw, so a frame
|
|
that reuses the scene must never be one on which the set is switched.
|
|
`_update_preview_interaction()` therefore reports whether the bound set changed, and a frame on
|
|
which it did redraws the scene. The canvas neither captures nor reuses the scene while the user
|
|
drags, so no reduced frame outlives a drag, and the frame that ends a wheel's settle time is
|
|
requested as a full frame.
|