mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-11-02 20:51:23 -07:00 
			
		
		
		
	Undo / Redo fixes
This commit is contained in:
		
							parent
							
								
									6a3fc5bde3
								
							
						
					
					
						commit
						4e2fda3315
					
				
					 11 changed files with 151 additions and 89 deletions
				
			
		| 
						 | 
				
			
			@ -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;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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));
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<int> &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<ConfigOptionFloat>("wipe_tower_x", true)->value = evt.data(0);
 | 
			
		||||
    cfg.opt<ConfigOptionFloat>("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<ConfigOptionFloat>("wipe_tower_x", true)->value = evt.data(0);
 | 
			
		||||
    cfg.opt<ConfigOptionFloat>("wipe_tower_y", true)->value = evt.data(1);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<size_t> 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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<Snapshot>& snapshots() const { return m_snapshots; }
 | 
			
		||||
	const std::vector<Snapshot>& 	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<typename T, typename T_AS> void StackImpl::load_mutable_object(const Sl
 | 
			
		|||
	archive(static_cast<T_AS&>(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<Snapshot>& Stack::snapshots() const { return pimpl->snapshots(); }
 | 
			
		||||
size_t Stack::active_snapshot_time() const { return pimpl->active_snapshot_time(); }
 | 
			
		||||
 | 
			
		||||
} // namespace UndoRedo
 | 
			
		||||
} // namespace Slic3r
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<Snapshot>& 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.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue