Let the User Choose What the Preview Draws While Dragging

The solid model is one answer to a preview that cannot keep up with a drag; a
reviewer asked for the two that keep the drag view made of toolpaths. The
checkbox is now a combobox, "Simplify preview while dragging", with Off, Solid
model, Skip layers and Outer walls, and a spin, "Draw one layer in every N",
for the toolpath modes. Off is the default, and with it nothing is built.

Skip layers keeps one layer in every N. Outer walls keeps the outer and overhang
perimeters of those layers, so the prime tower and supports, whose segments have
other roles, drop out of it. Both keep the bottom and top layers of the visible
range whole, as the solid model does, so the faces the range cuts open stay what
was printed there.

libvgcode takes the choice as EReducedDetailMode and a stride; the sets are
rebuilt when either changes, and drawing from the reduced one is still a buffer
binding. A mode change reaches the loaded preview at once, as the checkbox did.
This commit is contained in:
Hanif Koh
2026-09-23 12:14:16 +08:00
parent ca83497bb6
commit 493896260e
14 changed files with 278 additions and 110 deletions
+49 -36
View File
@@ -3,60 +3,73 @@
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. With the `preview_solid_model_while_dragging` preference (*Graphics > G-code Preview*,
off by default), the preview draws the sliced objects as solid meshes while the user drags and
puts the toolpaths back when they let go. A mesh costs its triangles once, however many layers it
has.
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.
`GCodeViewer` draws the solid model, libvgcode (`src/libvgcode`) keeps the toolpaths that cap it,
and `GLCanvas3D` decides when the user is dragging. The OpenGL ES path ignores the preference.
| Preference | Values | Effect |
|---|---|---|
| `preview_reduced_detail_mode` | `off`, `solid`, `layers`, `outer_walls` | 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
preferences 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 or stride 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 bottom and top layers 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.
### Modes
- `EndLayersOnly` (`solid` in the preference) keeps only the two end layers. `GCodeViewer` then
draws the sliced objects and the prime tower as opaque solids, see below.
- `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.
## The solid model
The preview already loads the sliced objects as shells for its translucent ghost.
`GCodeViewer::render_solid_model()` draws those shells opaque, in their filament colours, with the
`gouraud` shader, whose z range cuts them to the visible layer range. The shells hold only the
objects, so while the preference 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 preference changes, without
reloading the objects, keeps its opaque colour so that it never appears among the translucent
shells, and stays out of their bounding box. Supports have no mesh and are not shown.
`GCodeViewer::render_solid_model()` draws those shells opaque, in their filament colors, with the
`gouraud` shader, whose z range cuts them to the visible layer range. The two toolpath layers of
the reduced set are drawn afterwards and cap the cut with what was really printed there. 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 and are not shown,
and `load_shells()` drops every non-model-part volume, so a negative volume is not cut out.
A plate whose shells are not loaded keeps drawing toolpaths, since the solid model would leave
only the end layers.
## End-layer set
The cut faces of the solid model are capped with what was really printed there: the toolpaths of
the bottom and top layers of the visible range. While the preference is on,
`ViewerImpl::update_enabled_entities()` fills a second, **reduced** index buffer holding just those
two layers, in the same walk that fills the full one. Building both together is what makes
switching free: starting or ending a drag is a buffer binding, never a rebuild.
## 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 the camera, the navigator or either slider being 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 solid model 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.
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. 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 solid model is switched.
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 solid-model frame outlives a drag, and the frame that ends a wheel's settle time is
drags, so no reduced frame outlives a drag, and the frame that ends a wheel's settle time is
requested as a full frame.
## Per-frame lookups
The segment template draws its box from 8 corners through an index buffer, so the vertex shader
runs at most once per corner. `get_estimated_time_at()`, which the tool marker tooltip calls every
frame, starts from the running time at the first vertex of the vertex's layer, kept per layer at
load, and adds only that layer's vertices. The sum runs in vertex order, so it matches a full
accumulation exactly while costing memory per layer rather than per vertex.
+14 -3
View File
@@ -205,9 +205,20 @@ void AppConfig::set_defaults()
if (get("seq_top_layer_only").empty())
set("seq_top_layer_only", "1");
// draw the sliced objects instead of their toolpaths while the user drags the preview
if (get("preview_solid_model_while_dragging").empty())
set_bool("preview_solid_model_while_dragging", false);
// 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")
set("preview_reduced_detail_mode", "off");
int stride = 4;
try {
stride = std::stoi(get("preview_reduced_detail_layer_stride"));
}
catch (...) {
stride = 4;
}
set("preview_reduced_detail_layer_stride", std::to_string(std::max(1, std::min(stride, 20))));
}
// ORCA: darken the layers the preview layer slider is not scrubbed to
if (get("preview_dim_previous_layers").empty())
+17
View File
@@ -158,6 +158,23 @@ enum class EGCodeExtrusionRole : uint8_t
static constexpr std::size_t GCODE_EXTRUSION_ROLES_COUNT = static_cast<std::size_t>(EGCodeExtrusionRole::COUNT);
//
// What the reduced set, drawn while the user is dragging, holds in place of the full toolpaths
//
enum class EReducedDetailMode : uint8_t
{
// nothing: no reduced set is built
Off,
// only the bottom and top layers of the visible range, for a caller that draws the print
// itself some other way
EndLayersOnly,
// one layer in every stride, every role kept
LayersOnly,
// one layer in every stride, outer walls only
OuterWallsOnly,
COUNT
};
//
// Option types
//
+8 -5
View File
@@ -115,12 +115,15 @@ public:
bool is_dim_previous_layers() const;
void set_dim_previous_layers(bool value);
//
// The reduced set holds only the bottom and top layers of the visible range, for a caller that
// draws the print itself some other way while the user drags. While enabled it is built
// alongside the full set, so set_reduced_detail() rebuilds nothing. Ignored on the OpenGL ES path.
// The reduced set drawn while the user drags: what the mode keeps, one layer in every stride
// for the toolpath modes, and always the bottom and top layers of the visible range. While a
// mode is set it is built alongside the full set, so set_reduced_detail() rebuilds nothing.
// Ignored on the OpenGL ES path.
//
void set_reduced_detail_enabled(bool value);
bool is_reduced_detail_enabled() const;
EReducedDetailMode get_reduced_detail_mode() const;
void set_reduced_detail_mode(EReducedDetailMode mode);
uint32_t get_reduced_detail_layer_stride() const;
void set_reduced_detail_layer_stride(uint32_t value);
void set_reduced_detail(bool value);
bool is_reduced_detail() const;
float get_dim_previous_layers_brightness() const;
+4 -3
View File
@@ -25,9 +25,10 @@ struct Settings
// ORCA: how bright those darkened layers are rendered, 1.0 = unchanged, 0.0 = black
float dim_previous_layers_brightness{ 0.4f };
bool spiral_vase_mode{ false };
// whether the reduced set (the visible range's end layers) is built, and whether it is drawn.
// Ignored on the OpenGL ES path.
bool reduced_detail_enabled{ false };
// what the reduced set holds, one layer in every reduced_detail_layer_stride for the toolpath
// modes, and whether it is drawn. Ignored on the OpenGL ES path.
EReducedDetailMode reduced_detail_mode{ EReducedDetailMode::Off };
uint32_t reduced_detail_layer_stride{ 4 };
bool reduced_detail{ false };
//
// Required update flags
+14 -4
View File
@@ -102,14 +102,24 @@ bool Viewer::is_reduced_detail() const
return m_impl->is_reduced_detail();
}
void Viewer::set_reduced_detail_enabled(bool value)
EReducedDetailMode Viewer::get_reduced_detail_mode() const
{
m_impl->set_reduced_detail_enabled(value);
return m_impl->get_reduced_detail_mode();
}
bool Viewer::is_reduced_detail_enabled() const
void Viewer::set_reduced_detail_mode(EReducedDetailMode mode)
{
return m_impl->is_reduced_detail_enabled();
m_impl->set_reduced_detail_mode(mode);
}
uint32_t Viewer::get_reduced_detail_layer_stride() const
{
return m_impl->get_reduced_detail_layer_stride();
}
void Viewer::set_reduced_detail_layer_stride(uint32_t value)
{
m_impl->set_reduced_detail_layer_stride(value);
}
void Viewer::set_dim_previous_layers(bool value)
+42 -8
View File
@@ -1191,6 +1191,18 @@ void ViewerImpl::load(GCodeInputData&& gcode_data)
update_colors();
}
#ifndef ENABLE_OPENGL_ES
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;
default:
return true;
}
}
#endif // ENABLE_OPENGL_ES
void ViewerImpl::update_enabled_entities()
{
if (m_vertices.empty())
@@ -1199,9 +1211,12 @@ void ViewerImpl::update_enabled_entities()
std::vector<uint32_t> enabled_segments;
std::vector<uint32_t> enabled_options;
#ifndef ENABLE_OPENGL_ES
// the reduced set is filled by the same walk, so switching to it costs no rebuild. It keeps the
// bottom and top layers of the visible range: the surfaces the range cuts open
const bool build_reduced = m_settings.reduced_detail_enabled;
// the reduced set is filled by the same walk, so switching to it costs no rebuild. Whatever the
// mode leaves out, the bottom and top layers of the visible range are kept whole: they are the
// surfaces the range cuts open
const EReducedDetailMode reduced_mode = m_settings.reduced_detail_mode;
const bool build_reduced = reduced_mode != EReducedDetailMode::Off;
const uint32_t layer_stride = std::max<uint32_t>(1, m_settings.reduced_detail_layer_stride);
std::vector<uint32_t> enabled_segments_reduced;
std::vector<uint32_t> enabled_options_reduced;
const Interval& layers_range = m_layers.get_view_range();
@@ -1255,8 +1270,17 @@ void ViewerImpl::update_enabled_entities()
enabled_segments.push_back(static_cast<uint32_t>(i));
#ifndef ENABLE_OPENGL_ES
if (build_reduced && (v.layer_id == layers_range[0] || v.layer_id == layers_range[1]))
(v.is_option() ? enabled_options_reduced : enabled_segments_reduced).push_back(static_cast<uint32_t>(i));
if (build_reduced) {
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) {
if (v.is_option())
enabled_options_reduced.push_back(static_cast<uint32_t>(i));
else if (!v.is_extrusion() || reduced_set_keeps(v))
enabled_segments_reduced.push_back(static_cast<uint32_t>(i));
}
}
#endif // ENABLE_OPENGL_ES
}
@@ -1510,11 +1534,21 @@ void ViewerImpl::toggle_top_layer_only_view_range()
update_colors_texture();
}
void ViewerImpl::set_reduced_detail_enabled(bool value)
// Either changes which vertices land in the reduced set, so the sets are rebuilt.
void ViewerImpl::set_reduced_detail_mode(EReducedDetailMode mode)
{
if (m_settings.reduced_detail_enabled == value)
if (m_settings.reduced_detail_mode == mode)
return;
m_settings.reduced_detail_enabled = value;
m_settings.reduced_detail_mode = mode;
m_settings.update_enabled_entities = true;
}
void ViewerImpl::set_reduced_detail_layer_stride(uint32_t value)
{
value = std::max<uint32_t>(1, value);
if (m_settings.reduced_detail_layer_stride == value)
return;
m_settings.reduced_detail_layer_stride = value;
m_settings.update_enabled_entities = true;
}
+7 -3
View File
@@ -120,8 +120,10 @@ public:
m_settings.reduced_detail = value;
}
bool is_reduced_detail() const { return m_settings.reduced_detail; }
bool is_reduced_detail_enabled() const { return m_settings.reduced_detail_enabled; }
void set_reduced_detail_enabled(bool value);
EReducedDetailMode get_reduced_detail_mode() const { return m_settings.reduced_detail_mode; }
void set_reduced_detail_mode(EReducedDetailMode mode);
uint32_t get_reduced_detail_layer_stride() const { return m_settings.reduced_detail_layer_stride; }
void set_reduced_detail_layer_stride(uint32_t value);
float get_dim_previous_layers_brightness() const { return m_settings.dim_previous_layers_brightness; }
void set_dim_previous_layers_brightness(float value);
@@ -532,7 +534,9 @@ private:
size_t m_enabled_options_reduced_tex_size{ 0 };
// 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_enabled; }
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
bool reduced_set_keeps(const PathVertex& v) const;
struct ActiveSet
{
size_t count{ 0 };
+40 -15
View File
@@ -1267,7 +1267,7 @@ void GCodeViewer::load_as_gcode(const GCodeProcessorResult& gcode_result, const
if (current_top_layer_only != required_top_layer_only)
m_viewer.toggle_top_layer_only_view_range();
read_solid_model_preference();
read_reduced_detail_preferences();
// ORCA: darken the layers the preview layer slider is not scrubbed to
m_viewer.set_dim_previous_layers(get_app_config()->get_bool("preview_dim_previous_layers"));
@@ -1699,9 +1699,9 @@ void GCodeViewer::reset()
void GCodeViewer::render_scene(int canvas_width, int canvas_height)
{
glsafe(::glEnable(GL_DEPTH_TEST));
// while dragging with the solid model on, the objects stand in for their toolpaths, cut to the
// while dragging in the solid model mode, the objects stand in for their toolpaths, cut to the
// visible layer range; the toolpath set then holds only the range's bottom and top layers
if (m_viewer.is_reduced_detail())
if (m_viewer.is_reduced_detail() && solid_model_enabled())
render_solid_model(canvas_width, canvas_height);
else
render_shells(canvas_width, canvas_height);
@@ -2030,26 +2030,51 @@ void GCodeViewer::update_layers_slider_mode()
void GCodeViewer::set_interacting(bool interacting)
{
// with no shells to stand in for the toolpaths, the solid model would leave only the end layers
m_viewer.set_reduced_detail(m_solid_model_while_dragging && interacting && !m_shells.volumes.empty());
const bool usable = !solid_model_enabled() || !m_shells.volumes.empty();
m_viewer.set_reduced_detail(interacting && usable);
}
void GCodeViewer::set_solid_model_while_dragging(bool value)
void GCodeViewer::read_reduced_detail_preferences()
{
const bool was_enabled = m_solid_model_while_dragging;
m_solid_model_while_dragging = value;
m_viewer.set_reduced_detail_enabled(value);
reload_shells_if_solid_model_changed(was_enabled);
m_reduced_detail_mode = reduced_detail_mode_from_string(get_app_config()->get("preview_reduced_detail_mode"));
m_reduced_detail_layer_stride = static_cast<unsigned int>(std::max(1, std::stoi(get_app_config()->get("preview_reduced_detail_layer_stride"))));
apply_reduced_detail_settings();
}
void GCodeViewer::read_solid_model_preference()
void GCodeViewer::apply_reduced_detail_settings()
{
m_solid_model_while_dragging = get_app_config()->get_bool("preview_solid_model_while_dragging");
m_viewer.set_reduced_detail_enabled(m_solid_model_while_dragging);
m_viewer.set_reduced_detail_mode(m_reduced_detail_mode);
m_viewer.set_reduced_detail_layer_stride(m_reduced_detail_layer_stride);
}
void GCodeViewer::set_reduced_detail_mode(const std::string& mode)
{
const bool was_solid = solid_model_enabled();
m_reduced_detail_mode = reduced_detail_mode_from_string(mode);
apply_reduced_detail_settings();
reload_shells_if_solid_model_changed(was_solid);
}
void GCodeViewer::set_reduced_detail_layer_stride(unsigned int value)
{
m_reduced_detail_layer_stride = std::max(1u, value);
apply_reduced_detail_settings();
}
libvgcode::EReducedDetailMode GCodeViewer::reduced_detail_mode_from_string(const std::string& mode)
{
if (mode == "solid")
return libvgcode::EReducedDetailMode::EndLayersOnly;
if (mode == "layers")
return libvgcode::EReducedDetailMode::LayersOnly;
if (mode == "outer_walls")
return libvgcode::EReducedDetailMode::OuterWallsOnly;
return libvgcode::EReducedDetailMode::Off;
}
void GCodeViewer::reload_shells_if_solid_model_changed(bool was_enabled)
{
if (was_enabled == m_solid_model_while_dragging || m_shells.print_id == -1)
if (was_enabled == solid_model_enabled() || m_shells.print_id == -1)
return;
// only the prime tower comes and goes with the mode: a full reload would drop the shells
// whenever the print has moved on since they were loaded, leaving the solid model nothing to draw
@@ -2387,7 +2412,7 @@ void GCodeViewer::load_shells(const Print& print, bool initialized, bool force_p
{
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": initialized=%1%, force_previewing=%2%")%initialized %force_previewing;
// the shells can load before the first G-code does, so the preferences are read here as well
read_solid_model_preference();
read_reduced_detail_preferences();
if ((print.id().id == m_shells.print_id)&&(print.get_modified_count() == m_shells.print_modify_count)) {
// the prime tower comes and goes on its own, without reloading the objects
update_shell_wipe_tower(print, initialized);
@@ -2508,7 +2533,7 @@ void GCodeViewer::load_shells(const Print& print, bool initialized, bool force_p
// opaque colour, so it never appears among the translucent shells, and stays out of their bounding box.
void GCodeViewer::update_shell_wipe_tower(const Print& print, bool initialized)
{
const bool with_wipe_tower = m_solid_model_while_dragging && print.is_step_done(psWipeTower) && print.wipe_tower_data().wipe_tower_mesh_data;
const bool with_wipe_tower = solid_model_enabled() && print.is_step_done(psWipeTower) && print.wipe_tower_data().wipe_tower_mesh_data;
if (with_wipe_tower == m_shells.with_wipe_tower)
return;
m_shells.with_wipe_tower = with_wipe_tower;
+12 -5
View File
@@ -236,9 +236,14 @@ private:
bool m_legend_visible{ true };
bool m_legend_enabled{ true };
// while dragging, the sliced objects are drawn as solid shapes instead of toolpaths
bool m_solid_model_while_dragging{ false };
void read_solid_model_preference();
// the reduced-detail preferences, pushed to libvgcode by apply_reduced_detail_settings()
libvgcode::EReducedDetailMode m_reduced_detail_mode{ libvgcode::EReducedDetailMode::Off };
unsigned int m_reduced_detail_layer_stride{ 4 };
void read_reduced_detail_preferences();
void apply_reduced_detail_settings();
static libvgcode::EReducedDetailMode reduced_detail_mode_from_string(const std::string& mode);
// in the solid model mode, the sliced objects are drawn as solid shapes instead of toolpaths
bool solid_model_enabled() const { return m_reduced_detail_mode == libvgcode::EReducedDetailMode::EndLayersOnly; }
void render_solid_model(int canvas_width, int canvas_height);
// the prime tower is only among the shells for the solid model, so it is added or removed when that changes
void reload_shells_if_solid_model_changed(bool was_enabled);
@@ -374,10 +379,12 @@ public:
// whether the mouse is holding either slider's handle
bool is_slider_dragging() const { return m_layers_slider->is_dragging() || m_moves_slider->is_dragging(); }
// while the user drags the camera or a slider, draw the solid model, if the preference asks for it
// 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(); }
void set_solid_model_while_dragging(bool value);
// the preference's string value: "off", "solid", "layers" or "outer_walls"
void set_reduced_detail_mode(const std::string& mode);
void set_reduced_detail_layer_stride(unsigned int value);
void set_layers_z_range(const std::array<unsigned int, 2>& layers_z_range);
+5 -5
View File
@@ -2054,7 +2054,7 @@ void GLCanvas3D::_render_frame(bool scene_dirty, bool only_init)
const bool overlay_tick = m_fps_overlay_tick;
m_fps_overlay_tick = false;
// Whether the preview draws the solid model is decided before the cached scene is consulted,
// Whether the preview draws its reduced set is decided before the cached scene is consulted,
// since switching changes what the scene pass draws.
if (m_canvas_type == ECanvasType::CanvasPreview && m_render_preview && m_gcode_viewer.has_data() && _update_preview_interaction())
scene_dirty = true;
@@ -3240,7 +3240,7 @@ void GLCanvas3D::bind_event_handlers()
if (m_selection_edit.kind != SelectionEdit::None)
finish_selection_edit();
ImGui::SetWindowFocus(nullptr);
// a drag cut short never sees its button release, which would leave the solid model drawn
// a drag cut short never sees its button release, which would leave the reduced set drawn
if (m_canvas_type == CanvasPreview && m_mouse.dragging && m_gcode_viewer.is_reduced_detail())
mouse_up_cleanup();
render();
@@ -8741,9 +8741,9 @@ void GLCanvas3D::_render_wireframe_overlay()
shader->stop_using();
}
// The solid model is drawn while the camera, the navigator or either slider is dragged. A wheel
// step has no duration, so it holds the solid model for a settle time instead, and the frame that
// restores the toolpaths is scheduled for when that time runs out. Returns whether what the scene
// The reduced set is drawn while the camera, the navigator or either slider is dragged. A wheel
// step has no duration, so it holds the reduced set for a settle time instead, and the frame that
// restores the full toolpaths is scheduled for when that time runs out. Returns whether what the scene
// pass draws changed, since a frame that reuses the cached scene would hide the change.
bool GLCanvas3D::_update_preview_interaction()
{
+3 -3
View File
@@ -649,7 +649,7 @@ private:
ECursorType m_cursor_type;
GLSelectionRectangle m_rectangle_selection;
bool m_navigator_dragging{ false };
// until when a wheel step keeps the preview's solid model drawn
// until when a wheel step keeps the preview's reduced set drawn
std::chrono::time_point<std::chrono::steady_clock> m_preview_interaction_until{};
// whether the frame that restores the toolpaths once that time is up is still owed
bool m_preview_settle_pending{ false };
@@ -1221,7 +1221,7 @@ public:
void request_extra_frame() { m_extra_frame_requested = true; }
// whether the user is holding the camera, the navigator, a gizmo, the rectangle selection or a preview slider
bool is_user_interacting() const;
// a wheel step is over before the next frame, so it holds the preview's solid model for a settle time
// a wheel step is over before the next frame, so it holds the preview's reduced set for a settle time
void note_preview_interaction();
void schedule_extra_frame(int milliseconds);
@@ -1370,7 +1370,7 @@ private:
//BBS: GUI refactor: add canvas size as parameters
void _render_gcode(int canvas_width, int canvas_height);
void _render_gcode_overlay(int canvas_width, int canvas_height);
// decides whether the preview draws its solid model this frame and returns whether what the scene
// decides whether the preview draws its reduced set this frame and returns whether what the scene
// pass draws changed; runs before the cached scene is consulted
bool _update_preview_interaction();
//BBS: render a plane for assemble
+61 -19
View File
@@ -322,7 +322,7 @@ wxBoxSizer* PreferencesDialog::create_item_combobox(wxString title, wxString too
return sizer;
}
wxBoxSizer *PreferencesDialog::create_item_combobox(wxString title, wxString tooltip, std::string param, std::vector<wxString> vlist, std::vector<std::string> config_name_index, const wxString wiki_url)
wxBoxSizer *PreferencesDialog::create_item_combobox(wxString title, wxString tooltip, std::string param, std::vector<wxString> vlist, std::vector<std::string> config_name_index, std::function<void(std::string)> onchange, const wxString wiki_url)
{
assert(vlist.size() == config_name_index.size());
unsigned int current_index = 0;
@@ -338,8 +338,9 @@ wxBoxSizer *PreferencesDialog::create_item_combobox(wxString title, wxString too
auto [sizer, combobox] = create_item_combobox_base(title, tooltip, param, vlist, current_index);
//// save config
combobox->GetDropDown().Bind(wxEVT_COMBOBOX, [this, param, config_name_index](wxCommandEvent& e) {
combobox->GetDropDown().Bind(wxEVT_COMBOBOX, [this, param, config_name_index, onchange](wxCommandEvent& e) {
app_config->set(param, config_name_index[e.GetSelection()]);
if (onchange != nullptr) onchange(config_name_index[e.GetSelection()]);
e.Skip();
});
@@ -684,6 +685,12 @@ wxBoxSizer *PreferencesDialog::create_item_input(wxString title, wxString title2
return m_sizer;
}
// the reduced-detail modes that keep one layer in every N, so the stride applies
static bool reduced_detail_mode_skips_layers(const std::string& mode)
{
return mode == "layers" || mode == "outer_walls";
}
wxBoxSizer *PreferencesDialog::create_item_spinctrl(wxString title, wxString title2, wxString side_label, wxString tooltip, std::string param, int min, int max, std::function<void(int)> onchange, const wxString wiki_url)
{
auto tip = tooltip.IsEmpty() ? title : tooltip; // auto fill tooltips with title if its empty
@@ -698,6 +705,11 @@ wxBoxSizer *PreferencesDialog::create_item_spinctrl(wxString title, wxString tit
m_dim_previous_layers_brightness_input = input;
input->Enable(app_config->get_bool("preview_dim_previous_layers"));
}
// only the toolpath modes skip layers
else if (param == "preview_reduced_detail_layer_stride") {
m_reduced_detail_layer_stride_input = input;
input->Enable(reduced_detail_mode_skips_layers(app_config->get("preview_reduced_detail_mode")));
}
m_sizer->Add(input, 0, wxALIGN_CENTER_VERTICAL);
@@ -1060,16 +1072,6 @@ wxBoxSizer *PreferencesDialog::create_item_checkbox(wxString title, wxString too
}
}
}
// apply the solid model preference immediately to the currently loaded preview
else if (param == "preview_solid_model_while_dragging") {
if (Plater* plater = wxGetApp().plater()) {
if (GLCanvas3D* canvas = plater->get_preview_canvas3D()) {
canvas->get_gcode_viewer().set_solid_model_while_dragging(app_config->get_bool(param));
canvas->set_as_dirty();
canvas->request_extra_frame();
}
}
}
#ifdef __WXMSW__
if (param == "associate_3mf") {
@@ -2055,14 +2057,54 @@ void PreferencesDialog::create_items()
"preview_default_view_type", PreviewViewTypeLabels, PreviewViewTypeValues);
g_sizer->Add(item_preview_view_type);
auto item_solid_model_while_dragging = create_item_checkbox(
_L("Only render solid model when dragging"),
_L("While dragging the camera or a preview slider, or zooming with the mouse wheel, draw the sliced objects and the prime tower as solid shapes "
"in their filament colors instead of toolpaths, so that large prints stay responsive. They are cut to the visible layer range, with its bottom "
"and top layers drawn as toolpaths. Supports are not shown, and negative volumes are not cut out. The toolpaths are restored as soon as you let go."),
"preview_solid_model_while_dragging"
auto item_reduced_detail_mode = create_item_combobox(
_L("Simplify preview while dragging"),
_L("What the sliced preview draws while you drag the camera or a preview slider, or zoom with the mouse wheel, so that large prints stay responsive. "
"The full toolpaths are restored as soon as you let go.\n"
"Off: the full toolpaths.\n"
"Solid model: the sliced objects and the prime tower as solid shapes in their filament colors, cut to the visible layer range, "
"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"
"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"},
// apply the new mode immediately to the currently loaded preview
[this](std::string value) {
if (m_reduced_detail_layer_stride_input)
m_reduced_detail_layer_stride_input->Enable(reduced_detail_mode_skips_layers(value));
if (Plater* plater = wxGetApp().plater()) {
if (GLCanvas3D* canvas = plater->get_preview_canvas3D()) {
canvas->get_gcode_viewer().set_reduced_detail_mode(value);
canvas->set_as_dirty();
canvas->request_extra_frame();
}
}
}
);
g_sizer->Add(item_solid_model_while_dragging);
g_sizer->Add(item_reduced_detail_mode);
auto item_reduced_detail_layer_stride = create_item_spinctrl(
_L("Draw one layer in every"),
"",
_L("layers"),
_L("How many layers the simplified preview keeps one of while dragging: 1 draws every layer, 4 draws every fourth."),
"preview_reduced_detail_layer_stride",
1,
20,
// apply the new stride immediately to the currently loaded preview
[](int value) {
if (Plater* plater = wxGetApp().plater()) {
if (GLCanvas3D* canvas = plater->get_preview_canvas3D()) {
canvas->get_gcode_viewer().set_reduced_detail_layer_stride(static_cast<unsigned int>(value));
canvas->set_as_dirty();
canvas->request_extra_frame();
}
}
}
);
g_sizer->Add(item_reduced_detail_layer_stride);
auto item_dim_previous_layers = create_item_checkbox(
_L("Dim lower layers"),
+2 -1
View File
@@ -80,6 +80,7 @@ public:
::CheckBox * m_skip_identical_frames_checkbox = {nullptr};
::TextInput *m_backup_interval_textinput = {nullptr};
::SpinInput *m_dim_previous_layers_brightness_input = {nullptr};
::SpinInput *m_reduced_detail_layer_stride_input = {nullptr};
::ComboBox * m_network_version_combo = {nullptr};
std::vector<NetworkLibraryVersionInfo> m_available_versions;
@@ -93,7 +94,7 @@ public:
wxBoxSizer *create_item_title(wxString title);
wxBoxSizer *create_item_label(wxString label, const wxString tooltip = "", const wxString wiki_url = "");
wxBoxSizer *create_item_combobox(wxString title, wxString tooltip, std::string param, std::vector<wxString> vlist, std::function<void(wxString)> onchange = {}, const wxString wiki_url = "");
wxBoxSizer *create_item_combobox(wxString title, wxString tooltip, std::string param, std::vector<wxString> vlist, std::vector<std::string> config_name_index, const wxString wiki_url = "");
wxBoxSizer *create_item_combobox(wxString title, wxString tooltip, std::string param, std::vector<wxString> vlist, std::vector<std::string> config_name_index, std::function<void(std::string)> onchange = {}, const wxString wiki_url = "");
wxBoxSizer *create_item_region_combobox(wxString title, wxString tooltip);
wxBoxSizer *create_item_language_combobox(wxString title, wxString tooltip);
wxBoxSizer *create_item_loglevel_combobox(wxString title, wxString tooltip, std::vector<wxString> vlist);