WIP UndoRedo: Added Undo/Redo stack, added Platter::take_snapshot(),
experimental snapshots on loading STLs and increasing / decreasing model instances.
This commit is contained in:
parent
27ee68d2f9
commit
5e846112ee
13 changed files with 742 additions and 51 deletions
|
@ -22,19 +22,6 @@ namespace Slic3r {
|
|||
|
||||
unsigned int Model::s_auto_extruder_id = 1;
|
||||
|
||||
// Unique object / instance ID for the wipe tower.
|
||||
ObjectID wipe_tower_object_id()
|
||||
{
|
||||
static ObjectBase mine;
|
||||
return mine.id();
|
||||
}
|
||||
|
||||
ObjectID wipe_tower_instance_id()
|
||||
{
|
||||
static ObjectBase mine;
|
||||
return mine.id();
|
||||
}
|
||||
|
||||
Model& Model::assign_copy(const Model &rhs)
|
||||
{
|
||||
this->copy_id(rhs);
|
||||
|
@ -1068,11 +1055,11 @@ void ModelObject::mirror(Axis axis)
|
|||
}
|
||||
|
||||
// This method could only be called before the meshes of this ModelVolumes are not shared!
|
||||
void ModelObject::scale_mesh(const Vec3d &versor)
|
||||
void ModelObject::scale_mesh_after_creation(const Vec3d &versor)
|
||||
{
|
||||
for (ModelVolume *v : this->volumes)
|
||||
{
|
||||
v->scale_geometry(versor);
|
||||
v->scale_geometry_after_creation(versor);
|
||||
v->set_offset(versor.cwiseProduct(v->get_offset()));
|
||||
}
|
||||
this->invalidate_bounding_box();
|
||||
|
@ -1562,8 +1549,8 @@ void ModelVolume::center_geometry_after_creation()
|
|||
Vec3d shift = this->mesh().bounding_box().center();
|
||||
if (!shift.isApprox(Vec3d::Zero()))
|
||||
{
|
||||
m_mesh->translate(-(float)shift(0), -(float)shift(1), -(float)shift(2));
|
||||
m_convex_hull->translate(-(float)shift(0), -(float)shift(1), -(float)shift(2));
|
||||
const_cast<TriangleMesh*>(m_mesh.get())->translate(-(float)shift(0), -(float)shift(1), -(float)shift(2));
|
||||
const_cast<TriangleMesh*>(m_convex_hull.get())->translate(-(float)shift(0), -(float)shift(1), -(float)shift(2));
|
||||
translate(shift);
|
||||
}
|
||||
}
|
||||
|
@ -1716,10 +1703,10 @@ void ModelVolume::mirror(Axis axis)
|
|||
}
|
||||
|
||||
// This method could only be called before the meshes of this ModelVolumes are not shared!
|
||||
void ModelVolume::scale_geometry(const Vec3d& versor)
|
||||
void ModelVolume::scale_geometry_after_creation(const Vec3d& versor)
|
||||
{
|
||||
m_mesh->scale(versor);
|
||||
m_convex_hull->scale(versor);
|
||||
const_cast<TriangleMesh*>(m_mesh.get())->scale(versor);
|
||||
const_cast<TriangleMesh*>(m_convex_hull.get())->scale(versor);
|
||||
}
|
||||
|
||||
void ModelVolume::transform_this_mesh(const Transform3d &mesh_trafo, bool fix_left_handed)
|
||||
|
|
|
@ -27,6 +27,10 @@ class ModelVolume;
|
|||
class Print;
|
||||
class SLAPrint;
|
||||
|
||||
namespace UndoRedo {
|
||||
class StackImpl;
|
||||
}
|
||||
|
||||
typedef std::string t_model_material_id;
|
||||
typedef std::string t_model_material_attribute;
|
||||
typedef std::map<t_model_material_attribute, std::string> t_model_material_attributes;
|
||||
|
@ -36,10 +40,6 @@ typedef std::vector<ModelObject*> ModelObjectPtrs;
|
|||
typedef std::vector<ModelVolume*> ModelVolumePtrs;
|
||||
typedef std::vector<ModelInstance*> ModelInstancePtrs;
|
||||
|
||||
// Unique object / instance ID for the wipe tower.
|
||||
extern ObjectID wipe_tower_object_id();
|
||||
extern ObjectID wipe_tower_instance_id();
|
||||
|
||||
#define OBJECTBASE_DERIVED_COPY_MOVE_CLONE(TYPE) \
|
||||
/* Copy a model, copy the IDs. The Print::apply() will call the TYPE::copy() method */ \
|
||||
/* to make a private copy for background processing. */ \
|
||||
|
@ -75,7 +75,7 @@ private: \
|
|||
void assign_new_unique_ids_recursive();
|
||||
|
||||
// Material, which may be shared across multiple ModelObjects of a single Model.
|
||||
class ModelMaterial : public ObjectBase
|
||||
class ModelMaterial final : public ObjectBase
|
||||
{
|
||||
public:
|
||||
// Attributes are defined by the AMF file format, but they don't seem to be used by Slic3r for any purpose.
|
||||
|
@ -104,6 +104,7 @@ private:
|
|||
ModelMaterial& operator=(ModelMaterial &&rhs) = delete;
|
||||
|
||||
friend class cereal::access;
|
||||
friend class UndoRedo::StackImpl;
|
||||
ModelMaterial() : m_model(nullptr) {}
|
||||
template<class Archive> void serialize(Archive &ar) { ar(cereal::base_class<ObjectBase>(this)); ar(attributes, config); }
|
||||
};
|
||||
|
@ -112,7 +113,7 @@ private:
|
|||
// and possibly having multiple modifier volumes, each modifier volume with its set of parameters and materials.
|
||||
// Each ModelObject may be instantiated mutliple times, each instance having different placement on the print bed,
|
||||
// different rotation and different uniform scaling.
|
||||
class ModelObject : public ObjectBase
|
||||
class ModelObject final : public ObjectBase
|
||||
{
|
||||
friend class Model;
|
||||
public:
|
||||
|
@ -211,7 +212,7 @@ public:
|
|||
void mirror(Axis axis);
|
||||
|
||||
// This method could only be called before the meshes of this ModelVolumes are not shared!
|
||||
void scale_mesh(const Vec3d& versor);
|
||||
void scale_mesh_after_creation(const Vec3d& versor);
|
||||
|
||||
size_t materials_count() const;
|
||||
size_t facets_count() const;
|
||||
|
@ -240,12 +241,6 @@ public:
|
|||
// Get count of errors in the mesh( or all object's meshes, if volume index isn't defined)
|
||||
int get_mesh_errors_count(const int vol_idx = -1) const;
|
||||
|
||||
protected:
|
||||
friend class Print;
|
||||
friend class SLAPrint;
|
||||
// Called by Print::apply() to set the model pointer after making a copy.
|
||||
void set_model(Model *model) { m_model = model; }
|
||||
|
||||
private:
|
||||
ModelObject(Model *model) : m_model(model), origin_translation(Vec3d::Zero()),
|
||||
m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) {}
|
||||
|
@ -272,7 +267,14 @@ private:
|
|||
mutable BoundingBoxf3 m_raw_mesh_bounding_box;
|
||||
mutable bool m_raw_mesh_bounding_box_valid;
|
||||
|
||||
// Called by Print::apply() to set the model pointer after making a copy.
|
||||
friend class Print;
|
||||
friend class SLAPrint;
|
||||
void set_model(Model *model) { m_model = model; }
|
||||
|
||||
// Undo / Redo through the cereal serialization library
|
||||
friend class cereal::access;
|
||||
friend class UndoRedo::StackImpl;
|
||||
ModelObject() : m_model(nullptr), m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) {}
|
||||
template<class Archive> void serialize(Archive &ar) {
|
||||
ar(cereal::base_class<ObjectBase>(this));
|
||||
|
@ -292,17 +294,17 @@ enum class ModelVolumeType : int {
|
|||
|
||||
// An object STL, or a modifier volume, over which a different set of parameters shall be applied.
|
||||
// ModelVolume instances are owned by a ModelObject.
|
||||
class ModelVolume : public ObjectBase
|
||||
class ModelVolume final : public ObjectBase
|
||||
{
|
||||
public:
|
||||
std::string name;
|
||||
// The triangular model.
|
||||
const TriangleMesh& mesh() const { return *m_mesh.get(); }
|
||||
void set_mesh(const TriangleMesh &mesh) { m_mesh = std::make_shared<TriangleMesh>(mesh); }
|
||||
void set_mesh(TriangleMesh &&mesh) { m_mesh = std::make_shared<TriangleMesh>(std::move(mesh)); }
|
||||
void set_mesh(std::shared_ptr<TriangleMesh> &mesh) { m_mesh = mesh; }
|
||||
void set_mesh(std::unique_ptr<TriangleMesh> &&mesh) { m_mesh = std::move(mesh); }
|
||||
void reset_mesh() { m_mesh = std::make_shared<TriangleMesh>(); }
|
||||
void set_mesh(const TriangleMesh &mesh) { m_mesh = std::make_shared<const TriangleMesh>(mesh); }
|
||||
void set_mesh(TriangleMesh &&mesh) { m_mesh = std::make_shared<const TriangleMesh>(std::move(mesh)); }
|
||||
void set_mesh(std::shared_ptr<const TriangleMesh> &mesh) { m_mesh = mesh; }
|
||||
void set_mesh(std::unique_ptr<const TriangleMesh> &&mesh) { m_mesh = std::move(mesh); }
|
||||
void reset_mesh() { m_mesh = std::make_shared<const TriangleMesh>(); }
|
||||
// Configuration parameters specific to an object model geometry or a modifier volume,
|
||||
// overriding the global Slic3r settings and the ModelObject settings.
|
||||
DynamicPrintConfig config;
|
||||
|
@ -340,7 +342,7 @@ public:
|
|||
void mirror(Axis axis);
|
||||
|
||||
// This method could only be called before the meshes of this ModelVolumes are not shared!
|
||||
void scale_geometry(const Vec3d& versor);
|
||||
void scale_geometry_after_creation(const Vec3d& versor);
|
||||
|
||||
// Translates the mesh and the convex hull so that the origin of their vertices is in the center of this volume's bounding box.
|
||||
// Attention! This method may only be called just after ModelVolume creation! It must not be called once the TriangleMesh of this ModelVolume is shared!
|
||||
|
@ -400,15 +402,15 @@ protected:
|
|||
|
||||
private:
|
||||
// Parent object owning this ModelVolume.
|
||||
ModelObject* object;
|
||||
ModelObject* object;
|
||||
// The triangular model.
|
||||
std::shared_ptr<TriangleMesh> m_mesh;
|
||||
std::shared_ptr<const TriangleMesh> m_mesh;
|
||||
// Is it an object to be printed, or a modifier volume?
|
||||
ModelVolumeType m_type;
|
||||
t_model_material_id m_material_id;
|
||||
ModelVolumeType m_type;
|
||||
t_model_material_id m_material_id;
|
||||
// The convex hull of this model's mesh.
|
||||
std::shared_ptr<TriangleMesh> m_convex_hull;
|
||||
Geometry::Transformation m_transformation;
|
||||
std::shared_ptr<const TriangleMesh> m_convex_hull;
|
||||
Geometry::Transformation m_transformation;
|
||||
|
||||
// flag to optimize the checking if the volume is splittable
|
||||
// -1 -> is unknown value (before first cheking)
|
||||
|
@ -443,16 +445,16 @@ private:
|
|||
ModelVolume& operator=(ModelVolume &rhs) = delete;
|
||||
|
||||
friend class cereal::access;
|
||||
friend class UndoRedo::StackImpl;
|
||||
ModelVolume() : object(nullptr) {}
|
||||
template<class Archive> void serialize(Archive &ar) {
|
||||
ar(cereal::base_class<ObjectBase>(this));
|
||||
ar(name, config, m_mesh, m_type, m_material_id, m_convex_hull, m_transformation, m_is_splittable);
|
||||
}
|
||||
};
|
||||
|
||||
// A single instance of a ModelObject.
|
||||
// Knows the affine transformation of an object.
|
||||
class ModelInstance : public ObjectBase
|
||||
class ModelInstance final : public ObjectBase
|
||||
{
|
||||
public:
|
||||
enum EPrintVolumeState : unsigned char
|
||||
|
@ -538,6 +540,7 @@ private:
|
|||
ModelInstance& operator=(ModelInstance &&rhs) = delete;
|
||||
|
||||
friend class cereal::access;
|
||||
friend class UndoRedo::StackImpl;
|
||||
ModelInstance() : object(nullptr) {}
|
||||
template<class Archive> void serialize(Archive &ar) {
|
||||
ar(cereal::base_class<ObjectBase>(this));
|
||||
|
@ -550,7 +553,7 @@ private:
|
|||
// and with multiple modifier meshes.
|
||||
// A model groups multiple objects, each object having possibly multiple instances,
|
||||
// all objects may share mutliple materials.
|
||||
class Model : public ObjectBase
|
||||
class Model final : public ObjectBase
|
||||
{
|
||||
static unsigned int s_auto_extruder_id;
|
||||
|
||||
|
@ -633,6 +636,7 @@ private:
|
|||
OBJECTBASE_DERIVED_PRIVATE_COPY_MOVE(Model)
|
||||
|
||||
friend class cereal::access;
|
||||
friend class UndoRedo::StackImpl;
|
||||
template<class Archive> void serialize(Archive &ar) {
|
||||
ar(cereal::base_class<ObjectBase>(this), materials, objects);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,19 @@ namespace Slic3r {
|
|||
|
||||
size_t ObjectBase::s_last_id = 0;
|
||||
|
||||
// Unique object / instance ID for the wipe tower.
|
||||
ObjectID wipe_tower_object_id()
|
||||
{
|
||||
static ObjectBase mine;
|
||||
return mine.id();
|
||||
}
|
||||
|
||||
ObjectID wipe_tower_instance_id()
|
||||
{
|
||||
static ObjectBase mine;
|
||||
return mine.id();
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
// CEREAL_REGISTER_TYPE(Slic3r::ObjectBase)
|
||||
|
|
|
@ -9,6 +9,10 @@
|
|||
|
||||
namespace Slic3r {
|
||||
|
||||
namespace UndoRedo {
|
||||
class StackImpl;
|
||||
};
|
||||
|
||||
// Unique identifier of a mutable object accross the application.
|
||||
// Used to synchronize the front end (UI) with the back end (BackgroundSlicingProcess / Print / PrintObject)
|
||||
// (for Model, ModelObject, ModelVolume, ModelInstance or ModelMaterial classes)
|
||||
|
@ -75,6 +79,7 @@ private:
|
|||
|
||||
friend ObjectID wipe_tower_object_id();
|
||||
friend ObjectID wipe_tower_instance_id();
|
||||
friend class Slic3r::UndoRedo::StackImpl;
|
||||
|
||||
friend class cereal::access;
|
||||
template<class Archive> void serialize(Archive &ar) { ar(m_id); }
|
||||
|
@ -82,6 +87,10 @@ private:
|
|||
template<class Archive> static void load_and_construct(Archive & ar, cereal::construct<ObjectBase> &construct) { ObjectID id; ar(id); construct(id); }
|
||||
};
|
||||
|
||||
// Unique object / instance ID for the wipe tower.
|
||||
extern ObjectID wipe_tower_object_id();
|
||||
extern ObjectID wipe_tower_instance_id();
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_ObjectID_hpp_ */
|
||||
|
|
|
@ -43,6 +43,8 @@ struct SupportPoint {
|
|||
|
||||
bool operator==(const SupportPoint& sp) const { return (pos==sp.pos) && head_front_radius==sp.head_front_radius && is_new_island==sp.is_new_island; }
|
||||
bool operator!=(const SupportPoint& sp) const { return !(sp == (*this)); }
|
||||
|
||||
template<class Archive> void serialize(Archive &ar) { ar(pos, head_front_radius, is_new_island); }
|
||||
};
|
||||
|
||||
/// An index-triangle structure for libIGL functions. Also serves as an
|
||||
|
|
|
@ -171,4 +171,9 @@ extern int generate_layer_height_texture(
|
|||
|
||||
}; // namespace Slic3r
|
||||
|
||||
namespace cereal
|
||||
{
|
||||
template<class Archive> void serialize(Archive& archive, Slic3r::t_layer_height_range &lhr) { archive(lhr.first, lhr.second); }
|
||||
}
|
||||
|
||||
#endif /* slic3r_Slicing_hpp_ */
|
||||
|
|
|
@ -195,4 +195,23 @@ TriangleMesh make_sphere(double rho, double fa=(2*PI/360));
|
|||
|
||||
}
|
||||
|
||||
// Serialization through the Cereal library
|
||||
namespace cereal {
|
||||
template <class Archive> struct specialize<Archive, Slic3r::TriangleMesh, cereal::specialization::non_member_load_save> {};
|
||||
template<class Archive> void load(Archive &archive, Slic3r::TriangleMesh &mesh) {
|
||||
stl_file &stl = mesh.stl;
|
||||
stl.stats.type = inmemory;
|
||||
archive(stl.stats.number_of_facets, stl.stats.original_num_facets);
|
||||
stl_allocate(&stl);
|
||||
archive.loadBinary((char*)stl.facet_start.data(), stl.facet_start.size() * 50);
|
||||
stl_get_size(&stl);
|
||||
mesh.repair();
|
||||
}
|
||||
template<class Archive> void save(Archive &archive, const Slic3r::TriangleMesh &mesh) {
|
||||
const stl_file& stl = mesh.stl;
|
||||
archive(stl.stats.number_of_facets, stl.stats.original_num_facets);
|
||||
archive.saveBinary((char*)stl.facet_start.data(), stl.facet_start.size() * 50);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -146,6 +146,8 @@ set(SLIC3R_GUI_SOURCES
|
|||
Utils/PresetUpdater.hpp
|
||||
Utils/Time.cpp
|
||||
Utils/Time.hpp
|
||||
Utils/UndoRedo.cpp
|
||||
Utils/UndoRedo.hpp
|
||||
Utils/HexFile.cpp
|
||||
Utils/HexFile.hpp
|
||||
)
|
||||
|
|
|
@ -69,6 +69,7 @@
|
|||
#include "../Utils/ASCIIFolding.hpp"
|
||||
#include "../Utils/PrintHost.hpp"
|
||||
#include "../Utils/FixModelByWin10.hpp"
|
||||
#include "../Utils/UndoRedo.hpp"
|
||||
|
||||
#include <wx/glcanvas.h> // Needs to be last because reasons :-/
|
||||
#include "WipeTowerDialog.hpp"
|
||||
|
@ -1182,6 +1183,8 @@ const std::regex PlaterDropTarget::pattern_drop(".*[.](stl|obj|amf|3mf|prusa)",
|
|||
|
||||
bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &filenames)
|
||||
{
|
||||
plater->take_snapshot(_(L("Load Files")));
|
||||
|
||||
std::vector<fs::path> paths;
|
||||
|
||||
for (const auto &filename : filenames) {
|
||||
|
@ -1247,6 +1250,7 @@ struct Plater::priv
|
|||
Slic3r::Model model;
|
||||
PrinterTechnology printer_technology = ptFFF;
|
||||
Slic3r::GCodePreviewData gcode_preview_data;
|
||||
Slic3r::UndoRedo::Stack undo_redo_stack;
|
||||
|
||||
// GUI elements
|
||||
wxSizer* panel_sizer{ nullptr };
|
||||
|
@ -1545,6 +1549,10 @@ struct Plater::priv
|
|||
void split_object();
|
||||
void split_volume();
|
||||
void scale_selection_to_fit_print_volume();
|
||||
|
||||
void take_snapshot(const std::string& snapshot_name) { this->undo_redo_stack.take_snapshot(snapshot_name, model, view3D->get_canvas3d()->get_selection()); }
|
||||
void take_snapshot(const wxString& snapshot_name) { this->take_snapshot(std::string(snapshot_name.ToUTF8().data())); }
|
||||
|
||||
bool background_processing_enabled() const { return this->get_config("background_processing") == "1"; }
|
||||
void update_print_volume_state();
|
||||
void schedule_background_process();
|
||||
|
@ -1775,6 +1783,8 @@ 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());
|
||||
}
|
||||
|
||||
void Plater::priv::update(bool force_full_scene_refresh)
|
||||
|
@ -2139,7 +2149,7 @@ std::vector<size_t> Plater::priv::load_model_objects(const ModelObjectPtrs &mode
|
|||
// the size of the object is too big -> this could lead to overflow when moving to clipper coordinates,
|
||||
// so scale down the mesh
|
||||
double inv = 1. / max_ratio;
|
||||
object->scale_mesh(Vec3d(inv, inv, inv));
|
||||
object->scale_mesh_after_creation(Vec3d(inv, inv, inv));
|
||||
object->origin_translation = Vec3d::Zero();
|
||||
object->center_around_origin();
|
||||
scaled_down = true;
|
||||
|
@ -2355,6 +2365,8 @@ void Plater::priv::delete_object_from_model(size_t obj_idx)
|
|||
|
||||
void Plater::priv::reset()
|
||||
{
|
||||
this->take_snapshot(_(L("Reset Project")));
|
||||
|
||||
set_project_filename(wxEmptyString);
|
||||
|
||||
// Prevent toolpaths preview from rendering while we modify the Print object
|
||||
|
@ -3515,6 +3527,8 @@ void Plater::new_project()
|
|||
|
||||
void Plater::load_project()
|
||||
{
|
||||
this->take_snapshot(_(L("Load Project")));
|
||||
|
||||
wxString input_file;
|
||||
wxGetApp().load_project(this, input_file);
|
||||
|
||||
|
@ -3593,6 +3607,7 @@ void Plater::delete_object_from_model(size_t obj_idx) { p->delete_object_from_mo
|
|||
|
||||
void Plater::remove_selected()
|
||||
{
|
||||
this->take_snapshot(_(L("Delete Selected Objects")));
|
||||
this->p->view3D->delete_selected();
|
||||
}
|
||||
|
||||
|
@ -3600,6 +3615,8 @@ void Plater::increase_instances(size_t num)
|
|||
{
|
||||
if (! can_increase_instances()) { return; }
|
||||
|
||||
this->take_snapshot(_(L("Increase Instances")));
|
||||
|
||||
int obj_idx = p->get_selected_object_idx();
|
||||
|
||||
ModelObject* model_object = p->model.objects[obj_idx];
|
||||
|
@ -3634,6 +3651,8 @@ void Plater::decrease_instances(size_t num)
|
|||
{
|
||||
if (! can_decrease_instances()) { return; }
|
||||
|
||||
this->take_snapshot(_(L("Decrease Instances")));
|
||||
|
||||
int obj_idx = p->get_selected_object_idx();
|
||||
|
||||
ModelObject* model_object = p->model.objects[obj_idx];
|
||||
|
@ -3982,6 +4001,16 @@ void Plater::send_gcode()
|
|||
}
|
||||
}
|
||||
|
||||
void Plater::take_snapshot(const std::string &snapshot_name)
|
||||
{
|
||||
p->take_snapshot(snapshot_name);
|
||||
}
|
||||
|
||||
void Plater::take_snapshot(const wxString &snapshot_name)
|
||||
{
|
||||
p->take_snapshot(snapshot_name);
|
||||
}
|
||||
|
||||
void Plater::on_extruders_change(int num_extruders)
|
||||
{
|
||||
auto& choices = sidebar().combos_filament();
|
||||
|
|
|
@ -179,6 +179,9 @@ public:
|
|||
void fix_through_netfabb(const int obj_idx, const int vol_idx = -1);
|
||||
void send_gcode();
|
||||
|
||||
void take_snapshot(const std::string &snapshot_name);
|
||||
void take_snapshot(const wxString &snapshot_name);
|
||||
|
||||
void on_extruders_change(int extruders_count);
|
||||
void on_config_change(const DynamicPrintConfig &config);
|
||||
// On activating the parent window.
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include <set>
|
||||
#include "libslic3r/Geometry.hpp"
|
||||
#include "libslic3r/ObjectID.hpp"
|
||||
#include "3DScene.hpp"
|
||||
|
||||
#if ENABLE_RENDER_SELECTION_CENTER
|
||||
|
@ -66,7 +67,7 @@ private:
|
|||
Enum m_value;
|
||||
};
|
||||
|
||||
class Selection
|
||||
class Selection : public Slic3r::ObjectBase
|
||||
{
|
||||
public:
|
||||
typedef std::set<unsigned int> IndicesList;
|
||||
|
|
559
src/slic3r/Utils/UndoRedo.cpp
Normal file
559
src/slic3r/Utils/UndoRedo.cpp
Normal file
|
@ -0,0 +1,559 @@
|
|||
#include "UndoRedo.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
|
||||
#include <cereal/types/polymorphic.hpp>
|
||||
#include <cereal/types/map.hpp>
|
||||
#include <cereal/types/string.hpp>
|
||||
#include <cereal/types/vector.hpp>
|
||||
#include <cereal/archives/binary.hpp>
|
||||
#define CEREAL_FUTURE_EXPERIMENTAL
|
||||
#include <cereal/archives/adapters.hpp>
|
||||
|
||||
#include <libslic3r/ObjectID.hpp>
|
||||
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace UndoRedo {
|
||||
|
||||
// Time interval, start is closed, end is open.
|
||||
struct Interval
|
||||
{
|
||||
public:
|
||||
Interval(size_t begin, size_t end) : m_begin(begin), m_end(end) {}
|
||||
|
||||
size_t begin() const { return m_begin; }
|
||||
size_t end() const { return m_end; }
|
||||
|
||||
bool is_valid() const { return m_begin >= 0 && m_begin < m_end; }
|
||||
// This interval comes strictly before the rhs interval.
|
||||
bool strictly_before(const Interval &rhs) const { return this->is_valid() && rhs.is_valid() && m_end <= rhs.m_begin; }
|
||||
// This interval comes strictly after the rhs interval.
|
||||
bool strictly_after(const Interval &rhs) const { return this->is_valid() && rhs.is_valid() && rhs.m_end <= m_begin; }
|
||||
|
||||
bool operator<(const Interval &rhs) const { return (m_begin < rhs.m_begin) || (m_begin == rhs.m_begin && m_end < rhs.m_end); }
|
||||
bool operator==(const Interval &rhs) const { return m_begin == rhs.m_begin && m_end == rhs.m_end; }
|
||||
|
||||
void trim_end(size_t new_end) { m_end = std::min(m_end, new_end); }
|
||||
void extend_end(size_t new_end) { assert(new_end >= m_end); m_end = new_end; }
|
||||
|
||||
private:
|
||||
size_t m_begin;
|
||||
size_t m_end;
|
||||
};
|
||||
|
||||
// History of a single object tracked by the Undo / Redo stack. The object may be mutable or immutable.
|
||||
class ObjectHistoryBase
|
||||
{
|
||||
public:
|
||||
virtual ~ObjectHistoryBase() {}
|
||||
|
||||
// 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;
|
||||
|
||||
#ifndef NDEBUG
|
||||
virtual bool validate() = 0;
|
||||
#endif /* NDEBUG */
|
||||
};
|
||||
|
||||
template<typename T> class ObjectHistory : public ObjectHistoryBase
|
||||
{
|
||||
public:
|
||||
~ObjectHistory() override {}
|
||||
|
||||
// If the history is empty, the ObjectHistory object could be released.
|
||||
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 {
|
||||
assert(! m_history.empty());
|
||||
assert(this->validate());
|
||||
// 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()) {
|
||||
auto it_prev = it;
|
||||
-- it_prev;
|
||||
assert(it_prev->begin() < timestamp);
|
||||
// Trim the last interval with timestamp.
|
||||
it_prev->trim_end(timestamp);
|
||||
}
|
||||
m_history.erase(it, m_history.end());
|
||||
assert(this->validate());
|
||||
}
|
||||
|
||||
protected:
|
||||
std::vector<T> m_history;
|
||||
};
|
||||
|
||||
// Big objects (mainly the triangle meshes) are tracked by Slicer using the shared pointers
|
||||
// and they are immutable.
|
||||
// The Undo / Redo stack therefore may keep a shared pointer to these immutable objects
|
||||
// and as long as the ref counter of these objects is higher than 1 (1 reference is held
|
||||
// by the Undo / Redo stack), there is no cost associated to holding the object
|
||||
// at the Undo / Redo stack. Once the reference counter drops to 1 (only the Undo / Redo
|
||||
// stack holds the reference), the shared pointer may get serialized (and possibly compressed)
|
||||
// and the shared pointer may be released.
|
||||
// The history of a single immutable object may not be continuous, as an immutable object may
|
||||
// be removed from the scene while being kept at the Copy / Paste stack.
|
||||
template<typename T>
|
||||
class ImmutableObjectHistory : public ObjectHistory<Interval>
|
||||
{
|
||||
public:
|
||||
ImmutableObjectHistory(std::shared_ptr<const T> shared_object) : m_shared_object(shared_object) {}
|
||||
~ImmutableObjectHistory() override {}
|
||||
|
||||
void save(size_t active_snapshot_time, size_t current_time) {
|
||||
assert(m_history.empty() || m_history.back().end() <= active_snapshot_time);
|
||||
if (m_history.empty() || m_history.back().end() < active_snapshot_time)
|
||||
m_history.emplace_back(active_snapshot_time, current_time + 1);
|
||||
else
|
||||
m_history.back().extend_end(current_time + 1);
|
||||
}
|
||||
|
||||
bool has_snapshot(size_t timestamp) {
|
||||
if (m_history.empty())
|
||||
return false;
|
||||
auto it = std::lower_bound(m_history.begin(), m_history.end(), Interval(timestamp, timestamp));
|
||||
if (it == m_history.end() || it->begin() >= timestamp) {
|
||||
if (it == m_history.begin())
|
||||
return false;
|
||||
-- it;
|
||||
}
|
||||
return timestamp >= it->begin() && timestamp < it->end();
|
||||
}
|
||||
|
||||
bool is_serialized() const { return m_shared_object.get() == nullptr; }
|
||||
const std::string& serialized_data() const { return m_serialized; }
|
||||
std::shared_ptr<const T>& shared_ptr(StackImpl &stack) {
|
||||
if (m_shared_object.get() == nullptr && ! this->m_serialized.empty()) {
|
||||
// Deserialize the object.
|
||||
std::istringstream iss(m_serialized);
|
||||
{
|
||||
Slic3r::UndoRedo::InputArchive archive(stack, iss);
|
||||
std::unique_ptr<std::remove_const<T>::type> mesh(new std::remove_const<T>::type());
|
||||
archive(*mesh.get());
|
||||
m_shared_object = std::move(mesh);
|
||||
}
|
||||
}
|
||||
return m_shared_object;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
bool validate() override;
|
||||
#endif /* NDEBUG */
|
||||
|
||||
private:
|
||||
// Either the source object is held by a shared pointer and the m_serialized field is empty,
|
||||
// or the shared pointer is null and the object is being serialized into m_serialized.
|
||||
std::shared_ptr<const T> m_shared_object;
|
||||
std::string m_serialized;
|
||||
};
|
||||
|
||||
struct MutableHistoryInterval
|
||||
{
|
||||
private:
|
||||
struct Data
|
||||
{
|
||||
// Reference counter of this data chunk. We may have used shared_ptr, but the shared_ptr is thread safe
|
||||
// with the associated cost of CPU cache invalidation on refcount change.
|
||||
size_t refcnt;
|
||||
size_t size;
|
||||
char data[1];
|
||||
|
||||
bool matches(const std::string& rhs) { return this->size == rhs.size() && memcmp(this->data, rhs.data(), this->size) == 0; }
|
||||
};
|
||||
|
||||
Interval m_interval;
|
||||
Data *m_data;
|
||||
|
||||
public:
|
||||
MutableHistoryInterval(const Interval &interval, const std::string &input_data) : m_interval(interval), m_data(nullptr) {
|
||||
m_data = (Data*)new char[offsetof(Data, data) + input_data.size()];
|
||||
m_data->refcnt = 1;
|
||||
m_data->size = input_data.size();
|
||||
memcpy(m_data->data, input_data.data(), input_data.size());
|
||||
}
|
||||
|
||||
MutableHistoryInterval(const Interval &interval, MutableHistoryInterval &other) : m_interval(interval), m_data(other.m_data) {
|
||||
++ m_data->refcnt;
|
||||
}
|
||||
|
||||
// as a key for std::lower_bound
|
||||
MutableHistoryInterval(const size_t begin, const size_t end) : m_interval(begin, end), m_data(nullptr) {}
|
||||
|
||||
MutableHistoryInterval(MutableHistoryInterval&& rhs) : m_interval(rhs.m_interval), m_data(rhs.m_data) { rhs.m_data = nullptr; }
|
||||
MutableHistoryInterval& operator=(MutableHistoryInterval&& rhs) { m_interval = rhs.m_interval; m_data = rhs.m_data; rhs.m_data = nullptr; return *this; }
|
||||
|
||||
~MutableHistoryInterval() {
|
||||
if (m_data != nullptr && -- m_data->refcnt == 0)
|
||||
delete[] (char*)m_data;
|
||||
}
|
||||
|
||||
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_end(size_t timestamp) { m_interval.trim_end(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; }
|
||||
|
||||
const char* data() const { return m_data->data; }
|
||||
size_t size() const { return m_data->size; }
|
||||
size_t refcnt() const { return m_data->refcnt; }
|
||||
bool matches(const std::string& data) { return m_data->matches(data); }
|
||||
|
||||
private:
|
||||
MutableHistoryInterval(const MutableHistoryInterval &rhs);
|
||||
MutableHistoryInterval& operator=(const MutableHistoryInterval &rhs);
|
||||
};
|
||||
|
||||
// Smaller objects (Model, ModelObject, ModelInstance, ModelVolume, DynamicPrintConfig)
|
||||
// are mutable and there is not tracking of the changes, therefore a snapshot needs to be
|
||||
// taken every time and compared to the previous data at the Undo / Redo stack.
|
||||
// The serialized data is stored if it is different from the last value on the stack, otherwise
|
||||
// the serialized data is discarded.
|
||||
// The history of a single mutable object may not be continuous, as an mutable object may
|
||||
// be removed from the scene while being kept at the Copy / Paste stack, therefore an object snapshot
|
||||
// with the same serialized object data may be shared by multiple history intervals.
|
||||
template<typename T>
|
||||
class MutableObjectHistory : public ObjectHistory<MutableHistoryInterval>
|
||||
{
|
||||
public:
|
||||
~MutableObjectHistory() override {}
|
||||
|
||||
void save(size_t active_snapshot_time, size_t current_time, const std::string &data) {
|
||||
assert(m_history.empty() || m_history.back().end() <= active_snapshot_time);
|
||||
if (m_history.empty() || m_history.back().end() < active_snapshot_time) {
|
||||
if (! m_history.empty() && m_history.back().matches(data))
|
||||
// Share the previous data by reference counting.
|
||||
m_history.emplace_back(Interval(current_time, current_time + 1), m_history.back());
|
||||
else
|
||||
// Allocate new data.
|
||||
m_history.emplace_back(Interval(current_time, current_time + 1), data);
|
||||
} else {
|
||||
assert(! m_history.empty());
|
||||
assert(m_history.back().end() == active_snapshot_time);
|
||||
if (m_history.back().matches(data))
|
||||
// Just extend the last interval using the old data.
|
||||
m_history.back().extend_end(current_time + 1);
|
||||
else
|
||||
// Allocate new data time continuous with the previous data.
|
||||
m_history.emplace_back(Interval(active_snapshot_time, current_time + 1), data);
|
||||
}
|
||||
}
|
||||
|
||||
std::string load(size_t timestamp) const {
|
||||
assert(! m_history.empty());
|
||||
auto it = std::lower_bound(m_history.begin(), m_history.end(), MutableHistoryInterval(timestamp, timestamp));
|
||||
if (it == m_history.end() || it->begin() >= timestamp) {
|
||||
assert(it != m_history.begin());
|
||||
-- it;
|
||||
}
|
||||
assert(timestamp >= it->begin() && timestamp < it->end());
|
||||
return std::string(it->data(), it->data() + it->size());
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
bool validate() override;
|
||||
#endif /* NDEBUG */
|
||||
};
|
||||
|
||||
#ifndef NDEBUG
|
||||
template<typename T>
|
||||
bool ImmutableObjectHistory<T>::validate()
|
||||
{
|
||||
// The immutable object content is captured either by a shared object, or by its serialization, but not both.
|
||||
assert(! m_shared_object == ! m_serialized.empty());
|
||||
// Verify that the history intervals are sorted and do not overlap.
|
||||
if (! m_history.empty())
|
||||
for (size_t i = 1; i < m_history.size(); ++ i)
|
||||
assert(m_history[i - 1].strictly_before(m_history[i]));
|
||||
return true;
|
||||
}
|
||||
#endif /* NDEBUG */
|
||||
|
||||
#ifndef NDEBUG
|
||||
template<typename T>
|
||||
bool MutableObjectHistory<T>::validate()
|
||||
{
|
||||
// Verify that the history intervals are sorted and do not overlap, and that the data reference counters are correct.
|
||||
if (! m_history.empty()) {
|
||||
std::map<const char*, size_t> refcntrs;
|
||||
assert(m_history.front().data() != nullptr);
|
||||
++ refcntrs[m_history.front().data()];
|
||||
for (size_t i = 1; i < m_history.size(); ++ i) {
|
||||
assert(m_history[i - 1].interval().strictly_before(m_history[i].interval()));
|
||||
++ refcntrs[m_history[i].data()];
|
||||
}
|
||||
for (const auto &hi : m_history) {
|
||||
assert(hi.data() != nullptr);
|
||||
assert(refcntrs[hi.data()] == hi.refcnt());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif /* NDEBUG */
|
||||
|
||||
class StackImpl
|
||||
{
|
||||
public:
|
||||
// Stack needs to be initialized. An empty stack is not valid, there must be a "New Project" status stored at the beginning.
|
||||
StackImpl() : m_active_snapshot_time(0), m_current_time(0) {}
|
||||
|
||||
// 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, Slic3r::GUI::Selection &selection);
|
||||
|
||||
// Snapshot history (names with timestamps).
|
||||
const std::vector<Snapshot>& snapshots() const { return m_snapshots; }
|
||||
|
||||
//protected:
|
||||
void save_model(const Slic3r::Model &model, size_t snapshot_time);
|
||||
void save_selection(const Slic3r::Model& model, const Slic3r::GUI::Selection &selection, size_t snapshot_time);
|
||||
void load_model(const Slic3r::Model &model, size_t snapshot_time);
|
||||
void load_selection(const Slic3r::GUI::Selection &selection, size_t snapshot_time);
|
||||
|
||||
template<typename T> ObjectID save_mutable_object(const T &object);
|
||||
template<typename T> ObjectID save_immutable_object(std::shared_ptr<const T> &object);
|
||||
template<typename T> T* load_mutable_object(const Slic3r::ObjectID id);
|
||||
template<typename T> std::shared_ptr<const T> load_immutable_object(const Slic3r::ObjectID id);
|
||||
template<typename T> void load_mutable_object(const Slic3r::ObjectID id, T &target);
|
||||
|
||||
private:
|
||||
template<typename T> ObjectID immutable_object_id(const std::shared_ptr<const T> &ptr) {
|
||||
return this->immutable_object_id_impl((const void*)ptr.get());
|
||||
}
|
||||
ObjectID immutable_object_id_impl(const void *ptr) {
|
||||
auto it = m_shared_ptr_to_object_id.find(ptr);
|
||||
if (it == m_shared_ptr_to_object_id.end()) {
|
||||
// Allocate a new temporary ObjectID for this shared pointer.
|
||||
ObjectBase object_with_id;
|
||||
it = m_shared_ptr_to_object_id.insert(it, std::make_pair(ptr, object_with_id.id()));
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
// Each individual object (Model, ModelObject, ModelInstance, ModelVolume, Selection, TriangleMesh)
|
||||
// is stored with its own history, referenced by the ObjectID. Immutable objects do not provide
|
||||
// their own IDs, therefore there are temporary IDs generated for them and stored to m_shared_ptr_to_object_id.
|
||||
std::map<ObjectID, std::unique_ptr<ObjectHistoryBase>> m_objects;
|
||||
std::map<const void*, ObjectID> m_shared_ptr_to_object_id;
|
||||
// Snapshot history (names with timestamps).
|
||||
std::vector<Snapshot> m_snapshots;
|
||||
// Timestamp of the active snapshot.
|
||||
size_t m_active_snapshot_time;
|
||||
// Logical time counter. m_current_time is being incremented with each snapshot taken.
|
||||
size_t m_current_time;
|
||||
};
|
||||
|
||||
using InputArchive = cereal::UserDataAdapter<StackImpl, cereal::BinaryInputArchive>;
|
||||
using OutputArchive = cereal::UserDataAdapter<StackImpl, cereal::BinaryOutputArchive>;
|
||||
|
||||
} // namespace UndoRedo
|
||||
|
||||
class Model;
|
||||
class ModelObject;
|
||||
class ModelVolume;
|
||||
class ModelInstance;
|
||||
class ModelMaterial;
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
namespace cereal
|
||||
{
|
||||
// Let cereal know that there are load / save non-member functions declared for ModelObject*, ignore serialization of pointers triggering
|
||||
// static assert, that cereal does not support serialization of raw pointers.
|
||||
template <class Archive> struct specialize<Archive, Slic3r::Model*, cereal::specialization::non_member_load_save> {};
|
||||
template <class Archive> struct specialize<Archive, Slic3r::ModelObject*, cereal::specialization::non_member_load_save> {};
|
||||
template <class Archive> struct specialize<Archive, Slic3r::ModelVolume*, cereal::specialization::non_member_load_save> {};
|
||||
template <class Archive> struct specialize<Archive, Slic3r::ModelInstance*, cereal::specialization::non_member_load_save> {};
|
||||
template <class Archive> struct specialize<Archive, Slic3r::ModelMaterial*, cereal::specialization::non_member_load_save> {};
|
||||
|
||||
// Store ObjectBase derived class onto the Undo / Redo stack as a separate object,
|
||||
// store just the ObjectID to this stream.
|
||||
template <class T> void save(BinaryOutputArchive& ar, T* const& ptr)
|
||||
{
|
||||
ar(cereal::get_user_data<Slic3r::UndoRedo::StackImpl>(ar).save_mutable_object<T>(*ptr));
|
||||
}
|
||||
|
||||
// Load ObjectBase derived class from the Undo / Redo stack as a separate object
|
||||
// based on the ObjectID loaded from this stream.
|
||||
template <class T> void load(BinaryInputArchive& ar, T*& ptr)
|
||||
{
|
||||
Slic3r::UndoRedo::StackImpl& stack = cereal::get_user_data<Slic3r::UndoRedo::StackImpl>(ar);
|
||||
size_t id;
|
||||
ar(id);
|
||||
ptr = stack.load_mutable_object<T>(Slic3r::ObjectID(id));
|
||||
}
|
||||
|
||||
// Store ObjectBase derived class onto the Undo / Redo stack as a separate object,
|
||||
// store just the ObjectID to this stream.
|
||||
template <class T> void save(BinaryOutputArchive& ar, std::shared_ptr<const T>& ptr)
|
||||
{
|
||||
ar(cereal::get_user_data<Slic3r::UndoRedo::StackImpl>(ar).save_immutable_object<T>(ptr));
|
||||
}
|
||||
|
||||
// Load ObjectBase derived class from the Undo / Redo stack as a separate object
|
||||
// based on the ObjectID loaded from this stream.
|
||||
template <class T> void load(BinaryInputArchive& ar, std::shared_ptr<T>& ptr)
|
||||
{
|
||||
Slic3r::UndoRedo::StackImpl& stack = cereal::get_user_data<Slic3r::UndoRedo::StackImpl>(ar);
|
||||
size_t id;
|
||||
ar(id);
|
||||
ptr = std::const_pointer_cast<T>(stack.load_immutable_object<T>(Slic3r::ObjectID(id)));
|
||||
}
|
||||
|
||||
#if 0
|
||||
void save(BinaryOutputArchive &ar, const Slic3r::GUI::Selection &selection)
|
||||
{
|
||||
size_t num = selection.get_volume_idxs().size();
|
||||
ar(num);
|
||||
for (unsigned int volume_idx : selection.get_volume_idxs()) {
|
||||
const Slic3r::GLVolume::CompositeID &id = selection.get_volume(volume_idx)->composite_id;
|
||||
ar(id.object_id, id.volume_id, id.instance_id);
|
||||
}
|
||||
}
|
||||
|
||||
template <class T> void load(BinaryInputArchive &ar, Slic3r::GUI::Selection &selection)
|
||||
{
|
||||
size_t num;
|
||||
ar(num);
|
||||
for (size_t i = 0; i < num; ++ i) {
|
||||
Slic3r::GLVolume::CompositeID id;
|
||||
ar(id.object_id, id.volume_id, id.instance_id);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#include <libslic3r/Model.hpp>
|
||||
#include <libslic3r/TriangleMesh.hpp>
|
||||
#include <slic3r/GUI/Selection.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace UndoRedo {
|
||||
|
||||
template<typename T> ObjectID StackImpl::save_mutable_object(const T &object)
|
||||
{
|
||||
// First find or allocate a history stack for the ObjectID of this object instance.
|
||||
auto it_object_history = m_objects.find(object.id());
|
||||
if (it_object_history == m_objects.end())
|
||||
it_object_history = m_objects.insert(it_object_history, std::make_pair(object.id(), std::unique_ptr<MutableObjectHistory<T>>(new MutableObjectHistory<T>())));
|
||||
auto *object_history = static_cast<MutableObjectHistory<T>*>(it_object_history->second.get());
|
||||
// Then serialize the object into a string.
|
||||
std::ostringstream oss;
|
||||
{
|
||||
Slic3r::UndoRedo::OutputArchive archive(*this, oss);
|
||||
archive(object);
|
||||
}
|
||||
object_history->save(m_active_snapshot_time, m_current_time, oss.str());
|
||||
return object.id();
|
||||
}
|
||||
|
||||
template<typename T> ObjectID StackImpl::save_immutable_object(std::shared_ptr<const T> &object)
|
||||
{
|
||||
// First allocate a temporary ObjectID for this pointer.
|
||||
ObjectID object_id = this->immutable_object_id(object);
|
||||
// and find or allocate a history stack for the ObjectID associated to this shared_ptr.
|
||||
auto it_object_history = m_objects.find(object_id);
|
||||
if (it_object_history == m_objects.end())
|
||||
it_object_history = m_objects.insert(it_object_history, ObjectID, std::unique_ptr<ImmutableObjectHistory<T>>(new ImmutableObjectHistory(object)));
|
||||
auto *object_history = ;
|
||||
// Then save the interval.
|
||||
static_cast<const ImmutableObjectHistory<T>*>(it_object_history->second.get())->save(m_active_snapshot_time, m_current_time);
|
||||
return object_id;
|
||||
}
|
||||
|
||||
template<typename T> T* StackImpl::load_mutable_object(const Slic3r::ObjectID id)
|
||||
{
|
||||
T *target = new T();
|
||||
this->load_mutable_object(id, *target);
|
||||
return target;
|
||||
}
|
||||
|
||||
template<typename T> std::shared_ptr<const T> StackImpl::load_immutable_object(const Slic3r::ObjectID id)
|
||||
{
|
||||
// First find a history stack for the ObjectID of this object instance.
|
||||
auto it_object_history = m_objects.find(id);
|
||||
assert(it_object_history != m_objects.end());
|
||||
auto *object_history = static_cast<ImmutableObjectHistory<T>*>(it_object_history->second.get());
|
||||
assert(object_history->has_snapshot(m_active_snapshot_time));
|
||||
return object_history->shared_ptr(*this);
|
||||
}
|
||||
|
||||
template<typename T> void StackImpl::load_mutable_object(const Slic3r::ObjectID id, T &target)
|
||||
{
|
||||
// First find a history stack for the ObjectID of this object instance.
|
||||
auto it_object_history = m_objects.find(id);
|
||||
assert(it_object_history != m_objects.end());
|
||||
auto *object_history = static_cast<const MutableObjectHistory<T>*>(it_object_history->second.get());
|
||||
// Then get the data associated with the object history and m_active_snapshot_time.
|
||||
std::istringstream iss(object_history->load(m_active_snapshot_time));
|
||||
Slic3r::UndoRedo::InputArchive archive(*this, iss);
|
||||
archive(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("New Project", 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.
|
||||
++ m_active_snapshot_time;
|
||||
for (auto &kvp : m_objects)
|
||||
kvp.second->relese_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());
|
||||
}
|
||||
// Take new snapshots.
|
||||
this->save_mutable_object(model);
|
||||
// this->save_mutable_object(selection);
|
||||
// Save the snapshot info
|
||||
m_snapshots.emplace_back(snapshot_name, m_current_time ++, model.id().id);
|
||||
}
|
||||
|
||||
void StackImpl::load_snapshot(size_t timestamp, Slic3r::Model &model, Slic3r::GUI::Selection &selection)
|
||||
{
|
||||
// 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.end() || it_snapshot->timestamp != timestamp)
|
||||
throw std::runtime_error((boost::format("Snapshot with timestamp %1% does not exist") % timestamp).str());
|
||||
|
||||
this->load_mutable_object(ObjectID(it_snapshot->model_object_id), model);
|
||||
this->load_mutable_object(selection.id(), selection);
|
||||
this->m_active_snapshot_time = timestamp;
|
||||
}
|
||||
|
||||
// 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, Slic3r::GUI::Selection &selection) { pimpl->load_snapshot(timestamp, model, selection); }
|
||||
const std::vector<Snapshot>& Stack::snapshots() const { return pimpl->snapshots(); }
|
||||
|
||||
} // namespace UndoRedo
|
||||
} // namespace Slic3r
|
58
src/slic3r/Utils/UndoRedo.hpp
Normal file
58
src/slic3r/Utils/UndoRedo.hpp
Normal file
|
@ -0,0 +1,58 @@
|
|||
#ifndef slic3r_Utils_UndoRedo_hpp_
|
||||
#define slic3r_Utils_UndoRedo_hpp_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class Model;
|
||||
|
||||
namespace GUI {
|
||||
class Selection;
|
||||
} // namespace GUI
|
||||
|
||||
namespace UndoRedo {
|
||||
|
||||
struct Snapshot
|
||||
{
|
||||
Snapshot(size_t timestamp) : timestamp(timestamp) {}
|
||||
Snapshot(const std::string &name, size_t timestamp, size_t model_object_id) : name(name), timestamp(timestamp), model_object_id(model_object_id) {}
|
||||
|
||||
std::string name;
|
||||
size_t timestamp;
|
||||
size_t model_object_id;
|
||||
|
||||
bool operator< (const Snapshot &rhs) const { return this->timestamp < rhs.timestamp; }
|
||||
bool operator==(const Snapshot &rhs) const { return this->timestamp == rhs.timestamp; }
|
||||
};
|
||||
|
||||
class StackImpl;
|
||||
|
||||
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.
|
||||
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, Slic3r::GUI::Selection &selection);
|
||||
|
||||
// Snapshot history (names with timestamps).
|
||||
const std::vector<Snapshot>& snapshots() const;
|
||||
|
||||
private:
|
||||
friend class StackImpl;
|
||||
std::unique_ptr<StackImpl> pimpl;
|
||||
};
|
||||
|
||||
}; // namespace UndoRedo
|
||||
}; // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_Utils_UndoRedo_hpp_ */
|
Loading…
Reference in a new issue