diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index f8ebe3134..7551bd8cb 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -283,7 +283,7 @@ private: explicit ModelObject(int) : ObjectBase(-1), config(-1), m_model(nullptr), origin_translation(Vec3d::Zero()), m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) { assert(this->id().invalid()); assert(this->config.id().invalid()); } ~ModelObject(); - void assign_new_unique_ids_recursive(); + void assign_new_unique_ids_recursive() override; // To be able to return an object from own copy / clone methods. Hopefully the compiler will do the "Copy elision" // (Omits copy and move(since C++11) constructors, resulting in zero - copy pass - by - value semantics). @@ -463,6 +463,7 @@ protected: // Copies IDs of both the ModelVolume and its config. explicit ModelVolume(const ModelVolume &rhs) = default; void set_model_object(ModelObject *model_object) { object = model_object; } + void assign_new_unique_ids_recursive() override { ObjectBase::set_new_unique_id(); config.set_new_unique_id(); } void transform_this_mesh(const Transform3d& t, bool fix_left_handed); void transform_this_mesh(const Matrix3d& m, bool fix_left_handed); @@ -508,14 +509,13 @@ private: ModelVolume(ModelObject *object, const ModelVolume &other, const TriangleMesh &&mesh) : name(other.name), m_mesh(new TriangleMesh(std::move(mesh))), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation) { -// assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id()); -// assert(this->id() == other.id() && this->config.id() == other.config.id()); + assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id()); + assert(this->id() != other.id() && this->config.id() == other.config.id()); this->set_material_id(other.material_id()); this->config.set_new_unique_id(); if (mesh.stl.stats.number_of_facets > 1) calculate_convex_hull(); - assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id()); - assert(this->id() != other.id() && this->config.id() != other.config.id()); + assert(this->config.id().valid()); assert(this->config.id() != other.config.id()); assert(this->id() != this->config.id()); } ModelVolume& operator=(ModelVolume &rhs) = delete; diff --git a/src/libslic3r/ObjectID.hpp b/src/libslic3r/ObjectID.hpp index df16f9202..484d1173b 100644 --- a/src/libslic3r/ObjectID.hpp +++ b/src/libslic3r/ObjectID.hpp @@ -66,7 +66,7 @@ protected: void copy_id(const ObjectBase &rhs) { m_id = rhs.id(); } // Override this method if a ObjectBase derived class owns other ObjectBase derived instances. - void assign_new_unique_ids_recursive() { this->set_new_unique_id(); } + virtual void assign_new_unique_ids_recursive() { this->set_new_unique_id(); } private: ObjectID m_id; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 43d419eea..1d5cec04a 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2947,9 +2947,10 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) else if ((m_mouse.drag.move_volume_idx != -1) && m_mouse.dragging) { m_regenerate_volumes = false; + wxGetApp().plater()->take_snapshot(_(L("Move Object"))); do_move(); wxGetApp().obj_manipul()->set_dirty(); - // Let the platter know that the dragging finished, so a delayed refresh + // Let the plater know that the dragging finished, so a delayed refresh // of the scene with the background processing data should be performed. post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED)); } diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 4ef1cb0d2..582fe1007 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2116,7 +2116,7 @@ void ObjectList::part_selection_changed() panel.Thaw(); } -void ObjectList::add_object_to_list(size_t obj_idx) +void ObjectList::add_object_to_list(size_t obj_idx, bool call_selection_changed) { auto model_object = (*m_objects)[obj_idx]; const wxString& item_name = from_u8(model_object->name); @@ -2137,7 +2137,9 @@ void ObjectList::add_object_to_list(size_t obj_idx) false); auto opt_keys = volume->config.keys(); if (!opt_keys.empty() && !(opt_keys.size() == 1 && opt_keys[0] == "extruder")) { - select_item(m_objects_model->AddSettingsChild(vol_item)); + const wxDataViewItem &settings_item = m_objects_model->AddSettingsChild(vol_item); + if (call_selection_changed) + select_item(settings_item); Expand(vol_item); } } @@ -2151,7 +2153,9 @@ void ObjectList::add_object_to_list(size_t obj_idx) // add settings to the object, if it has those auto opt_keys = model_object->config.keys(); if (!opt_keys.empty() && !(opt_keys.size() == 1 && opt_keys[0] == "extruder")) { - select_item(m_objects_model->AddSettingsChild(item)); + const wxDataViewItem &settings_item = m_objects_model->AddSettingsChild(item); + if (call_selection_changed) + select_item(settings_item); Expand(item); } @@ -2159,7 +2163,8 @@ void ObjectList::add_object_to_list(size_t obj_idx) add_layer_root_item(item); #ifndef __WXOSX__ - selection_changed(); + if (call_selection_changed) + selection_changed(); #endif //__WXMSW__ } @@ -2963,11 +2968,10 @@ void ObjectList::change_part_type() void ObjectList::last_volume_is_deleted(const int obj_idx) { - if (obj_idx < 0 || m_objects->empty() || - obj_idx <= m_objects->size() || - (*m_objects)[obj_idx]->volumes.empty()) + if (obj_idx < 0 || obj_idx >= m_objects->size() || (*m_objects)[obj_idx]->volumes.empty()) return; - auto volume = (*m_objects)[obj_idx]->volumes[0]; + + auto volume = (*m_objects)[obj_idx]->volumes.front(); // clear volume's config values volume->config.clear(); @@ -3388,14 +3392,20 @@ void ObjectList::recreate_object_list() m_prevent_list_events = true; m_prevent_canvas_selection_update = true; + // Unselect all objects before deleting them, so that no change of selection is emitted during deletion. + this->UnselectAll(); m_objects_model->DeleteAll(); size_t obj_idx = 0; while (obj_idx < m_objects->size()) { - add_object_to_list(obj_idx); + add_object_to_list(obj_idx, false); ++obj_idx; } +#ifndef __WXOSX__ + selection_changed(); +#endif /* __WXOSX__ */ + m_prevent_canvas_selection_update = false; m_prevent_list_events = false; } diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 354f6c019..2a92ecbe4 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -261,7 +261,7 @@ public: void part_selection_changed(); // Add object to the list - void add_object_to_list(size_t obj_idx); + void add_object_to_list(size_t obj_idx, bool call_selection_changed = true); // Delete object from the list void delete_object_from_list(); void delete_object_from_list(const size_t obj_idx); diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 372cd79ef..79e45facd 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -655,6 +655,7 @@ void ObjectManipulation::change_position_value(int axis, double value) Selection& selection = canvas->get_selection(); selection.start_dragging(); selection.translate(position - m_cache.position, selection.requires_local_axes()); + wxGetApp().plater()->take_snapshot(_(L("Set Position"))); canvas->do_move(); m_cache.position = position; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index c7435636d..c8900d8da 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -673,6 +673,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas) { case Move: { + wxGetApp().plater()->take_snapshot(_(L("Move Object"))); canvas.disable_regenerate_volumes(); canvas.do_move(); break; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 0865ab713..bb394ffcf 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1773,9 +1773,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) view3D_canvas->Bind(EVT_GLCANVAS_QUESTION_MARK, [this](SimpleEvent&) { wxGetApp().keyboard_shortcuts(); }); view3D_canvas->Bind(EVT_GLCANVAS_INCREASE_INSTANCES, [this](Event &evt) { if (evt.data == 1) this->q->increase_instances(); else if (this->can_decrease_instances()) this->q->decrease_instances(); }); - view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_MOVED, [this](SimpleEvent&) { - this->take_snapshot(_(L("Instance Moved"))); - update(); }); + view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_MOVED, [this](SimpleEvent&) { update(); }); view3D_canvas->Bind(EVT_GLCANVAS_WIPETOWER_MOVED, &priv::on_wipetower_moved, this); view3D_canvas->Bind(EVT_GLCANVAS_WIPETOWER_ROTATED, &priv::on_wipetower_rotated, this); view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_ROTATED, [this](SimpleEvent&) { update(); }); @@ -1826,7 +1824,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) // updates camera type from .ini file camera.set_type(get_config("use_perspective_camera")); - this->undo_redo_stack.initialize(model, view3D->get_canvas3d()->get_selection()); + // Initialize the Undo / Redo stack with a first snapshot. this->take_snapshot(_(L("New Project"))); } @@ -3196,8 +3194,6 @@ void Plater::priv::on_right_click(Vec2dEvent& evt) void Plater::priv::on_wipetower_moved(Vec3dEvent &evt) { - this->take_snapshot(_(L("Wipe Tower Moved"))); - DynamicPrintConfig cfg; cfg.opt("wipe_tower_x", true)->value = evt.data(0); cfg.opt("wipe_tower_y", true)->value = evt.data(1); @@ -3206,8 +3202,6 @@ void Plater::priv::on_wipetower_moved(Vec3dEvent &evt) void Plater::priv::on_wipetower_rotated(Vec3dEvent& evt) { - this->take_snapshot(_(L("Wipe Tower Rotated"))); - DynamicPrintConfig cfg; cfg.opt("wipe_tower_x", true)->value = evt.data(0); cfg.opt("wipe_tower_y", true)->value = evt.data(1); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 2d5be01ff..a71005cf2 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -806,6 +806,8 @@ void Selection::scale_to_fit_print_volume(const DynamicPrintConfig& config) double s = std::min(sx, std::min(sy, sz)); if (s != 1.0) { + wxGetApp().plater()->take_snapshot(_(L("Scale To Fit"))); + TransformationType type; type.set_world(); type.set_relative(); @@ -2032,6 +2034,10 @@ bool Selection::is_from_fully_selected_instance(unsigned int volume_idx) const void Selection::paste_volumes_from_clipboard() { +#ifdef _DEBUG + check_model_ids_validity(*m_model); +#endif /* _DEBUG */ + int dst_obj_idx = get_object_idx(); if ((dst_obj_idx < 0) || ((int)m_model->objects.size() <= dst_obj_idx)) return; @@ -2073,6 +2079,9 @@ void Selection::paste_volumes_from_clipboard() } volumes.push_back(dst_volume); +#ifdef _DEBUG + check_model_ids_validity(*m_model); +#endif /* _DEBUG */ } // keeps relative position of multivolume selections @@ -2086,10 +2095,18 @@ void Selection::paste_volumes_from_clipboard() wxGetApp().obj_list()->paste_volumes_into_list(dst_obj_idx, volumes); } + +#ifdef _DEBUG + check_model_ids_validity(*m_model); +#endif /* _DEBUG */ } void Selection::paste_objects_from_clipboard() { +#ifdef _DEBUG + check_model_ids_validity(*m_model); +#endif /* _DEBUG */ + std::vector object_idxs; const ModelObjectPtrs& src_objects = m_clipboard.get_objects(); for (const ModelObject* src_object : src_objects) @@ -2103,9 +2120,16 @@ void Selection::paste_objects_from_clipboard() } object_idxs.push_back(m_model->objects.size() - 1); +#ifdef _DEBUG + check_model_ids_validity(*m_model); +#endif /* _DEBUG */ } wxGetApp().obj_list()->paste_objects_into_list(object_idxs); + +#ifdef _DEBUG + check_model_ids_validity(*m_model); +#endif /* _DEBUG */ } } // namespace GUI diff --git a/src/slic3r/Utils/UndoRedo.cpp b/src/slic3r/Utils/UndoRedo.cpp index ca40c6939..058062502 100644 --- a/src/slic3r/Utils/UndoRedo.cpp +++ b/src/slic3r/Utils/UndoRedo.cpp @@ -28,6 +28,13 @@ namespace Slic3r { namespace UndoRedo { +static std::string topmost_snapsnot_name = "@@@ Topmost @@@"; + +bool Snapshot::is_topmost() const +{ + return this->name == topmost_snapsnot_name; +} + // Time interval, start is closed, end is open. struct Interval { @@ -63,12 +70,13 @@ public: // Is the object captured by this history mutable or immutable? virtual bool is_mutable() const = 0; virtual bool is_immutable() const = 0; + virtual const void* immutable_object_ptr() const { return nullptr; } // If the history is empty, the ObjectHistory object could be released. virtual bool empty() = 0; // Release all data after the given timestamp. For the ImmutableObjectHistory, the shared pointer is NOT released. - virtual void relese_after_timestamp(size_t timestamp) = 0; + virtual void release_after_timestamp(size_t timestamp) = 0; #ifdef SLIC3R_UNDOREDO_DEBUG // Human readable debug information. @@ -89,12 +97,12 @@ public: bool empty() override { return m_history.empty(); } // Release all data after the given timestamp. The shared pointer is NOT released. - void relese_after_timestamp(size_t timestamp) override { + void release_after_timestamp(size_t timestamp) override { assert(! m_history.empty()); assert(this->valid()); // it points to an interval which either starts with timestamp, or follows the timestamp. auto it = std::lower_bound(m_history.begin(), m_history.end(), T(timestamp, timestamp)); - if (it == m_history.end()) { + if (it != m_history.begin()) { auto it_prev = it; -- it_prev; assert(it_prev->begin() < timestamp); @@ -128,6 +136,7 @@ public: bool is_mutable() const override { return false; } bool is_immutable() const override { return true; } + const void* immutable_object_ptr() const { return (const void*)m_shared_object.get(); } void save(size_t active_snapshot_time, size_t current_time) { assert(m_history.empty() || m_history.back().end() <= active_snapshot_time || @@ -160,9 +169,9 @@ public: std::string out = typeid(T).name(); out += this->is_serialized() ? std::string(" len:") + std::to_string(m_serialized.size()) : - std::string(" ptr:") + ptr_to_string(m_shared_object.get()); + std::string(" shared_ptr:") + ptr_to_string(m_shared_object.get()); for (const Interval &interval : m_history) - out += std::string(",<") + std::to_string(interval.begin()) + "," + std::to_string(interval.end()) + ")"; + out += std::string(", <") + std::to_string(interval.begin()) + "," + std::to_string(interval.end()) + ")"; return out; } #endif /* SLIC3R_UNDOREDO_DEBUG */ @@ -296,13 +305,8 @@ public: #ifdef SLIC3R_UNDOREDO_DEBUG std::string format() override { std::string out = typeid(T).name(); - bool first = true; - for (const MutableHistoryInterval &interval : m_history) { - if (! first) - out += ","; - out += std::string("ptr:") + ptr_to_string(interval.data()) + " len:" + std::to_string(interval.size()) + " <" + std::to_string(interval.begin()) + "," + std::to_string(interval.end()) + ")"; - first = false; - } + for (const MutableHistoryInterval &interval : m_history) + out += std::string(", ptr:") + ptr_to_string(interval.data()) + " len:" + std::to_string(interval.size()) + " <" + std::to_string(interval.begin()) + "," + std::to_string(interval.end()) + ")"; return out; } #endif /* SLIC3R_UNDOREDO_DEBUG */ @@ -364,11 +368,13 @@ public: bool has_undo_snapshot() const; bool has_redo_snapshot() const; - bool undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection); - bool redo(Slic3r::Model &model); + bool undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection, size_t jump_to_time); + bool redo(Slic3r::Model &model, size_t jump_to_time); // Snapshot history (names with timestamps). - const std::vector& snapshots() const { return m_snapshots; } + const std::vector& snapshots() const { return m_snapshots; } + // Timestamp of the active snapshot. + size_t active_snapshot_time() const { return m_active_snapshot_time; } const Selection& selection_deserialized() const { return m_selection; } @@ -388,7 +394,8 @@ public: for (const Snapshot &snapshot : m_snapshots) { if (snapshot.timestamp == m_active_snapshot_time) out += ">>> "; - out += std::string("Name:") + snapshot.name + ", timestamp: " + std::to_string(snapshot.timestamp) + ", Model ID:" + std::to_string(snapshot.model_id) + "\n"; + out += std::string("Name: \"") + snapshot.name + "\", timestamp: " + std::to_string(snapshot.timestamp) + + ", Model ID:" + ((snapshot.model_id == 0) ? "Invalid" : std::to_string(snapshot.model_id)) + "\n"; } if (m_active_snapshot_time > m_snapshots.back().timestamp) out += ">>>\n"; @@ -406,9 +413,9 @@ public: bool valid() const { assert(! m_snapshots.empty()); auto it = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time)); - assert(it == m_snapshots.end() || (it != m_snapshots.begin() && it->timestamp == m_active_snapshot_time)); - assert(it != m_snapshots.end() || m_active_snapshot_time > m_snapshots.back().timestamp); - for (auto it = m_objects.begin(); it != m_objects.end(); ++ it) + assert(it != m_snapshots.begin() && it != m_snapshots.end() && it->timestamp == m_active_snapshot_time); + assert(m_active_snapshot_time < m_snapshots.back().timestamp || m_snapshots.back().is_topmost()); + for (auto it = m_objects.begin(); it != m_objects.end(); ++ it) assert(it->second->valid()); return true; } @@ -624,29 +631,13 @@ template void StackImpl::load_mutable_object(const Sl archive(static_cast(target)); } -// The Undo / Redo stack is being initialized with an empty model and an empty selection. -// The first snapshot cannot be removed. -void StackImpl::initialize(const Slic3r::Model &model, const Slic3r::GUI::Selection &selection) -{ - assert(m_active_snapshot_time == 0); - assert(m_current_time == 0); - // The initial time interval will be <0, 1) - m_active_snapshot_time = SIZE_MAX; // let it overflow to zero in take_snapshot - m_current_time = 0; - this->take_snapshot("Internal - Initialized", model, selection); -} - // Store the current application state onto the Undo / Redo stack, remove all snapshots after m_active_snapshot_time. void StackImpl::take_snapshot(const std::string &snapshot_name, const Slic3r::Model &model, const Slic3r::GUI::Selection &selection) { // Release old snapshot data. - // The active snapshot may be above the last snapshot if there is no redo data available. - if (! m_snapshots.empty() && m_active_snapshot_time > m_snapshots.back().timestamp) - m_active_snapshot_time = m_snapshots.back().timestamp + 1; - else - ++ m_active_snapshot_time; + assert(m_active_snapshot_time <= m_current_time); for (auto &kvp : m_objects) - kvp.second->relese_after_timestamp(m_active_snapshot_time); + kvp.second->release_after_timestamp(m_active_snapshot_time); { auto it = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time)); m_snapshots.erase(it, m_snapshots.end()); @@ -662,6 +653,9 @@ void StackImpl::take_snapshot(const std::string &snapshot_name, const Slic3r::Mo // Save the snapshot info. m_snapshots.emplace_back(snapshot_name, m_current_time ++, model.id().id); 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_snapsnot_name, m_active_snapshot_time, 0); // Release empty objects from the history. this->collect_garbage(); assert(this->valid()); @@ -675,7 +669,7 @@ void StackImpl::load_snapshot(size_t timestamp, Slic3r::Model &model) { // Find the snapshot by time. It must exist. const auto it_snapshot = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(timestamp)); - if (it_snapshot == m_snapshots.begin() || it_snapshot == m_snapshots.end() || it_snapshot->timestamp != timestamp) + if (it_snapshot == m_snapshots.end() || it_snapshot->timestamp != timestamp) throw std::runtime_error((boost::format("Snapshot with timestamp %1% does not exist") % timestamp).str()); m_active_snapshot_time = timestamp; @@ -699,18 +693,37 @@ bool StackImpl::has_undo_snapshot() const } bool StackImpl::has_redo_snapshot() const -{ +{ + assert(this->valid()); auto it = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time)); - return it != m_snapshots.end() && ++ it != m_snapshots.end(); + return ++ it != m_snapshots.end(); } -bool StackImpl::undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection) +bool StackImpl::undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection, size_t time_to_load) { assert(this->valid()); - auto it_current = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time)); - if (-- it_current == m_snapshots.begin()) - return false; - this->load_snapshot(it_current->timestamp, model); + if (time_to_load == SIZE_MAX) { + auto it_current = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time)); + if (-- it_current == m_snapshots.begin()) + return false; + time_to_load = it_current->timestamp; + } + assert(time_to_load < m_active_snapshot_time); + assert(std::binary_search(m_snapshots.begin(), m_snapshots.end(), Snapshot(time_to_load))); + 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. + this->take_snapshot(topmost_snapsnot_name, model, selection); + // The line above entered another topmost_snapshot_name. + assert(m_snapshots.back().is_topmost()); + assert(! m_snapshots.back().is_topmost_captured()); + // Pop it back, it is not needed as there is now a captured topmost state. + m_snapshots.pop_back(); + // current_time was extended, but it should not cause any harm. Resetting it back may complicate the logic unnecessarily. + //-- m_current_time; + assert(m_snapshots.back().is_topmost()); + assert(m_snapshots.back().is_topmost_captured()); + } + this->load_snapshot(time_to_load, model); #ifdef SLIC3R_UNDOREDO_DEBUG std::cout << "After undo" << std::endl; this->print(); @@ -718,13 +731,18 @@ bool StackImpl::undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selecti return true; } -bool StackImpl::redo(Slic3r::Model &model) +bool StackImpl::redo(Slic3r::Model &model, size_t time_to_load) { assert(this->valid()); - auto it_current = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time)); - if (it_current == m_snapshots.end() || ++ it_current == m_snapshots.end()) - return false; - this->load_snapshot(it_current->timestamp, model); + if (time_to_load == SIZE_MAX) { + auto it_current = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time)); + if (++ it_current == m_snapshots.end()) + return false; + time_to_load = it_current->timestamp; + } + assert(time_to_load > m_active_snapshot_time); + assert(std::binary_search(m_snapshots.begin(), m_snapshots.end(), Snapshot(time_to_load))); + this->load_snapshot(time_to_load, model); #ifdef SLIC3R_UNDOREDO_DEBUG std::cout << "After redo" << std::endl; this->print(); @@ -737,9 +755,9 @@ void StackImpl::collect_garbage() // Purge objects with empty histories. for (auto it = m_objects.begin(); it != m_objects.end();) { if (it->second->empty()) { - if (it->second->is_immutable()) + if (it->second->immutable_object_ptr() != nullptr) // Release the immutable object from the ptr to ObjectID map. - this->m_objects.erase(it->first); + m_shared_ptr_to_object_id.erase(it->second->immutable_object_ptr()); it = m_objects.erase(it); } else ++ it; @@ -749,16 +767,15 @@ void StackImpl::collect_garbage() // Wrappers of the private implementation. Stack::Stack() : pimpl(new StackImpl()) {} Stack::~Stack() {} -void Stack::initialize(const Slic3r::Model &model, const Slic3r::GUI::Selection &selection) { pimpl->initialize(model, selection); } void Stack::take_snapshot(const std::string &snapshot_name, const Slic3r::Model &model, const Slic3r::GUI::Selection &selection) { pimpl->take_snapshot(snapshot_name, model, selection); } -void Stack::load_snapshot(size_t timestamp, Slic3r::Model &model) { pimpl->load_snapshot(timestamp, model); } bool Stack::has_undo_snapshot() const { return pimpl->has_undo_snapshot(); } bool Stack::has_redo_snapshot() const { return pimpl->has_redo_snapshot(); } -bool Stack::undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection) { return pimpl->undo(model, selection); } -bool Stack::redo(Slic3r::Model &model) { return pimpl->redo(model); } +bool Stack::undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection, size_t time_to_load) { return pimpl->undo(model, selection, time_to_load); } +bool Stack::redo(Slic3r::Model &model, size_t time_to_load) { return pimpl->redo(model, time_to_load); } const Selection& Stack::selection_deserialized() const { return pimpl->selection_deserialized(); } const std::vector& Stack::snapshots() const { return pimpl->snapshots(); } +size_t Stack::active_snapshot_time() const { return pimpl->active_snapshot_time(); } } // namespace UndoRedo } // namespace Slic3r diff --git a/src/slic3r/Utils/UndoRedo.hpp b/src/slic3r/Utils/UndoRedo.hpp index b08e410ff..178e5a11a 100644 --- a/src/slic3r/Utils/UndoRedo.hpp +++ b/src/slic3r/Utils/UndoRedo.hpp @@ -29,6 +29,12 @@ struct Snapshot bool operator< (const Snapshot &rhs) const { return this->timestamp < rhs.timestamp; } bool operator==(const Snapshot &rhs) const { return this->timestamp == rhs.timestamp; } + + // The topmost snapshot represents the current state when going forward. + bool is_topmost() const; + // The topmost snapshot is not being serialized to the Undo / Redo stack until going back in time, + // when the top most state is being serialized, so we can redo back to the top most state. + bool is_topmost_captured() const { assert(this->is_topmost()); return model_id > 0; } }; // Excerpt of Slic3r::GUI::Selection for serialization onto the Undo / Redo stack. @@ -44,25 +50,33 @@ class Stack { public: // Stack needs to be initialized. An empty stack is not valid, there must be a "New Project" status stored at the beginning. + // The first "New Project" snapshot shall not be removed. Stack(); ~Stack(); - // The Undo / Redo stack is being initialized with an empty model and an empty selection. - // The first snapshot cannot be removed. - void initialize(const Slic3r::Model &model, const Slic3r::GUI::Selection &selection); - // 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); - void load_snapshot(size_t timestamp, Slic3r::Model &model); + // To be queried to enable / disable the Undo / Redo buttons at the UI. bool has_undo_snapshot() const; bool has_redo_snapshot() const; - // Undoing an action may need to take a snapshot of the current application state. - bool undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection); - bool redo(Slic3r::Model &model); + + // Roll back the time. If time_to_load is SIZE_MAX, the previous snapshot is activated. + // Undoing an action may need to take a snapshot of the current application state, so that redo to the current state is possible. + bool undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection, size_t time_to_load = SIZE_MAX); + + // Jump forward in time. If time_to_load is SIZE_MAX, the next snapshot is activated. + bool redo(Slic3r::Model &model, size_t time_to_load = SIZE_MAX); // Snapshot history (names with timestamps). + // Each snapshot indicates start of an interval in which this operation is performed. + // There is one additional snapshot taken at the very end, which indicates the current unnamed state. + const std::vector& snapshots() const; + // Timestamp of the active snapshot. One of the snapshots of this->snapshots() shall have Snapshot::timestamp equal to this->active_snapshot_time(). + // The snapshot time indicates start of an operation, which is finished at the time of the following snapshot, therefore + // the active snapshot is the successive snapshot. The same logic applies to the time_to_load parameter of undo() and redo() operations. + size_t active_snapshot_time() 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.