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 37284194a5..bbc05d0c4e 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 883db9a1bf..3d020a5df5 100644 --- a/src/libvgcode/include/Viewer.hpp +++ b/src/libvgcode/include/Viewer.hpp @@ -99,12 +99,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 f95703bdfe..883abf8df7 100644 --- a/src/libvgcode/src/Viewer.cpp +++ b/src/libvgcode/src/Viewer.cpp @@ -87,14 +87,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 d4c535f028..92b0b25c62 100644 --- a/src/libvgcode/src/ViewerImpl.cpp +++ b/src/libvgcode/src/ViewerImpl.cpp @@ -1183,6 +1183,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()) @@ -1191,9 +1203,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(); @@ -1247,8 +1262,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 } @@ -1473,11 +1497,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 6e8ac104fc..c998a0ec72 100644 --- a/src/libvgcode/src/ViewerImpl.hpp +++ b/src/libvgcode/src/ViewerImpl.hpp @@ -102,8 +102,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); @@ -507,7 +509,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 4d315b3129..d2983604fe 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -1177,7 +1177,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")); @@ -1628,9 +1628,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); @@ -1936,26 +1936,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 @@ -2293,7 +2318,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); @@ -2414,7 +2439,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 2ead79da0f..b43d86b724 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -235,9 +235,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); @@ -356,10 +361,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 0648e3359c..bc06a8a315 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; @@ -3238,7 +3238,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(); @@ -8780,9 +8780,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 ba45543063..5d29952518 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); @@ -1369,7 +1369,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 2206abd452..928fd12a16 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") { @@ -2029,14 +2031,54 @@ void PreferencesDialog::create_items() //// GRAPHICS > G-code Preview g_sizer->Add(create_item_title(_L("G-code Preview")), 1, wxEXPAND); - 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);