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 | | 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` | 1–20 | one layer in every N is kept by the toolpath modes | | `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 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. 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. - `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 - `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. 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 ## 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 // 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"); 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"); set("preview_reduced_detail_mode", "off");
int stride = 4; int stride = 4;
try { try {
+2
View File
@@ -172,6 +172,8 @@ enum class EReducedDetailMode : uint8_t
LayersOnly, LayersOnly,
// one layer in every stride, outer walls only // one layer in every stride, outer walls only
OuterWallsOnly, OuterWallsOnly,
// every layer, every role but the infill under the skins and the gap fill: what can be seen from outside
ShellOnly,
COUNT COUNT
}; };
+22 -2
View File
@@ -17,6 +17,7 @@
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
#include <numeric> #include <numeric>
#include <cfloat>
namespace libvgcode { namespace libvgcode {
@@ -1194,17 +1195,34 @@ void ViewerImpl::load(GCodeInputData&& gcode_data)
} }
#ifndef ENABLE_OPENGL_ES #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 bool ViewerImpl::reduced_set_keeps(const PathVertex& v) const
{ {
switch (m_settings.reduced_detail_mode) { switch (m_settings.reduced_detail_mode) {
case EReducedDetailMode::OuterWallsOnly: 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: default:
return true; return true;
} }
} }
#endif // ENABLE_OPENGL_ES #endif // ENABLE_OPENGL_ES
void ViewerImpl::update_enabled_entities() void ViewerImpl::update_enabled_entities()
{ {
if (m_vertices.empty()) 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_segments_reduced;
std::vector<uint32_t> enabled_options_reduced; std::vector<uint32_t> enabled_options_reduced;
const Interval& layers_range = m_layers.get_view_range(); 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 #endif // ENABLE_OPENGL_ES
Interval range = m_view_range.get_visible(); 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]; const bool end_layer = v.layer_id == layers_range[0] || v.layer_id == layers_range[1];
if (end_layer) if (end_layer)
(v.is_option() ? enabled_options_reduced : enabled_segments_reduced).push_back(static_cast<uint32_t>(i)); (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()) if (v.is_option())
enabled_options_reduced.push_back(static_cast<uint32_t>(i)); enabled_options_reduced.push_back(static_cast<uint32_t>(i));
else if (!v.is_extrusion() || reduced_set_keeps(v)) 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. // 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; } 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; bool reduced_set_keeps(const PathVertex& v) const;
struct ActiveSet struct ActiveSet
{ {
+2
View File
@@ -2069,6 +2069,8 @@ libvgcode::EReducedDetailMode GCodeViewer::reduced_detail_mode_from_string(const
return libvgcode::EReducedDetailMode::LayersOnly; return libvgcode::EReducedDetailMode::LayersOnly;
if (mode == "outer_walls") if (mode == "outer_walls")
return libvgcode::EReducedDetailMode::OuterWallsOnly; return libvgcode::EReducedDetailMode::OuterWallsOnly;
if (mode == "shell")
return libvgcode::EReducedDetailMode::ShellOnly;
return libvgcode::EReducedDetailMode::Off; 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 // while the user drags the camera or a slider, draw the reduced set, if the preference asks for one
void set_interacting(bool interacting); void set_interacting(bool interacting);
bool is_reduced_detail() const { return m_viewer.is_reduced_detail(); } 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_mode(const std::string& mode);
void set_reduced_detail_layer_stride(unsigned int value); 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" "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" "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" "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."), "The bottom and top of the visible layer range are always drawn whole."),
"preview_reduced_detail_mode", "preview_reduced_detail_mode",
{_L("Off"), _L("Solid model"), _L("Skip layers"), _L("Outer walls")}, {_L("Off"), _L("Solid model"), _L("Skip layers"), _L("Outer walls"), _L("Shell only")},
{"off", "solid", "layers", "outer_walls"}, {"off", "solid", "layers", "outer_walls", "shell"},
// apply the new mode immediately to the currently loaded preview // apply the new mode immediately to the currently loaded preview
[this](std::string value) { [this](std::string value) {
if (m_reduced_detail_layer_stride_input) if (m_reduced_detail_layer_stride_input)