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;