Add a Shell-Only Drag Mode That Keeps the Visible Surface of the Print

Outer walls is a role filter, which loses the prime tower, whose every segment
shares one role, and every top and bottom surface between the range's ends. A
fifth mode, Shell only, keeps what a geometric classification marks as the
visible surface of the print, of one layer in every N, plus whatever a view from
straight above or below sees of the layers it skips, so that a step does not
vanish.

The classification runs once per load, the first time the mode is needed, and
is purely geometric: each layer is rasterized into a half-millimetre occupancy
grid and closed so that sparse infill reads as solid, a cell is on the shell
when any of its six neighbours is empty, and a segment is kept when at least
half of the cells it crosses are. The closing dilates each connected component
on its own and leaves a cell two components reach empty, so the gap between two
close objects is never bridged. The first inner wall beside an outer wall is
kept as well, since the step of a sloped surface is narrower than a cell, and
the interior infill roles and gap fill are never taken, whatever the geometry
says. The layers are split across up to eight workers, and an allocation
failure on a huge print falls back to keeping everything but the hidden infill.

Separate from the other modes so that it can be dropped on its own.
This commit is contained in:
Hanif Koh
2026-09-23 12:14:16 +08:00
parent 493896260e
commit 526a91d323
8 changed files with 583 additions and 13 deletions
+42 -1
View File
@@ -9,7 +9,7 @@ let go.
| Preference | Values | Effect |
|---|---|---|
| `preview_reduced_detail_mode` | `off`, `solid`, `layers`, `outer_walls` | what is drawn while dragging |
| `preview_reduced_detail_mode` | `off`, `solid`, `layers`, `outer_walls`, `shell` | what is drawn while dragging |
| `preview_reduced_detail_layer_stride` | 120 | one layer in every N is kept by the toolpath modes |
libvgcode (`src/libvgcode`) builds and binds the reduced toolpath set, `GCodeViewer` maps the
@@ -35,6 +35,10 @@ are the faces the range cuts open, and the top is what the user is looking at.
- `LayersOnly` (`layers`) keeps every role of one layer in every stride.
- `OuterWallsOnly` (`outer_walls`) keeps the outer and overhang perimeters of one layer in every
stride. The prime tower and supports have other roles and are left out.
- `ShellOnly` (`shell`) keeps what the shell extraction below marks as visible surface, of one
layer in every stride, plus whatever a view from above or below sees of the skipped layers, so
that a step does not vanish. It is the only mode that knows the prime tower's outside from its
inside.
## The solid model
@@ -51,6 +55,43 @@ and `load_shells()` drops every non-model-part volume, so a negative volume is n
A plate whose shells are not loaded keeps drawing toolpaths, since the solid model would leave
only the end layers.
## Shell extraction
`ViewerImpl::update_shell_bitset()` classifies every extrusion segment once per load, on demand
the first time the shell mode needs it, and records the result in four bit sets. It is purely
geometric so that the wipe tower, whose every segment shares one role, works as well as the
objects.
Each layer is rasterized into a coarse 2D **occupancy grid** over the print's footprint: cells
are 0.5 mm, or coarser so that the grid is at most 1024 cells across. The footprint is then
**closed** with a radius of 2.5 mm so that sparse infill and support read as the solid area they
belong to, while holes wider than 5 mm stay open. The closing dilates each 8-connected component
separately and leaves a cell that two components both reach empty, so the gap between two objects
standing close together is never bridged and both of their facing walls stay on the shell;
fragments under eight cells do not spread and are absorbed by whatever reaches them. A separable
erosion shrinks the result back, and the raw cells are OR-ed in again so that a closing never
loses one.
A cell is a **shell cell** when it is filled and any of its six neighbours, four in the layer,
one below, one above, is not. A segment is on the shell when at least half of the cells it
crosses are shell cells: walls run along the shell, infill only touches it at the ends. The
interior infill roles and gap fill are excluded regardless, since short infill segments hugging a
wall would otherwise pass by the thousand.
Two refinements keep sloped surfaces closed:
- **Near-shell inner walls.** The step between one layer's outer wall and the next is often
narrower than a cell. An inner wall (`Perimeter`) segment whose midpoint lies within a line and
a half of an outer or overhang perimeter of the same layer is kept as well.
- **Top and bottom visibility.** The same pass records the highest and lowest layer occupying
each cell over the whole print. A segment whose layer is the topmost occupant of any cell it
crosses is visible from above, and likewise from below with the lowest. These segments are kept
even when their layer is skipped by the stride.
The layer range is split across up to eight `std::async` workers, each owning its grids. An
allocation failure on a huge print falls back to marking every segment as shell, which leaves out
only the hidden infill roles.
## Deciding that the user is dragging
`GLCanvas3D::_update_preview_interaction()` runs at the top of every preview frame, before the