diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index f3a1d5988..26c5b470e 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -147,6 +147,9 @@ void AppConfig::set_defaults() if (get("order_volumes").empty()) set("order_volumes", "1"); + + if (get("clear_undo_redo_stack_on_new_project").empty()) + set("clear_undo_redo_stack_on_new_project", "1"); } else { #ifdef _WIN32 diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index 9f4015913..eca057a5b 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -6,6 +6,7 @@ #include "../GCode.hpp" #include "../Geometry.hpp" #include "../GCode/ThumbnailData.hpp" +#include "../Semver.hpp" #include "../Time.hpp" #include "../I18N.hpp" @@ -411,6 +412,8 @@ namespace Slic3r { unsigned int m_version; bool m_check_version; + // Semantic version of PrusaSlicer, that generated this 3MF. + boost::optional<Semver> m_prusaslicer_generator_version; unsigned int m_fdm_supports_painting_version = 0; unsigned int m_seam_painting_version = 0; unsigned int m_mm_painting_version = 0; @@ -1712,21 +1715,20 @@ namespace Slic3r { const std::string msg = (boost::format(_(L("The selected 3mf file has been saved with a newer version of %1% and is not compatible."))) % std::string(SLIC3R_APP_NAME)).str(); throw version_error(msg); } - } - - if (m_curr_metadata_name == SLIC3RPE_FDM_SUPPORTS_PAINTING_VERSION) { + } else if (m_curr_metadata_name == "Application") { + // Generator application of the 3MF. + // SLIC3R_APP_KEY - SLIC3R_VERSION + if (boost::starts_with(m_curr_characters, "PrusaSlicer-")) + m_prusaslicer_generator_version = Semver::parse(m_curr_characters.substr(12)); + } else if (m_curr_metadata_name == SLIC3RPE_FDM_SUPPORTS_PAINTING_VERSION) { m_fdm_supports_painting_version = (unsigned int) atoi(m_curr_characters.c_str()); check_painting_version(m_fdm_supports_painting_version, FDM_SUPPORTS_PAINTING_VERSION, _(L("The selected 3MF contains FDM supports painted object using a newer version of PrusaSlicer and is not compatible."))); - } - - if (m_curr_metadata_name == SLIC3RPE_SEAM_PAINTING_VERSION) { + } else if (m_curr_metadata_name == SLIC3RPE_SEAM_PAINTING_VERSION) { m_seam_painting_version = (unsigned int) atoi(m_curr_characters.c_str()); check_painting_version(m_seam_painting_version, SEAM_PAINTING_VERSION, _(L("The selected 3MF contains seam painted object using a newer version of PrusaSlicer and is not compatible."))); - } - - if (m_curr_metadata_name == SLIC3RPE_MM_PAINTING_VERSION) { + } else if (m_curr_metadata_name == SLIC3RPE_MM_PAINTING_VERSION) { m_mm_painting_version = (unsigned int) atoi(m_curr_characters.c_str()); check_painting_version(m_mm_painting_version, MM_PAINTING_VERSION, _(L("The selected 3MF contains multi-material painted object using a newer version of PrusaSlicer and is not compatible."))); @@ -1890,7 +1892,6 @@ namespace Slic3r { unsigned int geo_tri_count = (unsigned int)geometry.triangles.size(); unsigned int renamed_volumes_count = 0; - int processed_vertices_max_id = 0; for (const ObjectMetadata::VolumeMetadata& volume_data : volumes) { if (geo_tri_count <= volume_data.first_triangle_id || geo_tri_count <= volume_data.last_triangle_id || volume_data.last_triangle_id < volume_data.first_triangle_id) { @@ -1910,33 +1911,43 @@ namespace Slic3r { } // splits volume out of imported geometry - std::vector<Vec3i> faces(geometry.triangles.begin() + volume_data.first_triangle_id, geometry.triangles.begin() + volume_data.last_triangle_id + 1); - const size_t triangles_count = faces.size(); + indexed_triangle_set its; + its.indices.assign(geometry.triangles.begin() + volume_data.first_triangle_id, geometry.triangles.begin() + volume_data.last_triangle_id + 1); + const size_t triangles_count = its.indices.size(); + if (triangles_count == 0) { + add_error("An empty triangle mesh found"); + return false; + } - int min_id = faces.front()[0]; - int max_id = faces.front()[0]; - for (const Vec3i& face : faces) { - for (const int tri_id : face) { - if (tri_id < 0 || tri_id >= geometry.vertices.size()) { - add_error("Found invalid vertex id"); - return false; + { + int min_id = its.indices.front()[0]; + int max_id = min_id; + for (const Vec3i& face : its.indices) { + for (const int tri_id : face) { + if (tri_id < 0 || tri_id >= int(geometry.vertices.size())) { + add_error("Found invalid vertex id"); + return false; + } + min_id = std::min(min_id, tri_id); + max_id = std::max(max_id, tri_id); } - min_id = std::min(min_id, tri_id); - max_id = std::max(max_id, tri_id); } + its.vertices.assign(geometry.vertices.begin() + min_id, geometry.vertices.begin() + max_id + 1); + + // rebase indices to the current vertices list + for (Vec3i& face : its.indices) + for (int& tri_id : face) + tri_id -= min_id; } - // rebase indices to the current vertices list - for (Vec3i& face : faces) { - for (int& tri_id : face) { - tri_id -= min_id; - } - } + if (m_prusaslicer_generator_version && + *m_prusaslicer_generator_version >= *Semver::parse("2.4.0-alpha1") && + *m_prusaslicer_generator_version < *Semver::parse("2.4.0-alpha3")) + // PrusaSlicer 2.4.0-alpha2 contained a bug, where all vertices of a single object were saved for each volume the object contained. + // Remove the vertices, that are not referenced by any face. + its_compactify_vertices(its, true); - processed_vertices_max_id = 1 + std::max(processed_vertices_max_id, max_id); - - std::vector<Vec3f> vertices(geometry.vertices.begin() + min_id, geometry.vertices.begin() + max_id + 1); - TriangleMesh triangle_mesh(std::move(vertices), std::move(faces)); + TriangleMesh triangle_mesh(std::move(its)); if (m_version == 0) { // if the 3mf was not produced by PrusaSlicer and there is only one instance, diff --git a/src/libslic3r/Format/OBJ.cpp b/src/libslic3r/Format/OBJ.cpp index 54c373ce3..3b05bb574 100644 --- a/src/libslic3r/Format/OBJ.cpp +++ b/src/libslic3r/Format/OBJ.cpp @@ -73,7 +73,7 @@ bool load_obj(const char *path, TriangleMesh *meshptr) break; } else { assert(cnt < 4); - if (vertex.coordIdx < 0 || vertex.coordIdx >= its.vertices.size()) { + if (vertex.coordIdx < 0 || vertex.coordIdx >= int(its.vertices.size())) { BOOST_LOG_TRIVIAL(error) << "load_obj: failed to parse " << path << ". The file contains invalid vertex index."; return false; } diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index cb9f04e45..feaf0cac7 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -500,13 +500,15 @@ void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& p // Only run this code if just a filament / SLA material was installed by Config Wizard for an active Printer. auto printer_technology = printers.get_selected_preset().printer_technology(); if (printer_technology == ptFFF && ! preferred_selection.filament.empty()) { - if (auto it = filaments.find_preset_internal(preferred_selection.filament); it != filaments.end() && it->is_visible) { - filaments.select_preset_by_name_strict(preferred_selection.filament); + std::string preferred_preset_name = get_preset_name_by_alias(Preset::Type::TYPE_FILAMENT, preferred_selection.filament); + if (auto it = filaments.find_preset_internal(preferred_preset_name); it != filaments.end() && it->is_visible) { + filaments.select_preset_by_name_strict(preferred_preset_name); this->filament_presets.front() = filaments.get_selected_preset_name(); } } else if (printer_technology == ptSLA && ! preferred_selection.sla_material.empty()) { - if (auto it = sla_materials.find_preset_internal(preferred_selection.sla_material); it != sla_materials.end() && it->is_visible) - sla_materials.select_preset_by_name_strict(preferred_selection.sla_material); + std::string preferred_preset_name = get_preset_name_by_alias(Preset::Type::TYPE_SLA_MATERIAL, preferred_selection.sla_material); + if (auto it = sla_materials.find_preset_internal(preferred_preset_name); it != sla_materials.end() && it->is_visible) + sla_materials.select_preset_by_name_strict(preferred_preset_name); } } diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index 280936418..799e135ce 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -502,7 +502,7 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) // If raft is to be generated, the 1st top_contact layer will contain the 1st object layer silhouette with holes filled. // There is also a 1st intermediate layer containing bases of support columns. // Inflate the bases of the support columns and create the raft base under the object. - MyLayersPtr raft_layers = this->generate_raft_base(object, top_contacts, interface_layers, intermediate_layers, base_interface_layers, layer_storage); + MyLayersPtr raft_layers = this->generate_raft_base(object, top_contacts, interface_layers, base_interface_layers, intermediate_layers, layer_storage); #ifdef SLIC3R_DEBUG for (const MyLayer *l : interface_layers) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 4235a007b..9ba72e629 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -5151,7 +5151,8 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type) GLGizmosManager::EType type = gm.get_current_type(); if (type == GLGizmosManager::FdmSupports || type == GLGizmosManager::Seam - || type == GLGizmosManager::MmuSegmentation) { + || type == GLGizmosManager::MmuSegmentation + || type == GLGizmosManager::Simplify ) { shader->stop_using(); gm.render_painter_gizmo(); shader->start_using(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp index 735328881..393be1a4e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp @@ -237,9 +237,8 @@ void GLGizmoBase::render_input_window(float x, float y, float bottom_limit) std::string GLGizmoBase::get_name(bool include_shortcut) const { int key = get_shortcut_key(); - assert( key >= WXK_CONTROL_A && key <= WXK_CONTROL_Z); std::string out = on_get_name(); - if (include_shortcut) + if (include_shortcut && key >= WXK_CONTROL_A && key <= WXK_CONTROL_Z) out += std::string(" [") + char(int('A') + key - int(WXK_CONTROL_A)) + "]"; return out; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp index 05f6adb5e..6c2c6afaf 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp @@ -136,6 +136,7 @@ public: bool is_selectable() const { return on_is_selectable(); } CommonGizmosDataID get_requirements() const { return on_get_requirements(); } virtual bool wants_enter_leave_snapshots() const { return false; } + virtual std::string get_action_snapshot_name() { return _u8L("Gizmo action"); } void set_common_data_pool(CommonGizmosDataPool* ptr) { m_c = ptr; } unsigned int get_sprite_id() const { return m_sprite_id; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 01eeebe10..437106fed 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -8,6 +8,7 @@ #include "slic3r/GUI/ImGuiWrapper.hpp" #include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/GUI_ObjectList.hpp" +#include "slic3r/Utils/UndoRedo.hpp" #include <GL/glew.h> @@ -165,7 +166,8 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l ImGui::Separator(); if (m_imgui->button(m_desc.at("remove_all"))) { - Plater::TakeSnapshot snapshot(wxGetApp().plater(), wxString(_L("Reset selection"))); + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Reset selection"), + UndoRedo::SnapshotType::GizmoAction); ModelObject* mo = m_c->selection_info()->model_object(); int idx = -1; for (ModelVolume* mv : mo->volumes) { @@ -298,8 +300,6 @@ void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool block) } } - activate_internal_undo_redo_stack(true); - Plater::TakeSnapshot snapshot(wxGetApp().plater(), block ? _L("Block supports by angle") : _L("Add supports by angle")); update_model_object(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index f2c19ba5c..0fb03140a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -21,6 +21,8 @@ protected: std::string get_gizmo_entering_text() const override { return _u8L("Entering Paint-on supports"); } std::string get_gizmo_leaving_text() const override { return _u8L("Leaving Paint-on supports"); } + std::string get_action_snapshot_name() override { return _u8L("Paint-on supports editing"); } + private: bool on_init() override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index 30f7ff7cf..f0a627f90 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -11,6 +11,7 @@ #include "slic3r/GUI/NotificationManager.hpp" #include "libslic3r/PresetBundle.hpp" #include "libslic3r/Model.hpp" +#include "slic3r/Utils/UndoRedo.hpp" #include <GL/glew.h> @@ -503,7 +504,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott ImGui::Separator(); if (m_imgui->button(m_desc.at("remove_all"))) { - Plater::TakeSnapshot snapshot(wxGetApp().plater(), wxString(_L("Reset selection"))); + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Reset selection"), + UndoRedo::SnapshotType::GizmoAction); ModelObject * mo = m_c->selection_info()->model_object(); int idx = -1; for (ModelVolume *mv : mo->volumes) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp index ab58ba186..851a5ac4f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp @@ -130,6 +130,7 @@ protected: std::string get_gizmo_entering_text() const override { return _u8L("Entering Multimaterial painting"); } std::string get_gizmo_leaving_text() const override { return _u8L("Leaving Multimaterial painting"); } + std::string get_action_snapshot_name() override { return _u8L("Multimaterial painting editing"); } size_t m_first_selected_extruder_idx = 0; size_t m_second_selected_extruder_idx = 1; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index be8fc331d..42bdd0843 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -26,35 +26,6 @@ GLGizmoPainterBase::GLGizmoPainterBase(GLCanvas3D& parent, const std::string& ic m_vbo_sphere.finalize_geometry(true); } -// port of 948bc382655993721d93d3b9fce9b0186fcfb211 -void GLGizmoPainterBase::activate_internal_undo_redo_stack(bool activate) -{ - Plater* plater = wxGetApp().plater(); - - // Following is needed to prevent taking an extra snapshot when the activation of - // the internal stack happens when the gizmo is already active (such as open gizmo, - // close gizmo, undo, start painting). The internal stack does not activate on the - // undo, because that would obliterate all future of the main stack (user would - // have to close the gizmo himself, he has no access to main undo/redo after the - // internal stack opens). We don't want the "entering" snapshot taken in this case, - // because there already is one. - std::string last_snapshot_name; - plater->undo_redo_topmost_string_getter(plater->can_undo(), last_snapshot_name); - - if (activate && !m_internal_stack_active) { - if (std::string str = this->get_gizmo_entering_text(); last_snapshot_name != str) - Plater::TakeSnapshot(plater, str, UndoRedo::SnapshotType::EnteringGizmo); - plater->enter_gizmos_stack(); - m_internal_stack_active = true; - } - if (!activate && m_internal_stack_active) { - plater->leave_gizmos_stack(); - if (std::string str = this->get_gizmo_leaving_text(); last_snapshot_name != str) - Plater::TakeSnapshot(plater, str, UndoRedo::SnapshotType::LeavingGizmoWithAction); - m_internal_stack_active = false; - } -} - void GLGizmoPainterBase::set_painter_gizmo_data(const Selection& selection) { if (m_state != On) @@ -450,8 +421,7 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous && m_button_down != Button::None) { // Take snapshot and update ModelVolume data. wxString action_name = this->handle_snapshot_action_name(shift_down, m_button_down); - activate_internal_undo_redo_stack(true); - Plater::TakeSnapshot snapshot(wxGetApp().plater(), action_name); + Plater::TakeSnapshot snapshot(wxGetApp().plater(), action_name, UndoRedo::SnapshotType::GizmoAction); update_model_object(); m_button_down = Button::None; @@ -548,16 +518,10 @@ void GLGizmoPainterBase::on_set_state() if (m_state == On && m_old_state != On) { // the gizmo was just turned on on_opening(); - if (! m_parent.get_gizmos_manager().is_serializing()) { - wxGetApp().CallAfter([this]() { - activate_internal_undo_redo_stack(true); - }); - } } if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off // we are actually shutting down on_shutdown(); - activate_internal_undo_redo_stack(false); m_old_mo_id = -1; //m_iva.release_geometry(); m_triangle_selectors.clear(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp index 6a15ab2a5..8d37f2404 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp @@ -57,30 +57,32 @@ private: std::array<GLIndexedVertexArray, 3> m_varrays; }; +class GLGizmoTransparentRender +{ +public: + // Following function renders the triangles and cursor. Having this separated + // from usual on_render method allows to render them before transparent + // objects, so they can be seen inside them. The usual on_render is called + // after all volumes (including transparent ones) are rendered. + virtual void render_painter_gizmo() const = 0; +}; // Following class is a base class for a gizmo with ability to paint on mesh // using circular blush (such as FDM supports gizmo and seam painting gizmo). // The purpose is not to duplicate code related to mesh painting. -class GLGizmoPainterBase : public GLGizmoBase +class GLGizmoPainterBase : public GLGizmoTransparentRender, public GLGizmoBase { private: ObjectID m_old_mo_id; size_t m_old_volumes_size = 0; void on_render() override {} void on_render_for_picking() override {} - public: GLGizmoPainterBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); ~GLGizmoPainterBase() override = default; virtual void set_painter_gizmo_data(const Selection& selection); virtual bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); - // Following function renders the triangles and cursor. Having this separated - // from usual on_render method allows to render them before transparent objects, - // so they can be seen inside them. The usual on_render is called after all - // volumes (including transparent ones) are rendered. - virtual void render_painter_gizmo() const = 0; - protected: void render_triangles(const Selection& selection, const bool use_polygon_offset_fill = true) const; void render_cursor() const; @@ -88,7 +90,6 @@ protected: void render_cursor_sphere(const Transform3d& trafo) const; virtual void update_model_object() const = 0; virtual void update_from_model_object() = 0; - void activate_internal_undo_redo_stack(bool activate); virtual std::array<float, 4> get_cursor_sphere_left_button_color() const { return {0.f, 0.f, 1.f, 0.25f}; } virtual std::array<float, 4> get_cursor_sphere_right_button_color() const { return {1.f, 0.f, 0.f, 0.25f}; } @@ -170,6 +171,7 @@ protected: void on_load(cereal::BinaryInputArchive& ar) override; void on_save(cereal::BinaryOutputArchive& ar) const override {} CommonGizmosDataID on_get_requirements() const override; + bool wants_enter_leave_snapshots() const override { return true; } virtual wxString handle_snapshot_action_name(bool shift_down, Button button_down) const = 0; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp index a2ee56d9a..b23528772 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp @@ -8,6 +8,7 @@ #include "slic3r/GUI/ImGuiWrapper.hpp" #include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/GUI_ObjectList.hpp" +#include "slic3r/Utils/UndoRedo.hpp" #include <GL/glew.h> @@ -121,7 +122,8 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit) m_imgui->text(""); if (m_imgui->button(m_desc.at("remove_all"))) { - Plater::TakeSnapshot snapshot(wxGetApp().plater(), wxString(_L("Reset selection"))); + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Reset selection"), + UndoRedo::SnapshotType::GizmoAction); ModelObject* mo = m_c->selection_info()->model_object(); int idx = -1; for (ModelVolume* mv : mo->volumes) { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp index 7a00a9d8e..408c2ec4c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp @@ -22,6 +22,7 @@ protected: std::string get_gizmo_entering_text() const override { return _u8L("Entering Seam painting"); } std::string get_gizmo_leaving_text() const override { return _u8L("Leaving Seam painting"); } + std::string get_action_snapshot_name() override { return _u8L("Paint-on seam editing"); } private: bool on_init() override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp index d4ee885b3..4fab1bcb6 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp @@ -1,4 +1,5 @@ #include "GLGizmoSimplify.hpp" +#include "slic3r/GUI/3DScene.hpp" #include "slic3r/GUI/GLCanvas3D.hpp" #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/GUI_ObjectManipulation.hpp" @@ -21,18 +22,23 @@ GLGizmoSimplify::GLGizmoSimplify(GLCanvas3D & parent, , m_progress(0) , m_volume(nullptr) , m_obj_index(0) - , m_need_reload(false) + , m_need_reload(false) + , m_show_wireframe(false) , tr_mesh_name(_u8L("Mesh name")) , tr_triangles(_u8L("Triangles")) , tr_preview(_u8L("Preview")) , tr_detail_level(_u8L("Detail level")) , tr_decimate_ratio(_u8L("Decimate ratio")) + + , m_wireframe_VBO_id(0) + , m_wireframe_IBO_id(0) {} GLGizmoSimplify::~GLGizmoSimplify() { m_state = State::canceling; if (m_worker.joinable()) m_worker.join(); + free_gpu(); } bool GLGizmoSimplify::on_init() @@ -47,17 +53,23 @@ std::string GLGizmoSimplify::on_get_name() const return _u8L("Simplify"); } -void GLGizmoSimplify::on_render() {} +void GLGizmoSimplify::on_render() { } + void GLGizmoSimplify::on_render_for_picking() {} void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limit) { create_gui_cfg(); - const Selection &selection = m_parent.get_selection(); - int object_idx = selection.get_object_idx(); - if (!is_selected_object(&object_idx)) return; - ModelObject *obj = wxGetApp().plater()->model().objects[object_idx]; - ModelVolume *act_volume = obj->volumes.front(); + int obj_index; + ModelVolume *act_volume = get_selected_volume(&obj_index); + if (act_volume == nullptr) { + switch (m_state) { + case State::settings: close(); break; + case State::canceling: break; + default: m_state = State::canceling; + } + return; + } // Check selection of new volume // Do not reselect object when processing @@ -68,13 +80,14 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi set_its(*m_original_its); } - m_obj_index = object_idx; // to remember correct object + m_obj_index = obj_index; // to remember correct object m_volume = act_volume; m_original_its = {}; m_configuration.decimate_ratio = 50.; // default value m_configuration.fix_count_by_ratio(m_volume->mesh().its.indices.size()); m_is_valid_result = false; m_exist_preview = false; + init_wireframe(); if (change_window_position) { ImVec2 pos = ImGui::GetMousePos(); @@ -97,7 +110,7 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi int flag = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse; - m_imgui->begin(get_name(), flag); + m_imgui->begin(on_get_name(), flag); size_t triangle_count = m_volume->mesh().its.indices.size(); // already reduced mesh @@ -188,6 +201,11 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi ImGui::Text(_L("%d triangles").c_str(), m_configuration.wanted_count); m_imgui->disabled_end(); // use_count + if (ImGui::Checkbox(_L("Show wireframe").c_str(), &m_show_wireframe)) { + if (m_show_wireframe) init_wireframe(); + else free_gpu(); + } + if (m_state == State::settings) { if (m_imgui->button(_L("Cancel"))) { if (m_original_its.has_value()) { @@ -236,7 +254,7 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi // set m_state must be before close() !!! m_state = State::settings; if (close_on_end) after_apply(); - + else init_wireframe(); // Fix warning icon in object list wxGetApp().obj_list()->update_item_error_icon(m_obj_index, -1); } @@ -329,9 +347,18 @@ void GLGizmoSimplify::on_set_state() { // Closing gizmo. e.g. selecting another one if (GLGizmoBase::m_state == GLGizmoBase::Off) { + // can appear when delete objects + bool empty_selection = m_parent.get_selection().is_empty(); + + // cancel processing + if (empty_selection && + m_state != State::settings && + m_state != State::canceling) + m_state = State::canceling; + // refuse outgoing during simlification // object is not selected when it is deleted(cancel and close gizmo) - if (m_state != State::settings && is_selected_object()) { + if (m_state != State::settings && !empty_selection) { GLGizmoBase::m_state = GLGizmoBase::On; auto notification_manager = wxGetApp().plater()->get_notification_manager(); notification_manager->push_notification( @@ -343,9 +370,12 @@ void GLGizmoSimplify::on_set_state() // revert preview if (m_exist_preview) { - set_its(*m_original_its); - m_parent.reload_scene(true); - m_need_reload = false; + m_exist_preview = false; + if (exist_volume(m_volume)) { + set_its(*m_original_its); + m_parent.reload_scene(false); + m_need_reload = false; + } } // invalidate selected model @@ -383,20 +413,110 @@ void GLGizmoSimplify::request_rerender() { }); } -bool GLGizmoSimplify::is_selected_object(int *object_idx) -{ - int index = (object_idx != nullptr) ? *object_idx : - m_parent.get_selection().get_object_idx(); - // no selected object --> can appear after delete model - if (index < 0) { - switch (m_state) { - case State::settings: close(); break; - case State::canceling: break; - default: m_state = State::canceling; - } - return false; +bool GLGizmoSimplify::exist_volume(ModelVolume *volume) { + auto objs = wxGetApp().plater()->model().objects; + for (const auto &obj : objs) { + const auto &vlms = obj->volumes; + auto item = std::find(vlms.begin(), vlms.end(), volume); + if (item != vlms.end()) return true; + } + return false; +} + +ModelVolume *GLGizmoSimplify::get_selected_volume(int *object_idx_ptr) const +{ + const Selection &selection = m_parent.get_selection(); + int object_idx = selection.get_object_idx(); + if (object_idx_ptr != nullptr) *object_idx_ptr = object_idx; + if (object_idx < 0) return nullptr; + ModelObjectPtrs &objs = wxGetApp().plater()->model().objects; + if (static_cast<int>(objs.size()) <= object_idx) return nullptr; + ModelObject *obj = objs[object_idx]; + if (obj->volumes.empty()) return nullptr; + return obj->volumes.front(); +} + +void GLGizmoSimplify::init_wireframe() +{ + if (!m_show_wireframe) return; + const indexed_triangle_set &its = m_volume->mesh().its; + free_gpu(); + if (its.indices.empty()) return; + + // vertices + glsafe(::glGenBuffers(1, &m_wireframe_VBO_id)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_wireframe_VBO_id)); + glsafe(::glBufferData(GL_ARRAY_BUFFER, + its.vertices.size() * 3 * sizeof(float), + its.vertices.data(), GL_STATIC_DRAW)); + + // indices + std::vector<Vec2i> contour_indices; + contour_indices.reserve((its.indices.size() * 3) / 2); + for (const auto &triangle : its.indices) { + for (size_t ti1 = 0; ti1 < 3; ++ti1) { + size_t ti2 = (ti1 == 2) ? 0 : (ti1 + 1); + if (triangle[ti1] > triangle[ti2]) continue; + contour_indices.emplace_back(triangle[ti1], triangle[ti2]); + } + } + glsafe(::glGenBuffers(1, &m_wireframe_IBO_id)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_wireframe_IBO_id)); + glsafe(::glBufferData(GL_ARRAY_BUFFER, + 2*contour_indices.size() * sizeof(coord_t), + contour_indices.data(), GL_STATIC_DRAW)); + m_wireframe_IBO_size = contour_indices.size() * 2; + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); +} + +void GLGizmoSimplify::render_wireframe() const +{ + // is initialized? + if (m_wireframe_VBO_id == 0 || m_wireframe_IBO_id == 0) return; + if (!m_show_wireframe) return; + ModelVolume *act_volume = get_selected_volume(); + if (act_volume == nullptr) return; + const Transform3d trafo_matrix = + act_volume->get_object()->instances[m_parent.get_selection().get_instance_idx()] + ->get_transformation().get_matrix() * + act_volume->get_matrix(); + + glsafe(::glPushMatrix()); + glsafe(::glMultMatrixd(trafo_matrix.data())); + + auto *contour_shader = wxGetApp().get_shader("mm_contour"); + contour_shader->start_using(); + glsafe(::glDepthFunc(GL_LEQUAL)); + glsafe(::glLineWidth(1.0f)); + + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_wireframe_VBO_id)); + glsafe(::glVertexPointer(3, GL_FLOAT, 3 * sizeof(float), nullptr)); + glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); + + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_wireframe_IBO_id)); + glsafe(::glDrawElements(GL_LINES, m_wireframe_IBO_size, GL_UNSIGNED_INT, nullptr)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + + glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); + + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + glsafe(::glDepthFunc(GL_LESS)); + + glsafe(::glPopMatrix()); // pop trafo + contour_shader->stop_using(); +} + +void GLGizmoSimplify::free_gpu() +{ + if (m_wireframe_VBO_id != 0) { + glsafe(::glDeleteBuffers(1, &m_wireframe_VBO_id)); + m_wireframe_VBO_id = 0; + } + + if (m_wireframe_IBO_id != 0) { + glsafe(::glDeleteBuffers(1, &m_wireframe_IBO_id)); + m_wireframe_IBO_id = 0; } - return true; } } // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp index 856b6298d..d2358b2d3 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp @@ -4,11 +4,14 @@ // Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, // which overrides our localization "L" macro. #include "GLGizmoBase.hpp" +#include "GLGizmoPainterBase.hpp" // for render wireframe #include "admesh/stl.h" // indexed_triangle_set #include <thread> #include <optional> #include <atomic> +#include <GL/glew.h> // GLUint + namespace Slic3r { class ModelVolume; @@ -16,7 +19,7 @@ class ModelVolume; namespace GUI { -class GLGizmoSimplify : public GLGizmoBase +class GLGizmoSimplify: public GLGizmoBase, public GLGizmoTransparentRender // GLGizmoBase { public: GLGizmoSimplify(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); @@ -31,6 +34,8 @@ protected: virtual bool on_is_selectable() const override { return false; } virtual void on_set_state() override; + // GLGizmoPainterBase + virtual void render_painter_gizmo() const override{ render_wireframe(); } private: void after_apply(); void close(); @@ -38,16 +43,20 @@ private: void set_its(indexed_triangle_set &its); void create_gui_cfg(); void request_rerender(); - bool is_selected_object(int *object_idx = nullptr); + ModelVolume *get_selected_volume(int *object_idx = nullptr) const; + + // return false when volume was deleted + static bool exist_volume(ModelVolume *volume); std::atomic_bool m_is_valid_result; // differ what to do in apply std::atomic_bool m_exist_preview; // set when process end volatile int m_progress; // percent of done work - ModelVolume *m_volume; // + ModelVolume *m_volume; // keep pointer to actual working volume size_t m_obj_index; std::optional<indexed_triangle_set> m_original_its; + bool m_show_wireframe; volatile bool m_need_reload; // after simplify, glReload must be on main thread std::thread m_worker; @@ -100,6 +109,13 @@ private: const std::string tr_preview; const std::string tr_detail_level; const std::string tr_decimate_ratio; + + // rendering wireframe + void render_wireframe() const; + void init_wireframe(); + void free_gpu(); + GLuint m_wireframe_VBO_id, m_wireframe_IBO_id; + size_t m_wireframe_IBO_size; }; } // namespace GUI diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 7a9373a5f..562226c2e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -52,8 +52,6 @@ std::vector<size_t> GLGizmosManager::get_selectable_idxs() const return out; } - - size_t GLGizmosManager::get_gizmo_idx_from_mouse(const Vec2d& mouse_pos) const { if (! m_enabled) @@ -485,7 +483,7 @@ void GLGizmosManager::render_painter_gizmo() const if (!m_enabled || m_current == Undefined) return; - auto* gizmo = dynamic_cast<GLGizmoPainterBase*>(get_current()); + auto *gizmo = dynamic_cast<GLGizmoTransparentRender*>(get_current()); assert(gizmo); // check the precondition gizmo->render_painter_gizmo(); } @@ -1222,13 +1220,15 @@ bool GLGizmosManager::activate_gizmo(EType type) if (! m_parent.get_gizmos_manager().is_serializing() && old_gizmo->wants_enter_leave_snapshots()) Plater::TakeSnapshot snapshot(wxGetApp().plater(), - Slic3r::format(_utf8("Leaving %1%"), old_gizmo->get_name(false))); + Slic3r::format(_utf8("Leaving %1%"), old_gizmo->get_name(false)), + UndoRedo::SnapshotType::LeavingGizmoWithAction); } if (new_gizmo && ! m_parent.get_gizmos_manager().is_serializing() && new_gizmo->wants_enter_leave_snapshots()) Plater::TakeSnapshot snapshot(wxGetApp().plater(), - Slic3r::format(_utf8("Entering %1%"), new_gizmo->get_name(false))); + Slic3r::format(_utf8("Entering %1%"), new_gizmo->get_name(false)), + UndoRedo::SnapshotType::EnteringGizmo); m_current = type; diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index b42a9c7bf..79003f518 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -620,7 +620,8 @@ void MainFrame::update_title() // m_plater->get_project_filename() produces file name including path, but excluding extension. // Don't try to remove the extension, it would remove part of the file name after the last dot! wxString project = from_path(into_path(m_plater->get_project_filename()).filename()); - wxString dirty_marker = (!m_plater->model().objects.empty() && m_plater->is_project_dirty()) ? "*" : ""; +// wxString dirty_marker = (!m_plater->model().objects.empty() && m_plater->is_project_dirty()) ? "*" : ""; + wxString dirty_marker = m_plater->is_project_dirty() ? "*" : ""; if (!dirty_marker.empty() || !project.empty()) { if (!dirty_marker.empty() && project.empty()) project = _L("Untitled"); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 5833341e8..92a8ecd92 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1595,7 +1595,7 @@ struct Plater::priv } return res; } - void reset_project_dirty_after_save() { dirty_state.reset_after_save(); } + void reset_project_dirty_after_save() { m_undo_redo_stack_main.mark_current_as_saved(); dirty_state.reset_after_save(); } void reset_project_dirty_initial_presets() { dirty_state.reset_initial_presets(); } #if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW @@ -2066,6 +2066,9 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) // Initialize the Undo / Redo stack with a first snapshot. this->take_snapshot(_L("New Project"), UndoRedo::SnapshotType::ProjectSeparator); + // Reset the "dirty project" flag. + m_undo_redo_stack_main.mark_current_as_saved(); + dirty_state.update_from_undo_redo_stack(false); this->q->Bind(EVT_LOAD_MODEL_OTHER_INSTANCE, [this](LoadFromOtherInstanceEvent& evt) { BOOST_LOG_TRIVIAL(trace) << "Received load from other instance event."; @@ -4696,10 +4699,25 @@ void Plater::priv::take_snapshot(const std::string& snapshot_name, const UndoRed model.wipe_tower.position = Vec2d(config.opt_float("wipe_tower_x"), config.opt_float("wipe_tower_y")); model.wipe_tower.rotation = config.opt_float("wipe_tower_rotation_angle"); } - this->undo_redo_stack().take_snapshot(snapshot_name, model, view3D->get_canvas3d()->get_selection(), view3D->get_canvas3d()->get_gizmos_manager(), snapshot_data); + const GLGizmosManager& gizmos = view3D->get_canvas3d()->get_gizmos_manager(); + + if (snapshot_type == UndoRedo::SnapshotType::ProjectSeparator && wxGetApp().app_config->get("clear_undo_redo_stack_on_new_project") == "1") + this->undo_redo_stack().clear(); + this->undo_redo_stack().take_snapshot(snapshot_name, model, view3D->get_canvas3d()->get_selection(), gizmos, snapshot_data); + if (snapshot_type == UndoRedo::SnapshotType::LeavingGizmoWithAction) { + // Filter all but the last UndoRedo::SnapshotType::GizmoAction in a row between the last UndoRedo::SnapshotType::EnteringGizmo and UndoRedo::SnapshotType::LeavingGizmoWithAction. + // The remaining snapshot will be renamed to a more generic name, + // depending on what gizmo is being left. + assert(gizmos.get_current() != nullptr); + std::string new_name = gizmos.get_current()->get_action_snapshot_name(); + this->undo_redo_stack().reduce_noisy_snapshots(new_name); + } else if (snapshot_type == UndoRedo::SnapshotType::ProjectSeparator) { + // Reset the "dirty project" flag. + m_undo_redo_stack_main.mark_current_as_saved(); + } this->undo_redo_stack().release_least_recently_used(); - dirty_state.update_from_undo_redo_stack(ProjectDirtyStateManager::UpdateType::TakeSnapshot); + dirty_state.update_from_undo_redo_stack(m_undo_redo_stack_main.project_modified()); // Save the last active preset name of a particular printer technology. ((this->printer_technology == ptFFF) ? m_last_fff_printer_profile_name : m_last_sla_printer_profile_name) = wxGetApp().preset_bundle->printers.get_selected_preset_name(); @@ -4836,7 +4854,7 @@ void Plater::priv::undo_redo_to(std::vector<UndoRedo::Snapshot>::const_iterator view3D->get_canvas3d()->force_main_toolbar_left_action(view3D->get_canvas3d()->get_main_toolbar_item_id("layersediting")); } - dirty_state.update_from_undo_redo_stack(ProjectDirtyStateManager::UpdateType::UndoRedoTo); + dirty_state.update_from_undo_redo_stack(m_undo_redo_stack_main.project_modified()); } void Plater::priv::update_after_undo_redo(const UndoRedo::Snapshot& snapshot, bool /* temp_snapshot_was_taken */) @@ -6749,7 +6767,6 @@ bool Plater::can_mirror() const { return p->can_mirror(); } bool Plater::can_split(bool to_objects) const { return p->can_split(to_objects); } const UndoRedo::Stack& Plater::undo_redo_stack_main() const { return p->undo_redo_stack_main(); } void Plater::clear_undo_redo_stack_main() { p->undo_redo_stack_main().clear(); } -const UndoRedo::Stack& Plater::undo_redo_stack_active() const { return p->undo_redo_stack(); } void Plater::enter_gizmos_stack() { p->enter_gizmos_stack(); } void Plater::leave_gizmos_stack() { p->leave_gizmos_stack(); } bool Plater::inside_snapshot_capture() { return p->inside_snapshot_capture(); } diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 9607c25a8..a595ab44e 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -254,7 +254,6 @@ public: // For the memory statistics. const Slic3r::UndoRedo::Stack& undo_redo_stack_main() const; void clear_undo_redo_stack_main(); - const Slic3r::UndoRedo::Stack& undo_redo_stack_active() const; // Enter / leave the Gizmos specific Undo / Redo stack. To be used by the SLA support point editing gizmo. void enter_gizmos_stack(); void leave_gizmos_stack(); diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 0687399b2..880723693 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -238,6 +238,14 @@ void PreferencesDialog::build(size_t selected_tab) option = Option(def, "show_splash_screen"); m_optgroup_general->append_single_option_line(option); + // Clear Undo / Redo stack on new project + def.label = L("Clear Undo / Redo stack on new project"); + def.type = coBool; + def.tooltip = L("Clear Undo / Redo stack on new project or when an existing project is loaded."); + def.set_default_value(new ConfigOptionBool{ app_config->get("clear_undo_redo_stack_on_new_project") == "1" }); + option = Option(def, "clear_undo_redo_stack_on_new_project"); + m_optgroup_general->append_single_option_line(option); + #if defined(_WIN32) || defined(__APPLE__) def.label = L("Enable support for legacy 3DConnexion devices"); def.type = coBool; diff --git a/src/slic3r/GUI/ProjectDirtyStateManager.cpp b/src/slic3r/GUI/ProjectDirtyStateManager.cpp index 4ce806b2a..ecf596634 100644 --- a/src/slic3r/GUI/ProjectDirtyStateManager.cpp +++ b/src/slic3r/GUI/ProjectDirtyStateManager.cpp @@ -6,7 +6,6 @@ #include "MainFrame.hpp" #include "I18N.hpp" #include "Plater.hpp" -#include "../Utils/UndoRedo.hpp" #include <boost/algorithm/string/predicate.hpp> @@ -16,226 +15,38 @@ namespace Slic3r { namespace GUI { -enum class EStackType +void ProjectDirtyStateManager::update_from_undo_redo_stack(bool dirty) { - Main, - Gizmo -}; - -// returns the current active snapshot (the topmost snapshot in the undo part of the stack) in the given stack -static const UndoRedo::Snapshot* get_active_snapshot(const UndoRedo::Stack& stack) { - const std::vector<UndoRedo::Snapshot>& snapshots = stack.snapshots(); - const size_t active_snapshot_time = stack.active_snapshot_time(); - const auto it = std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(active_snapshot_time)); - const int idx = it - snapshots.begin() - 1; - const Slic3r::UndoRedo::Snapshot* ret = (0 <= idx && idx < int(snapshots.size()) - 1) ? - &snapshots[idx] : nullptr; - - assert(ret != nullptr); - return ret; -} - -// returns the last saveable snapshot (the topmost snapshot in the undo part of the stack that can be saved) in the given stack -static const UndoRedo::Snapshot* get_last_saveable_snapshot(EStackType type, const UndoRedo::Stack& stack, - const ProjectDirtyStateManager::DirtyState::Gizmos& gizmos, size_t last_save_main) { - - // returns true if the given snapshot is not saveable - auto skip_main = [&gizmos, last_save_main, &stack](const UndoRedo::Snapshot& snapshot) { - auto is_gizmo_with_modifications = [&gizmos, &stack](const UndoRedo::Snapshot& snapshot) { - if (boost::starts_with(snapshot.name, _utf8("Entering"))) { - if (gizmos.current) - return true; - - std::string topmost_redo; - wxGetApp().plater()->undo_redo_topmost_string_getter(false, topmost_redo); - if (boost::starts_with(topmost_redo, _utf8("Leaving"))) { - const std::vector<UndoRedo::Snapshot>& snapshots = stack.snapshots(); - const UndoRedo::Snapshot* leaving_snapshot = &(*std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(snapshot.timestamp + 1))); - if (gizmos.is_used_and_modified(*leaving_snapshot)) - return true; - } - } - return false; - }; - - if (snapshot.name == _utf8("New Project")) - return true; - else if (snapshot.name == _utf8("Reset Project")) - return true; - else if (boost::starts_with(snapshot.name, _utf8("Load Project"))) - return true; - else if (boost::starts_with(snapshot.name, _utf8("Selection"))) - return true; - else if (boost::starts_with(snapshot.name, _utf8("Entering"))) { - if (last_save_main != snapshot.timestamp + 1 && !is_gizmo_with_modifications(snapshot)) - return true; - } - else if (boost::starts_with(snapshot.name, _utf8("Leaving"))) { - if (last_save_main != snapshot.timestamp && !gizmos.is_used_and_modified(snapshot)) - return true; - } - - return false; - }; - - // returns true if the given snapshot is not saveable - auto skip_gizmo = [](const UndoRedo::Snapshot& snapshot) { - // put here any needed condition to skip the snapshot - return false; - }; - - const UndoRedo::Snapshot* curr = get_active_snapshot(stack); - const std::vector<UndoRedo::Snapshot>& snapshots = stack.snapshots(); - size_t shift = 1; - while (curr->timestamp > 0 && ((type == EStackType::Main && skip_main(*curr)) || (type == EStackType::Gizmo && skip_gizmo(*curr)))) { - const UndoRedo::Snapshot* temp = curr; - curr = &(*std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(curr->timestamp - shift))); - shift = (curr == temp) ? shift + 1 : 1; - } - if (type == EStackType::Main) { - if (boost::starts_with(curr->name, _utf8("Entering")) && last_save_main == curr->timestamp + 1) { - std::string topmost_redo; - wxGetApp().plater()->undo_redo_topmost_string_getter(false, topmost_redo); - if (boost::starts_with(topmost_redo, _utf8("Leaving"))) - curr = &(*std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(curr->timestamp + 1))); - } - } - return curr->timestamp > 0 ? curr : nullptr; -} - -// returns the name of the gizmo contained in the given string -static std::string extract_gizmo_name(const std::string& s) { - static const std::array<std::string, 2> prefixes = { _utf8("Entering"), _utf8("Leaving") }; - - std::string ret; - for (const std::string& prefix : prefixes) { - if (boost::starts_with(s, prefix)) - ret = s.substr(prefix.length() + 1); - - if (!ret.empty()) - break; - } - return ret; -} - -void ProjectDirtyStateManager::DirtyState::Gizmos::add_used(const UndoRedo::Snapshot& snapshot) -{ - const std::string name = extract_gizmo_name(snapshot.name); - auto it = used.find(name); - if (it == used.end()) - it = used.insert({ name, { {} } }).first; - - it->second.modified_timestamps.push_back(snapshot.timestamp); -} - -void ProjectDirtyStateManager::DirtyState::Gizmos::remove_obsolete_used(const Slic3r::UndoRedo::Stack& main_stack) -{ - const std::vector<UndoRedo::Snapshot>& snapshots = main_stack.snapshots(); - for (auto& item : used) { - auto it = item.second.modified_timestamps.begin(); - while (it != item.second.modified_timestamps.end()) { - size_t timestamp = *it; - auto snapshot_it = std::find_if(snapshots.begin(), snapshots.end(), [timestamp](const Slic3r::UndoRedo::Snapshot& snapshot) { return snapshot.timestamp == timestamp; }); - if (snapshot_it == snapshots.end()) - it = item.second.modified_timestamps.erase(it); - else - ++it; - } - } -} - -#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW -bool ProjectDirtyStateManager::DirtyState::Gizmos::any_used_modified() const -{ - for (auto& [name, gizmo] : used) { - if (!gizmo.modified_timestamps.empty()) - return true; - } - return false; -} -#endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW - -// returns true if the given snapshot is contained in any of the gizmos caches -bool ProjectDirtyStateManager::DirtyState::Gizmos::is_used_and_modified(const UndoRedo::Snapshot& snapshot) const -{ - for (const auto& item : used) { - for (size_t i : item.second.modified_timestamps) { - if (i == snapshot.timestamp) - return true; - } - } - return false; -} - -void ProjectDirtyStateManager::DirtyState::Gizmos::reset() -{ - used.clear(); -} - -void ProjectDirtyStateManager::update_from_undo_redo_stack(UpdateType type) -{ - if (!wxGetApp().initialized()) - return; - - const Plater* plater = wxGetApp().plater(); - if (plater == nullptr) - return; - - const UndoRedo::Stack& main_stack = plater->undo_redo_stack_main(); - const UndoRedo::Stack& active_stack = plater->undo_redo_stack_active(); - - if (&main_stack == &active_stack) - update_from_undo_redo_main_stack(type, main_stack); - else - update_from_undo_redo_gizmo_stack(type, active_stack); - - wxGetApp().mainframe->update_title(); + m_plater_dirty = dirty; + if (const Plater *plater = wxGetApp().plater(); plater && wxGetApp().initialized()) + wxGetApp().mainframe->update_title(); } void ProjectDirtyStateManager::update_from_presets() { - m_state.presets = false; + m_presets_dirty = false; // check switching of the presets only for exist/loaded project, but not for new if (!wxGetApp().plater()->get_project_filename().IsEmpty()) { for (const auto& [type, name] : wxGetApp().get_selected_presets()) - m_state.presets |= !m_initial_presets[type].empty() && m_initial_presets[type] != name; + m_presets_dirty |= !m_initial_presets[type].empty() && m_initial_presets[type] != name; } - m_state.presets |= wxGetApp().has_unsaved_preset_changes(); + m_presets_dirty |= wxGetApp().has_unsaved_preset_changes(); wxGetApp().mainframe->update_title(); } void ProjectDirtyStateManager::reset_after_save() { - const Plater* plater = wxGetApp().plater(); - const UndoRedo::Stack& main_stack = plater->undo_redo_stack_main(); - const UndoRedo::Stack& active_stack = plater->undo_redo_stack_active(); - - if (&main_stack == &active_stack) { - const UndoRedo::Snapshot* saveable_snapshot = get_last_saveable_snapshot(EStackType::Main, main_stack, m_state.gizmos, m_last_save.main); - m_last_save.main = (saveable_snapshot != nullptr) ? saveable_snapshot->timestamp : 0; - } - else { - // Gizmo is active with its own Undo / Redo stack (for example the SLA support point editing gizmo). - const UndoRedo::Snapshot* main_active_snapshot = get_active_snapshot(main_stack); - if (boost::starts_with(main_active_snapshot->name, _utf8("Entering"))) { - if (m_state.gizmos.current) - m_last_save.main = main_active_snapshot->timestamp + 1; - } - const UndoRedo::Snapshot* saveable_snapshot = get_last_saveable_snapshot(EStackType::Gizmo, active_stack, m_state.gizmos, m_last_save.main); - m_last_save.gizmo = (saveable_snapshot != nullptr) ? saveable_snapshot->timestamp : 0; - } - - reset_initial_presets(); - m_state.reset(); + this->reset_initial_presets(); + m_plater_dirty = false; + m_presets_dirty = false; wxGetApp().mainframe->update_title(); } void ProjectDirtyStateManager::reset_initial_presets() { - m_initial_presets = std::array<std::string, Preset::TYPE_COUNT>(); - for (const auto& [type, name] : wxGetApp().get_selected_presets()) { + m_initial_presets.fill(std::string{}); + for (const auto& [type, name] : wxGetApp().get_selected_presets()) m_initial_presets[type] = name; - } } #if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW @@ -324,89 +135,10 @@ void ProjectDirtyStateManager::render_debug_window() const } } - if (m_state.gizmos.any_used_modified()) { - if (ImGui::CollapsingHeader("Gizmos", ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::Indent(10.0f); - for (const auto& [name, gizmo] : m_state.gizmos.used) { - if (!gizmo.modified_timestamps.empty()) { - if (ImGui::CollapsingHeader(name.c_str(), ImGuiTreeNodeFlags_DefaultOpen)) { - std::string modified_timestamps; - for (size_t i = 0; i < gizmo.modified_timestamps.size(); ++i) { - if (i > 0) - modified_timestamps += " | "; - modified_timestamps += std::to_string(gizmo.modified_timestamps[i]); - } - imgui.text(modified_timestamps); - } - } - } - ImGui::Unindent(10.0f); - } - } - imgui.end(); } #endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW -void ProjectDirtyStateManager::update_from_undo_redo_main_stack(UpdateType type, const Slic3r::UndoRedo::Stack& stack) -{ - m_state.plater = false; - - if (type == UpdateType::TakeSnapshot) { - if (m_last_save.main != 0) { - const std::vector<UndoRedo::Snapshot>& snapshots = stack.snapshots(); - auto snapshot_it = std::find_if(snapshots.begin(), snapshots.end(), [this](const Slic3r::UndoRedo::Snapshot& snapshot) { return snapshot.timestamp == m_last_save.main; }); - if (snapshot_it == snapshots.end()) - m_last_save.main = 0; - } - m_state.gizmos.remove_obsolete_used(stack); - } - - const UndoRedo::Snapshot* active_snapshot = get_active_snapshot(stack); - if (active_snapshot->name == _utf8("New Project") || - active_snapshot->name == _utf8("Reset Project") || - boost::starts_with(active_snapshot->name, _utf8("Load Project"))) - return; - - if (boost::starts_with(active_snapshot->name, _utf8("Entering"))) { - if (type == UpdateType::UndoRedoTo) { - std::string topmost_redo; - wxGetApp().plater()->undo_redo_topmost_string_getter(false, topmost_redo); - if (boost::starts_with(topmost_redo, _utf8("Leaving"))) { - const std::vector<UndoRedo::Snapshot>& snapshots = stack.snapshots(); - const UndoRedo::Snapshot* leaving_snapshot = &(*std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(active_snapshot->timestamp + 1))); - if (m_state.gizmos.is_used_and_modified(*leaving_snapshot)) { - m_state.plater = (leaving_snapshot != nullptr && leaving_snapshot->timestamp != m_last_save.main); - return; - } - } - } - m_state.gizmos.current = false; - m_last_save.gizmo = 0; - } - else if (boost::starts_with(active_snapshot->name, _utf8("Leaving"))) { - if (m_state.gizmos.current) - m_state.gizmos.add_used(*active_snapshot); - m_state.gizmos.current = false; - m_last_save.gizmo = 0; - } - - const UndoRedo::Snapshot* last_saveable_snapshot = get_last_saveable_snapshot(EStackType::Main, stack, m_state.gizmos, m_last_save.main); - m_state.plater = (last_saveable_snapshot != nullptr && last_saveable_snapshot->timestamp != m_last_save.main); -} - -void ProjectDirtyStateManager::update_from_undo_redo_gizmo_stack(UpdateType type, const Slic3r::UndoRedo::Stack& stack) -{ - m_state.gizmos.current = false; - - const UndoRedo::Snapshot* active_snapshot = get_active_snapshot(stack); - if (active_snapshot->name == "Gizmos-Initial") - return; - - const UndoRedo::Snapshot* last_saveable_snapshot = get_last_saveable_snapshot(EStackType::Gizmo, stack, m_state.gizmos, m_last_save.main); - m_state.gizmos.current = (last_saveable_snapshot != nullptr && last_saveable_snapshot->timestamp != m_last_save.gizmo); -} - } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/ProjectDirtyStateManager.hpp b/src/slic3r/GUI/ProjectDirtyStateManager.hpp index 2151c9717..574e6912e 100644 --- a/src/slic3r/GUI/ProjectDirtyStateManager.hpp +++ b/src/slic3r/GUI/ProjectDirtyStateManager.hpp @@ -4,89 +4,32 @@ #include "libslic3r/Preset.hpp" namespace Slic3r { -namespace UndoRedo { -class Stack; -struct Snapshot; -} // namespace UndoRedo - namespace GUI { + class ProjectDirtyStateManager { -public: - enum class UpdateType : unsigned char - { - TakeSnapshot, - UndoRedoTo - }; - - struct DirtyState - { - struct Gizmos - { - struct Gizmo - { - std::vector<size_t> modified_timestamps; - }; - - bool current{ false }; - std::map<std::string, Gizmo> used; - - void add_used(const UndoRedo::Snapshot& snapshot); - void remove_obsolete_used(const Slic3r::UndoRedo::Stack& main_stack); -#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW - bool any_used_modified() const; -#endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW - bool is_used_and_modified(const UndoRedo::Snapshot& snapshot) const; - void reset(); - }; - - bool plater{ false }; - bool presets{ false }; - Gizmos gizmos; - - bool is_dirty() const { return plater || presets || gizmos.current; } - void reset() { - plater = false; - presets = false; - gizmos.current = false; - } - }; - -private: - struct LastSaveTimestamps - { - size_t main{ 0 }; - size_t gizmo{ 0 }; - - void reset() { - main = 0; - gizmo = 0; - } - }; - - DirtyState m_state; - LastSaveTimestamps m_last_save; - - // keeps track of initial selected presets - std::array<std::string, Preset::TYPE_COUNT> m_initial_presets; - -public: - bool is_dirty() const { return m_state.is_dirty(); } - void update_from_undo_redo_stack(UpdateType type); +public: + void update_from_undo_redo_stack(bool dirty); void update_from_presets(); void reset_after_save(); void reset_initial_presets(); + + bool is_dirty() const { return m_plater_dirty || m_presets_dirty; } + #if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW void render_debug_window() const; #endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW private: - void update_from_undo_redo_main_stack(UpdateType type, const Slic3r::UndoRedo::Stack& stack); - void update_from_undo_redo_gizmo_stack(UpdateType type, const Slic3r::UndoRedo::Stack& stack); + // Does the Undo / Redo stack indicate the project is dirty? + bool m_plater_dirty { false }; + // Do the presets indicate the project is dirty? + bool m_presets_dirty { false }; + // Keeps track of preset names selected at the time of last project save. + std::array<std::string, Preset::TYPE_COUNT> m_initial_presets; }; } // namespace GUI } // namespace Slic3r #endif // slic3r_ProjectDirtyStateManager_hpp_ - diff --git a/src/slic3r/Utils/UndoRedo.cpp b/src/slic3r/Utils/UndoRedo.cpp index f5d418fa7..3ea57edf2 100644 --- a/src/slic3r/Utils/UndoRedo.cpp +++ b/src/slic3r/Utils/UndoRedo.cpp @@ -45,10 +45,6 @@ static inline std::string ptr_to_string(const void* ptr) } #endif -SnapshotData::SnapshotData() : printer_technology(ptUnknown), flags(0), layer_range_idx(-1) -{ -} - static std::string topmost_snapshot_name = "@@@ Topmost @@@"; bool Snapshot::is_topmost() const @@ -76,6 +72,7 @@ public: void trim_begin(size_t new_begin) { m_begin = std::max(m_begin, new_begin); } void trim_end(size_t new_end) { m_end = std::min(m_end, new_end); } + void extend_begin(size_t new_begin) { assert(new_begin <= m_begin); m_begin = new_begin; } void extend_end(size_t new_end) { assert(new_end >= m_end); m_end = new_end; } size_t memsize() const { return sizeof(this); } @@ -108,6 +105,10 @@ public: // Release all data after the given timestamp. For the ImmutableObjectHistory, the shared pointer is NOT released. // Return the amount of memory released. virtual size_t release_after_timestamp(size_t timestamp) = 0; + // Release all data between the two timestamps. For the ImmutableObjectHistory, the shared pointer is NOT released. + // Used for reducing the number of snapshots for noisy operations like the support point edits. + // Return the amount of memory released. + virtual size_t release_between_timestamps(size_t timestamp_start, size_t timestamp_end) = 0; // Release all optional data of this history. virtual size_t release_optional() = 0; // Restore optional data possibly released by release_optional. @@ -184,6 +185,34 @@ public: return mem_released; } + // Release all data between the two timestamps. For the ImmutableObjectHistory, the shared pointer is NOT released. + // Used for reducing the number of snapshots for noisy operations like the support point edits. + // Return the amount of memory released. + size_t release_between_timestamps(size_t timestamp_start, size_t timestamp_end) override { + size_t mem_released = 0; + if (! m_history.empty()) { + assert(this->valid()); + // Find the span of m_history intervals that are fully in (timestamp_start, timestamp_end>, thus they will be never + // deserialized for any snapshot in <timestamp_start, timestamp_end). + auto it_lo = std::upper_bound(m_history.begin(), m_history.end(), timestamp_start, [](size_t l, const auto &r) { return l < r.begin(); }); + auto it_hi = std::upper_bound(it_lo, m_history.end(), timestamp_end, [](size_t l, const auto &r) { return l < r.end(); }); + if (it_lo != it_hi) { + // There are some intervals that start inside (timestamp_start, timestamp_end), that could be released. + assert(it_lo->begin() > timestamp_start && it_lo->end() <= timestamp_end); + assert(it_hi == m_history.end() || (it_hi->begin() > it_lo->begin() && it_hi->end() > timestamp_end)); + if (it_lo != m_history.begin() && it_hi != m_history.end()) { + // One may consider merging the two intervals. + //FIXME merge them. + } + for (auto it = it_lo; it != it_hi; ++ it) + mem_released += it->memsize(); + m_history.erase(it_lo, it_hi); + } + assert(this->valid()); + } + return mem_released; + } + protected: std::vector<T> m_history; }; @@ -352,9 +381,10 @@ public: const Interval& interval() const { return m_interval; } size_t begin() const { return m_interval.begin(); } size_t end() const { return m_interval.end(); } - void trim_begin(size_t timestamp) { m_interval.trim_begin(timestamp); } - void trim_end (size_t timestamp) { m_interval.trim_end(timestamp); } - void extend_end(size_t timestamp) { m_interval.extend_end(timestamp); } + void trim_begin (size_t timestamp) { m_interval.trim_begin(timestamp); } + void trim_end (size_t timestamp) { m_interval.trim_end(timestamp); } + void extend_begin(size_t timestamp) { m_interval.extend_begin(timestamp); } + void extend_end (size_t timestamp) { m_interval.extend_end(timestamp); } bool operator<(const MutableHistoryInterval& rhs) const { return m_interval < rhs.m_interval; } bool operator==(const MutableHistoryInterval& rhs) const { return m_interval == rhs.m_interval; } @@ -524,6 +554,7 @@ public: m_snapshots.clear(); m_active_snapshot_time = 0; m_current_time = 0; + m_saved_snapshot_time = size_t(-1); m_selection.clear(); } @@ -545,6 +576,7 @@ public: // Store the current application state onto the Undo / Redo stack, remove all snapshots after m_active_snapshot_time. void take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos, const SnapshotData &snapshot_data); + void reduce_noisy_snapshots(const std::string& new_name); void load_snapshot(size_t timestamp, Slic3r::Model& model, Slic3r::GUI::GLGizmosManager& gizmos); bool has_undo_snapshot() const; @@ -566,6 +598,10 @@ public: size_t active_snapshot_time() const { return m_active_snapshot_time; } bool temp_snapshot_active() const { return m_snapshots.back().timestamp == m_active_snapshot_time && ! m_snapshots.back().is_topmost_captured(); } + // Resets the "dirty project" status. + void mark_current_as_saved() { m_saved_snapshot_time = m_active_snapshot_time; } + bool project_modified() const; + const Selection& selection_deserialized() const { return m_selection; } //protected: @@ -628,6 +664,10 @@ private: } void collect_garbage(); + // Release snapshots between begin and end. Only erases data from m_snapshots, not from m_objects! + // Updates m_saved_snapshot_time. + std::vector<Snapshot>::iterator release_snapshots(std::vector<Snapshot>::iterator begin, std::vector<Snapshot>::iterator end); + // Maximum memory allowed to be occupied by the Undo / Redo stack. If the limit is exceeded, // least recently used snapshots will be released. size_t m_memory_limit; @@ -640,6 +680,9 @@ private: std::vector<Snapshot> m_snapshots; // Timestamp of the active snapshot. size_t m_active_snapshot_time; + // Time at which the project state was saved into a project file. + // If set to zero, the time is not known, thus the project state should be considered unsaved. + size_t m_saved_snapshot_time { size_t(-1) }; // Logical time counter. m_current_time is being incremented with each snapshot taken. size_t m_current_time; // Last selection serialized or deserialized. @@ -857,9 +900,12 @@ void StackImpl::take_snapshot(const std::string& snapshot_name, const Slic3r::Mo assert(m_active_snapshot_time <= m_current_time); for (auto &kvp : m_objects) kvp.second->release_after_timestamp(m_active_snapshot_time); - { + bool topmost_saved = false; + if (! m_snapshots.empty()) { + // If the project was saved for the topmost snapshot, restore the "saved" state after the "topmost" snapshot is taken. + topmost_saved = m_active_snapshot_time == m_saved_snapshot_time && m_active_snapshot_time == m_snapshots.back().timestamp; auto it = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time)); - m_snapshots.erase(it, m_snapshots.end()); + this->release_snapshots(it, m_snapshots.end()); } // Take new snapshots. this->save_mutable_object<Slic3r::Model>(model); @@ -871,8 +917,11 @@ void StackImpl::take_snapshot(const std::string& snapshot_name, const Slic3r::Mo this->save_mutable_object<Selection>(m_selection); this->save_mutable_object<Slic3r::GUI::GLGizmosManager>(gizmos); // Save the snapshot info. - m_snapshots.emplace_back(snapshot_name, m_current_time ++, model.id().id, snapshot_data); - m_active_snapshot_time = m_current_time; + m_snapshots.emplace_back(snapshot_name, m_current_time, model.id().id, snapshot_data); + if (topmost_saved) + // Restore the "saved" timestamp. + m_saved_snapshot_time = m_current_time; + m_active_snapshot_time = ++ m_current_time; // Save snapshot info of the last "current" aka "top most" state, that is only being serialized // if undoing an action. Such a snapshot has an invalid Model ID assigned if it was not taken yet. m_snapshots.emplace_back(topmost_snapshot_name, m_active_snapshot_time, 0, snapshot_data); @@ -885,6 +934,32 @@ void StackImpl::take_snapshot(const std::string& snapshot_name, const Slic3r::Mo #endif /* SLIC3R_UNDOREDO_DEBUG */ } +void StackImpl::reduce_noisy_snapshots(const std::string& new_name) +{ + // Preceding snapshot must be a "leave gizmo" snapshot. + assert(! m_snapshots.empty() && m_snapshots.back().is_topmost() && m_snapshots.back().timestamp == m_active_snapshot_time); + auto it_last = m_snapshots.end(); + -- it_last; -- it_last; + assert(it_last != m_snapshots.begin() && (it_last->snapshot_data.snapshot_type == SnapshotType::LeavingGizmoNoAction || it_last->snapshot_data.snapshot_type == SnapshotType::LeavingGizmoWithAction)); + if (it_last->snapshot_data.snapshot_type == SnapshotType::LeavingGizmoWithAction) { + for (-- it_last; it_last->snapshot_data.snapshot_type != SnapshotType::EnteringGizmo; -- it_last) { + if (it_last->snapshot_data.snapshot_type == SnapshotType::GizmoAction) { + it_last->name = new_name; + auto it = it_last; + for (-- it; it->snapshot_data.snapshot_type == SnapshotType::GizmoAction; -- it) ; + if (++ it < it_last) { + // Drop (it, it_last> + for (auto &kvp : m_objects) + // Drop products of <it + 1, it_last + 1> + kvp.second->release_between_timestamps(it->timestamp, (it_last + 1)->timestamp); + it_last = this->release_snapshots(it + 1, it_last + 1); + } + } + assert(it_last != m_snapshots.begin()); + } + } +} + void StackImpl::load_snapshot(size_t timestamp, Slic3r::Model& model, Slic3r::GUI::GLGizmosManager& gizmos) { // Find the snapshot by time. It must exist. @@ -940,6 +1015,7 @@ bool StackImpl::undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selecti bool new_snapshot_taken = false; if (m_active_snapshot_time == m_snapshots.back().timestamp && ! m_snapshots.back().is_topmost_captured()) { // The current state is temporary. The current state needs to be captured to be redoable. + //FIXME add new a "topmost" SnapshotType? this->take_snapshot(topmost_snapshot_name, model, selection, gizmos, snapshot_data); // The line above entered another topmost_snapshot_name. assert(m_snapshots.back().is_topmost()); @@ -985,6 +1061,46 @@ bool StackImpl::redo(Slic3r::Model& model, Slic3r::GUI::GLGizmosManager& gizmos, return true; } +// If a snapshot modifies the snapshot type, +static inline bool snapshot_modifies_project(SnapshotType type) +{ + return type == SnapshotType::Action || type == SnapshotType::GizmoAction || type == SnapshotType::ProjectSeparator; +} + +static inline bool snapshot_modifies_project(const Snapshot &snapshot) +{ + return snapshot_modifies_project(snapshot.snapshot_data.snapshot_type); +} + +// Release snapshots between begin and end. Only erases data from m_snapshots, not from m_objects! +// Updates m_saved_snapshot_time. +std::vector<Snapshot>::iterator StackImpl::release_snapshots(std::vector<Snapshot>::iterator begin, std::vector<Snapshot>::iterator end) +{ + assert(! m_snapshots.empty()); + assert(begin <= end); + if (m_saved_snapshot_time >= begin->timestamp && (end == m_snapshots.end() || m_saved_snapshot_time < end->timestamp)) { + assert(m_saved_snapshot_time <= m_snapshots.back().timestamp); + auto it_saved = std::lower_bound(begin, end, Snapshot(m_saved_snapshot_time)); + assert(it_saved != m_snapshots.end() && it_saved->timestamp == m_saved_snapshot_time); + auto it = it_saved; + for (; it != begin && ! snapshot_modifies_project(*it); -- it) ; + if (it == begin && ! snapshot_modifies_project(*it)) { + // Found a snapshot before begin, which captures the same project state. + m_saved_snapshot_time = (-- it)->timestamp; + } else { + auto it = it_saved; + for (; it != end && ! snapshot_modifies_project(*it); ++ it) ; + if (it == end && end != m_snapshots.end()) + // Found a snapshot after end, which captures the same project state. + m_saved_snapshot_time = (-- it)->timestamp; + else + // State of the project is being lost. Indicate a "likely modified" project state until the project is saved again. + m_saved_snapshot_time = size_t(-1); + } + } + return m_snapshots.erase(begin, end); +} + void StackImpl::collect_garbage() { // Purge objects with empty histories. @@ -1064,6 +1180,7 @@ void StackImpl::release_least_recently_used() } else ++ it; } + //FIXME update the "saved" snapshot time. m_snapshots.erase(m_snapshots.begin()); } assert(current_memsize >= mem_released); @@ -1082,6 +1199,44 @@ void StackImpl::release_least_recently_used() #endif /* SLIC3R_UNDOREDO_DEBUG */ } +bool StackImpl::project_modified() const +{ + assert(! m_snapshots.empty()); + + if (m_saved_snapshot_time == size_t(-1)) + // Don't know anything about the project state. + return true; + if (m_saved_snapshot_time == m_active_snapshot_time) + // Just saved at this step. + return false; + + assert(m_saved_snapshot_time >= m_snapshots.front().timestamp && m_saved_snapshot_time <= m_snapshots.back().timestamp); + + auto it_saved = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_saved_snapshot_time)); + assert(it_saved != m_snapshots.end() && it_saved->timestamp == m_saved_snapshot_time); + +#ifndef NDEBUG + // Verify that there is a snapshot with "current time". + auto it_current = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time)); + assert(it_current != m_snapshots.end() && it_current->timestamp == m_active_snapshot_time); +#endif // NDEBUG + + if (m_saved_snapshot_time < m_active_snapshot_time) { + // Search upwards. Ignore state of the "active" snapshot. + for (auto it = it_saved; it->timestamp < m_active_snapshot_time; ++ it) + if (snapshot_modifies_project(*it)) + return true; + } else { + // Search downwards. Ignore state of the "saved" snapshot. + assert(m_saved_snapshot_time > m_active_snapshot_time); + for (auto it = it_saved - 1; it->timestamp >= m_active_snapshot_time; -- it) + if (snapshot_modifies_project(*it)) + return true; + } + + return false; +} + // Wrappers of the private implementation. Stack::Stack() : pimpl(new StackImpl()) {} Stack::~Stack() {} @@ -1094,6 +1249,7 @@ size_t Stack::memsize() const { return pimpl->memsize(); } void Stack::release_least_recently_used() { pimpl->release_least_recently_used(); } void Stack::take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos, const SnapshotData &snapshot_data) { pimpl->take_snapshot(snapshot_name, model, selection, gizmos, snapshot_data); } +void Stack::reduce_noisy_snapshots(const std::string& new_name) { pimpl->reduce_noisy_snapshots(new_name); } bool Stack::has_undo_snapshot() const { return pimpl->has_undo_snapshot(); } bool Stack::has_undo_snapshot(size_t time_to_load) const { return pimpl->has_undo_snapshot(time_to_load); } bool Stack::has_redo_snapshot() const { return pimpl->has_redo_snapshot(); } @@ -1106,6 +1262,8 @@ const std::vector<Snapshot>& Stack::snapshots() const { return pimpl->snapshots( const Snapshot& Stack::snapshot(size_t time) const { return pimpl->snapshot(time); } size_t Stack::active_snapshot_time() const { return pimpl->active_snapshot_time(); } bool Stack::temp_snapshot_active() const { return pimpl->temp_snapshot_active(); } +void Stack::mark_current_as_saved() { pimpl->mark_current_as_saved(); } +bool Stack::project_modified() const { return pimpl->project_modified(); } } // namespace UndoRedo } // namespace Slic3r diff --git a/src/slic3r/Utils/UndoRedo.hpp b/src/slic3r/Utils/UndoRedo.hpp index d29c0e979..fdc1eb268 100644 --- a/src/slic3r/Utils/UndoRedo.hpp +++ b/src/slic3r/Utils/UndoRedo.hpp @@ -8,6 +8,7 @@ #include <cassert> #include <libslic3r/ObjectID.hpp> +#include <libslic3r/Config.hpp> typedef double coordf_t; typedef std::pair<coordf_t, coordf_t> t_layer_height_range; @@ -15,7 +16,6 @@ typedef std::pair<coordf_t, coordf_t> t_layer_height_range; namespace Slic3r { class Model; -enum PrinterTechnology : unsigned char; namespace GUI { class Selection; @@ -25,8 +25,10 @@ namespace GUI { namespace UndoRedo { enum class SnapshotType : unsigned char { - // Some action modifying project state. + // Some action modifying project state, outside any EnteringGizmo / LeavingGizmo interval. Action, + // Some action modifying project state, inside some EnteringGizmo / LeavingGizmo interval. + GizmoAction, // Selection change at the Plater. Selection, // New project, Reset project, Load project ... @@ -48,14 +50,11 @@ enum class SnapshotType : unsigned char { // which may be handy sometimes. struct SnapshotData { - // Constructor is defined in .cpp due to the forward declaration of enum PrinterTechnology. - SnapshotData(); - SnapshotType snapshot_type; - PrinterTechnology printer_technology; + PrinterTechnology printer_technology { ptUnknown }; // Bitmap of Flags (see the Flags enum). - unsigned int flags; - int layer_range_idx; + unsigned int flags { 0 }; + int layer_range_idx { -1 }; // Bitmask of various binary flags to be stored with the snapshot. enum Flags { @@ -121,6 +120,9 @@ public: // Store the current application state onto the Undo / Redo stack, remove all snapshots after m_active_snapshot_time. void take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos, const SnapshotData &snapshot_data); + // To be called just after take_snapshot() when leaving a gizmo, inside which small edits like support point add / remove events or paiting actions were allowed. + // Remove all but the last edit between the gizmo enter / leave snapshots. + void reduce_noisy_snapshots(const std::string& new_name); // To be queried to enable / disable the Undo / Redo buttons at the UI. bool has_undo_snapshot() const; @@ -151,6 +153,11 @@ public: // In that case the Undo action will capture the last snapshot. bool temp_snapshot_active() const; + // Resets the "dirty project" status. + void mark_current_as_saved(); + // Is the project modified with regard to the last "saved" state marked with mark_current_as_saved()? + bool project_modified() const; + // After load_snapshot() / undo() / redo() the selection is deserialized into a list of ObjectIDs, which needs to be converted // into the list of GLVolume pointers once the 3D scene is updated. const Selection& selection_deserialized() const;