mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-10-30 20:21:12 -06:00 
			
		
		
		
	Merge remote-tracking branch 'origin/et_project_dirty_state'
This commit is contained in:
		
						commit
						16756a86a7
					
				
					 19 changed files with 1013 additions and 77 deletions
				
			
		|  | @ -624,11 +624,17 @@ const std::vector<std::string>& Preset::sla_printer_options() | ||||||
| PresetCollection::PresetCollection(Preset::Type type, const std::vector<std::string> &keys, const Slic3r::StaticPrintConfig &defaults, const std::string &default_name) : | PresetCollection::PresetCollection(Preset::Type type, const std::vector<std::string> &keys, const Slic3r::StaticPrintConfig &defaults, const std::string &default_name) : | ||||||
|     m_type(type), |     m_type(type), | ||||||
|     m_edited_preset(type, "", false), |     m_edited_preset(type, "", false), | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  |     m_saved_preset(type, "", false), | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
|     m_idx_selected(0) |     m_idx_selected(0) | ||||||
| { | { | ||||||
|     // Insert just the default preset.
 |     // Insert just the default preset.
 | ||||||
|     this->add_default_preset(keys, defaults, default_name); |     this->add_default_preset(keys, defaults, default_name); | ||||||
|     m_edited_preset.config.apply(m_presets.front().config); |     m_edited_preset.config.apply(m_presets.front().config); | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  |     update_saved_preset_from_current_preset(); | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void PresetCollection::reset(bool delete_files) | void PresetCollection::reset(bool delete_files) | ||||||
|  | @ -805,6 +811,9 @@ std::pair<Preset*, bool> PresetCollection::load_external_preset( | ||||||
|             // The source config may contain keys from many possible preset types. Just copy those that relate to this preset.
 |             // The source config may contain keys from many possible preset types. Just copy those that relate to this preset.
 | ||||||
|             this->get_edited_preset().config.apply_only(combined_config, keys, true); |             this->get_edited_preset().config.apply_only(combined_config, keys, true); | ||||||
|             this->update_dirty(); |             this->update_dirty(); | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  |             update_saved_preset_from_current_preset(); | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
|                 assert(this->get_edited_preset().is_dirty); |                 assert(this->get_edited_preset().is_dirty); | ||||||
|             return std::make_pair(&(*it), this->get_edited_preset().is_dirty); |             return std::make_pair(&(*it), this->get_edited_preset().is_dirty); | ||||||
|         } |         } | ||||||
|  | @ -1215,6 +1224,9 @@ Preset& PresetCollection::select_preset(size_t idx) | ||||||
|         idx = first_visible_idx(); |         idx = first_visible_idx(); | ||||||
|     m_idx_selected = idx; |     m_idx_selected = idx; | ||||||
|     m_edited_preset = m_presets[idx]; |     m_edited_preset = m_presets[idx]; | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  |     update_saved_preset_from_current_preset(); | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
|     bool default_visible = ! m_default_suppressed || m_idx_selected < m_num_default_presets; |     bool default_visible = ! m_default_suppressed || m_idx_selected < m_num_default_presets; | ||||||
|     for (size_t i = 0; i < m_num_default_presets; ++i) |     for (size_t i = 0; i < m_num_default_presets; ++i) | ||||||
|         m_presets[i].is_visible = default_visible; |         m_presets[i].is_visible = default_visible; | ||||||
|  |  | ||||||
|  | @ -346,6 +346,11 @@ public: | ||||||
|     Preset&         get_edited_preset()         { return m_edited_preset; } |     Preset&         get_edited_preset()         { return m_edited_preset; } | ||||||
|     const Preset&   get_edited_preset() const   { return m_edited_preset; } |     const Preset&   get_edited_preset() const   { return m_edited_preset; } | ||||||
| 
 | 
 | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  |     // Return the last saved preset.
 | ||||||
|  |     const Preset& get_saved_preset() const { return m_saved_preset; } | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
|  | 
 | ||||||
|     // Return vendor of the first parent profile, for which the vendor is defined, or null if such profile does not exist.
 |     // Return vendor of the first parent profile, for which the vendor is defined, or null if such profile does not exist.
 | ||||||
|     PresetWithVendorProfile get_preset_with_vendor_profile(const Preset &preset) const; |     PresetWithVendorProfile get_preset_with_vendor_profile(const Preset &preset) const; | ||||||
|     PresetWithVendorProfile get_edited_preset_with_vendor_profile() const { return this->get_preset_with_vendor_profile(this->get_edited_preset()); } |     PresetWithVendorProfile get_edited_preset_with_vendor_profile() const { return this->get_preset_with_vendor_profile(this->get_edited_preset()); } | ||||||
|  | @ -365,7 +370,15 @@ public: | ||||||
|     // Return a preset by an index. If the preset is active, a temporary copy is returned.
 |     // Return a preset by an index. If the preset is active, a temporary copy is returned.
 | ||||||
|     Preset&         preset(size_t idx)          { return (idx == m_idx_selected) ? m_edited_preset : m_presets[idx]; } |     Preset&         preset(size_t idx)          { return (idx == m_idx_selected) ? m_edited_preset : m_presets[idx]; } | ||||||
|     const Preset&   preset(size_t idx) const    { return const_cast<PresetCollection*>(this)->preset(idx); } |     const Preset&   preset(size_t idx) const    { return const_cast<PresetCollection*>(this)->preset(idx); } | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  |     void            discard_current_changes() { | ||||||
|  |         m_presets[m_idx_selected].reset_dirty(); | ||||||
|  |         m_edited_preset = m_presets[m_idx_selected]; | ||||||
|  |         update_saved_preset_from_current_preset(); | ||||||
|  |     } | ||||||
|  | #else | ||||||
|     void            discard_current_changes()   { m_presets[m_idx_selected].reset_dirty(); m_edited_preset = m_presets[m_idx_selected]; } |     void            discard_current_changes()   { m_presets[m_idx_selected].reset_dirty(); m_edited_preset = m_presets[m_idx_selected]; } | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
| 
 | 
 | ||||||
|     // Return a preset by its name. If the preset is active, a temporary copy is returned.
 |     // Return a preset by its name. If the preset is active, a temporary copy is returned.
 | ||||||
|     // If a preset is not found by its name, null is returned.
 |     // If a preset is not found by its name, null is returned.
 | ||||||
|  | @ -440,6 +453,16 @@ public: | ||||||
|     std::vector<std::string>    current_different_from_parent_options(const bool deep_compare = false) const |     std::vector<std::string>    current_different_from_parent_options(const bool deep_compare = false) const | ||||||
|         { return dirty_options(&this->get_edited_preset(), this->get_selected_preset_parent(), deep_compare); } |         { return dirty_options(&this->get_edited_preset(), this->get_selected_preset_parent(), deep_compare); } | ||||||
| 
 | 
 | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  |     // Compare the content of get_saved_preset() with get_edited_preset() configs, return true if they differ.
 | ||||||
|  |     bool                        saved_is_dirty() const { return !this->saved_dirty_options().empty(); } | ||||||
|  |     // Compare the content of get_saved_preset() with get_edited_preset() configs, return the list of keys where they differ.
 | ||||||
|  |     std::vector<std::string>    saved_dirty_options(const bool deep_compare = false) const | ||||||
|  |         { return dirty_options(&this->get_edited_preset(), &this->get_saved_preset(), deep_compare); } | ||||||
|  |     // Copy edited preset into saved preset.
 | ||||||
|  |     void                        update_saved_preset_from_current_preset() { m_saved_preset = m_edited_preset; } | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
|  | 
 | ||||||
|     // Return a sorted list of system preset names.
 |     // Return a sorted list of system preset names.
 | ||||||
|     // Used for validating the "inherits" flag when importing user's config bundles.
 |     // Used for validating the "inherits" flag when importing user's config bundles.
 | ||||||
|     // Returns names of all system presets including the former names of these presets.
 |     // Returns names of all system presets including the former names of these presets.
 | ||||||
|  | @ -527,6 +550,11 @@ private: | ||||||
|     std::map<std::string, std::string> m_map_system_profile_renamed; |     std::map<std::string, std::string> m_map_system_profile_renamed; | ||||||
|     // Initially this preset contains a copy of the selected preset. Later on, this copy may be modified by the user.
 |     // Initially this preset contains a copy of the selected preset. Later on, this copy may be modified by the user.
 | ||||||
|     Preset                  m_edited_preset; |     Preset                  m_edited_preset; | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  |     // Contains a copy of the last saved selected preset.
 | ||||||
|  |     Preset                  m_saved_preset; | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
|  | 
 | ||||||
|     // Selected preset.
 |     // Selected preset.
 | ||||||
|     size_t                  m_idx_selected; |     size_t                  m_idx_selected; | ||||||
|     // Is the "- default -" preset suppressed?
 |     // Is the "- default -" preset suppressed?
 | ||||||
|  |  | ||||||
|  | @ -66,5 +66,10 @@ | ||||||
| // Enable visualization of seams in preview
 | // Enable visualization of seams in preview
 | ||||||
| #define ENABLE_SEAMS_VISUALIZATION (1 && ENABLE_2_4_0_ALPHA0) | #define ENABLE_SEAMS_VISUALIZATION (1 && ENABLE_2_4_0_ALPHA0) | ||||||
| 
 | 
 | ||||||
|  | // Enable project dirty state manager
 | ||||||
|  | #define ENABLE_PROJECT_DIRTY_STATE (1 && ENABLE_2_4_0_ALPHA0) | ||||||
|  | // Enable project dirty state manager debug window
 | ||||||
|  | #define ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW (0 && ENABLE_PROJECT_DIRTY_STATE) | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| #endif // _prusaslicer_technologies_h_
 | #endif // _prusaslicer_technologies_h_
 | ||||||
|  |  | ||||||
|  | @ -189,6 +189,8 @@ set(SLIC3R_GUI_SOURCES | ||||||
|     GUI/UnsavedChangesDialog.hpp |     GUI/UnsavedChangesDialog.hpp | ||||||
|     GUI/ExtraRenderers.cpp |     GUI/ExtraRenderers.cpp | ||||||
|     GUI/ExtraRenderers.hpp |     GUI/ExtraRenderers.hpp | ||||||
|  |     GUI/ProjectDirtyStateManager.hpp | ||||||
|  |     GUI/ProjectDirtyStateManager.cpp | ||||||
|     GUI/DesktopIntegrationDialog.cpp |     GUI/DesktopIntegrationDialog.cpp | ||||||
|     GUI/DesktopIntegrationDialog.hpp |     GUI/DesktopIntegrationDialog.hpp | ||||||
|     Utils/Http.cpp |     Utils/Http.cpp | ||||||
|  |  | ||||||
|  | @ -1723,6 +1723,11 @@ void GLCanvas3D::render() | ||||||
|     } |     } | ||||||
| #endif // ENABLE_RENDER_STATISTICS
 | #endif // ENABLE_RENDER_STATISTICS
 | ||||||
| 
 | 
 | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW | ||||||
|  |     if (wxGetApp().is_editor() && wxGetApp().plater()->is_view3D_shown()) | ||||||
|  |         wxGetApp().plater()->render_project_state_debug_window(); | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
 | ||||||
|  | 
 | ||||||
| #if ENABLE_CAMERA_STATISTICS | #if ENABLE_CAMERA_STATISTICS | ||||||
|     camera.debug_render(); |     camera.debug_render(); | ||||||
| #endif // ENABLE_CAMERA_STATISTICS
 | #endif // ENABLE_CAMERA_STATISTICS
 | ||||||
|  |  | ||||||
|  | @ -916,6 +916,14 @@ bool GUI_App::on_init_inner() | ||||||
|     } |     } | ||||||
|     else |     else | ||||||
|         load_current_presets(); |         load_current_presets(); | ||||||
|  | 
 | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  |     if (plater_ != nullptr) { | ||||||
|  |         plater_->reset_project_dirty_initial_presets(); | ||||||
|  |         plater_->update_project_dirty_from_presets(); | ||||||
|  |     } | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
|  | 
 | ||||||
|     mainframe->Show(true); |     mainframe->Show(true); | ||||||
| 
 | 
 | ||||||
|     obj_list()->set_min_height(); |     obj_list()->set_min_height(); | ||||||
|  | @ -1686,7 +1694,11 @@ void GUI_App::add_config_menu(wxMenuBar *menu) | ||||||
| #endif | #endif | ||||||
|         case ConfigMenuTakeSnapshot: |         case ConfigMenuTakeSnapshot: | ||||||
|             // Take a configuration snapshot.
 |             // Take a configuration snapshot.
 | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  |             if (check_and_save_current_preset_changes()) { | ||||||
|  | #else | ||||||
|             if (check_unsaved_changes()) { |             if (check_unsaved_changes()) { | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
|                 wxTextEntryDialog dlg(nullptr, _L("Taking configuration snapshot"), _L("Snapshot name")); |                 wxTextEntryDialog dlg(nullptr, _L("Taking configuration snapshot"), _L("Snapshot name")); | ||||||
|                  |                  | ||||||
|                 // set current normal font for dialog children, 
 |                 // set current normal font for dialog children, 
 | ||||||
|  | @ -1701,7 +1713,11 @@ void GUI_App::add_config_menu(wxMenuBar *menu) | ||||||
|             } |             } | ||||||
|             break; |             break; | ||||||
|         case ConfigMenuSnapshots: |         case ConfigMenuSnapshots: | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  |             if (check_and_save_current_preset_changes()) { | ||||||
|  | #else | ||||||
|             if (check_unsaved_changes()) { |             if (check_unsaved_changes()) { | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
|                 std::string on_snapshot; |                 std::string on_snapshot; | ||||||
|                 if (Config::SnapshotDB::singleton().is_on_snapshot(*app_config)) |                 if (Config::SnapshotDB::singleton().is_on_snapshot(*app_config)) | ||||||
|                     on_snapshot = app_config->get("on_snapshot"); |                     on_snapshot = app_config->get("on_snapshot"); | ||||||
|  | @ -1802,8 +1818,57 @@ void GUI_App::add_config_menu(wxMenuBar *menu) | ||||||
|     menu->Append(local_menu, _L("&Configuration")); |     menu->Append(local_menu, _L("&Configuration")); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  | bool GUI_App::has_unsaved_preset_changes() const | ||||||
|  | { | ||||||
|  |     PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); | ||||||
|  |     for (const Tab* const tab : tabs_list) { | ||||||
|  |         if (tab->supports_printer_technology(printer_technology) && tab->saved_preset_is_dirty()) | ||||||
|  |             return true; | ||||||
|  |     } | ||||||
|  |     return false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool GUI_App::has_current_preset_changes() const | ||||||
|  | { | ||||||
|  |     PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); | ||||||
|  |     for (const Tab* const tab : tabs_list) { | ||||||
|  |         if (tab->supports_printer_technology(printer_technology) && tab->current_preset_is_dirty()) | ||||||
|  |             return true; | ||||||
|  |     } | ||||||
|  |     return false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void GUI_App::update_saved_preset_from_current_preset() | ||||||
|  | { | ||||||
|  |     PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); | ||||||
|  |     for (Tab* tab : tabs_list) { | ||||||
|  |         if (tab->supports_printer_technology(printer_technology)) | ||||||
|  |             tab->update_saved_preset_from_current_preset(); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::vector<std::pair<unsigned int, std::string>> GUI_App::get_selected_presets() const | ||||||
|  | { | ||||||
|  |     std::vector<std::pair<unsigned int, std::string>> ret; | ||||||
|  |     PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); | ||||||
|  |     for (Tab* tab : tabs_list) { | ||||||
|  |         if (tab->supports_printer_technology(printer_technology)) { | ||||||
|  |             const PresetCollection* presets = tab->get_presets(); | ||||||
|  |             ret.push_back({ static_cast<unsigned int>(presets->type()), presets->get_selected_preset_name() }); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return ret; | ||||||
|  | } | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
|  | 
 | ||||||
| // This is called when closing the application, when loading a config file or when starting the config wizard
 | // This is called when closing the application, when loading a config file or when starting the config wizard
 | ||||||
| // to notify the user whether he is aware that some preset changes will be lost.
 | // to notify the user whether he is aware that some preset changes will be lost.
 | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  | bool GUI_App::check_and_save_current_preset_changes(const wxString& header) | ||||||
|  | { | ||||||
|  |     if (this->plater()->model().objects.empty() && has_current_preset_changes()) { | ||||||
|  | #else | ||||||
| bool GUI_App::check_unsaved_changes(const wxString &header) | bool GUI_App::check_unsaved_changes(const wxString &header) | ||||||
| { | { | ||||||
|     PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); |     PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); | ||||||
|  | @ -1815,8 +1880,8 @@ bool GUI_App::check_unsaved_changes(const wxString &header) | ||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|     if (has_unsaved_changes) |     if (has_unsaved_changes) { | ||||||
|     { | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
|         UnsavedChangesDialog dlg(header); |         UnsavedChangesDialog dlg(header); | ||||||
|         if (wxGetApp().app_config->get("default_action_on_close_application") == "none" && dlg.ShowModal() == wxID_CANCEL) |         if (wxGetApp().app_config->get("default_action_on_close_application") == "none" && dlg.ShowModal() == wxID_CANCEL) | ||||||
|             return false; |             return false; | ||||||
|  |  | ||||||
|  | @ -210,7 +210,15 @@ public: | ||||||
|     void            update_mode(); |     void            update_mode(); | ||||||
| 
 | 
 | ||||||
|     void            add_config_menu(wxMenuBar *menu); |     void            add_config_menu(wxMenuBar *menu); | ||||||
|     bool            check_unsaved_changes(const wxString &header = wxString()); | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  |     bool            has_unsaved_preset_changes() const; | ||||||
|  |     bool            has_current_preset_changes() const; | ||||||
|  |     void            update_saved_preset_from_current_preset(); | ||||||
|  |     std::vector<std::pair<unsigned int, std::string>> get_selected_presets() const; | ||||||
|  |     bool            check_and_save_current_preset_changes(const wxString& header = wxString()); | ||||||
|  | #else | ||||||
|  |     bool            check_unsaved_changes(const wxString& header = wxString()); | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
|     bool            check_print_host_queue(); |     bool            check_print_host_queue(); | ||||||
|     bool            checked_tab(Tab* tab); |     bool            checked_tab(Tab* tab); | ||||||
|     void            load_current_presets(bool check_printer_presets = true); |     void            load_current_presets(bool check_printer_presets = true); | ||||||
|  |  | ||||||
|  | @ -7,6 +7,9 @@ | ||||||
| #include "GUI_App.hpp" | #include "GUI_App.hpp" | ||||||
| #include "I18N.hpp" | #include "I18N.hpp" | ||||||
| #include "Plater.hpp" | #include "Plater.hpp" | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  | #include "MainFrame.hpp" | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
| 
 | 
 | ||||||
| #include "OptionsGroup.hpp" | #include "OptionsGroup.hpp" | ||||||
| #include "Tab.hpp" | #include "Tab.hpp" | ||||||
|  | @ -1457,12 +1460,15 @@ void ObjectList::load_shape_object(const std::string& type_name) | ||||||
|     if (obj_idx < 0) |     if (obj_idx < 0) | ||||||
|         return; |         return; | ||||||
| 
 | 
 | ||||||
|     take_snapshot(_(L("Add Shape"))); |     take_snapshot(_L("Add Shape")); | ||||||
| 
 | 
 | ||||||
|     // Create mesh
 |     // Create mesh
 | ||||||
|     BoundingBoxf3 bb; |     BoundingBoxf3 bb; | ||||||
|     TriangleMesh mesh = create_mesh(type_name, bb); |     TriangleMesh mesh = create_mesh(type_name, bb); | ||||||
|     load_mesh_object(mesh, _(L("Shape")) + "-" + _(type_name)); |     load_mesh_object(mesh, _L("Shape") + "-" + _(type_name)); | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  |     wxGetApp().mainframe->update_title(); | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void ObjectList::load_mesh_object(const TriangleMesh &mesh, const wxString &name, bool center) | void ObjectList::load_mesh_object(const TriangleMesh &mesh, const wxString &name, bool center) | ||||||
|  |  | ||||||
|  | @ -32,6 +32,42 @@ GLGizmoPainterBase::GLGizmoPainterBase(GLCanvas3D& parent, const std::string& ic | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  | // 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) { | ||||||
|  |         std::string str = get_painter_type() == PainterGizmoType::FDM_SUPPORTS | ||||||
|  |             ? _u8L("Entering Paint-on supports") | ||||||
|  |             : _u8L("Entering Seam painting"); | ||||||
|  |         if (last_snapshot_name != str) | ||||||
|  |             Plater::TakeSnapshot(plater, str); | ||||||
|  |         plater->enter_gizmos_stack(); | ||||||
|  |         m_internal_stack_active = true; | ||||||
|  |     } | ||||||
|  |     if (!activate && m_internal_stack_active) { | ||||||
|  |         plater->leave_gizmos_stack(); | ||||||
|  |         std::string str = get_painter_type() == PainterGizmoType::SEAM | ||||||
|  |             ? _u8L("Leaving Seam painting") | ||||||
|  |             : _u8L("Leaving Paint-on supports"); | ||||||
|  |         if (last_snapshot_name != str) | ||||||
|  |             Plater::TakeSnapshot(plater, str); | ||||||
|  |         m_internal_stack_active = false; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | #else | ||||||
| void GLGizmoPainterBase::activate_internal_undo_redo_stack(bool activate) | void GLGizmoPainterBase::activate_internal_undo_redo_stack(bool activate) | ||||||
| { | { | ||||||
|     if (activate && ! m_internal_stack_active) { |     if (activate && ! m_internal_stack_active) { | ||||||
|  | @ -51,6 +87,7 @@ void GLGizmoPainterBase::activate_internal_undo_redo_stack(bool activate) | ||||||
|         m_internal_stack_active = false; |         m_internal_stack_active = false; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -45,18 +45,18 @@ bool GLGizmoSlaSupports::on_init() | ||||||
| { | { | ||||||
|     m_shortcut_key = WXK_CONTROL_L; |     m_shortcut_key = WXK_CONTROL_L; | ||||||
| 
 | 
 | ||||||
|     m_desc["head_diameter"]    = _(L("Head diameter")) + ": "; |     m_desc["head_diameter"]    = _L("Head diameter") + ": "; | ||||||
|     m_desc["lock_supports"]    = _(L("Lock supports under new islands")); |     m_desc["lock_supports"]    = _L("Lock supports under new islands"); | ||||||
|     m_desc["remove_selected"]  = _(L("Remove selected points")); |     m_desc["remove_selected"]  = _L("Remove selected points"); | ||||||
|     m_desc["remove_all"]       = _(L("Remove all points")); |     m_desc["remove_all"]       = _L("Remove all points"); | ||||||
|     m_desc["apply_changes"]    = _(L("Apply changes")); |     m_desc["apply_changes"]    = _L("Apply changes"); | ||||||
|     m_desc["discard_changes"]  = _(L("Discard changes")); |     m_desc["discard_changes"]  = _L("Discard changes"); | ||||||
|     m_desc["minimal_distance"] = _(L("Minimal points distance")) + ": "; |     m_desc["minimal_distance"] = _L("Minimal points distance") + ": "; | ||||||
|     m_desc["points_density"]   = _(L("Support points density")) + ": "; |     m_desc["points_density"]   = _L("Support points density") + ": "; | ||||||
|     m_desc["auto_generate"]    = _(L("Auto-generate points")); |     m_desc["auto_generate"]    = _L("Auto-generate points"); | ||||||
|     m_desc["manual_editing"]   = _(L("Manual editing")); |     m_desc["manual_editing"]   = _L("Manual editing"); | ||||||
|     m_desc["clipping_of_view"] = _(L("Clipping of view"))+ ": "; |     m_desc["clipping_of_view"] = _L("Clipping of view")+ ": "; | ||||||
|     m_desc["reset_direction"]  = _(L("Reset direction")); |     m_desc["reset_direction"]  = _L("Reset direction"); | ||||||
| 
 | 
 | ||||||
|     return true; |     return true; | ||||||
| } | } | ||||||
|  | @ -372,7 +372,7 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous | ||||||
|             if (m_selection_empty) { |             if (m_selection_empty) { | ||||||
|                 std::pair<Vec3f, Vec3f> pos_and_normal; |                 std::pair<Vec3f, Vec3f> pos_and_normal; | ||||||
|                 if (unproject_on_mesh(mouse_position, pos_and_normal)) { // we got an intersection
 |                 if (unproject_on_mesh(mouse_position, pos_and_normal)) { // we got an intersection
 | ||||||
|                     Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Add support point"))); |                     Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Add support point")); | ||||||
|                     m_editing_cache.emplace_back(sla::SupportPoint(pos_and_normal.first, m_new_point_head_diameter/2.f, false), false, pos_and_normal.second); |                     m_editing_cache.emplace_back(sla::SupportPoint(pos_and_normal.first, m_new_point_head_diameter/2.f, false), false, pos_and_normal.second); | ||||||
|                     m_parent.set_as_dirty(); |                     m_parent.set_as_dirty(); | ||||||
|                     m_wait_for_up_event = true; |                     m_wait_for_up_event = true; | ||||||
|  | @ -512,7 +512,7 @@ void GLGizmoSlaSupports::delete_selected_points(bool force) | ||||||
|         std::abort(); |         std::abort(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Delete support point"))); |     Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Delete support point")); | ||||||
| 
 | 
 | ||||||
|     for (unsigned int idx=0; idx<m_editing_cache.size(); ++idx) { |     for (unsigned int idx=0; idx<m_editing_cache.size(); ++idx) { | ||||||
|         if (m_editing_cache[idx].selected && (!m_editing_cache[idx].support_point.is_new_island || !m_lock_unique_islands || force)) { |         if (m_editing_cache[idx].selected && (!m_editing_cache[idx].support_point.is_new_island || !m_lock_unique_islands || force)) { | ||||||
|  | @ -692,7 +692,7 @@ RENDER_AGAIN: | ||||||
|                     cache_entry.support_point.head_front_radius = m_old_point_head_diameter / 2.f; |                     cache_entry.support_point.head_front_radius = m_old_point_head_diameter / 2.f; | ||||||
|             float backup = m_new_point_head_diameter; |             float backup = m_new_point_head_diameter; | ||||||
|             m_new_point_head_diameter = m_old_point_head_diameter; |             m_new_point_head_diameter = m_old_point_head_diameter; | ||||||
|             Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Change point head diameter"))); |             Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Change point head diameter")); | ||||||
|             m_new_point_head_diameter = backup; |             m_new_point_head_diameter = backup; | ||||||
|             for (auto& cache_entry : m_editing_cache) |             for (auto& cache_entry : m_editing_cache) | ||||||
|                 if (cache_entry.selected) |                 if (cache_entry.selected) | ||||||
|  | @ -760,7 +760,7 @@ RENDER_AGAIN: | ||||||
|         if (slider_released) { |         if (slider_released) { | ||||||
|             mo->config.set("support_points_minimal_distance", m_minimal_point_distance_stash); |             mo->config.set("support_points_minimal_distance", m_minimal_point_distance_stash); | ||||||
|             mo->config.set("support_points_density_relative", (int)m_density_stash); |             mo->config.set("support_points_density_relative", (int)m_density_stash); | ||||||
|             Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Support parameter change"))); |             Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Support parameter change")); | ||||||
|             mo->config.set("support_points_minimal_distance", minimal_point_distance); |             mo->config.set("support_points_minimal_distance", minimal_point_distance); | ||||||
|             mo->config.set("support_points_density_relative", (int)density); |             mo->config.set("support_points_density_relative", (int)density); | ||||||
|             wxGetApp().obj_list()->update_and_show_object_settings_item(); |             wxGetApp().obj_list()->update_and_show_object_settings_item(); | ||||||
|  | @ -867,10 +867,9 @@ bool GLGizmoSlaSupports::on_is_selectable() const | ||||||
| 
 | 
 | ||||||
| std::string GLGizmoSlaSupports::on_get_name() const | std::string GLGizmoSlaSupports::on_get_name() const | ||||||
| { | { | ||||||
|     return (_(L("SLA Support Points")) + " [L]").ToUTF8().data(); |     return (_L("SLA Support Points") + " [L]").ToUTF8().data(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| CommonGizmosDataID GLGizmoSlaSupports::on_get_requirements() const | CommonGizmosDataID GLGizmoSlaSupports::on_get_requirements() const | ||||||
| { | { | ||||||
|     return CommonGizmosDataID( |     return CommonGizmosDataID( | ||||||
|  | @ -895,7 +894,11 @@ void GLGizmoSlaSupports::on_set_state() | ||||||
|             // data are not yet available, the CallAfter will postpone taking the
 |             // data are not yet available, the CallAfter will postpone taking the
 | ||||||
|             // snapshot until they are. No, it does not feel right.
 |             // snapshot until they are. No, it does not feel right.
 | ||||||
|             wxGetApp().CallAfter([]() { |             wxGetApp().CallAfter([]() { | ||||||
|                 Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("SLA gizmo turned on"))); | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  |                 Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Entering SLA gizmo")); | ||||||
|  | #else | ||||||
|  |                 Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("SLA gizmo turned on")); | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
|             }); |             }); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | @ -909,8 +912,8 @@ void GLGizmoSlaSupports::on_set_state() | ||||||
|             wxGetApp().CallAfter([this]() { |             wxGetApp().CallAfter([this]() { | ||||||
|                 // Following is called through CallAfter, because otherwise there was a problem
 |                 // Following is called through CallAfter, because otherwise there was a problem
 | ||||||
|                 // on OSX with the wxMessageDialog being shown several times when clicked into.
 |                 // on OSX with the wxMessageDialog being shown several times when clicked into.
 | ||||||
|                 wxMessageDialog dlg(GUI::wxGetApp().mainframe, _(L("Do you want to save your manually " |                 wxMessageDialog dlg(GUI::wxGetApp().mainframe, _L("Do you want to save your manually " | ||||||
|                     "edited support points?")) + "\n",_(L("Save changes?")), wxICON_QUESTION | wxYES | wxNO); |                     "edited support points?") + "\n",_L("Save changes?"), wxICON_QUESTION | wxYES | wxNO); | ||||||
|                     if (dlg.ShowModal() == wxID_YES) |                     if (dlg.ShowModal() == wxID_YES) | ||||||
|                         editing_mode_apply_changes(); |                         editing_mode_apply_changes(); | ||||||
|                     else |                     else | ||||||
|  | @ -922,7 +925,11 @@ void GLGizmoSlaSupports::on_set_state() | ||||||
|         else { |         else { | ||||||
|             // we are actually shutting down
 |             // we are actually shutting down
 | ||||||
|             disable_editing_mode(); // so it is not active next time the gizmo opens
 |             disable_editing_mode(); // so it is not active next time the gizmo opens
 | ||||||
|             Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("SLA gizmo turned off"))); | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  |             Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Leaving SLA gizmo")); | ||||||
|  | #else | ||||||
|  |             Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("SLA gizmo turned off")); | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
|             m_normal_cache.clear(); |             m_normal_cache.clear(); | ||||||
|             m_old_mo_id = -1; |             m_old_mo_id = -1; | ||||||
|         } |         } | ||||||
|  | @ -953,7 +960,7 @@ void GLGizmoSlaSupports::on_stop_dragging() | ||||||
|          && backup.support_point.pos != m_point_before_drag.support_point.pos) // and it was moved, not just selected
 |          && backup.support_point.pos != m_point_before_drag.support_point.pos) // and it was moved, not just selected
 | ||||||
|         { |         { | ||||||
|             m_editing_cache[m_hover_id] = m_point_before_drag; |             m_editing_cache[m_hover_id] = m_point_before_drag; | ||||||
|             Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Move support point"))); |             Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Move support point")); | ||||||
|             m_editing_cache[m_hover_id] = backup; |             m_editing_cache[m_hover_id] = backup; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | @ -1046,7 +1053,7 @@ void GLGizmoSlaSupports::editing_mode_apply_changes() | ||||||
|     disable_editing_mode(); // this leaves the editing mode undo/redo stack and must be done before the snapshot is taken
 |     disable_editing_mode(); // this leaves the editing mode undo/redo stack and must be done before the snapshot is taken
 | ||||||
| 
 | 
 | ||||||
|     if (unsaved_changes()) { |     if (unsaved_changes()) { | ||||||
|         Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Support points edit"))); |         Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Support points edit")); | ||||||
| 
 | 
 | ||||||
|         m_normal_cache.clear(); |         m_normal_cache.clear(); | ||||||
|         for (const CacheEntry& ce : m_editing_cache) |         for (const CacheEntry& ce : m_editing_cache) | ||||||
|  | @ -1125,14 +1132,14 @@ void GLGizmoSlaSupports::get_data_from_backend() | ||||||
| void GLGizmoSlaSupports::auto_generate() | void GLGizmoSlaSupports::auto_generate() | ||||||
| { | { | ||||||
|     wxMessageDialog dlg(GUI::wxGetApp().plater(),  |     wxMessageDialog dlg(GUI::wxGetApp().plater(),  | ||||||
|                         _(L("Autogeneration will erase all manually edited points.")) + "\n\n" + |                         _L("Autogeneration will erase all manually edited points.") + "\n\n" + | ||||||
|                         _(L("Are you sure you want to do it?")) + "\n", |                         _L("Are you sure you want to do it?") + "\n", | ||||||
|                         _(L("Warning")), wxICON_WARNING | wxYES | wxNO); |                         _L("Warning"), wxICON_WARNING | wxYES | wxNO); | ||||||
| 
 | 
 | ||||||
|     ModelObject* mo = m_c->selection_info()->model_object(); |     ModelObject* mo = m_c->selection_info()->model_object(); | ||||||
| 
 | 
 | ||||||
|     if (mo->sla_points_status != sla::PointsStatus::UserModified || m_normal_cache.empty() || dlg.ShowModal() == wxID_YES) { |     if (mo->sla_points_status != sla::PointsStatus::UserModified || m_normal_cache.empty() || dlg.ShowModal() == wxID_YES) { | ||||||
|         Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Autogenerate support points"))); |         Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Autogenerate support points")); | ||||||
|         wxGetApp().CallAfter([this]() { reslice_SLA_supports(); }); |         wxGetApp().CallAfter([this]() { reslice_SLA_supports(); }); | ||||||
|         mo->sla_points_status = sla::PointsStatus::Generating; |         mo->sla_points_status = sla::PointsStatus::Generating; | ||||||
|     } |     } | ||||||
|  | @ -1180,7 +1187,7 @@ bool GLGizmoSlaSupports::unsaved_changes() const | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| SlaGizmoHelpDialog::SlaGizmoHelpDialog() | SlaGizmoHelpDialog::SlaGizmoHelpDialog() | ||||||
| : wxDialog(nullptr, wxID_ANY, _(L("SLA gizmo keyboard shortcuts")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER) | : wxDialog(nullptr, wxID_ANY, _L("SLA gizmo keyboard shortcuts"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER) | ||||||
| { | { | ||||||
|     SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); |     SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); | ||||||
|     const wxString ctrl = GUI::shortkey_ctrl_prefix(); |     const wxString ctrl = GUI::shortkey_ctrl_prefix(); | ||||||
|  | @ -1191,7 +1198,7 @@ SlaGizmoHelpDialog::SlaGizmoHelpDialog() | ||||||
|     const wxFont& font = wxGetApp().small_font(); |     const wxFont& font = wxGetApp().small_font(); | ||||||
|     const wxFont& bold_font = wxGetApp().bold_font(); |     const wxFont& bold_font = wxGetApp().bold_font(); | ||||||
| 
 | 
 | ||||||
|     auto note_text = new wxStaticText(this, wxID_ANY, _(L("Note: some shortcuts work in (non)editing mode only."))); |     auto note_text = new wxStaticText(this, wxID_ANY, _L("Note: some shortcuts work in (non)editing mode only.")); | ||||||
|     note_text->SetFont(font); |     note_text->SetFont(font); | ||||||
| 
 | 
 | ||||||
|     auto vsizer    = new wxBoxSizer(wxVERTICAL); |     auto vsizer    = new wxBoxSizer(wxVERTICAL); | ||||||
|  | @ -1209,21 +1216,21 @@ SlaGizmoHelpDialog::SlaGizmoHelpDialog() | ||||||
|     vsizer->AddSpacer(20); |     vsizer->AddSpacer(20); | ||||||
| 
 | 
 | ||||||
|     std::vector<std::pair<wxString, wxString>> shortcuts; |     std::vector<std::pair<wxString, wxString>> shortcuts; | ||||||
|     shortcuts.push_back(std::make_pair(_(L("Left click")),          _(L("Add point")))); |     shortcuts.push_back(std::make_pair(_L("Left click"),              _L("Add point"))); | ||||||
|     shortcuts.push_back(std::make_pair(_(L("Right click")),         _(L("Remove point")))); |     shortcuts.push_back(std::make_pair(_L("Right click"),             _L("Remove point"))); | ||||||
|     shortcuts.push_back(std::make_pair(_(L("Drag")),                _(L("Move point")))); |     shortcuts.push_back(std::make_pair(_L("Drag"),                    _L("Move point"))); | ||||||
|     shortcuts.push_back(std::make_pair(ctrl+_(L("Left click")),     _(L("Add point to selection")))); |     shortcuts.push_back(std::make_pair(ctrl+_L("Left click"),         _L("Add point to selection"))); | ||||||
|     shortcuts.push_back(std::make_pair(alt+_(L("Left click")),      _(L("Remove point from selection")))); |     shortcuts.push_back(std::make_pair(alt+_L("Left click"),          _L("Remove point from selection"))); | ||||||
|     shortcuts.push_back(std::make_pair(wxString("Shift+")+_(L("Drag")), _(L("Select by rectangle")))); |     shortcuts.push_back(std::make_pair(wxString("Shift+")+_L("Drag"), _L("Select by rectangle"))); | ||||||
|     shortcuts.push_back(std::make_pair(alt+_(L("Drag")),            _(L("Deselect by rectangle")))); |     shortcuts.push_back(std::make_pair(alt+_(L("Drag")),              _L("Deselect by rectangle"))); | ||||||
|     shortcuts.push_back(std::make_pair(ctrl+"A",                    _(L("Select all points")))); |     shortcuts.push_back(std::make_pair(ctrl+"A",                      _L("Select all points"))); | ||||||
|     shortcuts.push_back(std::make_pair("Delete",                    _(L("Remove selected points")))); |     shortcuts.push_back(std::make_pair("Delete",                      _L("Remove selected points"))); | ||||||
|     shortcuts.push_back(std::make_pair(ctrl+_(L("Mouse wheel")),    _(L("Move clipping plane")))); |     shortcuts.push_back(std::make_pair(ctrl+_L("Mouse wheel"),        _L("Move clipping plane"))); | ||||||
|     shortcuts.push_back(std::make_pair("R",                         _(L("Reset clipping plane")))); |     shortcuts.push_back(std::make_pair("R",                           _L("Reset clipping plane"))); | ||||||
|     shortcuts.push_back(std::make_pair("Enter",                     _(L("Apply changes")))); |     shortcuts.push_back(std::make_pair("Enter",                       _L("Apply changes"))); | ||||||
|     shortcuts.push_back(std::make_pair("Esc",                       _(L("Discard changes")))); |     shortcuts.push_back(std::make_pair("Esc",                         _L("Discard changes"))); | ||||||
|     shortcuts.push_back(std::make_pair("M",                         _(L("Switch to editing mode")))); |     shortcuts.push_back(std::make_pair("M",                           _L("Switch to editing mode"))); | ||||||
|     shortcuts.push_back(std::make_pair("A",                         _(L("Auto-generate points")))); |     shortcuts.push_back(std::make_pair("A",                           _L("Auto-generate points"))); | ||||||
| 
 | 
 | ||||||
|     for (const auto& pair : shortcuts) { |     for (const auto& pair : shortcuts) { | ||||||
|         auto shortcut = new wxStaticText(this, wxID_ANY, pair.first); |         auto shortcut = new wxStaticText(this, wxID_ANY, pair.first); | ||||||
|  |  | ||||||
|  | @ -488,8 +488,7 @@ bool ImGuiWrapper::undo_redo_list(const ImVec2& size, const bool is_undo, bool ( | ||||||
| 
 | 
 | ||||||
|     int i=0; |     int i=0; | ||||||
|     const char* item_text; |     const char* item_text; | ||||||
|     while (items_getter(is_undo, i, &item_text)) |     while (items_getter(is_undo, i, &item_text)) { | ||||||
|     { |  | ||||||
|         ImGui::Selectable(item_text, i < hovered); |         ImGui::Selectable(item_text, i < hovered); | ||||||
| 
 | 
 | ||||||
|         if (ImGui::IsItemHovered()) { |         if (ImGui::IsItemHovered()) { | ||||||
|  |  | ||||||
|  | @ -206,7 +206,14 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S | ||||||
| 
 | 
 | ||||||
|     // declare events
 |     // declare events
 | ||||||
|     Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent& event) { |     Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent& event) { | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  |         if (m_plater != nullptr) | ||||||
|  |             m_plater->save_project_if_dirty(); | ||||||
|  | 
 | ||||||
|  |         if (event.CanVeto() && !wxGetApp().check_and_save_current_preset_changes()) { | ||||||
|  | #else | ||||||
|         if (event.CanVeto() && !wxGetApp().check_unsaved_changes()) { |         if (event.CanVeto() && !wxGetApp().check_unsaved_changes()) { | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
|             event.Veto(); |             event.Veto(); | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  | @ -487,8 +494,14 @@ void MainFrame::update_title() | ||||||
|         // m_plater->get_project_filename() produces file name including path, but excluding extension.
 |         // 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!
 |         // 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 project = from_path(into_path(m_plater->get_project_filename()).filename()); | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  |         wxString dirty_marker = (!m_plater->model().objects.empty() && m_plater->is_project_dirty()) ? "*" : ""; | ||||||
|  |         if (!dirty_marker.empty() || !project.empty()) | ||||||
|  |             title = dirty_marker + project + " - "; | ||||||
|  | #else | ||||||
|         if (!project.empty()) |         if (!project.empty()) | ||||||
|             title += (project + " - "); |             title += (project + " - "); | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     std::string build_id = wxGetApp().is_editor() ? SLIC3R_BUILD_ID : GCODEVIEWER_BUILD_ID; |     std::string build_id = wxGetApp().is_editor() ? SLIC3R_BUILD_ID : GCODEVIEWER_BUILD_ID; | ||||||
|  | @ -672,10 +685,36 @@ bool MainFrame::can_start_new_project() const | ||||||
|     return (m_plater != nullptr) && (!m_plater->get_project_filename(".3mf").IsEmpty() || !m_plater->model().objects.empty()); |     return (m_plater != nullptr) && (!m_plater->get_project_filename(".3mf").IsEmpty() || !m_plater->model().objects.empty()); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  | bool MainFrame::can_save() const | ||||||
|  | { | ||||||
|  |     return (m_plater != nullptr) && !m_plater->model().objects.empty() && !m_plater->get_project_filename().empty() && m_plater->is_project_dirty(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool MainFrame::can_save_as() const | ||||||
|  | { | ||||||
|  |     return (m_plater != nullptr) && !m_plater->model().objects.empty(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void MainFrame::save_project() | ||||||
|  | { | ||||||
|  |     save_project_as(m_plater->get_project_filename(".3mf")); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void MainFrame::save_project_as(const wxString& filename) | ||||||
|  | { | ||||||
|  |     bool ret = (m_plater != nullptr) ? m_plater->export_3mf(into_path(filename)) : false; | ||||||
|  |     if (ret) { | ||||||
|  | //        wxGetApp().update_saved_preset_from_current_preset();
 | ||||||
|  |         m_plater->reset_project_dirty_after_save(); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | #else | ||||||
| bool MainFrame::can_save() const | bool MainFrame::can_save() const | ||||||
| { | { | ||||||
|     return (m_plater != nullptr) && !m_plater->model().objects.empty(); |     return (m_plater != nullptr) && !m_plater->model().objects.empty(); | ||||||
| } | } | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
| 
 | 
 | ||||||
| bool MainFrame::can_export_model() const | bool MainFrame::can_export_model() const | ||||||
| { | { | ||||||
|  | @ -984,16 +1023,27 @@ void MainFrame::init_menubar_as_editor() | ||||||
| 
 | 
 | ||||||
|         Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(m_recent_projects.GetCount() > 0); }, recent_projects_submenu->GetId()); |         Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(m_recent_projects.GetCount() > 0); }, recent_projects_submenu->GetId()); | ||||||
| 
 | 
 | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  |         append_menu_item(fileMenu, wxID_ANY, _L("&Save Project") + "\tCtrl+S", _L("Save current project file"), | ||||||
|  |             [this](wxCommandEvent&) { save_project(); }, "save", nullptr, | ||||||
|  |             [this](){return m_plater != nullptr && can_save(); }, this); | ||||||
|  | #else | ||||||
|         append_menu_item(fileMenu, wxID_ANY, _L("&Save Project") + "\tCtrl+S", _L("Save current project file"), |         append_menu_item(fileMenu, wxID_ANY, _L("&Save Project") + "\tCtrl+S", _L("Save current project file"), | ||||||
|             [this](wxCommandEvent&) { if (m_plater) m_plater->export_3mf(into_path(m_plater->get_project_filename(".3mf"))); }, "save", nullptr, |             [this](wxCommandEvent&) { if (m_plater) m_plater->export_3mf(into_path(m_plater->get_project_filename(".3mf"))); }, "save", nullptr, | ||||||
|             [this](){return m_plater != nullptr && can_save(); }, this); |             [this](){return m_plater != nullptr && can_save(); }, this); | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
| #ifdef __APPLE__ | #ifdef __APPLE__ | ||||||
|         append_menu_item(fileMenu, wxID_ANY, _L("Save Project &as") + dots + "\tCtrl+Shift+S", _L("Save current project file as"), |         append_menu_item(fileMenu, wxID_ANY, _L("Save Project &as") + dots + "\tCtrl+Shift+S", _L("Save current project file as"), | ||||||
| #else | #else | ||||||
|         append_menu_item(fileMenu, wxID_ANY, _L("Save Project &as") + dots + "\tCtrl+Alt+S", _L("Save current project file as"), |         append_menu_item(fileMenu, wxID_ANY, _L("Save Project &as") + dots + "\tCtrl+Alt+S", _L("Save current project file as"), | ||||||
| #endif // __APPLE__
 | #endif // __APPLE__
 | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  |             [this](wxCommandEvent&) { save_project_as(); }, "save", nullptr, | ||||||
|  |             [this](){return m_plater != nullptr && can_save_as(); }, this); | ||||||
|  | #else | ||||||
|             [this](wxCommandEvent&) { if (m_plater) m_plater->export_3mf(); }, "save", nullptr, |             [this](wxCommandEvent&) { if (m_plater) m_plater->export_3mf(); }, "save", nullptr, | ||||||
|             [this](){return m_plater != nullptr && can_save(); }, this); |             [this](){return m_plater != nullptr && can_save(); }, this); | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
| 
 | 
 | ||||||
|         fileMenu->AppendSeparator(); |         fileMenu->AppendSeparator(); | ||||||
| 
 | 
 | ||||||
|  | @ -1518,7 +1568,11 @@ void MainFrame::export_config() | ||||||
| // Load a config file containing a Print, Filament & Printer preset.
 | // Load a config file containing a Print, Filament & Printer preset.
 | ||||||
| void MainFrame::load_config_file() | void MainFrame::load_config_file() | ||||||
| { | { | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  |     if (!wxGetApp().check_and_save_current_preset_changes()) | ||||||
|  | #else | ||||||
|     if (!wxGetApp().check_unsaved_changes()) |     if (!wxGetApp().check_unsaved_changes()) | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
|         return; |         return; | ||||||
|     wxFileDialog dlg(this, _L("Select configuration to load:"), |     wxFileDialog dlg(this, _L("Select configuration to load:"), | ||||||
|         !m_last_config.IsEmpty() ? get_dir_name(m_last_config) : wxGetApp().app_config->get_last_dir(), |         !m_last_config.IsEmpty() ? get_dir_name(m_last_config) : wxGetApp().app_config->get_last_dir(), | ||||||
|  | @ -1547,7 +1601,11 @@ bool MainFrame::load_config_file(const std::string &path) | ||||||
| 
 | 
 | ||||||
| void MainFrame::export_configbundle(bool export_physical_printers /*= false*/) | void MainFrame::export_configbundle(bool export_physical_printers /*= false*/) | ||||||
| { | { | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  |     if (!wxGetApp().check_and_save_current_preset_changes()) | ||||||
|  | #else | ||||||
|     if (!wxGetApp().check_unsaved_changes()) |     if (!wxGetApp().check_unsaved_changes()) | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
|         return; |         return; | ||||||
|     // validate current configuration in case it's dirty
 |     // validate current configuration in case it's dirty
 | ||||||
|     auto err = wxGetApp().preset_bundle->full_config().validate(); |     auto err = wxGetApp().preset_bundle->full_config().validate(); | ||||||
|  | @ -1579,7 +1637,11 @@ void MainFrame::export_configbundle(bool export_physical_printers /*= false*/) | ||||||
| // but that behavior was not documented and likely buggy.
 | // but that behavior was not documented and likely buggy.
 | ||||||
| void MainFrame::load_configbundle(wxString file/* = wxEmptyString, const bool reset_user_profile*/) | void MainFrame::load_configbundle(wxString file/* = wxEmptyString, const bool reset_user_profile*/) | ||||||
| { | { | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  |     if (!wxGetApp().check_and_save_current_preset_changes()) | ||||||
|  | #else | ||||||
|     if (!wxGetApp().check_unsaved_changes()) |     if (!wxGetApp().check_unsaved_changes()) | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
|         return; |         return; | ||||||
|     if (file.IsEmpty()) { |     if (file.IsEmpty()) { | ||||||
|         wxFileDialog dlg(this, _L("Select configuration to load:"), |         wxFileDialog dlg(this, _L("Select configuration to load:"), | ||||||
|  |  | ||||||
|  | @ -91,7 +91,9 @@ class MainFrame : public DPIFrame | ||||||
|     void on_value_changed(wxCommandEvent&); |     void on_value_changed(wxCommandEvent&); | ||||||
| 
 | 
 | ||||||
|     bool can_start_new_project() const; |     bool can_start_new_project() const; | ||||||
|  | #if !ENABLE_PROJECT_DIRTY_STATE | ||||||
|     bool can_save() const; |     bool can_save() const; | ||||||
|  | #endif // !ENABLE_PROJECT_DIRTY_STATE
 | ||||||
|     bool can_export_model() const; |     bool can_export_model() const; | ||||||
|     bool can_export_toolpaths() const; |     bool can_export_toolpaths() const; | ||||||
|     bool can_export_supports() const; |     bool can_export_supports() const; | ||||||
|  | @ -184,6 +186,13 @@ public: | ||||||
|     // Propagate changed configuration from the Tab to the Plater and save changes to the AppConfig
 |     // Propagate changed configuration from the Tab to the Plater and save changes to the AppConfig
 | ||||||
|     void        on_config_changed(DynamicPrintConfig* cfg) const ; |     void        on_config_changed(DynamicPrintConfig* cfg) const ; | ||||||
| 
 | 
 | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  |     bool can_save() const; | ||||||
|  |     bool can_save_as() const; | ||||||
|  |     void save_project(); | ||||||
|  |     void save_project_as(const wxString& filename = wxString()); | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
|  | 
 | ||||||
|     void        add_to_recent_projects(const wxString& filename); |     void        add_to_recent_projects(const wxString& filename); | ||||||
| 
 | 
 | ||||||
|     PrintHostQueueDialog* printhost_queue_dlg() { return m_printhost_queue_dlg; } |     PrintHostQueueDialog* printhost_queue_dlg() { return m_printhost_queue_dlg; } | ||||||
|  |  | ||||||
|  | @ -81,6 +81,9 @@ | ||||||
| #include "InstanceCheck.hpp" | #include "InstanceCheck.hpp" | ||||||
| #include "NotificationManager.hpp" | #include "NotificationManager.hpp" | ||||||
| #include "PresetComboBoxes.hpp" | #include "PresetComboBoxes.hpp" | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  | #include "ProjectDirtyStateManager.hpp" | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
| 
 | 
 | ||||||
| #ifdef __APPLE__ | #ifdef __APPLE__ | ||||||
| #include "Gizmos/GLGizmosManager.hpp" | #include "Gizmos/GLGizmosManager.hpp" | ||||||
|  | @ -1396,7 +1399,13 @@ bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &fi | ||||||
|     this->MSWUpdateDragImageOnLeave(); |     this->MSWUpdateDragImageOnLeave(); | ||||||
| #endif // WIN32
 | #endif // WIN32
 | ||||||
| 
 | 
 | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  |     bool res = (m_plater != nullptr) ? m_plater->load_files(filenames) : false; | ||||||
|  |     wxGetApp().mainframe->update_title(); | ||||||
|  |     return res; | ||||||
|  | #else | ||||||
|     return (m_plater != nullptr) ? m_plater->load_files(filenames) : false; |     return (m_plater != nullptr) ? m_plater->load_files(filenames) : false; | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // State to manage showing after export notifications and device ejecting
 | // State to manage showing after export notifications and device ejecting
 | ||||||
|  | @ -1440,6 +1449,10 @@ struct Plater::priv | ||||||
|     Preview *preview; |     Preview *preview; | ||||||
|     NotificationManager* notification_manager { nullptr }; |     NotificationManager* notification_manager { nullptr }; | ||||||
| 
 | 
 | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  |     ProjectDirtyStateManager dirty_state; | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
|  | 
 | ||||||
|     BackgroundSlicingProcess    background_process; |     BackgroundSlicingProcess    background_process; | ||||||
|     bool suppressed_backround_processing_update { false }; |     bool suppressed_backround_processing_update { false }; | ||||||
| 
 | 
 | ||||||
|  | @ -1510,6 +1523,31 @@ struct Plater::priv | ||||||
|     priv(Plater *q, MainFrame *main_frame); |     priv(Plater *q, MainFrame *main_frame); | ||||||
|     ~priv(); |     ~priv(); | ||||||
| 
 | 
 | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  |     bool is_project_dirty() const { return dirty_state.is_dirty(); } | ||||||
|  |     void update_project_dirty_from_presets() { dirty_state.update_from_presets(); } | ||||||
|  |     bool save_project_if_dirty() { | ||||||
|  |         if (dirty_state.is_dirty()) { | ||||||
|  |             MainFrame* mainframe = wxGetApp().mainframe; | ||||||
|  |             if (mainframe->can_save_as()) { | ||||||
|  |                 wxMessageDialog dlg(mainframe, _L("Do you want to save the changes to the current project ?"), wxString(SLIC3R_APP_NAME), wxYES_NO | wxCANCEL); | ||||||
|  |                 int res = dlg.ShowModal(); | ||||||
|  |                 if (res == wxID_YES) | ||||||
|  |                     mainframe->save_project_as(wxGetApp().plater()->get_project_filename()); | ||||||
|  |                 else if (res == wxID_CANCEL) | ||||||
|  |                     return false; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  |     void reset_project_dirty_after_save() { dirty_state.reset_after_save(); } | ||||||
|  |     void reset_project_dirty_initial_presets() { dirty_state.reset_initial_presets(); } | ||||||
|  | 
 | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW | ||||||
|  |     void render_project_state_debug_window() const { dirty_state.render_debug_window(); } | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
 | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
|  | 
 | ||||||
|     enum class UpdateParams { |     enum class UpdateParams { | ||||||
|         FORCE_FULL_SCREEN_REFRESH          = 1, |         FORCE_FULL_SCREEN_REFRESH          = 1, | ||||||
|         FORCE_BACKGROUND_PROCESSING_UPDATE = 2, |         FORCE_BACKGROUND_PROCESSING_UPDATE = 2, | ||||||
|  | @ -4231,6 +4269,11 @@ void Plater::priv::take_snapshot(const std::string& snapshot_name) | ||||||
|     } |     } | ||||||
|     this->undo_redo_stack().take_snapshot(snapshot_name, model, view3D->get_canvas3d()->get_selection(), view3D->get_canvas3d()->get_gizmos_manager(), snapshot_data); |     this->undo_redo_stack().take_snapshot(snapshot_name, model, view3D->get_canvas3d()->get_selection(), view3D->get_canvas3d()->get_gizmos_manager(), snapshot_data); | ||||||
|     this->undo_redo_stack().release_least_recently_used(); |     this->undo_redo_stack().release_least_recently_used(); | ||||||
|  | 
 | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  |     dirty_state.update_from_undo_redo_stack(ProjectDirtyStateManager::UpdateType::TakeSnapshot); | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
|  | 
 | ||||||
|     // Save the last active preset name of a particular printer technology.
 |     // 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(); |     ((this->printer_technology == ptFFF) ? m_last_fff_printer_profile_name : m_last_sla_printer_profile_name) = wxGetApp().preset_bundle->printers.get_selected_preset_name(); | ||||||
|     BOOST_LOG_TRIVIAL(info) << "Undo / Redo snapshot taken: " << snapshot_name << ", Undo / Redo stack memory: " << Slic3r::format_memsize_MB(this->undo_redo_stack().memsize()) << log_memory_info(); |     BOOST_LOG_TRIVIAL(info) << "Undo / Redo snapshot taken: " << snapshot_name << ", Undo / Redo stack memory: " << Slic3r::format_memsize_MB(this->undo_redo_stack().memsize()) << log_memory_info(); | ||||||
|  | @ -4271,8 +4314,13 @@ void Plater::priv::undo_redo_to(std::vector<UndoRedo::Snapshot>::const_iterator | ||||||
|     if (printer_technology_changed) { |     if (printer_technology_changed) { | ||||||
|         // Switching the printer technology when jumping forwards / backwards in time. Switch to the last active printer profile of the other type.
 |         // Switching the printer technology when jumping forwards / backwards in time. Switch to the last active printer profile of the other type.
 | ||||||
|         std::string s_pt = (it_snapshot->snapshot_data.printer_technology == ptFFF) ? "FFF" : "SLA"; |         std::string s_pt = (it_snapshot->snapshot_data.printer_technology == ptFFF) ? "FFF" : "SLA"; | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  |         if (!wxGetApp().check_and_save_current_preset_changes(format_wxstr(_L( | ||||||
|  |             "%1% printer was active at the time the target Undo / Redo snapshot was taken. Switching to %1% printer requires reloading of %1% presets."), s_pt))) | ||||||
|  | #else | ||||||
|         if (! wxGetApp().check_unsaved_changes(format_wxstr(_L( |         if (! wxGetApp().check_unsaved_changes(format_wxstr(_L( | ||||||
|             "%1% printer was active at the time the target Undo / Redo snapshot was taken. Switching to %1% printer requires reloading of %1% presets."), s_pt))) |             "%1% printer was active at the time the target Undo / Redo snapshot was taken. Switching to %1% printer requires reloading of %1% presets."), s_pt))) | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
|             // Don't switch the profiles.
 |             // Don't switch the profiles.
 | ||||||
|             return; |             return; | ||||||
|     } |     } | ||||||
|  | @ -4361,6 +4409,10 @@ void Plater::priv::undo_redo_to(std::vector<UndoRedo::Snapshot>::const_iterator | ||||||
|         if (! view3D->is_layers_editing_enabled() && this->layers_height_allowed() && new_variable_layer_editing_active) |         if (! view3D->is_layers_editing_enabled() && this->layers_height_allowed() && new_variable_layer_editing_active) | ||||||
|             view3D->get_canvas3d()->force_main_toolbar_left_action(view3D->get_canvas3d()->get_main_toolbar_item_id("layersediting")); |             view3D->get_canvas3d()->force_main_toolbar_left_action(view3D->get_canvas3d()->get_main_toolbar_item_id("layersediting")); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  |     dirty_state.update_from_undo_redo_stack(ProjectDirtyStateManager::UpdateType::UndoRedoTo); | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Plater::priv::update_after_undo_redo(const UndoRedo::Snapshot& snapshot, bool /* temp_snapshot_was_taken */) | void Plater::priv::update_after_undo_redo(const UndoRedo::Snapshot& snapshot, bool /* temp_snapshot_was_taken */) | ||||||
|  | @ -4444,9 +4496,16 @@ Plater::Plater(wxWindow *parent, MainFrame *main_frame) | ||||||
|     // Initialization performed in the private c-tor
 |     // Initialization performed in the private c-tor
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| Plater::~Plater() | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
| { | bool Plater::is_project_dirty() const { return p->is_project_dirty(); } | ||||||
| } | void Plater::update_project_dirty_from_presets() { p->update_project_dirty_from_presets(); } | ||||||
|  | bool Plater::save_project_if_dirty() { return p->save_project_if_dirty(); } | ||||||
|  | void Plater::reset_project_dirty_after_save() { p->reset_project_dirty_after_save(); } | ||||||
|  | void Plater::reset_project_dirty_initial_presets() { p->reset_project_dirty_initial_presets(); } | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW | ||||||
|  | void Plater::render_project_state_debug_window() const { p->render_project_state_debug_window(); } | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
 | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
| 
 | 
 | ||||||
| Sidebar&        Plater::sidebar()           { return *p->sidebar; } | Sidebar&        Plater::sidebar()           { return *p->sidebar; } | ||||||
| Model&          Plater::model()             { return p->model; } | Model&          Plater::model()             { return p->model; } | ||||||
|  | @ -4457,12 +4516,30 @@ SLAPrint&       Plater::sla_print()         { return p->sla_print; } | ||||||
| 
 | 
 | ||||||
| void Plater::new_project() | void Plater::new_project() | ||||||
| { | { | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  |     if (!p->save_project_if_dirty()) | ||||||
|  |         return; | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
|  | 
 | ||||||
|     p->select_view_3D("3D"); |     p->select_view_3D("3D"); | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  |     take_snapshot(_L("New Project")); | ||||||
|  |     Plater::SuppressSnapshots suppress(this); | ||||||
|  |     reset(); | ||||||
|  |     reset_project_dirty_initial_presets(); | ||||||
|  |     update_project_dirty_from_presets(); | ||||||
|  | #else | ||||||
|     wxPostEvent(p->view3D->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL)); |     wxPostEvent(p->view3D->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL)); | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Plater::load_project() | void Plater::load_project() | ||||||
| { | { | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  |     if (!p->save_project_if_dirty()) | ||||||
|  |         return; | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
|  | 
 | ||||||
|     // Ask user for a project file name.
 |     // Ask user for a project file name.
 | ||||||
|     wxString input_file; |     wxString input_file; | ||||||
|     wxGetApp().load_project(this, input_file); |     wxGetApp().load_project(this, input_file); | ||||||
|  | @ -4486,8 +4563,16 @@ void Plater::load_project(const wxString& filename) | ||||||
|     std::vector<size_t> res = load_files(input_paths); |     std::vector<size_t> res = load_files(input_paths); | ||||||
| 
 | 
 | ||||||
|     // if res is empty no data has been loaded
 |     // if res is empty no data has been loaded
 | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  |     if (!res.empty()) { | ||||||
|  |         p->set_project_filename(filename); | ||||||
|  |         reset_project_dirty_initial_presets(); | ||||||
|  |         update_project_dirty_from_presets(); | ||||||
|  |     } | ||||||
|  | #else | ||||||
|     if (!res.empty()) |     if (!res.empty()) | ||||||
|         p->set_project_filename(filename); |         p->set_project_filename(filename); | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Plater::add_model(bool imperial_units/* = false*/) | void Plater::add_model(bool imperial_units/* = false*/) | ||||||
|  | @ -4518,7 +4603,13 @@ void Plater::add_model(bool imperial_units/* = false*/) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     Plater::TakeSnapshot snapshot(this, snapshot_label); |     Plater::TakeSnapshot snapshot(this, snapshot_label); | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  |     std::vector<size_t> res = load_files(paths, true, false, imperial_units); | ||||||
|  |     if (!res.empty()) | ||||||
|  |         wxGetApp().mainframe->update_title(); | ||||||
|  | #else | ||||||
|     load_files(paths, true, false, imperial_units); |     load_files(paths, true, false, imperial_units); | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Plater::import_sl1_archive() | void Plater::import_sl1_archive() | ||||||
|  | @ -5233,24 +5324,39 @@ void Plater::export_amf() | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  | bool Plater::export_3mf(const boost::filesystem::path& output_path) | ||||||
|  | #else | ||||||
| void Plater::export_3mf(const boost::filesystem::path& output_path) | void Plater::export_3mf(const boost::filesystem::path& output_path) | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
| { | { | ||||||
|     if (p->model.objects.empty() |     if (p->model.objects.empty() | ||||||
|      || canvas3D()->get_gizmos_manager().is_in_editing_mode(true)) |      || canvas3D()->get_gizmos_manager().is_in_editing_mode(true)) | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  |         return false; | ||||||
|  | #else | ||||||
|         return; |         return; | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
| 
 | 
 | ||||||
|     wxString path; |     wxString path; | ||||||
|     bool export_config = true; |     bool export_config = true; | ||||||
|     if (output_path.empty()) |     if (output_path.empty()) { | ||||||
|     { |  | ||||||
|         path = p->get_export_file(FT_3MF); |         path = p->get_export_file(FT_3MF); | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  |         if (path.empty()) { return false; } | ||||||
|  | #else | ||||||
|         if (path.empty()) { return; } |         if (path.empty()) { return; } | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
|     } |     } | ||||||
|     else |     else | ||||||
|         path = from_path(output_path); |         path = from_path(output_path); | ||||||
| 
 | 
 | ||||||
|     if (!path.Lower().EndsWith(".3mf")) |     if (!path.Lower().EndsWith(".3mf")) | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  |         return false; | ||||||
|  | #else | ||||||
|         return; |         return; | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
| 
 | 
 | ||||||
|     DynamicPrintConfig cfg = wxGetApp().preset_bundle->full_config_secure(); |     DynamicPrintConfig cfg = wxGetApp().preset_bundle->full_config_secure(); | ||||||
|     const std::string path_u8 = into_u8(path); |     const std::string path_u8 = into_u8(path); | ||||||
|  | @ -5258,6 +5364,19 @@ void Plater::export_3mf(const boost::filesystem::path& output_path) | ||||||
|     bool full_pathnames = wxGetApp().app_config->get("export_sources_full_pathnames") == "1"; |     bool full_pathnames = wxGetApp().app_config->get("export_sources_full_pathnames") == "1"; | ||||||
|     ThumbnailData thumbnail_data; |     ThumbnailData thumbnail_data; | ||||||
|     p->generate_thumbnail(thumbnail_data, THUMBNAIL_SIZE_3MF.first, THUMBNAIL_SIZE_3MF.second, false, true, true, true); |     p->generate_thumbnail(thumbnail_data, THUMBNAIL_SIZE_3MF.first, THUMBNAIL_SIZE_3MF.second, false, true, true, true); | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  |     bool ret = Slic3r::store_3mf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr, full_pathnames, &thumbnail_data); | ||||||
|  |     if (ret) { | ||||||
|  |         // Success
 | ||||||
|  |         p->statusbar()->set_status_text(format_wxstr(_L("3MF file exported to %s"), path)); | ||||||
|  |         p->set_project_filename(path); | ||||||
|  |     } | ||||||
|  |     else { | ||||||
|  |         // Failure
 | ||||||
|  |         p->statusbar()->set_status_text(format_wxstr(_L("Error exporting 3MF file %s"), path)); | ||||||
|  |     } | ||||||
|  |     return ret; | ||||||
|  | #else | ||||||
|     if (Slic3r::store_3mf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr, full_pathnames, &thumbnail_data)) { |     if (Slic3r::store_3mf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr, full_pathnames, &thumbnail_data)) { | ||||||
|         // Success
 |         // Success
 | ||||||
|         p->statusbar()->set_status_text(format_wxstr(_L("3MF file exported to %s"), path)); |         p->statusbar()->set_status_text(format_wxstr(_L("3MF file exported to %s"), path)); | ||||||
|  | @ -5267,6 +5386,7 @@ void Plater::export_3mf(const boost::filesystem::path& output_path) | ||||||
|         // Failure
 |         // Failure
 | ||||||
|         p->statusbar()->set_status_text(format_wxstr(_L("Error exporting 3MF file %s"), path)); |         p->statusbar()->set_status_text(format_wxstr(_L("Error exporting 3MF file %s"), path)); | ||||||
|     } |     } | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Plater::reload_from_disk() | void Plater::reload_from_disk() | ||||||
|  | @ -6127,6 +6247,9 @@ bool Plater::can_mirror() const { return p->can_mirror(); } | ||||||
| bool Plater::can_split(bool to_objects) const { return p->can_split(to_objects); } | 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(); } | 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(); } | void Plater::clear_undo_redo_stack_main() { p->undo_redo_stack_main().clear(); } | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  | const UndoRedo::Stack& Plater::undo_redo_stack_active() const { return p->undo_redo_stack(); } | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
| void Plater::enter_gizmos_stack() { p->enter_gizmos_stack(); } | void Plater::enter_gizmos_stack() { p->enter_gizmos_stack(); } | ||||||
| void Plater::leave_gizmos_stack() { p->leave_gizmos_stack(); } | void Plater::leave_gizmos_stack() { p->leave_gizmos_stack(); } | ||||||
| bool Plater::inside_snapshot_capture() { return p->inside_snapshot_capture(); } | bool Plater::inside_snapshot_capture() { return p->inside_snapshot_capture(); } | ||||||
|  |  | ||||||
|  | @ -128,7 +128,18 @@ public: | ||||||
|     Plater(const Plater &) = delete; |     Plater(const Plater &) = delete; | ||||||
|     Plater &operator=(Plater &&) = delete; |     Plater &operator=(Plater &&) = delete; | ||||||
|     Plater &operator=(const Plater &) = delete; |     Plater &operator=(const Plater &) = delete; | ||||||
|     ~Plater(); |     ~Plater() = default; | ||||||
|  | 
 | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  |     bool is_project_dirty() const; | ||||||
|  |     void update_project_dirty_from_presets(); | ||||||
|  |     bool save_project_if_dirty(); | ||||||
|  |     void reset_project_dirty_after_save(); | ||||||
|  |     void reset_project_dirty_initial_presets(); | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW | ||||||
|  |     void render_project_state_debug_window() const; | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
 | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
| 
 | 
 | ||||||
|     Sidebar& sidebar(); |     Sidebar& sidebar(); | ||||||
|     Model& model(); |     Model& model(); | ||||||
|  | @ -198,7 +209,11 @@ public: | ||||||
|     void export_gcode(bool prefer_removable); |     void export_gcode(bool prefer_removable); | ||||||
|     void export_stl(bool extended = false, bool selection_only = false); |     void export_stl(bool extended = false, bool selection_only = false); | ||||||
|     void export_amf(); |     void export_amf(); | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  |     bool export_3mf(const boost::filesystem::path& output_path = boost::filesystem::path()); | ||||||
|  | #else | ||||||
|     void export_3mf(const boost::filesystem::path& output_path = boost::filesystem::path()); |     void export_3mf(const boost::filesystem::path& output_path = boost::filesystem::path()); | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
|     void reload_from_disk(); |     void reload_from_disk(); | ||||||
|     void reload_all_from_disk(); |     void reload_all_from_disk(); | ||||||
|     bool has_toolpaths_to_export() const; |     bool has_toolpaths_to_export() const; | ||||||
|  | @ -228,6 +243,9 @@ public: | ||||||
|     // For the memory statistics. 
 |     // For the memory statistics. 
 | ||||||
|     const Slic3r::UndoRedo::Stack& undo_redo_stack_main() const; |     const Slic3r::UndoRedo::Stack& undo_redo_stack_main() const; | ||||||
|     void clear_undo_redo_stack_main(); |     void clear_undo_redo_stack_main(); | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  |     const Slic3r::UndoRedo::Stack& undo_redo_stack_active() const; | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
|     // Enter / leave the Gizmos specific Undo / Redo stack. To be used by the SLA support point editing gizmo.
 |     // Enter / leave the Gizmos specific Undo / Redo stack. To be used by the SLA support point editing gizmo.
 | ||||||
|     void enter_gizmos_stack(); |     void enter_gizmos_stack(); | ||||||
|     void leave_gizmos_stack(); |     void leave_gizmos_stack(); | ||||||
|  |  | ||||||
							
								
								
									
										415
									
								
								src/slic3r/GUI/ProjectDirtyStateManager.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										415
									
								
								src/slic3r/GUI/ProjectDirtyStateManager.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,415 @@ | ||||||
|  | #include "libslic3r/libslic3r.h" | ||||||
|  | 
 | ||||||
|  | #include "ProjectDirtyStateManager.hpp" | ||||||
|  | #include "ImGuiWrapper.hpp" | ||||||
|  | #include "GUI_App.hpp" | ||||||
|  | #include "MainFrame.hpp" | ||||||
|  | #include "I18N.hpp" | ||||||
|  | #include "Plater.hpp" | ||||||
|  | #include "../Utils/UndoRedo.hpp" | ||||||
|  | 
 | ||||||
|  | #include <boost/algorithm/string/predicate.hpp> | ||||||
|  | 
 | ||||||
|  | #include <algorithm> | ||||||
|  | #include <assert.h> | ||||||
|  | 
 | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  | 
 | ||||||
|  | namespace Slic3r { | ||||||
|  | namespace GUI { | ||||||
|  | 
 | ||||||
|  | enum class EStackType | ||||||
|  | { | ||||||
|  |     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 && (size_t)idx < 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& [name, gizmo] : used) { | ||||||
|  |         auto it = gizmo.modified_timestamps.begin(); | ||||||
|  |         while (it != gizmo.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 = gizmo.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 (auto& [name, gizmo] : used) { | ||||||
|  |         for (size_t i : gizmo.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(); | ||||||
|  |     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(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void ProjectDirtyStateManager::update_from_presets() | ||||||
|  | { | ||||||
|  |     m_state.presets = false; | ||||||
|  |     std::vector<std::pair<unsigned int, std::string>> selected_presets = wxGetApp().get_selected_presets(); | ||||||
|  |     for (const auto& [type, name] : selected_presets) { | ||||||
|  |         m_state.presets |= !m_initial_presets[type].empty() && m_initial_presets[type] != name; | ||||||
|  |     } | ||||||
|  |     m_state.presets |= 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 { | ||||||
|  |         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->timestamp; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     reset_initial_presets(); | ||||||
|  |     m_state.reset(); | ||||||
|  |     wxGetApp().mainframe->update_title(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void ProjectDirtyStateManager::reset_initial_presets() | ||||||
|  | { | ||||||
|  |     m_initial_presets = std::array<std::string, Preset::TYPE_COUNT>(); | ||||||
|  |     std::vector<std::pair<unsigned int, std::string>> selected_presets = wxGetApp().get_selected_presets(); | ||||||
|  |     for (const auto& [type, name] : selected_presets) { | ||||||
|  |         m_initial_presets[type] = name; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW | ||||||
|  | void ProjectDirtyStateManager::render_debug_window() const | ||||||
|  | { | ||||||
|  |     ImGuiWrapper& imgui = *wxGetApp().imgui(); | ||||||
|  | 
 | ||||||
|  |     auto color = [](bool value) { | ||||||
|  |         return value ? ImVec4(1.0f, 0.49f, 0.216f, 1.0f) /* orange */: ImVec4(1.0f, 1.0f, 1.0f, 1.0f) /* white */; | ||||||
|  |     }; | ||||||
|  |     auto bool_to_text = [](bool value) { | ||||||
|  |         return value ? "true" : "false"; | ||||||
|  |     }; | ||||||
|  |     auto append_bool_item = [color, bool_to_text, &imgui](const std::string& name, bool value) { | ||||||
|  |         imgui.text_colored(color(value), name); | ||||||
|  |         ImGui::SameLine(); | ||||||
|  |         imgui.text_colored(color(value), bool_to_text(value)); | ||||||
|  |     }; | ||||||
|  |     auto append_int_item = [&imgui](const std::string& name, int value) { | ||||||
|  |         imgui.text(name); | ||||||
|  |         ImGui::SameLine(); | ||||||
|  |         imgui.text(std::to_string(value)); | ||||||
|  |     }; | ||||||
|  |     auto append_snapshot_item = [&imgui](const std::string& label, const UndoRedo::Snapshot* snapshot) { | ||||||
|  |         imgui.text(label); | ||||||
|  |         ImGui::SameLine(100); | ||||||
|  |         if (snapshot != nullptr) | ||||||
|  |             imgui.text(snapshot->name + " (" + std::to_string(snapshot->timestamp) + ")"); | ||||||
|  |         else | ||||||
|  |             imgui.text("-"); | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     imgui.begin(std::string("Project dirty state statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); | ||||||
|  | 
 | ||||||
|  |     if (ImGui::CollapsingHeader("Dirty state", ImGuiTreeNodeFlags_DefaultOpen)) { | ||||||
|  |         append_bool_item("Overall:", is_dirty()); | ||||||
|  |         ImGui::Separator(); | ||||||
|  |         append_bool_item("Plater:", m_state.plater); | ||||||
|  |         append_bool_item("Presets:", m_state.presets); | ||||||
|  |         append_bool_item("Current gizmo:", m_state.gizmos.current); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (ImGui::CollapsingHeader("Last save timestamps", ImGuiTreeNodeFlags_DefaultOpen)) { | ||||||
|  |         append_int_item("Main:", m_last_save.main); | ||||||
|  |         append_int_item("Current gizmo:", m_last_save.gizmo); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const UndoRedo::Stack& main_stack = wxGetApp().plater()->undo_redo_stack_main(); | ||||||
|  |     const UndoRedo::Snapshot* main_active_snapshot = get_active_snapshot(main_stack); | ||||||
|  |     const UndoRedo::Snapshot* main_last_saveable_snapshot = get_last_saveable_snapshot(EStackType::Main, main_stack, m_state.gizmos, m_last_save.main); | ||||||
|  |     const std::vector<UndoRedo::Snapshot>& main_snapshots = main_stack.snapshots(); | ||||||
|  | 
 | ||||||
|  |     if (ImGui::CollapsingHeader("Main snapshots", ImGuiTreeNodeFlags_DefaultOpen)) { | ||||||
|  |         append_snapshot_item("Active:", main_active_snapshot); | ||||||
|  |         append_snapshot_item("Last saveable:", main_last_saveable_snapshot); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (ImGui::CollapsingHeader("Main undo/redo stack", ImGuiTreeNodeFlags_DefaultOpen)) { | ||||||
|  |         for (const UndoRedo::Snapshot& snapshot : main_snapshots) { | ||||||
|  |             bool active = main_active_snapshot->timestamp == snapshot.timestamp; | ||||||
|  |             imgui.text_colored(color(active), snapshot.name); | ||||||
|  |             ImGui::SameLine(150); | ||||||
|  |             imgui.text_colored(color(active), " (" + std::to_string(snapshot.timestamp) + ")"); | ||||||
|  |             if (&snapshot == main_last_saveable_snapshot) { | ||||||
|  |                 ImGui::SameLine(); | ||||||
|  |                 imgui.text_colored(color(active), " (S)"); | ||||||
|  |             } | ||||||
|  |             if (m_last_save.main > 0 && m_last_save.main == snapshot.timestamp) { | ||||||
|  |                 ImGui::SameLine(); | ||||||
|  |                 imgui.text_colored(color(active), " (LS)"); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const UndoRedo::Stack& active_stack = wxGetApp().plater()->undo_redo_stack_active(); | ||||||
|  |     if (&active_stack != &main_stack) { | ||||||
|  |         if (ImGui::CollapsingHeader("Gizmo undo/redo stack", ImGuiTreeNodeFlags_DefaultOpen)) { | ||||||
|  |             const UndoRedo::Snapshot* active_active_snapshot = get_active_snapshot(active_stack); | ||||||
|  |             const std::vector<UndoRedo::Snapshot>& active_snapshots = active_stack.snapshots(); | ||||||
|  |             for (const UndoRedo::Snapshot& snapshot : active_snapshots) { | ||||||
|  |                 bool active = active_active_snapshot->timestamp == snapshot.timestamp; | ||||||
|  |                 imgui.text_colored(color(active), snapshot.name); | ||||||
|  |                 ImGui::SameLine(150); | ||||||
|  |                 imgui.text_colored(color(active), " (" + std::to_string(snapshot.timestamp) + ")"); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     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; | ||||||
|  | 
 | ||||||
|  |     size_t search_timestamp = 0; | ||||||
|  |     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; | ||||||
|  |         search_timestamp = m_last_save.main; | ||||||
|  |     } | ||||||
|  |     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; | ||||||
|  |         search_timestamp = m_last_save.main; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     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
 | ||||||
|  | 
 | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
|  | 
 | ||||||
							
								
								
									
										96
									
								
								src/slic3r/GUI/ProjectDirtyStateManager.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								src/slic3r/GUI/ProjectDirtyStateManager.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,96 @@ | ||||||
|  | #ifndef slic3r_ProjectDirtyStateManager_hpp_ | ||||||
|  | #define slic3r_ProjectDirtyStateManager_hpp_ | ||||||
|  | 
 | ||||||
|  | #include "libslic3r/Preset.hpp" | ||||||
|  | 
 | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  | 
 | ||||||
|  | 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); | ||||||
|  |     void update_from_presets(); | ||||||
|  |     void reset_after_save(); | ||||||
|  |     void reset_initial_presets(); | ||||||
|  | #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); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } // namespace GUI
 | ||||||
|  | } // namespace Slic3r
 | ||||||
|  | 
 | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
|  | 
 | ||||||
|  | #endif // slic3r_ProjectDirtyStateManager_hpp_
 | ||||||
|  | 
 | ||||||
|  | @ -1217,9 +1217,8 @@ void Tab::apply_config_from_cache() | ||||||
| // to update number of "filament" selection boxes when the number of extruders change.
 | // to update number of "filament" selection boxes when the number of extruders change.
 | ||||||
| void Tab::on_presets_changed() | void Tab::on_presets_changed() | ||||||
| { | { | ||||||
|     if (wxGetApp().plater() == nullptr) { |     if (wxGetApp().plater() == nullptr) | ||||||
|         return; |         return; | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     // Instead of PostEvent (EVT_TAB_PRESETS_CHANGED) just call update_presets
 |     // Instead of PostEvent (EVT_TAB_PRESETS_CHANGED) just call update_presets
 | ||||||
|     wxGetApp().plater()->sidebar().update_presets(m_type); |     wxGetApp().plater()->sidebar().update_presets(m_type); | ||||||
|  | @ -1237,6 +1236,10 @@ void Tab::on_presets_changed() | ||||||
|     // clear m_dependent_tabs after first update from select_preset()
 |     // clear m_dependent_tabs after first update from select_preset()
 | ||||||
|     // to avoid needless preset loading from update() function
 |     // to avoid needless preset loading from update() function
 | ||||||
|     m_dependent_tabs.clear(); |     m_dependent_tabs.clear(); | ||||||
|  | 
 | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  |     wxGetApp().plater()->update_project_dirty_from_presets(); | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Tab::build_preset_description_line(ConfigOptionsGroup* optgroup) | void Tab::build_preset_description_line(ConfigOptionsGroup* optgroup) | ||||||
|  | @ -2113,10 +2116,16 @@ wxSizer* Tab::description_line_widget(wxWindow* parent, ogStaticText* *StaticTex | ||||||
|     return sizer; |     return sizer; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  | bool Tab::saved_preset_is_dirty() const { return m_presets->saved_is_dirty(); } | ||||||
|  | void Tab::update_saved_preset_from_current_preset() { m_presets->update_saved_preset_from_current_preset(); } | ||||||
|  | bool Tab::current_preset_is_dirty() const { return m_presets->current_is_dirty(); } | ||||||
|  | #else | ||||||
| bool Tab::current_preset_is_dirty() | bool Tab::current_preset_is_dirty() | ||||||
| { | { | ||||||
|     return m_presets->current_is_dirty(); |     return m_presets->current_is_dirty(); | ||||||
| } | } | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
| 
 | 
 | ||||||
| void TabPrinter::build() | void TabPrinter::build() | ||||||
| { | { | ||||||
|  |  | ||||||
|  | @ -270,7 +270,11 @@ public: | ||||||
|     Preset::Type type()  const { return m_type; } |     Preset::Type type()  const { return m_type; } | ||||||
|     // The tab is already constructed.
 |     // The tab is already constructed.
 | ||||||
|     bool 		completed() const { return m_completed; } |     bool 		completed() const { return m_completed; } | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  | 	virtual bool supports_printer_technology(const PrinterTechnology tech) const = 0; | ||||||
|  | #else | ||||||
| 	virtual bool supports_printer_technology(const PrinterTechnology tech) = 0; | 	virtual bool supports_printer_technology(const PrinterTechnology tech) = 0; | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
| 
 | 
 | ||||||
| 	void		create_preset_tab(); | 	void		create_preset_tab(); | ||||||
|     void        add_scaled_button(wxWindow* parent, ScalableButton** btn, const std::string& icon_name,  |     void        add_scaled_button(wxWindow* parent, ScalableButton** btn, const std::string& icon_name,  | ||||||
|  | @ -333,7 +337,13 @@ public: | ||||||
|     Field*          get_field(const t_config_option_key &opt_key, Page** selected_page, int opt_index = -1); |     Field*          get_field(const t_config_option_key &opt_key, Page** selected_page, int opt_index = -1); | ||||||
| 	void			toggle_option(const std::string& opt_key, bool toggle, int opt_index = -1); | 	void			toggle_option(const std::string& opt_key, bool toggle, int opt_index = -1); | ||||||
| 	wxSizer*		description_line_widget(wxWindow* parent, ogStaticText** StaticText, wxString text = wxEmptyString); | 	wxSizer*		description_line_widget(wxWindow* parent, ogStaticText** StaticText, wxString text = wxEmptyString); | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  | 	bool			current_preset_is_dirty() const; | ||||||
|  | 	bool			saved_preset_is_dirty() const; | ||||||
|  | 	void            update_saved_preset_from_current_preset(); | ||||||
|  | #else | ||||||
| 	bool			current_preset_is_dirty(); | 	bool			current_preset_is_dirty(); | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
| 
 | 
 | ||||||
| 	DynamicPrintConfig*	get_config() { return m_config; } | 	DynamicPrintConfig*	get_config() { return m_config; } | ||||||
| 	PresetCollection*	get_presets() { return m_presets; } | 	PresetCollection*	get_presets() { return m_presets; } | ||||||
|  | @ -377,8 +387,8 @@ class TabPrint : public Tab | ||||||
| { | { | ||||||
| public: | public: | ||||||
| 	TabPrint(wxNotebook* parent) :  | 	TabPrint(wxNotebook* parent) :  | ||||||
| // 		Tab(parent, _(L("Print Settings")), L("print")) {}
 | // 		Tab(parent, _L("Print Settings"), L("print")) {}
 | ||||||
|         Tab(parent, _(L("Print Settings")), Slic3r::Preset::TYPE_PRINT) {} |         Tab(parent, _L("Print Settings"), Slic3r::Preset::TYPE_PRINT) {} | ||||||
| 	~TabPrint() {} | 	~TabPrint() {} | ||||||
| 
 | 
 | ||||||
| 	void		build() override; | 	void		build() override; | ||||||
|  | @ -387,7 +397,11 @@ public: | ||||||
| 	void		toggle_options() override; | 	void		toggle_options() override; | ||||||
| 	void		update() override; | 	void		update() override; | ||||||
| 	void		clear_pages() override; | 	void		clear_pages() override; | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  | 	bool 		supports_printer_technology(const PrinterTechnology tech) const override { return tech == ptFFF; } | ||||||
|  | #else | ||||||
| 	bool 		supports_printer_technology(const PrinterTechnology tech) override { return tech == ptFFF; } | 	bool 		supports_printer_technology(const PrinterTechnology tech) override { return tech == ptFFF; } | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
| 	ogStaticText*	m_recommended_thin_wall_thickness_description_line = nullptr; | 	ogStaticText*	m_recommended_thin_wall_thickness_description_line = nullptr; | ||||||
|  | @ -407,8 +421,8 @@ private: | ||||||
|     std::map<std::string, wxCheckBox*> m_overrides_options; |     std::map<std::string, wxCheckBox*> m_overrides_options; | ||||||
| public: | public: | ||||||
| 	TabFilament(wxNotebook* parent) :  | 	TabFilament(wxNotebook* parent) :  | ||||||
| // 		Tab(parent, _(L("Filament Settings")), L("filament")) {}
 | // 		Tab(parent, _L("Filament Settings"), L("filament")) {}
 | ||||||
| 		Tab(parent, _(L("Filament Settings")), Slic3r::Preset::TYPE_FILAMENT) {} | 		Tab(parent, _L("Filament Settings"), Slic3r::Preset::TYPE_FILAMENT) {} | ||||||
| 	~TabFilament() {} | 	~TabFilament() {} | ||||||
| 
 | 
 | ||||||
| 	void		build() override; | 	void		build() override; | ||||||
|  | @ -417,7 +431,11 @@ public: | ||||||
| 	void		toggle_options() override; | 	void		toggle_options() override; | ||||||
| 	void		update() override; | 	void		update() override; | ||||||
| 	void		clear_pages() override; | 	void		clear_pages() override; | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  | 	bool 		supports_printer_technology(const PrinterTechnology tech) const override { return tech == ptFFF; } | ||||||
|  | #else | ||||||
| 	bool 		supports_printer_technology(const PrinterTechnology tech) override { return tech == ptFFF; } | 	bool 		supports_printer_technology(const PrinterTechnology tech) override { return tech == ptFFF; } | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| class TabPrinter : public Tab | class TabPrinter : public Tab | ||||||
|  | @ -450,7 +468,7 @@ public: | ||||||
| 
 | 
 | ||||||
| // 	TabPrinter(wxNotebook* parent) : Tab(parent, _(L("Printer Settings")), L("printer")) {}
 | // 	TabPrinter(wxNotebook* parent) : Tab(parent, _(L("Printer Settings")), L("printer")) {}
 | ||||||
|     TabPrinter(wxNotebook* parent) :  |     TabPrinter(wxNotebook* parent) :  | ||||||
|         Tab(parent, _(L("Printer Settings")), Slic3r::Preset::TYPE_PRINTER) {} |         Tab(parent, _L("Printer Settings"), Slic3r::Preset::TYPE_PRINTER) {} | ||||||
| 	~TabPrinter() {} | 	~TabPrinter() {} | ||||||
| 
 | 
 | ||||||
| 	void		build() override; | 	void		build() override; | ||||||
|  | @ -472,7 +490,11 @@ public: | ||||||
| 	void		init_options_list() override; | 	void		init_options_list() override; | ||||||
| 	void		msw_rescale() override; | 	void		msw_rescale() override; | ||||||
| 	void		sys_color_changed() override; | 	void		sys_color_changed() override; | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  | 	bool 		supports_printer_technology(const PrinterTechnology /* tech */) const override { return true; } | ||||||
|  | #else | ||||||
| 	bool 		supports_printer_technology(const PrinterTechnology /* tech */) override { return true; } | 	bool 		supports_printer_technology(const PrinterTechnology /* tech */) override { return true; } | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
| 
 | 
 | ||||||
| 	wxSizer*	create_bed_shape_widget(wxWindow* parent); | 	wxSizer*	create_bed_shape_widget(wxWindow* parent); | ||||||
| 	void		cache_extruder_cnt(); | 	void		cache_extruder_cnt(); | ||||||
|  | @ -483,8 +505,8 @@ class TabSLAMaterial : public Tab | ||||||
| { | { | ||||||
| public: | public: | ||||||
|     TabSLAMaterial(wxNotebook* parent) : |     TabSLAMaterial(wxNotebook* parent) : | ||||||
| // 		Tab(parent, _(L("Material Settings")), L("sla_material")) {}
 | // 		Tab(parent, _L("Material Settings"), L("sla_material")) {}
 | ||||||
| 		Tab(parent, _(L("Material Settings")), Slic3r::Preset::TYPE_SLA_MATERIAL) {} | 		Tab(parent, _L("Material Settings"), Slic3r::Preset::TYPE_SLA_MATERIAL) {} | ||||||
|     ~TabSLAMaterial() {} |     ~TabSLAMaterial() {} | ||||||
| 
 | 
 | ||||||
| 	void		build() override; | 	void		build() override; | ||||||
|  | @ -492,15 +514,19 @@ public: | ||||||
| 	void		toggle_options() override {}; | 	void		toggle_options() override {}; | ||||||
| 	void		update() override; | 	void		update() override; | ||||||
|     void		init_options_list() override; |     void		init_options_list() override; | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  | 	bool 		supports_printer_technology(const PrinterTechnology tech) const override { return tech == ptSLA; } | ||||||
|  | #else | ||||||
| 	bool 		supports_printer_technology(const PrinterTechnology tech) override { return tech == ptSLA; } | 	bool 		supports_printer_technology(const PrinterTechnology tech) override { return tech == ptSLA; } | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| class TabSLAPrint : public Tab | class TabSLAPrint : public Tab | ||||||
| { | { | ||||||
| public: | public: | ||||||
|     TabSLAPrint(wxNotebook* parent) : |     TabSLAPrint(wxNotebook* parent) : | ||||||
| //         Tab(parent, _(L("Print Settings")), L("sla_print")) {}
 | //         Tab(parent, _L("Print Settings"), L("sla_print")) {}
 | ||||||
|         Tab(parent, _(L("Print Settings")), Slic3r::Preset::TYPE_SLA_PRINT) {} |         Tab(parent, _L("Print Settings"), Slic3r::Preset::TYPE_SLA_PRINT) {} | ||||||
|     ~TabSLAPrint() {} |     ~TabSLAPrint() {} | ||||||
| 
 | 
 | ||||||
| 	ogStaticText* m_support_object_elevation_description_line = nullptr; | 	ogStaticText* m_support_object_elevation_description_line = nullptr; | ||||||
|  | @ -511,7 +537,11 @@ public: | ||||||
| 	void		toggle_options() override; | 	void		toggle_options() override; | ||||||
|     void		update() override; |     void		update() override; | ||||||
| 	void		clear_pages() override; | 	void		clear_pages() override; | ||||||
|  | #if ENABLE_PROJECT_DIRTY_STATE | ||||||
|  | 	bool 		supports_printer_technology(const PrinterTechnology tech) const override { return tech == ptSLA; } | ||||||
|  | #else | ||||||
| 	bool 		supports_printer_technology(const PrinterTechnology tech) override { return tech == ptSLA; } | 	bool 		supports_printer_technology(const PrinterTechnology tech) override { return tech == ptSLA; } | ||||||
|  | #endif // ENABLE_PROJECT_DIRTY_STATE
 | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| } // GUI
 | } // GUI
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 enricoturri1966
						enricoturri1966