Add a Shell-Only Drag Mode That Keeps What Can Be Seen From Outside

A third mode of the drag preference: the reduced set 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, on every layer. 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, and the two end layers of the visible range stay whole as in every mode.

The roles come from the slicer, so the classification is exact wherever the slicer's is.
What a role cannot tell apart is the inside of the prime tower or of a support from its
outside; both are kept whole. A profile with no top or bottom shell layers exposes infill,
which this mode hides.
This commit is contained in:
Hanif Koh
2026-09-28 17:10:54 +08:00
parent dec146f1e6
commit 44d5a22a32
8 changed files with 42 additions and 9 deletions
+9 -2
View File
@@ -9,8 +9,8 @@ let go.
| Preference | Values | Effect |
|---|---|---|
| `preview_reduced_detail_mode` | `off`, `solid`, `layers`, `outer_walls` | what is drawn while dragging |
| `preview_reduced_detail_layer_stride` | 1–20 | one layer in every N is kept by the toolpath modes |
| `preview_reduced_detail_mode` | `off`, `solid`, `layers`, `outer_walls`, `shell` | what is drawn while dragging |
| `preview_reduced_detail_layer_stride` | 1–20 | one layer in every N is kept by the skip-layers and outer-walls modes |
libvgcode (`src/libvgcode`) builds and binds the reduced toolpath set, `GCodeViewer` maps the
preferences onto it and draws the solid model, and `GLCanvas3D` decides when the user is dragging.
@@ -35,6 +35,13 @@ 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 every layer, and 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, and the stride does not apply. 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 exposes infill, which this mode hides.
## The solid model
+1 -1
View File
@@ -208,7 +208,7 @@ void AppConfig::set_defaults()
// what the preview draws while the user drags it, and one layer in how many the toolpath modes keep
{
const std::string mode = get("preview_reduced_detail_mode");
if (mode != "off" && mode != "solid" && mode != "layers" && mode != "outer_walls")
if (mode != "off" && mode != "solid" && mode != "layers" && mode != "outer_walls" && mode != "shell")
set("preview_reduced_detail_mode", "off");
int stride = 4;
try {
+2
View File
@@ -172,6 +172,8 @@ enum class EReducedDetailMode : uint8_t
LayersOnly,
// one layer in every stride, outer walls only
OuterWallsOnly,
// every layer, every role but the infill under the skins and the gap fill: what can be seen from outside
ShellOnly,
COUNT
};
+22 -2
View File
@@ -17,6 +17,7 @@
#include <algorithm>
#include <cmath>
#include <numeric>
#include <cfloat>
namespace libvgcode {
@@ -1194,17 +1195,34 @@ void ViewerImpl::load(GCodeInputData&& gcode_data)
}
#ifndef ENABLE_OPENGL_ES
static bool is_outer_wall(EGCodeExtrusionRole role)
{
return role == EGCodeExtrusionRole::ExternalPerimeter || role == EGCodeExtrusionRole::OverhangPerimeter;
}
// the roles that lie under a skin or between walls, never seen from outside the print
static bool is_hidden_in_shell(EGCodeExtrusionRole role)
{
return role == EGCodeExtrusionRole::InternalInfill ||
role == EGCodeExtrusionRole::SolidInfill ||
role == EGCodeExtrusionRole::InternalBridgeInfill ||
role == EGCodeExtrusionRole::GapFill;
}
bool ViewerImpl::reduced_set_keeps(const PathVertex& v) const
{
switch (m_settings.reduced_detail_mode) {
case EReducedDetailMode::OuterWallsOnly:
return v.role == EGCodeExtrusionRole::ExternalPerimeter || v.role == EGCodeExtrusionRole::OverhangPerimeter;
return is_outer_wall(v.role);
case EReducedDetailMode::ShellOnly:
return !is_hidden_in_shell(v.role);
default:
return true;
}
}
#endif // ENABLE_OPENGL_ES
void ViewerImpl::update_enabled_entities()
{
if (m_vertices.empty())
@@ -1222,6 +1240,8 @@ void ViewerImpl::update_enabled_entities()
std::vector<uint32_t> enabled_segments_reduced;
std::vector<uint32_t> enabled_options_reduced;
const Interval& layers_range = m_layers.get_view_range();
// the shell mode draws every layer, the other toolpath modes one in every stride
const bool shell_reduced = reduced_mode == EReducedDetailMode::ShellOnly;
#endif // ENABLE_OPENGL_ES
Interval range = m_view_range.get_visible();
@@ -1276,7 +1296,7 @@ void ViewerImpl::update_enabled_entities()
const bool end_layer = v.layer_id == layers_range[0] || v.layer_id == layers_range[1];
if (end_layer)
(v.is_option() ? enabled_options_reduced : enabled_segments_reduced).push_back(static_cast<uint32_t>(i));
else if (reduced_mode != EReducedDetailMode::EndLayersOnly && (v.layer_id % layer_stride) == 0) {
else if (reduced_mode != EReducedDetailMode::EndLayersOnly && (shell_reduced || (v.layer_id % layer_stride) == 0)) {
if (v.is_option())
enabled_options_reduced.push_back(static_cast<uint32_t>(i));
else if (!v.is_extrusion() || reduced_set_keeps(v))
+1 -1
View File
@@ -537,7 +537,7 @@ private:
// The set the next draw reads from: the reduced one while dragging, if one is built.
bool use_reduced_set() const { return m_settings.reduced_detail && m_settings.reduced_detail_mode != EReducedDetailMode::Off; }
// Whether an extrusion segment belongs to the reduced set under the current mode
// Whether the extrusion segment starting at vertex v belongs to the reduced set under the current mode
bool reduced_set_keeps(const PathVertex& v) const;
struct ActiveSet
{
+2
View File
@@ -2069,6 +2069,8 @@ libvgcode::EReducedDetailMode GCodeViewer::reduced_detail_mode_from_string(const
return libvgcode::EReducedDetailMode::LayersOnly;
if (mode == "outer_walls")
return libvgcode::EReducedDetailMode::OuterWallsOnly;
if (mode == "shell")
return libvgcode::EReducedDetailMode::ShellOnly;
return libvgcode::EReducedDetailMode::Off;
}
+1 -1
View File
@@ -382,7 +382,7 @@ public:
// while the user drags the camera or a slider, draw the reduced set, if the preference asks for one
void set_interacting(bool interacting);
bool is_reduced_detail() const { return m_viewer.is_reduced_detail(); }
// the preference's string value: "off", "solid", "layers" or "outer_walls"
// the preference's string value: "off", "solid", "layers", "outer_walls" or "shell"
void set_reduced_detail_mode(const std::string& mode);
void set_reduced_detail_layer_stride(unsigned int value);
+4 -2
View File
@@ -2065,10 +2065,12 @@ void PreferencesDialog::create_items()
"with its bottom and top layers drawn as toolpaths. Supports are not shown, and negative volumes are not cut out.\n"
"Skip layers: the toolpaths of one layer in every N, set below.\n"
"Outer walls: only the outer walls of one layer in every N. The prime tower and supports are left out.\n"
"Shell only: every layer without its sparse infill, internal solid infill and gap fill, which lie under the walls and skins. "
"Walls, top and bottom surfaces, bridges, supports and the prime tower are drawn whole, so the print looks the same from outside.\n"
"The bottom and top of the visible layer range are always drawn whole."),
"preview_reduced_detail_mode",
{_L("Off"), _L("Solid model"), _L("Skip layers"), _L("Outer walls")},
{"off", "solid", "layers", "outer_walls"},
{_L("Off"), _L("Solid model"), _L("Skip layers"), _L("Outer walls"), _L("Shell only")},
{"off", "solid", "layers", "outer_walls", "shell"},
// apply the new mode immediately to the currently loaded preview
[this](std::string value) {
if (m_reduced_detail_layer_stride_input)