From 493896260e5c23764fb2b262ee1e8e42aef5c253 Mon Sep 17 00:00:00 2001 From: Hanif Koh Date: Wed, 23 Sep 2026 01:34:34 +0800 Subject: [PATCH] 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. --- docs/HLSD/gcode-preview-dragging.md | 85 +++++++++++++++++------------ src/libslic3r/AppConfig.cpp | 17 +++++- src/libvgcode/include/Types.hpp | 17 ++++++ src/libvgcode/include/Viewer.hpp | 13 +++-- src/libvgcode/src/Settings.hpp | 7 ++- src/libvgcode/src/Viewer.cpp | 18 ++++-- src/libvgcode/src/ViewerImpl.cpp | 50 ++++++++++++++--- src/libvgcode/src/ViewerImpl.hpp | 10 +++- src/slic3r/GUI/GCodeViewer.cpp | 55 ++++++++++++++----- src/slic3r/GUI/GCodeViewer.hpp | 17 ++++-- src/slic3r/GUI/GLCanvas3D.cpp | 10 ++-- src/slic3r/GUI/GLCanvas3D.hpp | 6 +- src/slic3r/GUI/Preferences.cpp | 80 ++++++++++++++++++++------- src/slic3r/GUI/Preferences.hpp | 3 +- 14 files changed, 278 insertions(+), 110 deletions(-) diff --git a/docs/HLSD/gcode-preview-dragging.md b/docs/HLSD/gcode-preview-dragging.md index 0567e52082..d2935f84e9 100644 --- a/docs/HLSD/gcode-preview-dragging.md +++ b/docs/HLSD/gcode-preview-dragging.md @@ -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` | 1–20 | 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. diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index 380e30ba5d..699efad7f5 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -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()) diff --git a/src/libvgcode/include/Types.hpp b/src/libvgcode/include/Types.hpp index 2187238ef0..ee4d555ffe 100644 --- a/src/libvgcode/include/Types.hpp +++ b/src/libvgcode/include/Types.hpp @@ -158,6 +158,23 @@ enum class EGCodeExtrusionRole : uint8_t static constexpr std::size_t GCODE_EXTRUSION_ROLES_COUNT = static_cast(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 // diff --git a/src/libvgcode/include/Viewer.hpp b/src/libvgcode/include/Viewer.hpp index 6735198b42..126bc3bc9e 100644 --- a/src/libvgcode/include/Viewer.hpp +++ b/src/libvgcode/include/Viewer.hpp @@ -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; diff --git a/src/libvgcode/src/Settings.hpp b/src/libvgcode/src/Settings.hpp index 93b7aa02dc..02480000eb 100644 --- a/src/libvgcode/src/Settings.hpp +++ b/src/libvgcode/src/Settings.hpp @@ -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 diff --git a/src/libvgcode/src/Viewer.cpp b/src/libvgcode/src/Viewer.cpp index 6059060e84..957aa67a35 100644 --- a/src/libvgcode/src/Viewer.cpp +++ b/src/libvgcode/src/Viewer.cpp @@ -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) diff --git a/src/libvgcode/src/ViewerImpl.cpp b/src/libvgcode/src/ViewerImpl.cpp index 43709e10c2..92fb3c30a1 100644 --- a/src/libvgcode/src/ViewerImpl.cpp +++ b/src/libvgcode/src/ViewerImpl.cpp @@ -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 enabled_segments; std::vector 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(1, m_settings.reduced_detail_layer_stride); std::vector enabled_segments_reduced; std::vector 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(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(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(i)); + else if (reduced_mode != EReducedDetailMode::EndLayersOnly && (v.layer_id % layer_stride) == 0) { + if (v.is_option()) + enabled_options_reduced.push_back(static_cast(i)); + else if (!v.is_extrusion() || reduced_set_keeps(v)) + enabled_segments_reduced.push_back(static_cast(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(1, value); + if (m_settings.reduced_detail_layer_stride == value) + return; + m_settings.reduced_detail_layer_stride = value; m_settings.update_enabled_entities = true; } diff --git a/src/libvgcode/src/ViewerImpl.hpp b/src/libvgcode/src/ViewerImpl.hpp index 597722cf69..c09ed20091 100644 --- a/src/libvgcode/src/ViewerImpl.hpp +++ b/src/libvgcode/src/ViewerImpl.hpp @@ -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 }; diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 6369ce3554..0ef4a07e92 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -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(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; diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 9073b1e763..e6428e7615 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -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& layers_z_range); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index ff1b8eaf3c..2b7d1e852c 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -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() { diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index c8d480fee7..fd539243de 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -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 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 diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 14b223f592..b6a1ef1b41 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -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 vlist, std::vector config_name_index, const wxString wiki_url) +wxBoxSizer *PreferencesDialog::create_item_combobox(wxString title, wxString tooltip, std::string param, std::vector vlist, std::vector config_name_index, std::function 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 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(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"), diff --git a/src/slic3r/GUI/Preferences.hpp b/src/slic3r/GUI/Preferences.hpp index e3b6ffdc7d..405c74a0dd 100644 --- a/src/slic3r/GUI/Preferences.hpp +++ b/src/slic3r/GUI/Preferences.hpp @@ -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 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 vlist, std::function onchange = {}, const wxString wiki_url = ""); - wxBoxSizer *create_item_combobox(wxString title, wxString tooltip, std::string param, std::vector vlist, std::vector config_name_index, const wxString wiki_url = ""); + wxBoxSizer *create_item_combobox(wxString title, wxString tooltip, std::string param, std::vector vlist, std::vector config_name_index, std::function 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 vlist);