mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-11-02 20:51:23 -07:00 
			
		
		
		
	Merge branch 'vb_project_state'
This commit is contained in:
		
						commit
						8afaa004b7
					
				
					 18 changed files with 262 additions and 381 deletions
				
			
		| 
						 | 
				
			
			@ -147,6 +147,9 @@ void AppConfig::set_defaults()
 | 
			
		|||
 | 
			
		||||
        if (get("order_volumes").empty())
 | 
			
		||||
            set("order_volumes", "1");
 | 
			
		||||
 | 
			
		||||
        if (get("clear_undo_redo_stack_on_new_project").empty())
 | 
			
		||||
            set("clear_undo_redo_stack_on_new_project", "1");
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
#ifdef _WIN32
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -136,6 +136,7 @@ public:
 | 
			
		|||
    bool is_selectable() const { return on_is_selectable(); }
 | 
			
		||||
    CommonGizmosDataID get_requirements() const { return on_get_requirements(); }
 | 
			
		||||
    virtual bool wants_enter_leave_snapshots() const { return false; }
 | 
			
		||||
    virtual std::string get_action_snapshot_name() { return _u8L("Gizmo action"); }
 | 
			
		||||
    void set_common_data_pool(CommonGizmosDataPool* ptr) { m_c = ptr; }
 | 
			
		||||
 | 
			
		||||
    unsigned int get_sprite_id() const { return m_sprite_id; }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,6 +8,7 @@
 | 
			
		|||
#include "slic3r/GUI/ImGuiWrapper.hpp"
 | 
			
		||||
#include "slic3r/GUI/Plater.hpp"
 | 
			
		||||
#include "slic3r/GUI/GUI_ObjectList.hpp"
 | 
			
		||||
#include "slic3r/Utils/UndoRedo.hpp"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#include <GL/glew.h>
 | 
			
		||||
| 
						 | 
				
			
			@ -165,7 +166,8 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
 | 
			
		|||
    ImGui::Separator();
 | 
			
		||||
 | 
			
		||||
    if (m_imgui->button(m_desc.at("remove_all"))) {
 | 
			
		||||
        Plater::TakeSnapshot snapshot(wxGetApp().plater(), wxString(_L("Reset selection")));
 | 
			
		||||
        Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Reset selection"),
 | 
			
		||||
                                      UndoRedo::SnapshotType::GizmoAction);
 | 
			
		||||
        ModelObject* mo = m_c->selection_info()->model_object();
 | 
			
		||||
        int idx = -1;
 | 
			
		||||
        for (ModelVolume* mv : mo->volumes) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -21,6 +21,8 @@ protected:
 | 
			
		|||
 | 
			
		||||
    std::string get_gizmo_entering_text() const override { return _u8L("Entering Paint-on supports"); }
 | 
			
		||||
    std::string get_gizmo_leaving_text() const override { return _u8L("Leaving Paint-on supports"); }
 | 
			
		||||
    std::string get_action_snapshot_name() override { return _u8L("Paint-on supports editing"); }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    bool on_init() override;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,6 +11,7 @@
 | 
			
		|||
#include "slic3r/GUI/NotificationManager.hpp"
 | 
			
		||||
#include "libslic3r/PresetBundle.hpp"
 | 
			
		||||
#include "libslic3r/Model.hpp"
 | 
			
		||||
#include "slic3r/Utils/UndoRedo.hpp"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#include <GL/glew.h>
 | 
			
		||||
| 
						 | 
				
			
			@ -503,7 +504,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
 | 
			
		|||
 | 
			
		||||
    ImGui::Separator();
 | 
			
		||||
    if (m_imgui->button(m_desc.at("remove_all"))) {
 | 
			
		||||
        Plater::TakeSnapshot snapshot(wxGetApp().plater(), wxString(_L("Reset selection")));
 | 
			
		||||
        Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Reset selection"),
 | 
			
		||||
                                      UndoRedo::SnapshotType::GizmoAction);
 | 
			
		||||
        ModelObject *        mo  = m_c->selection_info()->model_object();
 | 
			
		||||
        int                  idx = -1;
 | 
			
		||||
        for (ModelVolume *mv : mo->volumes)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -130,6 +130,7 @@ protected:
 | 
			
		|||
 | 
			
		||||
    std::string get_gizmo_entering_text() const override { return _u8L("Entering Multimaterial painting"); }
 | 
			
		||||
    std::string get_gizmo_leaving_text() const override { return _u8L("Leaving Multimaterial painting"); }
 | 
			
		||||
    std::string get_action_snapshot_name() override { return _u8L("Multimaterial painting editing"); }
 | 
			
		||||
 | 
			
		||||
    size_t                            m_first_selected_extruder_idx  = 0;
 | 
			
		||||
    size_t                            m_second_selected_extruder_idx = 1;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -421,7 +421,7 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
 | 
			
		|||
      && m_button_down != Button::None) {
 | 
			
		||||
        // Take snapshot and update ModelVolume data.
 | 
			
		||||
        wxString action_name = this->handle_snapshot_action_name(shift_down, m_button_down);
 | 
			
		||||
        Plater::TakeSnapshot snapshot(wxGetApp().plater(), action_name);
 | 
			
		||||
        Plater::TakeSnapshot snapshot(wxGetApp().plater(), action_name, UndoRedo::SnapshotType::GizmoAction);
 | 
			
		||||
        update_model_object();
 | 
			
		||||
 | 
			
		||||
        m_button_down = Button::None;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,6 +8,7 @@
 | 
			
		|||
#include "slic3r/GUI/ImGuiWrapper.hpp"
 | 
			
		||||
#include "slic3r/GUI/Plater.hpp"
 | 
			
		||||
#include "slic3r/GUI/GUI_ObjectList.hpp"
 | 
			
		||||
#include "slic3r/Utils/UndoRedo.hpp"
 | 
			
		||||
 | 
			
		||||
#include <GL/glew.h>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -121,7 +122,8 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit)
 | 
			
		|||
    m_imgui->text("");
 | 
			
		||||
 | 
			
		||||
    if (m_imgui->button(m_desc.at("remove_all"))) {
 | 
			
		||||
        Plater::TakeSnapshot snapshot(wxGetApp().plater(), wxString(_L("Reset selection")));
 | 
			
		||||
        Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Reset selection"),
 | 
			
		||||
                                      UndoRedo::SnapshotType::GizmoAction);
 | 
			
		||||
        ModelObject* mo = m_c->selection_info()->model_object();
 | 
			
		||||
        int idx = -1;
 | 
			
		||||
        for (ModelVolume* mv : mo->volumes) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,6 +22,7 @@ protected:
 | 
			
		|||
 | 
			
		||||
    std::string get_gizmo_entering_text() const override { return _u8L("Entering Seam painting"); }
 | 
			
		||||
    std::string get_gizmo_leaving_text() const override { return _u8L("Leaving Seam painting"); }
 | 
			
		||||
    std::string get_action_snapshot_name() override { return _u8L("Paint-on seam editing"); }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    bool on_init() override;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1220,13 +1220,15 @@ bool GLGizmosManager::activate_gizmo(EType type)
 | 
			
		|||
        if (! m_parent.get_gizmos_manager().is_serializing()
 | 
			
		||||
         && old_gizmo->wants_enter_leave_snapshots())
 | 
			
		||||
            Plater::TakeSnapshot snapshot(wxGetApp().plater(),
 | 
			
		||||
                Slic3r::format(_utf8("Leaving %1%"), old_gizmo->get_name(false)));
 | 
			
		||||
                Slic3r::format(_utf8("Leaving %1%"), old_gizmo->get_name(false)),
 | 
			
		||||
                UndoRedo::SnapshotType::LeavingGizmoWithAction);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (new_gizmo && ! m_parent.get_gizmos_manager().is_serializing()
 | 
			
		||||
     && new_gizmo->wants_enter_leave_snapshots())
 | 
			
		||||
        Plater::TakeSnapshot snapshot(wxGetApp().plater(),
 | 
			
		||||
            Slic3r::format(_utf8("Entering %1%"), new_gizmo->get_name(false)));
 | 
			
		||||
            Slic3r::format(_utf8("Entering %1%"), new_gizmo->get_name(false)),
 | 
			
		||||
            UndoRedo::SnapshotType::EnteringGizmo);
 | 
			
		||||
 | 
			
		||||
    m_current = type;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -620,7 +620,8 @@ void MainFrame::update_title()
 | 
			
		|||
        // m_plater->get_project_filename() produces file name including path, but excluding extension.
 | 
			
		||||
        // Don't try to remove the extension, it would remove part of the file name after the last dot!
 | 
			
		||||
        wxString project = from_path(into_path(m_plater->get_project_filename()).filename());
 | 
			
		||||
        wxString dirty_marker = (!m_plater->model().objects.empty() && m_plater->is_project_dirty()) ? "*" : "";
 | 
			
		||||
//        wxString dirty_marker = (!m_plater->model().objects.empty() && m_plater->is_project_dirty()) ? "*" : "";
 | 
			
		||||
        wxString dirty_marker = m_plater->is_project_dirty() ? "*" : "";
 | 
			
		||||
        if (!dirty_marker.empty() || !project.empty()) {
 | 
			
		||||
            if (!dirty_marker.empty() && project.empty())
 | 
			
		||||
                project = _L("Untitled");
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1595,7 +1595,7 @@ struct Plater::priv
 | 
			
		|||
        }
 | 
			
		||||
        return res;
 | 
			
		||||
    }
 | 
			
		||||
    void reset_project_dirty_after_save() { dirty_state.reset_after_save(); }
 | 
			
		||||
    void reset_project_dirty_after_save() { m_undo_redo_stack_main.mark_current_as_saved(); dirty_state.reset_after_save(); }
 | 
			
		||||
    void reset_project_dirty_initial_presets() { dirty_state.reset_initial_presets(); }
 | 
			
		||||
 | 
			
		||||
#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
 | 
			
		||||
| 
						 | 
				
			
			@ -2064,6 +2064,9 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
 | 
			
		|||
 | 
			
		||||
    // Initialize the Undo / Redo stack with a first snapshot.
 | 
			
		||||
    this->take_snapshot(_L("New Project"), UndoRedo::SnapshotType::ProjectSeparator);
 | 
			
		||||
    // Reset the "dirty project" flag.
 | 
			
		||||
    m_undo_redo_stack_main.mark_current_as_saved();
 | 
			
		||||
    dirty_state.update_from_undo_redo_stack(false);
 | 
			
		||||
 | 
			
		||||
    this->q->Bind(EVT_LOAD_MODEL_OTHER_INSTANCE, [this](LoadFromOtherInstanceEvent& evt) {
 | 
			
		||||
        BOOST_LOG_TRIVIAL(trace) << "Received load from other instance event.";
 | 
			
		||||
| 
						 | 
				
			
			@ -4692,10 +4695,25 @@ void Plater::priv::take_snapshot(const std::string& snapshot_name, const UndoRed
 | 
			
		|||
        model.wipe_tower.position = Vec2d(config.opt_float("wipe_tower_x"), config.opt_float("wipe_tower_y"));
 | 
			
		||||
        model.wipe_tower.rotation = config.opt_float("wipe_tower_rotation_angle");
 | 
			
		||||
    }
 | 
			
		||||
    this->undo_redo_stack().take_snapshot(snapshot_name, model, view3D->get_canvas3d()->get_selection(), view3D->get_canvas3d()->get_gizmos_manager(), snapshot_data);
 | 
			
		||||
    const GLGizmosManager& gizmos = view3D->get_canvas3d()->get_gizmos_manager();
 | 
			
		||||
 | 
			
		||||
    if (snapshot_type == UndoRedo::SnapshotType::ProjectSeparator && wxGetApp().app_config->get("clear_undo_redo_stack_on_new_project") == "1")
 | 
			
		||||
        this->undo_redo_stack().clear();
 | 
			
		||||
    this->undo_redo_stack().take_snapshot(snapshot_name, model, view3D->get_canvas3d()->get_selection(), gizmos, snapshot_data);
 | 
			
		||||
    if (snapshot_type == UndoRedo::SnapshotType::LeavingGizmoWithAction) {
 | 
			
		||||
        // Filter all but the last UndoRedo::SnapshotType::GizmoAction in a row between the last UndoRedo::SnapshotType::EnteringGizmo and UndoRedo::SnapshotType::LeavingGizmoWithAction.
 | 
			
		||||
        // The remaining snapshot will be renamed to a more generic name,
 | 
			
		||||
        // depending on what gizmo is being left.
 | 
			
		||||
        assert(gizmos.get_current() != nullptr);
 | 
			
		||||
        std::string new_name = gizmos.get_current()->get_action_snapshot_name();
 | 
			
		||||
        this->undo_redo_stack().reduce_noisy_snapshots(new_name);
 | 
			
		||||
    } else if (snapshot_type == UndoRedo::SnapshotType::ProjectSeparator) {
 | 
			
		||||
        // Reset the "dirty project" flag.
 | 
			
		||||
        m_undo_redo_stack_main.mark_current_as_saved();
 | 
			
		||||
    }
 | 
			
		||||
    this->undo_redo_stack().release_least_recently_used();
 | 
			
		||||
 | 
			
		||||
    dirty_state.update_from_undo_redo_stack(ProjectDirtyStateManager::UpdateType::TakeSnapshot);
 | 
			
		||||
    dirty_state.update_from_undo_redo_stack(m_undo_redo_stack_main.project_modified());
 | 
			
		||||
 | 
			
		||||
    // Save the last active preset name of a particular printer technology.
 | 
			
		||||
    ((this->printer_technology == ptFFF) ? m_last_fff_printer_profile_name : m_last_sla_printer_profile_name) = wxGetApp().preset_bundle->printers.get_selected_preset_name();
 | 
			
		||||
| 
						 | 
				
			
			@ -4832,7 +4850,7 @@ void Plater::priv::undo_redo_to(std::vector<UndoRedo::Snapshot>::const_iterator
 | 
			
		|||
            view3D->get_canvas3d()->force_main_toolbar_left_action(view3D->get_canvas3d()->get_main_toolbar_item_id("layersediting"));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    dirty_state.update_from_undo_redo_stack(ProjectDirtyStateManager::UpdateType::UndoRedoTo);
 | 
			
		||||
    dirty_state.update_from_undo_redo_stack(m_undo_redo_stack_main.project_modified());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Plater::priv::update_after_undo_redo(const UndoRedo::Snapshot& snapshot, bool /* temp_snapshot_was_taken */)
 | 
			
		||||
| 
						 | 
				
			
			@ -6743,7 +6761,6 @@ bool Plater::can_mirror() const { return p->can_mirror(); }
 | 
			
		|||
bool Plater::can_split(bool to_objects) const { return p->can_split(to_objects); }
 | 
			
		||||
const UndoRedo::Stack& Plater::undo_redo_stack_main() const { return p->undo_redo_stack_main(); }
 | 
			
		||||
void Plater::clear_undo_redo_stack_main() { p->undo_redo_stack_main().clear(); }
 | 
			
		||||
const UndoRedo::Stack& Plater::undo_redo_stack_active() const { return p->undo_redo_stack(); }
 | 
			
		||||
void Plater::enter_gizmos_stack() { p->enter_gizmos_stack(); }
 | 
			
		||||
void Plater::leave_gizmos_stack() { p->leave_gizmos_stack(); }
 | 
			
		||||
bool Plater::inside_snapshot_capture() { return p->inside_snapshot_capture(); }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -254,7 +254,6 @@ public:
 | 
			
		|||
    // For the memory statistics. 
 | 
			
		||||
    const Slic3r::UndoRedo::Stack& undo_redo_stack_main() const;
 | 
			
		||||
    void clear_undo_redo_stack_main();
 | 
			
		||||
    const Slic3r::UndoRedo::Stack& undo_redo_stack_active() const;
 | 
			
		||||
    // Enter / leave the Gizmos specific Undo / Redo stack. To be used by the SLA support point editing gizmo.
 | 
			
		||||
    void enter_gizmos_stack();
 | 
			
		||||
    void leave_gizmos_stack();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -238,6 +238,14 @@ void PreferencesDialog::build(size_t selected_tab)
 | 
			
		|||
	option = Option(def, "show_splash_screen");
 | 
			
		||||
	m_optgroup_general->append_single_option_line(option);
 | 
			
		||||
 | 
			
		||||
    // Clear Undo / Redo stack on new project
 | 
			
		||||
	def.label = L("Clear Undo / Redo stack on new project");
 | 
			
		||||
	def.type = coBool;
 | 
			
		||||
	def.tooltip = L("Clear Undo / Redo stack on new project or when an existing project is loaded.");
 | 
			
		||||
	def.set_default_value(new ConfigOptionBool{ app_config->get("clear_undo_redo_stack_on_new_project") == "1" });
 | 
			
		||||
	option = Option(def, "clear_undo_redo_stack_on_new_project");
 | 
			
		||||
	m_optgroup_general->append_single_option_line(option);
 | 
			
		||||
 | 
			
		||||
#if defined(_WIN32) || defined(__APPLE__)
 | 
			
		||||
	def.label = L("Enable support for legacy 3DConnexion devices");
 | 
			
		||||
	def.type = coBool;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,7 +6,6 @@
 | 
			
		|||
#include "MainFrame.hpp"
 | 
			
		||||
#include "I18N.hpp"
 | 
			
		||||
#include "Plater.hpp"
 | 
			
		||||
#include "../Utils/UndoRedo.hpp"
 | 
			
		||||
 | 
			
		||||
#include <boost/algorithm/string/predicate.hpp>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -16,226 +15,38 @@
 | 
			
		|||
namespace Slic3r {
 | 
			
		||||
namespace GUI {
 | 
			
		||||
 | 
			
		||||
enum class EStackType
 | 
			
		||||
void ProjectDirtyStateManager::update_from_undo_redo_stack(bool dirty)
 | 
			
		||||
{
 | 
			
		||||
    Main,
 | 
			
		||||
    Gizmo
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// returns the current active snapshot (the topmost snapshot in the undo part of the stack) in the given stack
 | 
			
		||||
static const UndoRedo::Snapshot* get_active_snapshot(const UndoRedo::Stack& stack) {
 | 
			
		||||
    const std::vector<UndoRedo::Snapshot>& snapshots = stack.snapshots();
 | 
			
		||||
    const size_t active_snapshot_time = stack.active_snapshot_time();
 | 
			
		||||
    const auto it = std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(active_snapshot_time));
 | 
			
		||||
    const int idx = it - snapshots.begin() - 1;
 | 
			
		||||
    const Slic3r::UndoRedo::Snapshot* ret = (0 <= idx && idx < int(snapshots.size()) - 1) ?
 | 
			
		||||
        &snapshots[idx] : nullptr;
 | 
			
		||||
 | 
			
		||||
    assert(ret != nullptr);
 | 
			
		||||
    return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// returns the last saveable snapshot (the topmost snapshot in the undo part of the stack that can be saved) in the given stack
 | 
			
		||||
static const UndoRedo::Snapshot* get_last_saveable_snapshot(EStackType type, const UndoRedo::Stack& stack,
 | 
			
		||||
    const ProjectDirtyStateManager::DirtyState::Gizmos& gizmos, size_t last_save_main) {
 | 
			
		||||
 | 
			
		||||
    // returns true if the given snapshot is not saveable
 | 
			
		||||
    auto skip_main = [&gizmos, last_save_main, &stack](const UndoRedo::Snapshot& snapshot) {
 | 
			
		||||
        auto is_gizmo_with_modifications = [&gizmos, &stack](const UndoRedo::Snapshot& snapshot) {
 | 
			
		||||
            if (boost::starts_with(snapshot.name, _utf8("Entering"))) {
 | 
			
		||||
                if (gizmos.current)
 | 
			
		||||
                    return true;
 | 
			
		||||
 | 
			
		||||
                std::string topmost_redo;
 | 
			
		||||
                wxGetApp().plater()->undo_redo_topmost_string_getter(false, topmost_redo);
 | 
			
		||||
                if (boost::starts_with(topmost_redo, _utf8("Leaving"))) {
 | 
			
		||||
                    const std::vector<UndoRedo::Snapshot>& snapshots = stack.snapshots();
 | 
			
		||||
                    const UndoRedo::Snapshot* leaving_snapshot = &(*std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(snapshot.timestamp + 1)));
 | 
			
		||||
                    if (gizmos.is_used_and_modified(*leaving_snapshot))
 | 
			
		||||
                        return true;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return false;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        if (snapshot.name == _utf8("New Project"))
 | 
			
		||||
            return true;
 | 
			
		||||
        else if (snapshot.name == _utf8("Reset Project"))
 | 
			
		||||
            return true;
 | 
			
		||||
        else if (boost::starts_with(snapshot.name, _utf8("Load Project")))
 | 
			
		||||
            return true;
 | 
			
		||||
        else if (boost::starts_with(snapshot.name, _utf8("Selection")))
 | 
			
		||||
            return true;
 | 
			
		||||
        else if (boost::starts_with(snapshot.name, _utf8("Entering"))) {
 | 
			
		||||
            if (last_save_main != snapshot.timestamp + 1 && !is_gizmo_with_modifications(snapshot))
 | 
			
		||||
                return true;
 | 
			
		||||
        }
 | 
			
		||||
        else if (boost::starts_with(snapshot.name, _utf8("Leaving"))) {
 | 
			
		||||
            if (last_save_main != snapshot.timestamp && !gizmos.is_used_and_modified(snapshot))
 | 
			
		||||
                return true;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return false;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // returns true if the given snapshot is not saveable
 | 
			
		||||
    auto skip_gizmo = [](const UndoRedo::Snapshot& snapshot) {
 | 
			
		||||
        // put here any needed condition to skip the snapshot
 | 
			
		||||
        return false;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const UndoRedo::Snapshot* curr = get_active_snapshot(stack);
 | 
			
		||||
    const std::vector<UndoRedo::Snapshot>& snapshots = stack.snapshots();
 | 
			
		||||
    size_t shift = 1;
 | 
			
		||||
    while (curr->timestamp > 0 && ((type == EStackType::Main && skip_main(*curr)) || (type == EStackType::Gizmo && skip_gizmo(*curr)))) {
 | 
			
		||||
        const UndoRedo::Snapshot* temp = curr;
 | 
			
		||||
        curr = &(*std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(curr->timestamp - shift)));
 | 
			
		||||
        shift = (curr == temp) ? shift + 1 : 1;
 | 
			
		||||
    }
 | 
			
		||||
    if (type == EStackType::Main) {
 | 
			
		||||
        if (boost::starts_with(curr->name, _utf8("Entering")) && last_save_main == curr->timestamp + 1) {
 | 
			
		||||
            std::string topmost_redo;
 | 
			
		||||
            wxGetApp().plater()->undo_redo_topmost_string_getter(false, topmost_redo);
 | 
			
		||||
            if (boost::starts_with(topmost_redo, _utf8("Leaving")))
 | 
			
		||||
                curr = &(*std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(curr->timestamp + 1)));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return curr->timestamp > 0 ? curr : nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// returns the name of the gizmo contained in the given string
 | 
			
		||||
static std::string extract_gizmo_name(const std::string& s) {
 | 
			
		||||
    static const std::array<std::string, 2> prefixes = { _utf8("Entering"), _utf8("Leaving") };
 | 
			
		||||
 | 
			
		||||
    std::string ret;
 | 
			
		||||
    for (const std::string& prefix : prefixes) {
 | 
			
		||||
        if (boost::starts_with(s, prefix))
 | 
			
		||||
            ret = s.substr(prefix.length() + 1);
 | 
			
		||||
 | 
			
		||||
        if (!ret.empty())
 | 
			
		||||
            break;
 | 
			
		||||
    }
 | 
			
		||||
    return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ProjectDirtyStateManager::DirtyState::Gizmos::add_used(const UndoRedo::Snapshot& snapshot)
 | 
			
		||||
{
 | 
			
		||||
    const std::string name = extract_gizmo_name(snapshot.name);
 | 
			
		||||
    auto it = used.find(name);
 | 
			
		||||
    if (it == used.end())
 | 
			
		||||
        it = used.insert({ name, { {} } }).first;
 | 
			
		||||
 | 
			
		||||
    it->second.modified_timestamps.push_back(snapshot.timestamp);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ProjectDirtyStateManager::DirtyState::Gizmos::remove_obsolete_used(const Slic3r::UndoRedo::Stack& main_stack)
 | 
			
		||||
{
 | 
			
		||||
    const std::vector<UndoRedo::Snapshot>& snapshots = main_stack.snapshots();
 | 
			
		||||
    for (auto& item : used) {
 | 
			
		||||
        auto it = item.second.modified_timestamps.begin();
 | 
			
		||||
        while (it != item.second.modified_timestamps.end()) {
 | 
			
		||||
            size_t timestamp = *it;
 | 
			
		||||
            auto snapshot_it = std::find_if(snapshots.begin(), snapshots.end(), [timestamp](const Slic3r::UndoRedo::Snapshot& snapshot) { return snapshot.timestamp == timestamp; });
 | 
			
		||||
            if (snapshot_it == snapshots.end())
 | 
			
		||||
                it = item.second.modified_timestamps.erase(it);
 | 
			
		||||
            else
 | 
			
		||||
                ++it;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
 | 
			
		||||
bool ProjectDirtyStateManager::DirtyState::Gizmos::any_used_modified() const
 | 
			
		||||
{
 | 
			
		||||
    for (auto& [name, gizmo] : used) {
 | 
			
		||||
        if (!gizmo.modified_timestamps.empty())
 | 
			
		||||
            return true;
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
#endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
 | 
			
		||||
 | 
			
		||||
// returns true if the given snapshot is contained in any of the gizmos caches
 | 
			
		||||
bool ProjectDirtyStateManager::DirtyState::Gizmos::is_used_and_modified(const UndoRedo::Snapshot& snapshot) const
 | 
			
		||||
{
 | 
			
		||||
    for (const auto& item : used) {
 | 
			
		||||
        for (size_t i : item.second.modified_timestamps) {
 | 
			
		||||
            if (i == snapshot.timestamp)
 | 
			
		||||
                return true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ProjectDirtyStateManager::DirtyState::Gizmos::reset()
 | 
			
		||||
{
 | 
			
		||||
    used.clear();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ProjectDirtyStateManager::update_from_undo_redo_stack(UpdateType type)
 | 
			
		||||
{
 | 
			
		||||
    if (!wxGetApp().initialized())
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    const Plater* plater = wxGetApp().plater();
 | 
			
		||||
    if (plater == nullptr)
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    const UndoRedo::Stack& main_stack = plater->undo_redo_stack_main();
 | 
			
		||||
    const UndoRedo::Stack& active_stack = plater->undo_redo_stack_active();
 | 
			
		||||
 | 
			
		||||
    if (&main_stack == &active_stack)
 | 
			
		||||
        update_from_undo_redo_main_stack(type, main_stack);
 | 
			
		||||
    else
 | 
			
		||||
        update_from_undo_redo_gizmo_stack(type, active_stack);
 | 
			
		||||
 | 
			
		||||
    wxGetApp().mainframe->update_title();
 | 
			
		||||
    m_plater_dirty = dirty;
 | 
			
		||||
    if (const Plater *plater = wxGetApp().plater(); plater && wxGetApp().initialized())
 | 
			
		||||
        wxGetApp().mainframe->update_title();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ProjectDirtyStateManager::update_from_presets()
 | 
			
		||||
{
 | 
			
		||||
    m_state.presets = false;
 | 
			
		||||
    m_presets_dirty = false;
 | 
			
		||||
    // check switching of the presets only for exist/loaded project, but not for new
 | 
			
		||||
    if (!wxGetApp().plater()->get_project_filename().IsEmpty()) {
 | 
			
		||||
        for (const auto& [type, name] : wxGetApp().get_selected_presets())
 | 
			
		||||
            m_state.presets |= !m_initial_presets[type].empty() && m_initial_presets[type] != name;
 | 
			
		||||
            m_presets_dirty |= !m_initial_presets[type].empty() && m_initial_presets[type] != name;
 | 
			
		||||
    }
 | 
			
		||||
    m_state.presets |= wxGetApp().has_unsaved_preset_changes();
 | 
			
		||||
    m_presets_dirty |= wxGetApp().has_unsaved_preset_changes();
 | 
			
		||||
    wxGetApp().mainframe->update_title();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ProjectDirtyStateManager::reset_after_save()
 | 
			
		||||
{
 | 
			
		||||
    const Plater* plater = wxGetApp().plater();
 | 
			
		||||
    const UndoRedo::Stack& main_stack = plater->undo_redo_stack_main();
 | 
			
		||||
    const UndoRedo::Stack& active_stack = plater->undo_redo_stack_active();
 | 
			
		||||
 | 
			
		||||
    if (&main_stack == &active_stack) {
 | 
			
		||||
        const UndoRedo::Snapshot* saveable_snapshot = get_last_saveable_snapshot(EStackType::Main, main_stack, m_state.gizmos, m_last_save.main);
 | 
			
		||||
        m_last_save.main = (saveable_snapshot != nullptr) ? saveable_snapshot->timestamp : 0;
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        // Gizmo is active with its own Undo / Redo stack (for example the SLA support point editing gizmo).
 | 
			
		||||
        const UndoRedo::Snapshot* main_active_snapshot = get_active_snapshot(main_stack);
 | 
			
		||||
        if (boost::starts_with(main_active_snapshot->name, _utf8("Entering"))) {
 | 
			
		||||
            if (m_state.gizmos.current)
 | 
			
		||||
                m_last_save.main = main_active_snapshot->timestamp + 1;
 | 
			
		||||
        }
 | 
			
		||||
        const UndoRedo::Snapshot* saveable_snapshot = get_last_saveable_snapshot(EStackType::Gizmo, active_stack, m_state.gizmos, m_last_save.main);
 | 
			
		||||
        m_last_save.gizmo = (saveable_snapshot != nullptr) ? saveable_snapshot->timestamp : 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    reset_initial_presets();
 | 
			
		||||
    m_state.reset();
 | 
			
		||||
    this->reset_initial_presets();
 | 
			
		||||
    m_plater_dirty  = false;
 | 
			
		||||
    m_presets_dirty = false;
 | 
			
		||||
    wxGetApp().mainframe->update_title();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ProjectDirtyStateManager::reset_initial_presets()
 | 
			
		||||
{
 | 
			
		||||
    m_initial_presets = std::array<std::string, Preset::TYPE_COUNT>();
 | 
			
		||||
    for (const auto& [type, name] : wxGetApp().get_selected_presets()) {
 | 
			
		||||
    m_initial_presets.fill(std::string{});
 | 
			
		||||
    for (const auto& [type, name] : wxGetApp().get_selected_presets())
 | 
			
		||||
        m_initial_presets[type] = name;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
 | 
			
		||||
| 
						 | 
				
			
			@ -324,89 +135,10 @@ void ProjectDirtyStateManager::render_debug_window() const
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (m_state.gizmos.any_used_modified()) {
 | 
			
		||||
        if (ImGui::CollapsingHeader("Gizmos", ImGuiTreeNodeFlags_DefaultOpen)) {
 | 
			
		||||
            ImGui::Indent(10.0f);
 | 
			
		||||
            for (const auto& [name, gizmo] : m_state.gizmos.used) {
 | 
			
		||||
                if (!gizmo.modified_timestamps.empty()) {
 | 
			
		||||
                    if (ImGui::CollapsingHeader(name.c_str(), ImGuiTreeNodeFlags_DefaultOpen)) {
 | 
			
		||||
                        std::string modified_timestamps;
 | 
			
		||||
                        for (size_t i = 0; i < gizmo.modified_timestamps.size(); ++i) {
 | 
			
		||||
                            if (i > 0)
 | 
			
		||||
                                modified_timestamps += " | ";
 | 
			
		||||
                            modified_timestamps += std::to_string(gizmo.modified_timestamps[i]);
 | 
			
		||||
                        }
 | 
			
		||||
                        imgui.text(modified_timestamps);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            ImGui::Unindent(10.0f);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    imgui.end();
 | 
			
		||||
}
 | 
			
		||||
#endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
 | 
			
		||||
 | 
			
		||||
void ProjectDirtyStateManager::update_from_undo_redo_main_stack(UpdateType type, const Slic3r::UndoRedo::Stack& stack)
 | 
			
		||||
{
 | 
			
		||||
    m_state.plater = false;
 | 
			
		||||
 | 
			
		||||
    if (type == UpdateType::TakeSnapshot) {
 | 
			
		||||
        if (m_last_save.main != 0) {
 | 
			
		||||
            const std::vector<UndoRedo::Snapshot>& snapshots = stack.snapshots();
 | 
			
		||||
            auto snapshot_it = std::find_if(snapshots.begin(), snapshots.end(), [this](const Slic3r::UndoRedo::Snapshot& snapshot) { return snapshot.timestamp == m_last_save.main; });
 | 
			
		||||
            if (snapshot_it == snapshots.end())
 | 
			
		||||
                m_last_save.main = 0;
 | 
			
		||||
        }
 | 
			
		||||
        m_state.gizmos.remove_obsolete_used(stack);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const UndoRedo::Snapshot* active_snapshot = get_active_snapshot(stack);
 | 
			
		||||
    if (active_snapshot->name == _utf8("New Project") ||
 | 
			
		||||
        active_snapshot->name == _utf8("Reset Project") ||
 | 
			
		||||
        boost::starts_with(active_snapshot->name, _utf8("Load Project")))
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    if (boost::starts_with(active_snapshot->name, _utf8("Entering"))) {
 | 
			
		||||
        if (type == UpdateType::UndoRedoTo) {
 | 
			
		||||
            std::string topmost_redo;
 | 
			
		||||
            wxGetApp().plater()->undo_redo_topmost_string_getter(false, topmost_redo);
 | 
			
		||||
            if (boost::starts_with(topmost_redo, _utf8("Leaving"))) {
 | 
			
		||||
                const std::vector<UndoRedo::Snapshot>& snapshots = stack.snapshots();
 | 
			
		||||
                const UndoRedo::Snapshot* leaving_snapshot = &(*std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(active_snapshot->timestamp + 1)));
 | 
			
		||||
                if (m_state.gizmos.is_used_and_modified(*leaving_snapshot)) {
 | 
			
		||||
                    m_state.plater = (leaving_snapshot != nullptr && leaving_snapshot->timestamp != m_last_save.main);
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        m_state.gizmos.current = false;
 | 
			
		||||
        m_last_save.gizmo = 0;
 | 
			
		||||
    }
 | 
			
		||||
    else if (boost::starts_with(active_snapshot->name, _utf8("Leaving"))) {
 | 
			
		||||
        if (m_state.gizmos.current)
 | 
			
		||||
            m_state.gizmos.add_used(*active_snapshot);
 | 
			
		||||
        m_state.gizmos.current = false;
 | 
			
		||||
        m_last_save.gizmo = 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const UndoRedo::Snapshot* last_saveable_snapshot = get_last_saveable_snapshot(EStackType::Main, stack, m_state.gizmos, m_last_save.main);
 | 
			
		||||
    m_state.plater = (last_saveable_snapshot != nullptr && last_saveable_snapshot->timestamp != m_last_save.main);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ProjectDirtyStateManager::update_from_undo_redo_gizmo_stack(UpdateType type, const Slic3r::UndoRedo::Stack& stack)
 | 
			
		||||
{
 | 
			
		||||
    m_state.gizmos.current = false;
 | 
			
		||||
 | 
			
		||||
    const UndoRedo::Snapshot* active_snapshot = get_active_snapshot(stack);
 | 
			
		||||
    if (active_snapshot->name == "Gizmos-Initial")
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    const UndoRedo::Snapshot* last_saveable_snapshot = get_last_saveable_snapshot(EStackType::Gizmo, stack, m_state.gizmos, m_last_save.main);
 | 
			
		||||
    m_state.gizmos.current = (last_saveable_snapshot != nullptr && last_saveable_snapshot->timestamp != m_last_save.gizmo);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace GUI
 | 
			
		||||
} // namespace Slic3r
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,89 +4,32 @@
 | 
			
		|||
#include "libslic3r/Preset.hpp"
 | 
			
		||||
 | 
			
		||||
namespace Slic3r {
 | 
			
		||||
namespace UndoRedo {
 | 
			
		||||
class Stack;
 | 
			
		||||
struct Snapshot;
 | 
			
		||||
} // namespace UndoRedo
 | 
			
		||||
 | 
			
		||||
namespace GUI {
 | 
			
		||||
 | 
			
		||||
class ProjectDirtyStateManager
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
    enum class UpdateType : unsigned char
 | 
			
		||||
    {
 | 
			
		||||
        TakeSnapshot,
 | 
			
		||||
        UndoRedoTo
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    struct DirtyState
 | 
			
		||||
    {
 | 
			
		||||
        struct Gizmos
 | 
			
		||||
        {
 | 
			
		||||
            struct Gizmo
 | 
			
		||||
            {
 | 
			
		||||
                std::vector<size_t> modified_timestamps;
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            bool current{ false };
 | 
			
		||||
            std::map<std::string, Gizmo> used;
 | 
			
		||||
 | 
			
		||||
            void add_used(const UndoRedo::Snapshot& snapshot);
 | 
			
		||||
            void remove_obsolete_used(const Slic3r::UndoRedo::Stack& main_stack);
 | 
			
		||||
#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
 | 
			
		||||
            bool any_used_modified() const;
 | 
			
		||||
#endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
 | 
			
		||||
            bool is_used_and_modified(const UndoRedo::Snapshot& snapshot) const;
 | 
			
		||||
            void reset();
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        bool plater{ false };
 | 
			
		||||
        bool presets{ false };
 | 
			
		||||
        Gizmos gizmos;
 | 
			
		||||
 | 
			
		||||
        bool is_dirty() const { return plater || presets || gizmos.current; }
 | 
			
		||||
        void reset() {
 | 
			
		||||
            plater = false;
 | 
			
		||||
            presets = false;
 | 
			
		||||
            gizmos.current = false;
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    struct LastSaveTimestamps
 | 
			
		||||
    {
 | 
			
		||||
        size_t main{ 0 };
 | 
			
		||||
        size_t gizmo{ 0 };
 | 
			
		||||
 | 
			
		||||
        void reset() {
 | 
			
		||||
            main = 0;
 | 
			
		||||
            gizmo = 0;
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    DirtyState m_state;
 | 
			
		||||
    LastSaveTimestamps m_last_save;
 | 
			
		||||
 | 
			
		||||
    // keeps track of initial selected presets
 | 
			
		||||
    std::array<std::string, Preset::TYPE_COUNT> m_initial_presets;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    bool is_dirty() const { return m_state.is_dirty(); }
 | 
			
		||||
    void update_from_undo_redo_stack(UpdateType type);
 | 
			
		||||
public:    
 | 
			
		||||
    void update_from_undo_redo_stack(bool dirty);
 | 
			
		||||
    void update_from_presets();
 | 
			
		||||
    void reset_after_save();
 | 
			
		||||
    void reset_initial_presets();
 | 
			
		||||
 | 
			
		||||
    bool is_dirty() const { return m_plater_dirty || m_presets_dirty; }
 | 
			
		||||
 | 
			
		||||
#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
 | 
			
		||||
    void render_debug_window() const;
 | 
			
		||||
#endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    void update_from_undo_redo_main_stack(UpdateType type, const Slic3r::UndoRedo::Stack& stack);
 | 
			
		||||
    void update_from_undo_redo_gizmo_stack(UpdateType type, const Slic3r::UndoRedo::Stack& stack);
 | 
			
		||||
    // Does the Undo / Redo stack indicate the project is dirty?
 | 
			
		||||
    bool                                        m_plater_dirty { false };
 | 
			
		||||
    // Do the presets indicate the project is dirty?
 | 
			
		||||
    bool                                        m_presets_dirty { false };
 | 
			
		||||
    // Keeps track of preset names selected at the time of last project save.
 | 
			
		||||
    std::array<std::string, Preset::TYPE_COUNT> m_initial_presets;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} // namespace GUI
 | 
			
		||||
} // namespace Slic3r
 | 
			
		||||
 | 
			
		||||
#endif // slic3r_ProjectDirtyStateManager_hpp_
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -45,10 +45,6 @@ static inline std::string ptr_to_string(const void* ptr)
 | 
			
		|||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
SnapshotData::SnapshotData() : printer_technology(ptUnknown), flags(0), layer_range_idx(-1)
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static std::string topmost_snapshot_name = "@@@ Topmost @@@";
 | 
			
		||||
 | 
			
		||||
bool Snapshot::is_topmost() const
 | 
			
		||||
| 
						 | 
				
			
			@ -76,6 +72,7 @@ public:
 | 
			
		|||
 | 
			
		||||
	void 	trim_begin(size_t new_begin)  { m_begin = std::max(m_begin, new_begin); }
 | 
			
		||||
	void    trim_end(size_t new_end) { m_end = std::min(m_end, new_end); }
 | 
			
		||||
	void 	extend_begin(size_t new_begin) { assert(new_begin <= m_begin); m_begin = new_begin; }
 | 
			
		||||
	void 	extend_end(size_t new_end) { assert(new_end >= m_end); m_end = new_end; }
 | 
			
		||||
 | 
			
		||||
	size_t 	memsize() const { return sizeof(this); }
 | 
			
		||||
| 
						 | 
				
			
			@ -108,6 +105,10 @@ public:
 | 
			
		|||
	// Release all data after the given timestamp. For the ImmutableObjectHistory, the shared pointer is NOT released.
 | 
			
		||||
	// Return the amount of memory released.
 | 
			
		||||
	virtual size_t release_after_timestamp(size_t timestamp) = 0;
 | 
			
		||||
	// Release all data between the two timestamps. For the ImmutableObjectHistory, the shared pointer is NOT released.
 | 
			
		||||
	// Used for reducing the number of snapshots for noisy operations like the support point edits.
 | 
			
		||||
	// Return the amount of memory released.
 | 
			
		||||
	virtual size_t release_between_timestamps(size_t timestamp_start, size_t timestamp_end) = 0;
 | 
			
		||||
	// Release all optional data of this history.
 | 
			
		||||
	virtual size_t release_optional() = 0;
 | 
			
		||||
	// Restore optional data possibly released by release_optional.
 | 
			
		||||
| 
						 | 
				
			
			@ -184,6 +185,34 @@ public:
 | 
			
		|||
		return mem_released;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Release all data between the two timestamps. For the ImmutableObjectHistory, the shared pointer is NOT released.
 | 
			
		||||
	// Used for reducing the number of snapshots for noisy operations like the support point edits.
 | 
			
		||||
	// Return the amount of memory released.
 | 
			
		||||
	size_t release_between_timestamps(size_t timestamp_start, size_t timestamp_end) override {
 | 
			
		||||
		size_t mem_released = 0;
 | 
			
		||||
		if (! m_history.empty()) {
 | 
			
		||||
			assert(this->valid());
 | 
			
		||||
			// Find the span of m_history intervals that are fully in (timestamp_start, timestamp_end>, thus they will be never
 | 
			
		||||
			// deserialized for any snapshot in <timestamp_start, timestamp_end).
 | 
			
		||||
			auto it_lo = std::upper_bound(m_history.begin(), m_history.end(), timestamp_start, [](size_t l, const auto &r) { return l < r.begin(); });
 | 
			
		||||
			auto it_hi = std::upper_bound(it_lo, m_history.end(), timestamp_end, [](size_t l, const auto &r) { return l < r.end(); });
 | 
			
		||||
			if (it_lo != it_hi) {
 | 
			
		||||
				// There are some intervals that start inside (timestamp_start, timestamp_end), that could be released.
 | 
			
		||||
				assert(it_lo->begin() > timestamp_start && it_lo->end() <= timestamp_end);
 | 
			
		||||
				assert(it_hi == m_history.end() || (it_hi->begin() > it_lo->begin() && it_hi->end() > timestamp_end));
 | 
			
		||||
				if (it_lo != m_history.begin() && it_hi != m_history.end()) {
 | 
			
		||||
					// One may consider merging the two intervals.
 | 
			
		||||
					//FIXME merge them.
 | 
			
		||||
				}
 | 
			
		||||
				for (auto it = it_lo; it != it_hi; ++ it)
 | 
			
		||||
					mem_released += it->memsize();
 | 
			
		||||
				m_history.erase(it_lo, it_hi);
 | 
			
		||||
			}
 | 
			
		||||
			assert(this->valid());
 | 
			
		||||
		}
 | 
			
		||||
		return mem_released;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
protected:
 | 
			
		||||
	std::vector<T>	m_history;
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -352,9 +381,10 @@ public:
 | 
			
		|||
	const Interval& interval() const { return m_interval; }
 | 
			
		||||
	size_t		begin() const { return m_interval.begin(); }
 | 
			
		||||
	size_t		end()   const { return m_interval.end(); }
 | 
			
		||||
	void 		trim_begin(size_t timestamp) { m_interval.trim_begin(timestamp); }
 | 
			
		||||
	void 		trim_end  (size_t timestamp) { m_interval.trim_end(timestamp); }
 | 
			
		||||
	void 		extend_end(size_t timestamp) { m_interval.extend_end(timestamp); }
 | 
			
		||||
	void 		trim_begin  (size_t timestamp) { m_interval.trim_begin(timestamp); }
 | 
			
		||||
	void 		trim_end    (size_t timestamp) { m_interval.trim_end(timestamp); }
 | 
			
		||||
	void 		extend_begin(size_t timestamp) { m_interval.extend_begin(timestamp); }
 | 
			
		||||
	void 		extend_end  (size_t timestamp) { m_interval.extend_end(timestamp); }
 | 
			
		||||
 | 
			
		||||
	bool		operator<(const MutableHistoryInterval& rhs) const { return m_interval < rhs.m_interval; }
 | 
			
		||||
	bool 		operator==(const MutableHistoryInterval& rhs) const { return m_interval == rhs.m_interval; }
 | 
			
		||||
| 
						 | 
				
			
			@ -524,6 +554,7 @@ public:
 | 
			
		|||
		m_snapshots.clear();
 | 
			
		||||
		m_active_snapshot_time = 0;
 | 
			
		||||
		m_current_time = 0;
 | 
			
		||||
		m_saved_snapshot_time = size_t(-1);
 | 
			
		||||
		m_selection.clear();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -545,6 +576,7 @@ public:
 | 
			
		|||
 | 
			
		||||
    // Store the current application state onto the Undo / Redo stack, remove all snapshots after m_active_snapshot_time.
 | 
			
		||||
    void take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos, const SnapshotData &snapshot_data);
 | 
			
		||||
    void reduce_noisy_snapshots(const std::string& new_name);
 | 
			
		||||
    void load_snapshot(size_t timestamp, Slic3r::Model& model, Slic3r::GUI::GLGizmosManager& gizmos);
 | 
			
		||||
 | 
			
		||||
	bool has_undo_snapshot() const;
 | 
			
		||||
| 
						 | 
				
			
			@ -566,6 +598,10 @@ public:
 | 
			
		|||
	size_t 							active_snapshot_time() const { return m_active_snapshot_time; }
 | 
			
		||||
	bool 							temp_snapshot_active() const { return m_snapshots.back().timestamp == m_active_snapshot_time && ! m_snapshots.back().is_topmost_captured(); }
 | 
			
		||||
 | 
			
		||||
	// Resets the "dirty project" status.
 | 
			
		||||
    void 							mark_current_as_saved() { m_saved_snapshot_time = m_active_snapshot_time; }
 | 
			
		||||
    bool 							project_modified() const;
 | 
			
		||||
 | 
			
		||||
	const Selection& 				selection_deserialized() const { return m_selection; }
 | 
			
		||||
 | 
			
		||||
//protected:
 | 
			
		||||
| 
						 | 
				
			
			@ -628,6 +664,10 @@ private:
 | 
			
		|||
	}
 | 
			
		||||
	void 							collect_garbage();
 | 
			
		||||
 | 
			
		||||
	// Release snapshots between begin and end. Only erases data from m_snapshots, not from m_objects!
 | 
			
		||||
	// Updates m_saved_snapshot_time.
 | 
			
		||||
	std::vector<Snapshot>::iterator release_snapshots(std::vector<Snapshot>::iterator begin, std::vector<Snapshot>::iterator end);
 | 
			
		||||
 | 
			
		||||
	// Maximum memory allowed to be occupied by the Undo / Redo stack. If the limit is exceeded,
 | 
			
		||||
	// least recently used snapshots will be released.
 | 
			
		||||
	size_t 													m_memory_limit;
 | 
			
		||||
| 
						 | 
				
			
			@ -640,6 +680,9 @@ private:
 | 
			
		|||
	std::vector<Snapshot>									m_snapshots;
 | 
			
		||||
	// Timestamp of the active snapshot.
 | 
			
		||||
	size_t 													m_active_snapshot_time;
 | 
			
		||||
	// Time at which the project state was saved into a project file.
 | 
			
		||||
	// If set to zero, the time is not known, thus the project state should be considered unsaved.
 | 
			
		||||
	size_t 													m_saved_snapshot_time { size_t(-1) };
 | 
			
		||||
	// Logical time counter. m_current_time is being incremented with each snapshot taken.
 | 
			
		||||
	size_t 													m_current_time;
 | 
			
		||||
	// Last selection serialized or deserialized.
 | 
			
		||||
| 
						 | 
				
			
			@ -857,9 +900,12 @@ void StackImpl::take_snapshot(const std::string& snapshot_name, const Slic3r::Mo
 | 
			
		|||
	assert(m_active_snapshot_time <= m_current_time);
 | 
			
		||||
	for (auto &kvp : m_objects)
 | 
			
		||||
		kvp.second->release_after_timestamp(m_active_snapshot_time);
 | 
			
		||||
	{
 | 
			
		||||
	bool topmost_saved = false;
 | 
			
		||||
	if (! m_snapshots.empty()) {
 | 
			
		||||
		// If the project was saved for the topmost snapshot, restore the "saved" state after the "topmost" snapshot is taken.
 | 
			
		||||
		topmost_saved = m_active_snapshot_time == m_saved_snapshot_time && m_active_snapshot_time == m_snapshots.back().timestamp;
 | 
			
		||||
		auto it = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time));
 | 
			
		||||
		m_snapshots.erase(it, m_snapshots.end());
 | 
			
		||||
		this->release_snapshots(it, m_snapshots.end());
 | 
			
		||||
	}
 | 
			
		||||
	// Take new snapshots.
 | 
			
		||||
	this->save_mutable_object<Slic3r::Model>(model);
 | 
			
		||||
| 
						 | 
				
			
			@ -871,8 +917,11 @@ void StackImpl::take_snapshot(const std::string& snapshot_name, const Slic3r::Mo
 | 
			
		|||
	this->save_mutable_object<Selection>(m_selection);
 | 
			
		||||
    this->save_mutable_object<Slic3r::GUI::GLGizmosManager>(gizmos);
 | 
			
		||||
    // Save the snapshot info.
 | 
			
		||||
	m_snapshots.emplace_back(snapshot_name, m_current_time ++, model.id().id, snapshot_data);
 | 
			
		||||
	m_active_snapshot_time = m_current_time;
 | 
			
		||||
	m_snapshots.emplace_back(snapshot_name, m_current_time, model.id().id, snapshot_data);
 | 
			
		||||
	if (topmost_saved)
 | 
			
		||||
		// Restore the "saved" timestamp.
 | 
			
		||||
		m_saved_snapshot_time = m_current_time;
 | 
			
		||||
	m_active_snapshot_time = ++ m_current_time;
 | 
			
		||||
	// Save snapshot info of the last "current" aka "top most" state, that is only being serialized
 | 
			
		||||
	// if undoing an action. Such a snapshot has an invalid Model ID assigned if it was not taken yet.
 | 
			
		||||
	m_snapshots.emplace_back(topmost_snapshot_name, m_active_snapshot_time, 0, snapshot_data);
 | 
			
		||||
| 
						 | 
				
			
			@ -885,6 +934,32 @@ void StackImpl::take_snapshot(const std::string& snapshot_name, const Slic3r::Mo
 | 
			
		|||
#endif /* SLIC3R_UNDOREDO_DEBUG */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void StackImpl::reduce_noisy_snapshots(const std::string& new_name)
 | 
			
		||||
{
 | 
			
		||||
	// Preceding snapshot must be a "leave gizmo" snapshot.
 | 
			
		||||
	assert(! m_snapshots.empty() && m_snapshots.back().is_topmost() && m_snapshots.back().timestamp == m_active_snapshot_time);
 | 
			
		||||
	auto it_last = m_snapshots.end();
 | 
			
		||||
	-- it_last; -- it_last;
 | 
			
		||||
	assert(it_last != m_snapshots.begin() && (it_last->snapshot_data.snapshot_type == SnapshotType::LeavingGizmoNoAction || it_last->snapshot_data.snapshot_type == SnapshotType::LeavingGizmoWithAction));
 | 
			
		||||
	if (it_last->snapshot_data.snapshot_type == SnapshotType::LeavingGizmoWithAction) {
 | 
			
		||||
		for (-- it_last; it_last->snapshot_data.snapshot_type != SnapshotType::EnteringGizmo; -- it_last) {
 | 
			
		||||
			if (it_last->snapshot_data.snapshot_type == SnapshotType::GizmoAction) {
 | 
			
		||||
                it_last->name = new_name;
 | 
			
		||||
                auto it = it_last;
 | 
			
		||||
				for (-- it; it->snapshot_data.snapshot_type == SnapshotType::GizmoAction; -- it) ;
 | 
			
		||||
				if (++ it < it_last) {
 | 
			
		||||
					// Drop (it, it_last>
 | 
			
		||||
					for (auto &kvp : m_objects)
 | 
			
		||||
						// Drop products of <it + 1, it_last + 1>
 | 
			
		||||
						kvp.second->release_between_timestamps(it->timestamp, (it_last + 1)->timestamp);
 | 
			
		||||
					it_last = this->release_snapshots(it + 1, it_last + 1);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			assert(it_last != m_snapshots.begin());
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void StackImpl::load_snapshot(size_t timestamp, Slic3r::Model& model, Slic3r::GUI::GLGizmosManager& gizmos)
 | 
			
		||||
{
 | 
			
		||||
	// Find the snapshot by time. It must exist.
 | 
			
		||||
| 
						 | 
				
			
			@ -940,6 +1015,7 @@ bool StackImpl::undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selecti
 | 
			
		|||
	bool new_snapshot_taken = false;
 | 
			
		||||
	if (m_active_snapshot_time == m_snapshots.back().timestamp && ! m_snapshots.back().is_topmost_captured()) {
 | 
			
		||||
		// The current state is temporary. The current state needs to be captured to be redoable.
 | 
			
		||||
		//FIXME add new a "topmost" SnapshotType? 
 | 
			
		||||
        this->take_snapshot(topmost_snapshot_name, model, selection, gizmos, snapshot_data);
 | 
			
		||||
        // The line above entered another topmost_snapshot_name.
 | 
			
		||||
		assert(m_snapshots.back().is_topmost());
 | 
			
		||||
| 
						 | 
				
			
			@ -985,6 +1061,46 @@ bool StackImpl::redo(Slic3r::Model& model, Slic3r::GUI::GLGizmosManager& gizmos,
 | 
			
		|||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// If a snapshot modifies the snapshot type, 
 | 
			
		||||
static inline bool snapshot_modifies_project(SnapshotType type)
 | 
			
		||||
{
 | 
			
		||||
	return type == SnapshotType::Action || type == SnapshotType::GizmoAction || type == SnapshotType::ProjectSeparator;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static inline bool snapshot_modifies_project(const Snapshot &snapshot)
 | 
			
		||||
{
 | 
			
		||||
	return snapshot_modifies_project(snapshot.snapshot_data.snapshot_type);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Release snapshots between begin and end. Only erases data from m_snapshots, not from m_objects!
 | 
			
		||||
// Updates m_saved_snapshot_time.
 | 
			
		||||
std::vector<Snapshot>::iterator StackImpl::release_snapshots(std::vector<Snapshot>::iterator begin, std::vector<Snapshot>::iterator end)
 | 
			
		||||
{
 | 
			
		||||
	assert(! m_snapshots.empty());
 | 
			
		||||
	assert(begin <= end);
 | 
			
		||||
	if (m_saved_snapshot_time >= begin->timestamp && (end == m_snapshots.end() || m_saved_snapshot_time < end->timestamp)) {
 | 
			
		||||
		assert(m_saved_snapshot_time <= m_snapshots.back().timestamp);
 | 
			
		||||
		auto it_saved = std::lower_bound(begin, end, Snapshot(m_saved_snapshot_time));
 | 
			
		||||
		assert(it_saved != m_snapshots.end() && it_saved->timestamp == m_saved_snapshot_time);
 | 
			
		||||
		auto it = it_saved;
 | 
			
		||||
		for (; it != begin && ! snapshot_modifies_project(*it); -- it) ;
 | 
			
		||||
		if (it == begin && ! snapshot_modifies_project(*it)) {
 | 
			
		||||
			// Found a snapshot before begin, which captures the same project state.
 | 
			
		||||
			m_saved_snapshot_time = (-- it)->timestamp;
 | 
			
		||||
		} else {
 | 
			
		||||
			auto it = it_saved;
 | 
			
		||||
			for (; it != end && ! snapshot_modifies_project(*it); ++ it) ;
 | 
			
		||||
			if (it == end && end != m_snapshots.end())
 | 
			
		||||
				// Found a snapshot after end, which captures the same project state.
 | 
			
		||||
				m_saved_snapshot_time = (-- it)->timestamp;				
 | 
			
		||||
			else
 | 
			
		||||
				// State of the project is being lost. Indicate a "likely modified" project state until the project is saved again.
 | 
			
		||||
				m_saved_snapshot_time = size_t(-1);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return m_snapshots.erase(begin, end);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void StackImpl::collect_garbage()
 | 
			
		||||
{
 | 
			
		||||
	// Purge objects with empty histories.
 | 
			
		||||
| 
						 | 
				
			
			@ -1064,6 +1180,7 @@ void StackImpl::release_least_recently_used()
 | 
			
		|||
				} else
 | 
			
		||||
					++ it;
 | 
			
		||||
			}
 | 
			
		||||
			//FIXME update the "saved" snapshot time.
 | 
			
		||||
			m_snapshots.erase(m_snapshots.begin());
 | 
			
		||||
		}
 | 
			
		||||
		assert(current_memsize >= mem_released);
 | 
			
		||||
| 
						 | 
				
			
			@ -1082,6 +1199,44 @@ void StackImpl::release_least_recently_used()
 | 
			
		|||
#endif /* SLIC3R_UNDOREDO_DEBUG */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool StackImpl::project_modified() const
 | 
			
		||||
{
 | 
			
		||||
	assert(! m_snapshots.empty());
 | 
			
		||||
 | 
			
		||||
	if (m_saved_snapshot_time == size_t(-1))
 | 
			
		||||
		// Don't know anything about the project state.
 | 
			
		||||
		return true;
 | 
			
		||||
	if (m_saved_snapshot_time == m_active_snapshot_time)
 | 
			
		||||
		// Just saved at this step.
 | 
			
		||||
		return false;
 | 
			
		||||
 | 
			
		||||
	assert(m_saved_snapshot_time >= m_snapshots.front().timestamp && m_saved_snapshot_time <= m_snapshots.back().timestamp);
 | 
			
		||||
 | 
			
		||||
	auto it_saved = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_saved_snapshot_time));
 | 
			
		||||
	assert(it_saved != m_snapshots.end() && it_saved->timestamp == m_saved_snapshot_time);
 | 
			
		||||
 | 
			
		||||
#ifndef NDEBUG
 | 
			
		||||
	// Verify that there is a snapshot with "current time".
 | 
			
		||||
	auto it_current = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time));
 | 
			
		||||
	assert(it_current != m_snapshots.end() && it_current->timestamp == m_active_snapshot_time);
 | 
			
		||||
#endif // NDEBUG
 | 
			
		||||
 | 
			
		||||
	if (m_saved_snapshot_time < m_active_snapshot_time) {
 | 
			
		||||
		// Search upwards. Ignore state of the "active" snapshot.
 | 
			
		||||
		for (auto it = it_saved; it->timestamp < m_active_snapshot_time; ++ it)
 | 
			
		||||
			if (snapshot_modifies_project(*it))
 | 
			
		||||
				return true;
 | 
			
		||||
	} else {
 | 
			
		||||
		// Search downwards. Ignore state of the "saved" snapshot.
 | 
			
		||||
		assert(m_saved_snapshot_time > m_active_snapshot_time);
 | 
			
		||||
		for (auto it = it_saved - 1; it->timestamp >= m_active_snapshot_time; -- it)
 | 
			
		||||
			if (snapshot_modifies_project(*it))
 | 
			
		||||
				return true;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Wrappers of the private implementation.
 | 
			
		||||
Stack::Stack() : pimpl(new StackImpl()) {}
 | 
			
		||||
Stack::~Stack() {}
 | 
			
		||||
| 
						 | 
				
			
			@ -1094,6 +1249,7 @@ size_t Stack::memsize() const { return pimpl->memsize(); }
 | 
			
		|||
void Stack::release_least_recently_used() { pimpl->release_least_recently_used(); }
 | 
			
		||||
void Stack::take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos, const SnapshotData &snapshot_data)
 | 
			
		||||
	{ pimpl->take_snapshot(snapshot_name, model, selection, gizmos, snapshot_data); }
 | 
			
		||||
void Stack::reduce_noisy_snapshots(const std::string& new_name) { pimpl->reduce_noisy_snapshots(new_name); }
 | 
			
		||||
bool Stack::has_undo_snapshot() const { return pimpl->has_undo_snapshot(); }
 | 
			
		||||
bool Stack::has_undo_snapshot(size_t time_to_load) const { return pimpl->has_undo_snapshot(time_to_load); }
 | 
			
		||||
bool Stack::has_redo_snapshot() const { return pimpl->has_redo_snapshot(); }
 | 
			
		||||
| 
						 | 
				
			
			@ -1106,6 +1262,8 @@ const std::vector<Snapshot>& Stack::snapshots() const { return pimpl->snapshots(
 | 
			
		|||
const Snapshot& Stack::snapshot(size_t time) const { return pimpl->snapshot(time); }
 | 
			
		||||
size_t Stack::active_snapshot_time() const { return pimpl->active_snapshot_time(); }
 | 
			
		||||
bool Stack::temp_snapshot_active() const { return pimpl->temp_snapshot_active(); }
 | 
			
		||||
void Stack::mark_current_as_saved() { pimpl->mark_current_as_saved(); }
 | 
			
		||||
bool Stack::project_modified() const { return pimpl->project_modified(); }
 | 
			
		||||
 | 
			
		||||
} // namespace UndoRedo
 | 
			
		||||
} // namespace Slic3r
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,6 +8,7 @@
 | 
			
		|||
#include <cassert>
 | 
			
		||||
 | 
			
		||||
#include <libslic3r/ObjectID.hpp>
 | 
			
		||||
#include <libslic3r/Config.hpp>
 | 
			
		||||
 | 
			
		||||
typedef double                          coordf_t;
 | 
			
		||||
typedef std::pair<coordf_t, coordf_t>   t_layer_height_range;
 | 
			
		||||
| 
						 | 
				
			
			@ -15,7 +16,6 @@ typedef std::pair<coordf_t, coordf_t>   t_layer_height_range;
 | 
			
		|||
namespace Slic3r {
 | 
			
		||||
 | 
			
		||||
class Model;
 | 
			
		||||
enum PrinterTechnology : unsigned char;
 | 
			
		||||
 | 
			
		||||
namespace GUI {
 | 
			
		||||
	class Selection;
 | 
			
		||||
| 
						 | 
				
			
			@ -25,8 +25,10 @@ namespace GUI {
 | 
			
		|||
namespace UndoRedo {
 | 
			
		||||
 | 
			
		||||
enum class SnapshotType : unsigned char {
 | 
			
		||||
	// Some action modifying project state.
 | 
			
		||||
	// Some action modifying project state, outside any EnteringGizmo / LeavingGizmo interval.
 | 
			
		||||
	Action,
 | 
			
		||||
	// Some action modifying project state, inside some EnteringGizmo / LeavingGizmo interval.
 | 
			
		||||
	GizmoAction,
 | 
			
		||||
	// Selection change at the Plater.
 | 
			
		||||
	Selection,
 | 
			
		||||
	// New project, Reset project, Load project ...
 | 
			
		||||
| 
						 | 
				
			
			@ -48,14 +50,11 @@ enum class SnapshotType : unsigned char {
 | 
			
		|||
// which may be handy sometimes.
 | 
			
		||||
struct SnapshotData
 | 
			
		||||
{
 | 
			
		||||
	// Constructor is defined in .cpp due to the forward declaration of enum PrinterTechnology.
 | 
			
		||||
	SnapshotData();
 | 
			
		||||
 | 
			
		||||
	SnapshotType        snapshot_type;
 | 
			
		||||
	PrinterTechnology 	printer_technology;
 | 
			
		||||
	PrinterTechnology 	printer_technology { ptUnknown };
 | 
			
		||||
	// Bitmap of Flags (see the Flags enum).
 | 
			
		||||
	unsigned int        flags;
 | 
			
		||||
    int                 layer_range_idx;
 | 
			
		||||
	unsigned int        flags { 0 };
 | 
			
		||||
    int                 layer_range_idx { -1 };
 | 
			
		||||
 | 
			
		||||
	// Bitmask of various binary flags to be stored with the snapshot.
 | 
			
		||||
	enum Flags {
 | 
			
		||||
| 
						 | 
				
			
			@ -121,6 +120,9 @@ public:
 | 
			
		|||
 | 
			
		||||
	// Store the current application state onto the Undo / Redo stack, remove all snapshots after m_active_snapshot_time.
 | 
			
		||||
    void take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos, const SnapshotData &snapshot_data);
 | 
			
		||||
    // To be called just after take_snapshot() when leaving a gizmo, inside which small edits like support point add / remove events or paiting actions were allowed.
 | 
			
		||||
    // Remove all but the last edit between the gizmo enter / leave snapshots.
 | 
			
		||||
    void reduce_noisy_snapshots(const std::string& new_name);
 | 
			
		||||
 | 
			
		||||
	// To be queried to enable / disable the Undo / Redo buttons at the UI.
 | 
			
		||||
	bool has_undo_snapshot() const;
 | 
			
		||||
| 
						 | 
				
			
			@ -151,6 +153,11 @@ public:
 | 
			
		|||
	// In that case the Undo action will capture the last snapshot.
 | 
			
		||||
	bool   temp_snapshot_active() const;
 | 
			
		||||
 | 
			
		||||
	// Resets the "dirty project" status.
 | 
			
		||||
    void   mark_current_as_saved();
 | 
			
		||||
    // Is the project modified with regard to the last "saved" state marked with mark_current_as_saved()?
 | 
			
		||||
    bool   project_modified() const;
 | 
			
		||||
 | 
			
		||||
	// After load_snapshot() / undo() / redo() the selection is deserialized into a list of ObjectIDs, which needs to be converted
 | 
			
		||||
	// into the list of GLVolume pointers once the 3D scene is updated.
 | 
			
		||||
	const Selection& selection_deserialized() const;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue