mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-10-30 20:21:12 -06:00 
			
		
		
		
	Merge branch 'master' of https://github.com/prusa3d/PrusaSlicer into et_sinking_objects_collision
This commit is contained in:
		
						commit
						1f82eb5624
					
				
					 25 changed files with 477 additions and 500 deletions
				
			
		|  | @ -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 | ||||
|  |  | |||
|  | @ -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 >= int(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,
 | ||||
|  |  | |||
|  | @ -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); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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) | ||||
|  |  | |||
|  | @ -5201,7 +5201,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(); | ||||
|  |  | |||
|  | @ -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; } | ||||
|  |  | |||
|  | @ -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(); | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
|  | @ -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) | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
|  | @ -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) | ||||
|  | @ -462,8 +433,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; | ||||
|  | @ -560,16 +530,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(); | ||||
|  |  | |||
|  | @ -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; | ||||
| 
 | ||||
|  |  | |||
|  | @ -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) { | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
|  | @ -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,7 +53,8 @@ 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) | ||||
|  | @ -55,8 +62,12 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi | |||
|     create_gui_cfg(); | ||||
|     int obj_index; | ||||
|     ModelVolume *act_volume = get_selected_volume(&obj_index); | ||||
|     if (act_volume == nullptr) {  | ||||
|         close();  | ||||
|     if (act_volume == nullptr) { | ||||
|         switch (m_state) { | ||||
|         case State::settings: close(); break; | ||||
|         case State::canceling: break; | ||||
|         default: m_state = State::canceling; | ||||
|         } | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|  | @ -76,6 +87,7 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi | |||
|         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(); | ||||
|  | @ -98,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
 | ||||
|  | @ -189,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()) {  | ||||
|  | @ -237,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); | ||||
|     } | ||||
|  | @ -330,10 +347,18 @@ void GLGizmoSimplify::on_set_state() | |||
| { | ||||
|     // Closing gizmo. e.g. selecting another one
 | ||||
|     if (GLGizmoBase::m_state == GLGizmoBase::Off) { | ||||
|         bool exist_selected_object = is_selected_object(); | ||||
|         // 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 && exist_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( | ||||
|  | @ -344,10 +369,13 @@ void GLGizmoSimplify::on_set_state() | |||
|         } | ||||
| 
 | ||||
|         // revert preview
 | ||||
|         if (m_exist_preview && exist_selected_object) { | ||||
|             set_its(*m_original_its); | ||||
|             m_parent.reload_scene(true); | ||||
|             m_need_reload = false; | ||||
|         if (m_exist_preview) { | ||||
|             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
 | ||||
|  | @ -385,33 +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 true; | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| ModelVolume *GLGizmoSimplify::get_selected_volume(int *object_idx_ptr) | ||||
| 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 (!is_selected_object(&object_idx)) return nullptr; | ||||
|     if (object_idx < 0) return nullptr; | ||||
|     ModelObjectPtrs &objs = wxGetApp().plater()->model().objects; | ||||
|     if (objs.size() <= object_idx) return nullptr; | ||||
|     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();     | ||||
|     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; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| } // namespace Slic3r::GUI
 | ||||
|  |  | |||
|  | @ -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,8 +43,10 @@ private: | |||
|     void set_its(indexed_triangle_set &its); | ||||
|     void create_gui_cfg(); | ||||
|     void request_rerender(); | ||||
|     bool is_selected_object(int *object_idx_ptr = nullptr); | ||||
|     ModelVolume *get_selected_volume(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
 | ||||
|  | @ -49,6 +56,7 @@ private: | |||
|     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; | ||||
|  | @ -101,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
 | ||||
|  |  | |||
|  | @ -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; | ||||
| 
 | ||||
|  |  | |||
|  | @ -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"); | ||||
|  |  | |||
|  | @ -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 | ||||
|  | @ -2064,6 +2064,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."; | ||||
|  | @ -4699,10 +4702,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(); | ||||
|  | @ -4839,7 +4857,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 */) | ||||
|  | @ -6750,7 +6768,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(); } | ||||
|  |  | |||
|  | @ -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(); | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
|  | @ -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
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -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_
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -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
 | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 enricoturri1966
						enricoturri1966