mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-10-31 04:31:15 -06:00 
			
		
		
		
	Merge branch 'master' of https://github.com/prusa3d/PrusaSlicer into et_custom_bed
This commit is contained in:
		
						commit
						3344650255
					
				
					 24 changed files with 730 additions and 212 deletions
				
			
		|  | @ -430,6 +430,8 @@ private: | |||
| // floats of the first edge matches all six floats of the second edge.
 | ||||
| void stl_check_facets_exact(stl_file *stl) | ||||
| { | ||||
| 	assert(stl->facet_start.size() == stl->neighbors_start.size()); | ||||
| 
 | ||||
|   	stl->stats.connected_edges         = 0; | ||||
|   	stl->stats.connected_facets_1_edge = 0; | ||||
|   	stl->stats.connected_facets_2_edge = 0; | ||||
|  |  | |||
|  | @ -127,6 +127,10 @@ struct stl_file { | |||
| 		this->stats.reset(); | ||||
| 	} | ||||
| 
 | ||||
| 	size_t memsize() const { | ||||
| 		return sizeof(*this) + sizeof(stl_facet) * facet_start.size() + sizeof(stl_neighbors) * neighbors_start.size(); | ||||
| 	} | ||||
| 
 | ||||
| 	std::vector<stl_facet>     		facet_start; | ||||
| 	std::vector<stl_neighbors> 		neighbors_start; | ||||
| 	// Statistics
 | ||||
|  | @ -139,6 +143,10 @@ struct indexed_triangle_set | |||
| 
 | ||||
| 	void clear() { indices.clear(); vertices.clear(); } | ||||
| 
 | ||||
| 	size_t memsize() const { | ||||
| 		return sizeof(*this) + sizeof(stl_triangle_vertex_indices) * indices.size() + sizeof(stl_vertex) * vertices.size(); | ||||
| 	} | ||||
| 
 | ||||
| 	std::vector<stl_triangle_vertex_indices> 	indices; | ||||
| 	std::vector<stl_vertex>       				vertices; | ||||
| 	//FIXME add normals once we get rid of the stl_file from TriangleMesh completely.
 | ||||
|  |  | |||
|  | @ -93,7 +93,7 @@ enum ConfigOptionMode { | |||
|     comExpert | ||||
| }; | ||||
| 
 | ||||
| enum PrinterTechnology | ||||
| enum PrinterTechnology : unsigned char | ||||
| { | ||||
|     // Fused Filament Fabrication
 | ||||
|     ptFFF, | ||||
|  |  | |||
|  | @ -1877,6 +1877,31 @@ bool model_volume_list_changed(const ModelObject &model_object_old, const ModelO | |||
|     return false; | ||||
| } | ||||
| 
 | ||||
| extern bool model_has_multi_part_objects(const Model &model) | ||||
| { | ||||
|     for (const ModelObject *model_object : model.objects) | ||||
|     	if (model_object->volumes.size() != 1 || ! model_object->volumes.front()->is_model_part()) | ||||
|     		return true; | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| extern bool model_has_advanced_features(const Model &model) | ||||
| { | ||||
| 	auto config_is_advanced = [](const DynamicPrintConfig &config) { | ||||
|         return ! (config.empty() || (config.size() == 1 && config.cbegin()->first == "extruder")); | ||||
| 	}; | ||||
|     for (const ModelObject *model_object : model.objects) { | ||||
|         // Is there more than one instance or advanced config data?
 | ||||
|         if (model_object->instances.size() > 1 || config_is_advanced(model_object->config)) | ||||
|         	return true; | ||||
|         // Is there any modifier or advanced config data?
 | ||||
|         for (const ModelVolume* model_volume : model_object->volumes) | ||||
|             if (! model_volume->is_model_part() || config_is_advanced(model_volume->config)) | ||||
|             	return true; | ||||
|     } | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| #ifndef NDEBUG | ||||
| // Verify whether the IDs of Model / ModelObject / ModelVolume / ModelInstance / ModelMaterial are valid and unique.
 | ||||
| void check_model_ids_validity(const Model &model) | ||||
|  |  | |||
|  | @ -17,6 +17,13 @@ | |||
| #include <utility> | ||||
| #include <vector> | ||||
| 
 | ||||
| namespace cereal { | ||||
| 	class BinaryInputArchive; | ||||
| 	class BinaryOutputArchive; | ||||
| 	template <class T> void load_optional(BinaryInputArchive &ar, std::shared_ptr<const T> &ptr); | ||||
| 	template <class T> void save_optional(BinaryOutputArchive &ar, const std::shared_ptr<const T> &ptr); | ||||
| } | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
| class Model; | ||||
|  | @ -526,8 +533,23 @@ private: | |||
| 	ModelVolume() : ObjectBase(-1), config(-1), object(nullptr) { | ||||
| 		assert(this->id().invalid()); assert(this->config.id().invalid()); | ||||
| 	} | ||||
| 	template<class Archive> void serialize(Archive &ar) { | ||||
| 		ar(name, config, m_mesh, m_type, m_material_id, m_convex_hull, m_transformation, m_is_splittable); | ||||
| 	template<class Archive> void load(Archive &ar) { | ||||
| 		bool has_convex_hull; | ||||
| 		ar(name, config, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull); | ||||
| 		assert(m_mesh); | ||||
| 		if (has_convex_hull) { | ||||
| 			cereal::load_optional(ar, m_convex_hull); | ||||
| 			if (! m_convex_hull && ! m_mesh->empty()) | ||||
| 				// The convex hull was released from the Undo / Redo stack to conserve memory. Recalculate it.
 | ||||
| 				this->calculate_convex_hull(); | ||||
| 		} else | ||||
| 			m_convex_hull.reset(); | ||||
| 	} | ||||
| 	template<class Archive> void save(Archive &ar) const { | ||||
| 		bool has_convex_hull = m_convex_hull.get() != nullptr; | ||||
| 		ar(name, config, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull); | ||||
| 		if (has_convex_hull) | ||||
| 			cereal::save_optional(ar, m_convex_hull); | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
|  | @ -739,6 +761,12 @@ extern bool model_object_list_extended(const Model &model_old, const Model &mode | |||
| // than the old ModelObject.
 | ||||
| extern bool model_volume_list_changed(const ModelObject &model_object_old, const ModelObject &model_object_new, const ModelVolumeType type); | ||||
| 
 | ||||
| // If the model has multi-part objects, then it is currently not supported by the SLA mode.
 | ||||
| // Either the model cannot be loaded, or a SLA printer has to be activated.
 | ||||
| extern bool model_has_multi_part_objects(const Model &model); | ||||
| // If the model has advanced features, then it cannot be processed in simple mode.
 | ||||
| extern bool model_has_advanced_features(const Model &model); | ||||
| 
 | ||||
| #ifndef NDEBUG | ||||
| // Verify whether the IDs of Model / ModelObject / ModelVolume / ModelInstance / ModelMaterial are valid and unique.
 | ||||
| void check_model_ids_validity(const Model &model); | ||||
|  | @ -747,4 +775,9 @@ void check_model_ids_equal(const Model &model1, const Model &model2); | |||
| 
 | ||||
| } // namespace Slic3r
 | ||||
| 
 | ||||
| namespace cereal | ||||
| { | ||||
| 	template <class Archive> struct specialize<Archive, Slic3r::ModelVolume, cereal::specialization::member_load_save> {}; | ||||
| } | ||||
| 
 | ||||
| #endif /* slic3r_Model_hpp_ */ | ||||
|  |  | |||
|  | @ -386,7 +386,7 @@ static inline std::vector<Vec2f> poisson_disk_from_samples(const std::vector<Vec | |||
|             } else { | ||||
|                 // This is a new cell.
 | ||||
|                 PoissonDiskGridEntry data; | ||||
|                 data.first_sample_idx = i; | ||||
|                 data.first_sample_idx = int(i); | ||||
|                 data.sample_cnt       = 1; | ||||
|                 auto result     = cells.insert({sample.cell_id, data}); | ||||
|                 last_cell_id    = sample.cell_id; | ||||
|  |  | |||
|  | @ -12,10 +12,18 @@ | |||
| #include "SLABoostAdapter.hpp" | ||||
| #include "boost/geometry/index/rtree.hpp" | ||||
| 
 | ||||
| #ifdef _MSC_VER | ||||
| #pragma warning(push) | ||||
| #pragma warning(disable: 4244) | ||||
| #pragma warning(disable: 4267) | ||||
| #endif | ||||
| #include <igl/ray_mesh_intersect.h> | ||||
| #include <igl/point_mesh_squared_distance.h> | ||||
| #include <igl/remove_duplicate_vertices.h> | ||||
| #include <igl/signed_distance.h> | ||||
| #ifdef _MSC_VER | ||||
| #pragma warning(pop) | ||||
| #endif | ||||
| 
 | ||||
| #include <tbb/parallel_for.h> | ||||
| 
 | ||||
|  |  | |||
|  | @ -607,6 +607,40 @@ void TriangleMesh::require_shared_vertices() | |||
|     BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - end"; | ||||
| } | ||||
| 
 | ||||
| size_t TriangleMesh::memsize() const | ||||
| { | ||||
| 	size_t memsize = 8 + this->stl.memsize() + this->its.memsize(); | ||||
| 	return memsize; | ||||
| } | ||||
| 
 | ||||
| // Release optional data from the mesh if the object is on the Undo / Redo stack only. Returns the amount of memory released.
 | ||||
| size_t TriangleMesh::release_optional() | ||||
| { | ||||
| 	size_t memsize_released = sizeof(stl_neighbors) * this->stl.neighbors_start.size() + this->its.memsize(); | ||||
| 	// The indexed triangle set may be recalculated using the stl_generate_shared_vertices() function.
 | ||||
| 	this->its.clear(); | ||||
| 	// The neighbors structure may be recalculated using the stl_check_facets_exact() function.
 | ||||
| 	this->stl.neighbors_start.clear(); | ||||
| 	return memsize_released; | ||||
| } | ||||
| 
 | ||||
| // Restore optional data possibly released by release_optional().
 | ||||
| void TriangleMesh::restore_optional() | ||||
| { | ||||
| 	if (! this->stl.facet_start.empty()) { | ||||
| 		// Save the old stats before calling stl_check_faces_exact, as it may modify the statistics.
 | ||||
| 		stl_stats stats = this->stl.stats; | ||||
| 		if (this->stl.neighbors_start.empty()) { | ||||
| 			stl_reallocate(&this->stl); | ||||
| 			stl_check_facets_exact(&this->stl); | ||||
| 		} | ||||
| 		if (this->its.vertices.empty()) | ||||
| 			stl_generate_shared_vertices(&this->stl, this->its); | ||||
| 		// Restore the old statistics.
 | ||||
| 		this->stl.stats = stats; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| void TriangleMeshSlicer::init(const TriangleMesh *_mesh, throw_on_cancel_callback_type throw_on_cancel) | ||||
| { | ||||
|     mesh = _mesh; | ||||
|  |  | |||
|  | @ -67,6 +67,12 @@ public: | |||
|     size_t facets_count() const { return this->stl.stats.number_of_facets; } | ||||
|     bool   empty() const { return this->facets_count() == 0; } | ||||
|     bool is_splittable() const; | ||||
|     // Estimate of the memory occupied by this structure, important for keeping an eye on the Undo / Redo stack allocation.
 | ||||
|     size_t memsize() const; | ||||
|     // Release optional data from the mesh if the object is on the Undo / Redo stack only. Returns the amount of memory released.
 | ||||
|     size_t release_optional(); | ||||
| 	// Restore optional data possibly released by release_optional().
 | ||||
| 	void restore_optional(); | ||||
| 
 | ||||
|     stl_file stl; | ||||
|     indexed_triangle_set its; | ||||
|  |  | |||
|  | @ -21,6 +21,8 @@ extern std::string format_memsize_MB(size_t n); | |||
| // The string is non-empty only if the loglevel >= info (3).
 | ||||
| extern std::string log_memory_info(); | ||||
| extern void disable_multi_threading(); | ||||
| // Returns the size of physical memory (RAM) in bytes.
 | ||||
| extern size_t total_physical_memory(); | ||||
| 
 | ||||
| // Set a path with GUI resource files.
 | ||||
| void set_var_dir(const std::string &path); | ||||
|  |  | |||
|  | @ -7,10 +7,15 @@ | |||
| #include <stdio.h> | ||||
| 
 | ||||
| #ifdef WIN32 | ||||
| #include <windows.h> | ||||
| #include <psapi.h> | ||||
| 	#include <windows.h> | ||||
| 	#include <psapi.h> | ||||
| #else | ||||
| #include <unistd.h> | ||||
| 	#include <unistd.h> | ||||
| 	#include <sys/types.h> | ||||
| 	#include <sys/param.h> | ||||
| 	#ifdef BSD | ||||
| 		#include <sys/sysctl.h> | ||||
| 	#endif | ||||
| #endif | ||||
| 
 | ||||
| #include <boost/log/core.hpp> | ||||
|  | @ -467,4 +472,75 @@ std::string log_memory_info() | |||
| } | ||||
| #endif | ||||
| 
 | ||||
| // Returns the size of physical memory (RAM) in bytes.
 | ||||
| // http://nadeausoftware.com/articles/2012/09/c_c_tip_how_get_physical_memory_size_system
 | ||||
| size_t total_physical_memory() | ||||
| { | ||||
| #if defined(_WIN32) && (defined(__CYGWIN__) || defined(__CYGWIN32__)) | ||||
| 	// Cygwin under Windows. ------------------------------------
 | ||||
| 	// New 64-bit MEMORYSTATUSEX isn't available.  Use old 32.bit
 | ||||
| 	MEMORYSTATUS status; | ||||
| 	status.dwLength = sizeof(status); | ||||
| 	GlobalMemoryStatus( &status ); | ||||
| 	return (size_t)status.dwTotalPhys; | ||||
| #elif defined(_WIN32) | ||||
| 	// Windows. -------------------------------------------------
 | ||||
| 	// Use new 64-bit MEMORYSTATUSEX, not old 32-bit MEMORYSTATUS
 | ||||
| 	MEMORYSTATUSEX status; | ||||
| 	status.dwLength = sizeof(status); | ||||
| 	GlobalMemoryStatusEx( &status ); | ||||
| 	return (size_t)status.ullTotalPhys; | ||||
| #elif defined(__unix__) || defined(__unix) || defined(unix) || (defined(__APPLE__) && defined(__MACH__)) | ||||
| 	// UNIX variants. -------------------------------------------
 | ||||
| 	// Prefer sysctl() over sysconf() except sysctl() HW_REALMEM and HW_PHYSMEM
 | ||||
| 
 | ||||
| #if defined(CTL_HW) && (defined(HW_MEMSIZE) || defined(HW_PHYSMEM64)) | ||||
| 	int mib[2]; | ||||
| 	mib[0] = CTL_HW; | ||||
| #if defined(HW_MEMSIZE) | ||||
| 	mib[1] = HW_MEMSIZE;            // OSX. ---------------------
 | ||||
| #elif defined(HW_PHYSMEM64) | ||||
| 	mib[1] = HW_PHYSMEM64;          // NetBSD, OpenBSD. ---------
 | ||||
| #endif | ||||
| 	int64_t size = 0;               // 64-bit
 | ||||
| 	size_t len = sizeof( size ); | ||||
| 	if ( sysctl( mib, 2, &size, &len, NULL, 0 ) == 0 ) | ||||
| 		return (size_t)size; | ||||
| 	return 0L;			// Failed?
 | ||||
| 
 | ||||
| #elif defined(_SC_AIX_REALMEM) | ||||
| 	// AIX. -----------------------------------------------------
 | ||||
| 	return (size_t)sysconf( _SC_AIX_REALMEM ) * (size_t)1024L; | ||||
| 
 | ||||
| #elif defined(_SC_PHYS_PAGES) && defined(_SC_PAGESIZE) | ||||
| 	// FreeBSD, Linux, OpenBSD, and Solaris. --------------------
 | ||||
| 	return (size_t)sysconf( _SC_PHYS_PAGES ) * | ||||
| 		(size_t)sysconf( _SC_PAGESIZE ); | ||||
| 
 | ||||
| #elif defined(_SC_PHYS_PAGES) && defined(_SC_PAGE_SIZE) | ||||
| 	// Legacy. --------------------------------------------------
 | ||||
| 	return (size_t)sysconf( _SC_PHYS_PAGES ) * | ||||
| 		(size_t)sysconf( _SC_PAGE_SIZE ); | ||||
| 
 | ||||
| #elif defined(CTL_HW) && (defined(HW_PHYSMEM) || defined(HW_REALMEM)) | ||||
| 	// DragonFly BSD, FreeBSD, NetBSD, OpenBSD, and OSX. --------
 | ||||
| 	int mib[2]; | ||||
| 	mib[0] = CTL_HW; | ||||
| #if defined(HW_REALMEM) | ||||
| 	mib[1] = HW_REALMEM;		// FreeBSD. -----------------
 | ||||
| #elif defined(HW_PYSMEM) | ||||
| 	mib[1] = HW_PHYSMEM;		// Others. ------------------
 | ||||
| #endif | ||||
| 	unsigned int size = 0;		// 32-bit
 | ||||
| 	size_t len = sizeof( size ); | ||||
| 	if ( sysctl( mib, 2, &size, &len, NULL, 0 ) == 0 ) | ||||
| 		return (size_t)size; | ||||
| 	return 0L;			// Failed?
 | ||||
| #endif // sysctl and sysconf variants
 | ||||
| 
 | ||||
| #else | ||||
| 	return 0L;			// Unknown OS.
 | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| }; // namespace Slic3r
 | ||||
|  |  | |||
|  | @ -135,8 +135,7 @@ void config_wizard(int reason) | |||
| 
 | ||||
| 	wxGetApp().load_current_presets(); | ||||
| 
 | ||||
|     if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA && | ||||
|         wxGetApp().obj_list()->has_multi_part_objects()) | ||||
|     if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA && model_has_multi_part_objects(wxGetApp().model())) | ||||
|     { | ||||
|         show_info(nullptr, | ||||
|             _(L("It's impossible to print multi-part object(s) with SLA technology.")) + "\n\n" + | ||||
|  |  | |||
|  | @ -537,7 +537,7 @@ void GUI_App::persist_window_geometry(wxTopLevelWindow *window, bool default_max | |||
|     }); | ||||
| } | ||||
| 
 | ||||
| void GUI_App::load_project(wxWindow *parent, wxString& input_file) | ||||
| void GUI_App::load_project(wxWindow *parent, wxString& input_file) const | ||||
| { | ||||
|     input_file.Clear(); | ||||
|     wxFileDialog dialog(parent ? parent : GetTopWindow(), | ||||
|  | @ -549,7 +549,7 @@ void GUI_App::load_project(wxWindow *parent, wxString& input_file) | |||
|         input_file = dialog.GetPath(); | ||||
| } | ||||
| 
 | ||||
| void GUI_App::import_model(wxWindow *parent, wxArrayString& input_files) | ||||
| void GUI_App::import_model(wxWindow *parent, wxArrayString& input_files) const | ||||
| { | ||||
|     input_files.Clear(); | ||||
|     wxFileDialog dialog(parent ? parent : GetTopWindow(), | ||||
|  | @ -857,7 +857,7 @@ void GUI_App::add_config_menu(wxMenuBar *menu) | |||
| 
 | ||||
| // This is called when closing the application, when loading a config file or when starting the config wizard
 | ||||
| // to notify the user whether he is aware that some preset changes will be lost.
 | ||||
| bool GUI_App::check_unsaved_changes() | ||||
| bool GUI_App::check_unsaved_changes(const wxString &header) | ||||
| { | ||||
|     wxString dirty; | ||||
|     PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); | ||||
|  | @ -871,8 +871,12 @@ bool GUI_App::check_unsaved_changes() | |||
|         // No changes, the application may close or reload presets.
 | ||||
|         return true; | ||||
|     // Ask the user.
 | ||||
|     wxString message; | ||||
|     if (! header.empty()) | ||||
|     	message = header + "\n\n"; | ||||
|     message += _(L("The presets on the following tabs were modified")) + ": " + dirty + "\n\n" + _(L("Discard changes and continue anyway?")); | ||||
|     wxMessageDialog dialog(mainframe, | ||||
|         _(L("The presets on the following tabs were modified")) + ": " + dirty + "\n\n" + _(L("Discard changes and continue anyway?")), | ||||
|         message, | ||||
|         wxString(SLIC3R_APP_NAME) + " - " + _(L("Unsaved Presets")), | ||||
|         wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT); | ||||
|     return dialog.ShowModal() == wxID_YES; | ||||
|  | @ -947,9 +951,9 @@ Plater* GUI_App::plater() | |||
|     return plater_; | ||||
| } | ||||
| 
 | ||||
| ModelObjectPtrs* GUI_App::model_objects() | ||||
| Model& GUI_App::model() | ||||
| { | ||||
|     return &plater_->model().objects; | ||||
|     return plater_->model(); | ||||
| } | ||||
| 
 | ||||
| wxNotebook* GUI_App::tab_panel() const | ||||
|  |  | |||
|  | @ -124,8 +124,8 @@ public: | |||
|     void            recreate_GUI(); | ||||
|     void            system_info(); | ||||
|     void            keyboard_shortcuts(); | ||||
|     void            load_project(wxWindow *parent, wxString& input_file); | ||||
|     void            import_model(wxWindow *parent, wxArrayString& input_files); | ||||
|     void            load_project(wxWindow *parent, wxString& input_file) const; | ||||
|     void            import_model(wxWindow *parent, wxArrayString& input_files) const; | ||||
|     static bool     catch_error(std::function<void()> cb, const std::string& err); | ||||
| 
 | ||||
|     void            persist_window_geometry(wxTopLevelWindow *window, bool default_maximized = false); | ||||
|  | @ -142,7 +142,7 @@ public: | |||
|     void            update_mode(); | ||||
| 
 | ||||
|     void            add_config_menu(wxMenuBar *menu); | ||||
|     bool            check_unsaved_changes(); | ||||
|     bool            check_unsaved_changes(const wxString &header = wxString()); | ||||
|     bool            checked_tab(Tab* tab); | ||||
|     void            load_current_presets(); | ||||
| 
 | ||||
|  | @ -161,7 +161,7 @@ public: | |||
|     ObjectList*         obj_list(); | ||||
|     ObjectLayers*       obj_layers(); | ||||
|     Plater*             plater(); | ||||
|     std::vector<ModelObject*> *model_objects(); | ||||
|     Model&      		model(); | ||||
| 
 | ||||
|     AppConfig*      app_config{ nullptr }; | ||||
|     PresetBundle*   preset_bundle{ nullptr }; | ||||
|  |  | |||
|  | @ -71,9 +71,6 @@ static void take_snapshot(const wxString& snapshot_name) | |||
|     wxGetApp().plater()->take_snapshot(snapshot_name); | ||||
| } | ||||
| 
 | ||||
| static void suppress_snapshots(){ wxGetApp().plater()->suppress_snapshots(); } | ||||
| static void allow_snapshots()   { wxGetApp().plater()->allow_snapshots(); } | ||||
| 
 | ||||
| ObjectList::ObjectList(wxWindow* parent) : | ||||
|     wxDataViewCtrl(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxDV_MULTIPLE), | ||||
|     m_parent(parent) | ||||
|  | @ -2406,8 +2403,7 @@ void ObjectList::remove() | |||
| 
 | ||||
|     wxDataViewItem  parent = wxDataViewItem(0); | ||||
| 
 | ||||
|     take_snapshot(_(L("Delete Selected"))); | ||||
|     suppress_snapshots(); | ||||
|     Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Delete Selected"))); | ||||
| 
 | ||||
|     for (auto& item : sels) | ||||
|     { | ||||
|  | @ -2429,8 +2425,6 @@ void ObjectList::remove() | |||
| 
 | ||||
|     if (parent) | ||||
|         select_item(parent); | ||||
| 
 | ||||
|     allow_snapshots(); | ||||
| } | ||||
| 
 | ||||
| void ObjectList::del_layer_range(const t_layer_height_range& range) | ||||
|  | @ -2505,8 +2499,7 @@ void ObjectList::add_layer_range_after_current(const t_layer_height_range& curre | |||
|              | ||||
|             t_layer_height_range new_range = { midl_layer, next_range.second }; | ||||
| 
 | ||||
|             take_snapshot(_(L("Add New Layers Range"))); | ||||
|             suppress_snapshots(); | ||||
|             Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Add New Layers Range"))); | ||||
| 
 | ||||
|             // create new 2 layers instead of deleted one
 | ||||
| 
 | ||||
|  | @ -2521,7 +2514,6 @@ void ObjectList::add_layer_range_after_current(const t_layer_height_range& curre | |||
|             new_range = { current_range.second, midl_layer }; | ||||
|             ranges[new_range] = get_default_layer_config(obj_idx); | ||||
|             add_layer_item(new_range, layers_item, layer_idx); | ||||
|             allow_snapshots(); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|  | @ -2611,7 +2603,7 @@ bool ObjectList::edit_layer_range(const t_layer_height_range& range, const t_lay | |||
| 
 | ||||
| void ObjectList::init_objects() | ||||
| { | ||||
|     m_objects = wxGetApp().model_objects(); | ||||
|     m_objects = &wxGetApp().model().objects; | ||||
| } | ||||
| 
 | ||||
| bool ObjectList::multiple_selection() const  | ||||
|  | @ -3088,19 +3080,6 @@ void ObjectList::last_volume_is_deleted(const int obj_idx) | |||
|     volume->config.set_key_value("extruder", new ConfigOptionInt(0)); | ||||
| } | ||||
| 
 | ||||
| bool ObjectList::has_multi_part_objects() | ||||
| { | ||||
|     if (!m_objects_model->IsEmpty()) { | ||||
|         wxDataViewItemArray items; | ||||
|         m_objects_model->GetChildren(wxDataViewItem(0), items); | ||||
| 
 | ||||
|         for (auto& item : items) | ||||
|             if (m_objects_model->GetItemByType(item, itVolume)) | ||||
|                 return true; | ||||
|     } | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| /* #lm_FIXME_delete_after_testing
 | ||||
| void ObjectList::update_settings_items() | ||||
| { | ||||
|  | @ -3531,7 +3510,7 @@ void ObjectList::update_after_undo_redo() | |||
|     m_prevent_list_events = true; | ||||
|     m_prevent_canvas_selection_update = true; | ||||
| 
 | ||||
|     suppress_snapshots(); | ||||
|     Plater::SuppressSnapshots suppress(wxGetApp().plater()); | ||||
| 
 | ||||
|     // Unselect all objects before deleting them, so that no change of selection is emitted during deletion.
 | ||||
|     this->UnselectAll(); | ||||
|  | @ -3543,8 +3522,6 @@ void ObjectList::update_after_undo_redo() | |||
|         ++obj_idx; | ||||
|     } | ||||
| 
 | ||||
|     allow_snapshots(); | ||||
| 
 | ||||
| #ifndef __WXOSX__  | ||||
|     selection_changed(); | ||||
| #endif /* __WXOSX__ */ | ||||
|  |  | |||
|  | @ -317,7 +317,6 @@ public: | |||
|     void change_part_type(); | ||||
| 
 | ||||
|     void last_volume_is_deleted(const int obj_idx); | ||||
|     bool has_multi_part_objects(); | ||||
|     void update_settings_items(); | ||||
|     void update_and_show_object_settings_item(); | ||||
|     void update_settings_item_and_selection(wxDataViewItem item, wxDataViewItemArray& selections); | ||||
|  |  | |||
|  | @ -25,7 +25,7 @@ static double get_volume_min_z(const GLVolume* volume) | |||
|     const Transform3f& world_matrix = volume->world_matrix().cast<float>(); | ||||
| 
 | ||||
|     // need to get the ModelVolume pointer
 | ||||
|     const ModelObject* mo = wxGetApp().model_objects()->at(volume->composite_id.object_id); | ||||
|     const ModelObject* mo = wxGetApp().model().objects[volume->composite_id.object_id]; | ||||
|     const ModelVolume* mv = mo->volumes[volume->composite_id.volume_id]; | ||||
|     const TriangleMesh& hull = mv->get_convex_hull(); | ||||
| 
 | ||||
|  | @ -466,7 +466,7 @@ void ObjectManipulation::update_settings_value(const Selection& selection) | |||
| 			m_new_scale    = m_new_size.cwiseProduct(selection.get_unscaled_instance_bounding_box().size().cwiseInverse()) * 100.; | ||||
| 		} else { | ||||
| 			m_new_rotation = volume->get_instance_rotation() * (180. / M_PI); | ||||
| 			m_new_size     = volume->get_instance_transformation().get_scaling_factor().cwiseProduct((*wxGetApp().model_objects())[volume->object_idx()]->raw_mesh_bounding_box().size()); | ||||
| 			m_new_size     = volume->get_instance_transformation().get_scaling_factor().cwiseProduct(wxGetApp().model().objects[volume->object_idx()]->raw_mesh_bounding_box().size()); | ||||
| 			m_new_scale    = volume->get_instance_scaling_factor() * 100.; | ||||
| 		} | ||||
| 
 | ||||
|  | @ -779,7 +779,7 @@ void ObjectManipulation::change_size_value(int axis, double value) | |||
|     else if (selection.is_single_full_instance()) | ||||
| 		ref_size = m_world_coordinates ?  | ||||
|             selection.get_unscaled_instance_bounding_box().size() : | ||||
|             (*wxGetApp().model_objects())[selection.get_volume(*selection.get_volume_idxs().begin())->object_idx()]->raw_mesh_bounding_box().size(); | ||||
|             wxGetApp().model().objects[selection.get_volume(*selection.get_volume_idxs().begin())->object_idx()]->raw_mesh_bounding_box().size(); | ||||
| 
 | ||||
|     this->do_scale(axis, 100. * Vec3d(size(0) / ref_size(0), size(1) / ref_size(1), size(2) / ref_size(2))); | ||||
| 
 | ||||
|  | @ -902,7 +902,7 @@ void ObjectManipulation::set_uniform_scaling(const bool new_value) | |||
|                 return; | ||||
|             } | ||||
|             // Bake the rotation into the meshes of the object.
 | ||||
|             (*wxGetApp().model_objects())[volume->composite_id.object_id]->bake_xy_rotation_into_meshes(volume->composite_id.instance_id); | ||||
|             wxGetApp().model().objects[volume->composite_id.object_id]->bake_xy_rotation_into_meshes(volume->composite_id.instance_id); | ||||
|             // Update the 3D scene, selections etc.
 | ||||
|             wxGetApp().plater()->update(); | ||||
|             // Recalculate cached values at this panel, refresh the screen.
 | ||||
|  |  | |||
|  | @ -29,7 +29,7 @@ void GLGizmoFlatten::on_set_state() | |||
|     // m_model_object pointer can be invalid (for instance because of undo/redo action),
 | ||||
|     // we should recover it from the object id
 | ||||
|     m_model_object = nullptr; | ||||
|     for (const auto mo : *wxGetApp().model_objects()) { | ||||
|     for (const auto mo : wxGetApp().model().objects) { | ||||
|         if (mo->id() == m_model_object_id) { | ||||
|             m_model_object = mo; | ||||
|             break; | ||||
|  |  | |||
|  | @ -1049,7 +1049,7 @@ void GLGizmoSlaSupports::on_set_state() | |||
|     // we should recover it from the object id
 | ||||
|     const ModelObject* old_model_object = m_model_object; | ||||
|     m_model_object = nullptr; | ||||
|     for (const auto mo : *wxGetApp().model_objects()) { | ||||
|     for (const auto mo : wxGetApp().model().objects) { | ||||
|         if (mo->id() == m_current_mesh_object_id) { | ||||
|             m_model_object = mo; | ||||
|             break; | ||||
|  |  | |||
|  | @ -1255,13 +1255,9 @@ 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) { | ||||
|         fs::path path(into_path(filename)); | ||||
| 
 | ||||
|         if (std::regex_match(path.string(), pattern_drop)) { | ||||
|             paths.push_back(std::move(path)); | ||||
|         } else { | ||||
|  | @ -1269,6 +1265,23 @@ bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &fi | |||
|         } | ||||
|     } | ||||
| 
 | ||||
| 	wxString snapshot_label; | ||||
| 	assert(! paths.empty()); | ||||
| 	if (paths.size() == 1) { | ||||
| 		snapshot_label = _(L("Load File")); | ||||
| 		snapshot_label += ": "; | ||||
| 		snapshot_label += wxString::FromUTF8(paths.front().filename().string().c_str()); | ||||
| 	} else { | ||||
| 		snapshot_label = _(L("Load Files")); | ||||
| 		snapshot_label += ": "; | ||||
| 		snapshot_label += wxString::FromUTF8(paths.front().filename().string().c_str()); | ||||
| 		for (size_t i = 1; i < paths.size(); ++ i) { | ||||
| 			snapshot_label += ", "; | ||||
| 			snapshot_label += wxString::FromUTF8(paths[i].filename().string().c_str()); | ||||
| 		} | ||||
| 	} | ||||
| 	Plater::TakeSnapshot snapshot(plater, snapshot_label); | ||||
| 
 | ||||
|     // FIXME: when drag and drop is done on a .3mf or a .amf file we should clear the plater for consistence with the open project command
 | ||||
|     // (the following call to plater->load_files() will load the config data, if present)
 | ||||
| 
 | ||||
|  | @ -1595,7 +1608,7 @@ struct Plater::priv | |||
|     priv(Plater *q, MainFrame *main_frame); | ||||
|     ~priv(); | ||||
| 
 | ||||
|     void update(bool force_full_scene_refresh = false); | ||||
|     void update(bool force_full_scene_refresh = false, bool force_background_processing_update = false); | ||||
|     void select_view(const std::string& direction); | ||||
|     void select_view_3D(const std::string& name); | ||||
|     void select_next_view_3D(); | ||||
|  | @ -1633,14 +1646,18 @@ struct Plater::priv | |||
|         if (this->m_prevent_snapshots > 0)  | ||||
|             return; | ||||
|         assert(this->m_prevent_snapshots >= 0); | ||||
|         this->undo_redo_stack.take_snapshot(snapshot_name, model, view3D->get_canvas3d()->get_selection(), view3D->get_canvas3d()->get_gizmos_manager()); | ||||
|     } | ||||
|         this->undo_redo_stack.take_snapshot(snapshot_name, model, view3D->get_canvas3d()->get_selection(), view3D->get_canvas3d()->get_gizmos_manager(), this->printer_technology); | ||||
| 	    this->undo_redo_stack.release_least_recently_used(); | ||||
| 	    // Save the last active preset name of a particular printer technology.
 | ||||
| 	    ((this->printer_technology == ptFFF) ? m_last_fff_printer_profile_name : m_last_sla_printer_profile_name) = wxGetApp().preset_bundle->printers.get_selected_preset_name(); | ||||
|     	BOOST_LOG_TRIVIAL(info) << "Undo / Redo snapshot taken: " << snapshot_name << ", Undo / Redo stack memory: " << Slic3r::format_memsize_MB(this->undo_redo_stack.memsize()) << log_memory_info(); | ||||
| 	} | ||||
| 	void take_snapshot(const wxString& snapshot_name) { this->take_snapshot(std::string(snapshot_name.ToUTF8().data())); } | ||||
|     int  get_active_snapshot_index(); | ||||
|     void undo(); | ||||
|     void redo(); | ||||
|     void undo_to(size_t time_to_load); | ||||
|     void redo_to(size_t time_to_load); | ||||
|     void undo_redo_to(size_t time_to_load); | ||||
| 
 | ||||
|     void suppress_snapshots()   { this->m_prevent_snapshots++; } | ||||
|     void allow_snapshots()      { this->m_prevent_snapshots--; } | ||||
| 
 | ||||
|  | @ -1734,10 +1751,13 @@ private: | |||
| 
 | ||||
|     void update_fff_scene(); | ||||
|     void update_sla_scene(); | ||||
|     void update_after_undo_redo(); | ||||
| 	void undo_redo_to(std::vector<UndoRedo::Snapshot>::const_iterator it_snapshot); | ||||
|     void update_after_undo_redo(bool temp_snapshot_was_taken = false); | ||||
| 
 | ||||
|     // path to project file stored with no extension
 | ||||
|     wxString m_project_filename; | ||||
|     std::string m_last_fff_printer_profile_name; | ||||
|     std::string m_last_sla_printer_profile_name; | ||||
| }; | ||||
| 
 | ||||
| const std::regex Plater::priv::pattern_bundle(".*[.](amf|amf[.]xml|zip[.]amf|3mf|prusa)", std::regex::icase); | ||||
|  | @ -1896,7 +1916,7 @@ Plater::priv::~priv() | |||
|         delete config; | ||||
| } | ||||
| 
 | ||||
| void Plater::priv::update(bool force_full_scene_refresh) | ||||
| void Plater::priv::update(bool force_full_scene_refresh, bool force_background_processing_update) | ||||
| { | ||||
|     // the following line, when enabled, causes flickering on NVIDIA graphics cards
 | ||||
| //    wxWindowUpdateLocker freeze_guard(q);
 | ||||
|  | @ -1909,7 +1929,7 @@ void Plater::priv::update(bool force_full_scene_refresh) | |||
|     } | ||||
| 
 | ||||
|     unsigned int update_status = 0; | ||||
|     if (this->printer_technology == ptSLA) | ||||
|     if (this->printer_technology == ptSLA || force_background_processing_update) | ||||
|         // Update the SLAPrint from the current Model, so that the reload_scene()
 | ||||
|         // pulls the correct data.
 | ||||
|         update_status = this->update_background_process(false); | ||||
|  | @ -2095,66 +2115,22 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_ | |||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             else if ((wxGetApp().get_mode() == comSimple) && (type_3mf || type_any_amf)) | ||||
|             { | ||||
|                 bool advanced = false; | ||||
|                 for (const ModelObject* model_object : model.objects) | ||||
|             else if ((wxGetApp().get_mode() == comSimple) && (type_3mf || type_any_amf) && model_has_advanced_features(model)) { | ||||
|                 wxMessageDialog dlg(q, _(L("This file cannot be loaded in a simple mode. Do you want to switch to an advanced mode?\n")), | ||||
|                     _(L("Detected advanced data")), wxICON_WARNING | wxYES | wxNO); | ||||
|                 if (dlg.ShowModal() == wxID_YES) | ||||
|                 { | ||||
|                     // is there more than one instance ?
 | ||||
|                     if (model_object->instances.size() > 1) | ||||
|                     { | ||||
|                         advanced = true; | ||||
|                         break; | ||||
|                     } | ||||
| 
 | ||||
|                     // is there any advanced config data ?
 | ||||
|                     auto opt_keys = model_object->config.keys(); | ||||
|                     if (!opt_keys.empty() && !((opt_keys.size() == 1) && (opt_keys[0] == "extruder"))) | ||||
|                     { | ||||
|                         advanced = true; | ||||
|                         break; | ||||
|                     } | ||||
| 
 | ||||
|                     // is there any modifier ?
 | ||||
|                     for (const ModelVolume* model_volume : model_object->volumes) | ||||
|                     { | ||||
|                         if (!model_volume->is_model_part()) | ||||
|                         { | ||||
|                             advanced = true; | ||||
|                             break; | ||||
|                         } | ||||
| 
 | ||||
|                         // is there any advanced config data ?
 | ||||
|                         opt_keys = model_volume->config.keys(); | ||||
|                         if (!opt_keys.empty() && !((opt_keys.size() == 1) && (opt_keys[0] == "extruder"))) | ||||
|                         { | ||||
|                             advanced = true; | ||||
|                             break; | ||||
|                         } | ||||
|                     } | ||||
| 
 | ||||
|                     if (advanced) | ||||
|                         break; | ||||
|                 } | ||||
| 
 | ||||
|                 if (advanced) | ||||
|                 { | ||||
|                     wxMessageDialog dlg(q, _(L("This file cannot be loaded in a simple mode. Do you want to switch to an advanced mode?\n")), | ||||
|                         _(L("Detected advanced data")), wxICON_WARNING | wxYES | wxNO); | ||||
|                     if (dlg.ShowModal() == wxID_YES) | ||||
|                     { | ||||
|                         Slic3r::GUI::wxGetApp().save_mode(comAdvanced); | ||||
|                         view3D->set_as_dirty(); | ||||
|                     } | ||||
|                     else | ||||
|                         return obj_idxs; | ||||
|                     Slic3r::GUI::wxGetApp().save_mode(comAdvanced); | ||||
|                     view3D->set_as_dirty(); | ||||
|                 } | ||||
|                 else | ||||
|                     return obj_idxs; | ||||
|             } | ||||
| 
 | ||||
|                 for (ModelObject* model_object : model.objects) { | ||||
|                     model_object->center_around_origin(false); | ||||
|                     model_object->ensure_on_bed(); | ||||
|                 } | ||||
|             for (ModelObject* model_object : model.objects) { | ||||
|                 model_object->center_around_origin(false); | ||||
|                 model_object->ensure_on_bed(); | ||||
|             } | ||||
| 
 | ||||
|             // check multi-part object adding for the SLA-printing
 | ||||
|             if (printer_technology == ptSLA) | ||||
|  | @ -2467,7 +2443,10 @@ void Plater::priv::remove(size_t obj_idx) | |||
| 
 | ||||
| void Plater::priv::delete_object_from_model(size_t obj_idx) | ||||
| { | ||||
|     this->take_snapshot(_(L("Delete Object"))); | ||||
| 	wxString snapshot_label = _(L("Delete Object")); | ||||
| 	if (! model.objects[obj_idx]->name.empty()) | ||||
| 		snapshot_label += ": " + wxString::FromUTF8(model.objects[obj_idx]->name.c_str()); | ||||
| 	Plater::TakeSnapshot snapshot(q, snapshot_label); | ||||
|     model.delete_object(obj_idx); | ||||
|     update(); | ||||
|     object_list_changed(); | ||||
|  | @ -2475,7 +2454,7 @@ void Plater::priv::delete_object_from_model(size_t obj_idx) | |||
| 
 | ||||
| void Plater::priv::reset() | ||||
| { | ||||
| 	this->take_snapshot(_(L("Reset Project"))); | ||||
| 	Plater::TakeSnapshot snapshot(q, _(L("Reset Project"))); | ||||
| 
 | ||||
|     set_project_filename(wxEmptyString); | ||||
| 
 | ||||
|  | @ -2671,7 +2650,7 @@ void Plater::priv::split_object() | |||
|         Slic3r::GUI::warning_catcher(q, _(L("The selected object couldn't be split because it contains only one part."))); | ||||
|     else | ||||
|     { | ||||
|         this->take_snapshot(_(L("Split to Objects"))); | ||||
| 		Plater::TakeSnapshot snapshot(q, _(L("Split to Objects"))); | ||||
| 
 | ||||
|         unsigned int counter = 1; | ||||
|         for (ModelObject* m : new_objects) | ||||
|  | @ -2911,7 +2890,7 @@ void Plater::priv::update_sla_scene() | |||
| 
 | ||||
| void Plater::priv::reload_from_disk() | ||||
| { | ||||
|     this->take_snapshot(_(L("Reload from Disk"))); | ||||
| 	Plater::TakeSnapshot snapshot(q, _(L("Reload from Disk"))); | ||||
| 
 | ||||
|     const auto &selection = get_selection(); | ||||
|     const auto obj_orig_idx = selection.get_object_idx(); | ||||
|  | @ -2947,7 +2926,7 @@ void Plater::priv::fix_through_netfabb(const int obj_idx, const int vol_idx/* = | |||
|     if (obj_idx < 0) | ||||
|         return; | ||||
| 
 | ||||
|     this->take_snapshot(_(L("Fix Throught NetFabb"))); | ||||
| 	Plater::TakeSnapshot snapshot(q, _(L("Fix Throught NetFabb"))); | ||||
| 
 | ||||
|     fix_model_by_win10_sdk_gui(*model.objects[obj_idx], vol_idx); | ||||
|     this->update(); | ||||
|  | @ -3611,7 +3590,7 @@ void Plater::priv::show_action_buttons(const bool is_ready_to_slice) const | |||
| 
 | ||||
| int Plater::priv::get_active_snapshot_index() | ||||
| { | ||||
|     const size_t& active_snapshot_time = this->undo_redo_stack.active_snapshot_time(); | ||||
|     const size_t active_snapshot_time = this->undo_redo_stack.active_snapshot_time(); | ||||
|     const std::vector<UndoRedo::Snapshot>& ss_stack = this->undo_redo_stack.snapshots(); | ||||
|     const auto it = std::lower_bound(ss_stack.begin(), ss_stack.end(), UndoRedo::Snapshot(active_snapshot_time)); | ||||
|     return it - ss_stack.begin(); | ||||
|  | @ -3619,40 +3598,87 @@ int Plater::priv::get_active_snapshot_index() | |||
| 
 | ||||
| void Plater::priv::undo() | ||||
| { | ||||
|     if (this->undo_redo_stack.undo(model, this->view3D->get_canvas3d()->get_selection(), this->view3D->get_canvas3d()->get_gizmos_manager())) | ||||
|         this->update_after_undo_redo(); | ||||
| 	const std::vector<UndoRedo::Snapshot> &snapshots = this->undo_redo_stack.snapshots(); | ||||
| 	auto it_current = std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(this->undo_redo_stack.active_snapshot_time())); | ||||
| 	if (-- it_current != snapshots.begin()) | ||||
| 		this->undo_redo_to(it_current); | ||||
| } | ||||
| 
 | ||||
| void Plater::priv::redo() | ||||
| {  | ||||
|     if (this->undo_redo_stack.redo(model, this->view3D->get_canvas3d()->get_gizmos_manager())) | ||||
|         this->update_after_undo_redo(); | ||||
| 	const std::vector<UndoRedo::Snapshot> &snapshots = this->undo_redo_stack.snapshots(); | ||||
| 	auto it_current = std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(this->undo_redo_stack.active_snapshot_time())); | ||||
| 	if (++ it_current != snapshots.end()) | ||||
| 		this->undo_redo_to(it_current); | ||||
| } | ||||
| 
 | ||||
| void Plater::priv::undo_to(size_t time_to_load) | ||||
| void Plater::priv::undo_redo_to(size_t time_to_load) | ||||
| { | ||||
|     if (this->undo_redo_stack.undo(model, this->view3D->get_canvas3d()->get_selection(), this->view3D->get_canvas3d()->get_gizmos_manager(), time_to_load)) | ||||
|         this->update_after_undo_redo(); | ||||
| 	const std::vector<UndoRedo::Snapshot> &snapshots = this->undo_redo_stack.snapshots(); | ||||
| 	auto it_current = std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(time_to_load)); | ||||
| 	assert(it_current != snapshots.end()); | ||||
| 	this->undo_redo_to(it_current); | ||||
| } | ||||
| 
 | ||||
| void Plater::priv::redo_to(size_t time_to_load) | ||||
| {  | ||||
|     if (this->undo_redo_stack.redo(model, this->view3D->get_canvas3d()->get_gizmos_manager(), time_to_load)) | ||||
|         this->update_after_undo_redo(); | ||||
| } | ||||
| 
 | ||||
| void Plater::priv::update_after_undo_redo() | ||||
| void Plater::priv::undo_redo_to(std::vector<UndoRedo::Snapshot>::const_iterator it_snapshot) | ||||
| { | ||||
|     this->view3D->get_canvas3d()->get_selection().clear(); | ||||
| 	this->update(false); // update volumes from the deserializd model
 | ||||
| 	bool 				temp_snapshot_was_taken 	= this->undo_redo_stack.temp_snapshot_active(); | ||||
| 	PrinterTechnology 	new_printer_technology 		= it_snapshot->printer_technology; | ||||
| 	bool 				printer_technology_changed 	= this->printer_technology != new_printer_technology; | ||||
| 	if (printer_technology_changed) { | ||||
| 		// Switching the printer technology when jumping forwards / backwards in time. Switch to the last active printer profile of the other type.
 | ||||
| 		std::string s_pt = (it_snapshot->printer_technology == ptFFF) ? "FFF" : "SLA"; | ||||
| 		if (! wxGetApp().check_unsaved_changes(from_u8((boost::format(_utf8( | ||||
| 			L("%1% printer was active at the time the target Undo / Redo snapshot was taken. Switching to %1% printer requires reloading of %1% presets."))) % s_pt).str()))) | ||||
| 			// Don't switch the profiles.
 | ||||
| 			return; | ||||
| 	} | ||||
|     // Save the last active preset name of a particular printer technology.
 | ||||
|     ((this->printer_technology == ptFFF) ? m_last_fff_printer_profile_name : m_last_sla_printer_profile_name) = wxGetApp().preset_bundle->printers.get_selected_preset_name(); | ||||
|     // Do the jump in time.
 | ||||
|     if (it_snapshot->timestamp < this->undo_redo_stack.active_snapshot_time() ? | ||||
| 		this->undo_redo_stack.undo(model, this->view3D->get_canvas3d()->get_selection(), this->view3D->get_canvas3d()->get_gizmos_manager(), this->printer_technology, it_snapshot->timestamp) : | ||||
| 		this->undo_redo_stack.redo(model, this->view3D->get_canvas3d()->get_gizmos_manager(), it_snapshot->timestamp)) { | ||||
| 		if (printer_technology_changed) { | ||||
| 			// Switch to the other printer technology. Switch to the last printer active for that particular technology.
 | ||||
| 		    AppConfig *app_config = wxGetApp().app_config; | ||||
|     		app_config->set("presets", "printer", (new_printer_technology == ptFFF) ? m_last_fff_printer_profile_name : m_last_sla_printer_profile_name); | ||||
| 			wxGetApp().preset_bundle->load_presets(*app_config); | ||||
|         	// Load the currently selected preset into the GUI, update the preset selection box.
 | ||||
|         	// This also switches the printer technology based on the printer technology of the active printer profile.
 | ||||
|         	wxGetApp().load_current_presets(); | ||||
|         } | ||||
| 		this->update_after_undo_redo(temp_snapshot_was_taken); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| void Plater::priv::update_after_undo_redo(bool /* temp_snapshot_was_taken */) | ||||
| { | ||||
| 	this->view3D->get_canvas3d()->get_selection().clear(); | ||||
| 	// Update volumes from the deserializd model, always stop / update the background processing (for both the SLA and FFF technologies).
 | ||||
| 	this->update(false, true); | ||||
| 	// Release old snapshots if the memory allocated is excessive. This may remove the top most snapshot if jumping to the very first snapshot.
 | ||||
| 	//if (temp_snapshot_was_taken)
 | ||||
| 	// Release the old snapshots always, as it may have happened, that some of the triangle meshes got deserialized from the snapshot, while some
 | ||||
| 	// triangle meshes may have gotten released from the scene or the background processing, therefore now being calculated into the Undo / Redo stack size.
 | ||||
| 		this->undo_redo_stack.release_least_recently_used(); | ||||
| 	//YS_FIXME update obj_list from the deserialized model (maybe store ObjectIDs into the tree?) (no selections at this point of time)
 | ||||
|     this->view3D->get_canvas3d()->get_selection().set_deserialized(GUI::Selection::EMode(this->undo_redo_stack.selection_deserialized().mode), this->undo_redo_stack.selection_deserialized().volumes_and_instances); | ||||
|     this->view3D->get_canvas3d()->get_gizmos_manager().update_after_undo_redo(); | ||||
| 
 | ||||
|     wxGetApp().obj_list()->update_after_undo_redo(); | ||||
| 
 | ||||
|     if (wxGetApp().get_mode() == comSimple && model_has_advanced_features(this->model)) { | ||||
|     	// If the user jumped to a snapshot that require user interface with advanced features, switch to the advanced mode without asking.
 | ||||
|     	// There is a little risk of surprising the user, as he already must have had the advanced or expert mode active for such a snapshot to be taken.
 | ||||
|         Slic3r::GUI::wxGetApp().save_mode(comAdvanced); | ||||
|         view3D->set_as_dirty(); | ||||
|     } | ||||
| 
 | ||||
| 	//FIXME what about the state of the manipulators?
 | ||||
| 	//FIXME what about the focus? Cursor in the side panel?
 | ||||
| 
 | ||||
|     BOOST_LOG_TRIVIAL(info) << "Undo / Redo snapshot reloaded. Undo / Redo stack memory: " << Slic3r::format_memsize_MB(this->undo_redo_stack.memsize()) << log_memory_info(); | ||||
| } | ||||
| 
 | ||||
| void Sidebar::set_btn_label(const ActionButtonType btn_type, const wxString& label) const | ||||
|  | @ -3692,10 +3718,12 @@ void Plater::new_project() | |||
| 
 | ||||
| void Plater::load_project() | ||||
| { | ||||
| 	this->take_snapshot(_(L("Load Project"))); | ||||
| 
 | ||||
|     // Ask user for a project file name.
 | ||||
|     wxString input_file; | ||||
|     wxGetApp().load_project(this, input_file); | ||||
|     // Take the Undo / Redo snapshot.
 | ||||
| 	Plater::TakeSnapshot snapshot(this, _(L("Load Project")) + ": " + wxString::FromUTF8(into_path(input_file).stem().string().c_str())); | ||||
|     // And finally load the new project.
 | ||||
|     load_project(input_file); | ||||
| } | ||||
| 
 | ||||
|  | @ -3719,13 +3747,28 @@ void Plater::add_model() | |||
|     if (input_files.empty()) | ||||
|         return; | ||||
| 
 | ||||
|     this->take_snapshot(_(L("Add object(s)"))); | ||||
|     std::vector<fs::path> paths; | ||||
|     for (const auto &file : input_files) | ||||
|         paths.push_back(into_path(file)); | ||||
| 
 | ||||
|     std::vector<fs::path> input_paths; | ||||
|     for (const auto &file : input_files) { | ||||
|         input_paths.push_back(into_path(file)); | ||||
|     } | ||||
|     load_files(input_paths, true, false); | ||||
| 	wxString snapshot_label; | ||||
| 	assert(! paths.empty()); | ||||
| 	if (paths.size() == 1) { | ||||
| 		snapshot_label = _(L("Import Object")); | ||||
| 		snapshot_label += ": "; | ||||
| 		snapshot_label += wxString::FromUTF8(paths.front().filename().string().c_str()); | ||||
| 	} else { | ||||
| 		snapshot_label = _(L("Import Objects")); | ||||
| 		snapshot_label += ": "; | ||||
| 		snapshot_label += wxString::FromUTF8(paths.front().filename().string().c_str()); | ||||
| 		for (size_t i = 1; i < paths.size(); ++ i) { | ||||
| 			snapshot_label += ", "; | ||||
| 			snapshot_label += wxString::FromUTF8(paths[i].filename().string().c_str()); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	Plater::TakeSnapshot snapshot(this, snapshot_label); | ||||
|     load_files(paths, true, false); | ||||
| } | ||||
| 
 | ||||
| void Plater::extract_config_from_project() | ||||
|  | @ -3778,17 +3821,15 @@ 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->suppress_snapshots(); | ||||
| 	Plater::TakeSnapshot snapshot(this, _(L("Delete Selected Objects"))); | ||||
|     this->p->view3D->delete_selected(); | ||||
|     this->allow_snapshots(); | ||||
| } | ||||
| 
 | ||||
| void Plater::increase_instances(size_t num) | ||||
| { | ||||
|     if (! can_increase_instances()) { return; } | ||||
| 
 | ||||
| 	this->take_snapshot(_(L("Increase Instances"))); | ||||
| 	Plater::TakeSnapshot snapshot(this, _(L("Increase Instances"))); | ||||
| 
 | ||||
|     int obj_idx = p->get_selected_object_idx(); | ||||
| 
 | ||||
|  | @ -3824,7 +3865,7 @@ void Plater::decrease_instances(size_t num) | |||
| { | ||||
|     if (! can_decrease_instances()) { return; } | ||||
| 
 | ||||
| 	this->take_snapshot(_(L("Decrease Instances"))); | ||||
| 	Plater::TakeSnapshot snapshot(this, _(L("Decrease Instances"))); | ||||
| 
 | ||||
|     int obj_idx = p->get_selected_object_idx(); | ||||
| 
 | ||||
|  | @ -3860,16 +3901,13 @@ void Plater::set_number_of_copies(/*size_t num*/) | |||
|     if (num < 0) | ||||
|         return; | ||||
| 
 | ||||
|     this->take_snapshot(wxString::Format(_(L("Set numbers of copies to %d")), num)); | ||||
|     this->suppress_snapshots(); | ||||
| 	Plater::TakeSnapshot snapshot(this, wxString::Format(_(L("Set numbers of copies to %d")), num)); | ||||
| 
 | ||||
|     int diff = (int)num - (int)model_object->instances.size(); | ||||
|     if (diff > 0) | ||||
|         increase_instances(diff); | ||||
|     else if (diff < 0) | ||||
|         decrease_instances(-diff); | ||||
| 
 | ||||
|     this->allow_snapshots(); | ||||
| } | ||||
| 
 | ||||
| bool Plater::is_selection_empty() const | ||||
|  | @ -3893,7 +3931,7 @@ void Plater::cut(size_t obj_idx, size_t instance_idx, coordf_t z, bool keep_uppe | |||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     this->take_snapshot(_(L("Gizmo-Cut"))); | ||||
| 	Plater::TakeSnapshot snapshot(this, _(L("Cut by Plane"))); | ||||
| 
 | ||||
|     wxBusyCursor wait; | ||||
|     const auto new_objects = object->cut(instance_idx, z, keep_upper, keep_lower, rotate_lower); | ||||
|  | @ -4198,7 +4236,7 @@ void Plater::undo_to(int selection) | |||
|     } | ||||
|      | ||||
|     const int idx = p->get_active_snapshot_index() - selection - 1; | ||||
|     p->undo_to(p->undo_redo_stack.snapshots()[idx].timestamp); | ||||
|     p->undo_redo_to(p->undo_redo_stack.snapshots()[idx].timestamp); | ||||
| } | ||||
| void Plater::redo_to(int selection) | ||||
| { | ||||
|  | @ -4208,7 +4246,7 @@ void Plater::redo_to(int selection) | |||
|     } | ||||
|      | ||||
|     const int idx = p->get_active_snapshot_index() + selection + 1; | ||||
|     p->redo_to(p->undo_redo_stack.snapshots()[idx].timestamp); | ||||
|     p->undo_redo_to(p->undo_redo_stack.snapshots()[idx].timestamp); | ||||
| } | ||||
| bool Plater::undo_redo_string_getter(const bool is_undo, int idx, const char** out_text) | ||||
| { | ||||
|  |  | |||
|  | @ -186,8 +186,6 @@ public: | |||
| 
 | ||||
|     void take_snapshot(const std::string &snapshot_name); | ||||
|     void take_snapshot(const wxString &snapshot_name); | ||||
|     void suppress_snapshots(); | ||||
|     void allow_snapshots(); | ||||
|     void undo(); | ||||
|     void redo(); | ||||
|     void undo_to(int selection); | ||||
|  | @ -235,10 +233,46 @@ public: | |||
| 
 | ||||
|     const Camera& get_camera() const; | ||||
| 
 | ||||
| 	// ROII wrapper for suppressing the Undo / Redo snapshot to be taken.
 | ||||
| 	class SuppressSnapshots | ||||
| 	{ | ||||
| 	public: | ||||
| 		SuppressSnapshots(Plater *plater) : m_plater(plater) | ||||
| 		{ | ||||
| 			m_plater->suppress_snapshots(); | ||||
| 		} | ||||
| 		~SuppressSnapshots() | ||||
| 		{ | ||||
| 			m_plater->allow_snapshots(); | ||||
| 		} | ||||
| 	private: | ||||
| 		Plater *m_plater; | ||||
| 	}; | ||||
| 
 | ||||
| 	// ROII wrapper for taking an Undo / Redo snapshot while disabling the snapshot taking by the methods called from inside this snapshot.
 | ||||
| 	class TakeSnapshot | ||||
| 	{ | ||||
| 	public: | ||||
| 		TakeSnapshot(Plater *plater, const wxString &snapshot_name) : m_plater(plater) | ||||
| 		{ | ||||
| 			m_plater->take_snapshot(snapshot_name); | ||||
| 			m_plater->suppress_snapshots(); | ||||
| 		} | ||||
| 		~TakeSnapshot() | ||||
| 		{ | ||||
| 			m_plater->allow_snapshots(); | ||||
| 		} | ||||
| 	private: | ||||
| 		Plater *m_plater; | ||||
| 	}; | ||||
| 
 | ||||
| private: | ||||
|     struct priv; | ||||
|     std::unique_ptr<priv> p; | ||||
| 
 | ||||
|     void suppress_snapshots(); | ||||
|     void allow_snapshots(); | ||||
| 
 | ||||
|     friend class SuppressBackgroundProcessingUpdate; | ||||
| }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -2916,7 +2916,7 @@ bool Tab::may_discard_current_dirty_preset(PresetCollection* presets /*= nullptr | |||
| // Because of we can't to print the multi-part objects with SLA technology.
 | ||||
| bool Tab::may_switch_to_SLA_preset() | ||||
| { | ||||
|     if (wxGetApp().obj_list()->has_multi_part_objects()) | ||||
|     if (model_has_multi_part_objects(wxGetApp().model())) | ||||
|     { | ||||
|         show_info( parent(),  | ||||
|                     _(L("It's impossible to print multi-part object(s) with SLA technology.")) + "\n\n" + | ||||
|  |  | |||
|  | @ -17,20 +17,29 @@ | |||
| #define CEREAL_FUTURE_EXPERIMENTAL | ||||
| #include <cereal/archives/adapters.hpp> | ||||
| 
 | ||||
| #include <libslic3r/ObjectID.hpp> | ||||
| #include <libslic3r/Utils.hpp> | ||||
| 
 | ||||
| #include <boost/foreach.hpp> | ||||
| 
 | ||||
| #ifndef NDEBUG | ||||
| // #define SLIC3R_UNDOREDO_DEBUG
 | ||||
| #endif /* NDEBUG */ | ||||
| #if 0 | ||||
| 	// Stop at a fraction of the normal Undo / Redo stack size.
 | ||||
| 	#define UNDO_REDO_DEBUG_LOW_MEM_FACTOR 10000 | ||||
| #else | ||||
| 	#define UNDO_REDO_DEBUG_LOW_MEM_FACTOR 1 | ||||
| #endif | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| namespace UndoRedo { | ||||
| 
 | ||||
| static std::string topmost_snapsnot_name = "@@@ Topmost @@@"; | ||||
| static std::string topmost_snapshot_name = "@@@ Topmost @@@"; | ||||
| 
 | ||||
| bool Snapshot::is_topmost() const | ||||
| { | ||||
| 	return this->name == topmost_snapsnot_name; | ||||
| 	return this->name == topmost_snapshot_name; | ||||
| } | ||||
| 
 | ||||
| // Time interval, start is closed, end is open.
 | ||||
|  | @ -51,9 +60,12 @@ public: | |||
| 	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_begin(size_t new_begin)  { m_begin = std::max(m_begin, new_begin); } | ||||
| 	void    trim_end(size_t new_end) { m_end = std::min(m_end, new_end); } | ||||
| 	void 	extend_end(size_t new_end) { assert(new_end >= m_end); m_end = new_end; } | ||||
| 
 | ||||
| 	size_t 	memsize() const { return sizeof(this); } | ||||
| 
 | ||||
| private: | ||||
| 	size_t 	m_begin; | ||||
| 	size_t 	m_end; | ||||
|  | @ -68,13 +80,27 @@ public: | |||
| 	// Is the object captured by this history mutable or immutable?
 | ||||
| 	virtual bool is_mutable() const = 0; | ||||
| 	virtual bool is_immutable() const = 0; | ||||
| 	// The object is optional, it may be released if the Undo / Redo stack memory grows over the limits.
 | ||||
| 	virtual bool is_optional() const { return false; } | ||||
| 	// If it is an immutable object, return its pointer. There is a map assigning a temporary ObjectID to the immutable object pointer.
 | ||||
| 	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 before the given timestamp. For the ImmutableObjectHistory, the shared pointer is NOT released.
 | ||||
| 	// Return the amount of memory released.
 | ||||
| 	virtual size_t release_before_timestamp(size_t timestamp) = 0; | ||||
| 	// Release all data after the given timestamp. For the ImmutableObjectHistory, the shared pointer is NOT released.
 | ||||
| 	virtual void release_after_timestamp(size_t timestamp) = 0; | ||||
| 	// Return the amount of memory released.
 | ||||
| 	virtual size_t release_after_timestamp(size_t timestamp) = 0; | ||||
| 	// Release all optional data of this history.
 | ||||
| 	virtual size_t release_optional() = 0; | ||||
| 	// Restore optional data possibly released by release_optional.
 | ||||
| 	virtual void   restore_optional() = 0; | ||||
| 
 | ||||
| 	// Estimated size in memory, to be used to drop least recently used snapshots.
 | ||||
| 	virtual size_t memsize() const = 0; | ||||
| 
 | ||||
| #ifdef SLIC3R_UNDOREDO_DEBUG | ||||
| 	// Human readable debug information.
 | ||||
|  | @ -94,21 +120,54 @@ public: | |||
| 	// 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 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.begin()) { | ||||
| 			auto it_prev = it; | ||||
| 			-- it_prev; | ||||
| 			assert(it_prev->begin() < timestamp); | ||||
| 			// Trim the last interval with timestamp.
 | ||||
| 			it_prev->trim_end(timestamp); | ||||
| 	// Release all data before the given timestamp. For the ImmutableObjectHistory, the shared pointer is NOT released.
 | ||||
| 	size_t release_before_timestamp(size_t timestamp) override { | ||||
| 		size_t mem_released = 0; | ||||
| 		if (! 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)); | ||||
| 			// Find the first iterator with begin() < timestamp.
 | ||||
| 			if (it == m_history.end()) | ||||
| 				-- it; | ||||
| 			while (it != m_history.begin() && it->begin() >= timestamp) | ||||
| 				-- it; | ||||
| 			if (it->begin() < timestamp && it->end() > timestamp) { | ||||
| 				it->trim_begin(timestamp); | ||||
| 				if (it != m_history.begin()) | ||||
| 					-- it; | ||||
| 			} | ||||
| 			if (it->end() <= timestamp) { | ||||
| 				auto it_end = ++ it; | ||||
| 				for (it = m_history.begin(); it != it_end; ++ it) | ||||
| 					mem_released += it->memsize(); | ||||
| 				m_history.erase(m_history.begin(), it_end); | ||||
| 			} | ||||
| 			assert(this->valid()); | ||||
| 		} | ||||
| 		m_history.erase(it, m_history.end()); | ||||
| 		assert(this->valid()); | ||||
| 		return mem_released; | ||||
| 	} | ||||
| 
 | ||||
| 	// Release all data after the given timestamp. The shared pointer is NOT released.
 | ||||
| 	size_t release_after_timestamp(size_t timestamp) override { | ||||
| 		size_t mem_released = 0; | ||||
| 		if (! 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.begin()) { | ||||
| 				auto it_prev = it; | ||||
| 				-- it_prev; | ||||
| 				assert(it_prev->begin() < timestamp); | ||||
| 				// Trim the last interval with timestamp.
 | ||||
| 				it_prev->trim_end(timestamp); | ||||
| 			} | ||||
| 			for (auto it2 = it; it2 != m_history.end(); ++ it2) | ||||
| 				mem_released += it2->memsize(); | ||||
| 			m_history.erase(it, m_history.end()); | ||||
| 			assert(this->valid()); | ||||
| 		} | ||||
| 		return mem_released; | ||||
| 	} | ||||
| 
 | ||||
| protected: | ||||
|  | @ -129,13 +188,27 @@ template<typename T> | |||
| class ImmutableObjectHistory : public ObjectHistory<Interval> | ||||
| { | ||||
| public: | ||||
| 	ImmutableObjectHistory(std::shared_ptr<const T>	shared_object) : m_shared_object(shared_object) {} | ||||
| 	ImmutableObjectHistory(std::shared_ptr<const T>	shared_object, bool optional) : m_shared_object(shared_object), m_optional(optional) {} | ||||
| 	~ImmutableObjectHistory() override {} | ||||
| 
 | ||||
| 	bool is_mutable() const override { return false; } | ||||
| 	bool is_immutable() const override { return true; } | ||||
| 	bool is_optional() const override { return m_optional; } | ||||
| 	// If it is an immutable object, return its pointer. There is a map assigning a temporary ObjectID to the immutable object pointer.
 | ||||
| 	const void* immutable_object_ptr() const { return (const void*)m_shared_object.get(); } | ||||
| 
 | ||||
| 	// Estimated size in memory, to be used to drop least recently used snapshots.
 | ||||
| 	size_t memsize() const override { | ||||
| 		size_t memsize = sizeof(*this); | ||||
| 		if (this->is_serialized()) | ||||
| 			memsize += m_serialized.size(); | ||||
| 		else if (m_shared_object.use_count() == 1) | ||||
| 			// Only count the shared object's memsize into the total Undo / Redo stack memsize if it is referenced from the Undo / Redo stack only.
 | ||||
| 			memsize += m_shared_object->memsize(); | ||||
| 		memsize += m_history.size() * sizeof(Interval); | ||||
| 		return memsize; | ||||
| 	} | ||||
| 
 | ||||
| 	void save(size_t active_snapshot_time, size_t current_time) { | ||||
| 		assert(m_history.empty() || m_history.back().end() <= active_snapshot_time ||  | ||||
| 			// The snapshot of an immutable object may have already been taken from another mutable object.
 | ||||
|  | @ -158,6 +231,37 @@ public: | |||
| 		return timestamp >= it->begin() && timestamp < it->end(); | ||||
| 	} | ||||
| 
 | ||||
| 	// Release all optional data of this history.
 | ||||
| 	size_t release_optional() override { | ||||
| 		size_t mem_released = 0; | ||||
| 		if (m_optional) { | ||||
| 			bool released = false; | ||||
| 			if (this->is_serialized()) { | ||||
| 				mem_released += m_serialized.size(); | ||||
| 				m_serialized.clear(); | ||||
| 				released = true; | ||||
| 			} else if (m_shared_object.use_count() == 1) { | ||||
| 				mem_released += m_shared_object->memsize(); | ||||
| 				m_shared_object.reset(); | ||||
| 				released = true; | ||||
| 			} | ||||
| 			if (released) { | ||||
| 				mem_released += m_history.size() * sizeof(Interval); | ||||
| 				m_history.clear(); | ||||
| 			} | ||||
| 		} else if (m_shared_object.use_count() == 1) { | ||||
| 			// The object is in memory, but it is not shared with the scene. Let the object decide whether there is any optional data to release.
 | ||||
| 			const_cast<T*>(m_shared_object.get())->release_optional(); | ||||
| 		} | ||||
| 		return mem_released; | ||||
| 	} | ||||
| 
 | ||||
| 	// Restore optional data possibly released by this->release_optional().
 | ||||
| 	void restore_optional() override { | ||||
| 		if (m_shared_object.use_count() == 1) | ||||
| 			const_cast<T*>(m_shared_object.get())->restore_optional(); | ||||
| 	} | ||||
| 
 | ||||
| 	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); | ||||
|  | @ -182,6 +286,8 @@ 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; | ||||
| 	// If this object is optional, then it may be deleted from the Undo / Redo stack and recalculated from other data (for example mesh convex hull).
 | ||||
| 	bool 						m_optional; | ||||
| 	std::string 				m_serialized; | ||||
| }; | ||||
| 
 | ||||
|  | @ -228,7 +334,8 @@ public: | |||
| 	const Interval& interval() const { return m_interval; } | ||||
| 	size_t		begin() const { return m_interval.begin(); } | ||||
| 	size_t		end()   const { return m_interval.end(); } | ||||
| 	void 		trim_end(size_t timestamp) { m_interval.trim_end(timestamp); } | ||||
| 	void 		trim_begin(size_t timestamp) { m_interval.trim_begin(timestamp); } | ||||
| 	void 		trim_end  (size_t timestamp) { m_interval.trim_end(timestamp); } | ||||
| 	void 		extend_end(size_t timestamp) { m_interval.extend_end(timestamp); } | ||||
| 
 | ||||
| 	bool		operator<(const MutableHistoryInterval& rhs) const { return m_interval < rhs.m_interval; } | ||||
|  | @ -238,6 +345,13 @@ public: | |||
| 	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); } | ||||
| 	size_t 		memsize() const {  | ||||
| 		return m_data->refcnt == 1 ? | ||||
| 			// Count just the size of the snapshot data.
 | ||||
| 			m_data->size : | ||||
| 			// Count the size of the snapshot data divided by the number of references, rounded up.
 | ||||
| 			(m_data->size + m_data->refcnt - 1) / m_data->refcnt; | ||||
| 	} | ||||
| 
 | ||||
| private: | ||||
| 	MutableHistoryInterval(const MutableHistoryInterval &rhs); | ||||
|  | @ -268,6 +382,15 @@ public: | |||
| 	bool is_mutable() const override { return true; } | ||||
| 	bool is_immutable() const override { return false; } | ||||
| 
 | ||||
| 	// Estimated size in memory, to be used to drop least recently used snapshots.
 | ||||
| 	size_t memsize() const override { | ||||
| 		size_t memsize = sizeof(*this); | ||||
| 		memsize += m_history.size() * sizeof(MutableHistoryInterval); | ||||
| 		for (const MutableHistoryInterval &interval : m_history) | ||||
| 			memsize += interval.memsize(); | ||||
| 		return memsize; | ||||
| 	} | ||||
| 
 | ||||
| 	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) { | ||||
|  | @ -300,6 +423,11 @@ public: | |||
| 		return std::string(it->data(), it->data() + it->size()); | ||||
| 	} | ||||
| 
 | ||||
| 	// Currently all mutable snapshots are mandatory.
 | ||||
| 	size_t release_optional() override { return 0; } | ||||
| 	// Currently there is no way to release optional data from the mutable objects.
 | ||||
| 	void   restore_optional() override {} | ||||
| 
 | ||||
| #ifdef SLIC3R_UNDOREDO_DEBUG | ||||
| 	std::string format() override { | ||||
| 		std::string out = typeid(T).name(); | ||||
|  | @ -354,29 +482,41 @@ 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) {} | ||||
| 	// Initially enable Undo / Redo stack to occupy maximum 10% of the total system physical memory.
 | ||||
| 	StackImpl() : m_memory_limit(std::min(Slic3r::total_physical_memory() / 10, size_t(1 * 16384 * 65536 / UNDO_REDO_DEBUG_LOW_MEM_FACTOR))), m_active_snapshot_time(0), m_current_time(0) {} | ||||
| 
 | ||||
| 	void set_memory_limit(size_t memsize) { m_memory_limit = memsize; } | ||||
| 
 | ||||
| 	size_t memsize() const { | ||||
| 		size_t memsize = 0; | ||||
| 		for (const auto &object : m_objects) | ||||
| 			memsize += object.second->memsize(); | ||||
| 		return memsize; | ||||
| 	} | ||||
| 
 | ||||
|     // Store the current application state onto the Undo / Redo stack, remove all snapshots after m_active_snapshot_time.
 | ||||
|     void take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos); | ||||
|     void take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos, Slic3r::PrinterTechnology printer_technology); | ||||
|     void load_snapshot(size_t timestamp, Slic3r::Model& model, Slic3r::GUI::GLGizmosManager& gizmos); | ||||
| 
 | ||||
| 	bool has_undo_snapshot() const; | ||||
| 	bool has_redo_snapshot() const; | ||||
|     bool undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selection, Slic3r::GUI::GLGizmosManager& gizmos, size_t jump_to_time); | ||||
|     bool redo(Slic3r::Model& model, Slic3r::GUI::GLGizmosManager& gizmos, size_t jump_to_time); | ||||
|     bool undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection, Slic3r::GUI::GLGizmosManager &gizmos, PrinterTechnology printer_technology, size_t jump_to_time); | ||||
|     bool redo(Slic3r::Model &model, Slic3r::GUI::GLGizmosManager &gizmos, size_t jump_to_time); | ||||
| 	void release_least_recently_used(); | ||||
| 
 | ||||
| 	// Snapshot history (names with timestamps).
 | ||||
| 	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; } | ||||
| 	bool 							temp_snapshot_active() const { return m_snapshots.back().timestamp == m_active_snapshot_time && ! m_snapshots.back().is_topmost_captured(); } | ||||
| 
 | ||||
| 	const Selection& selection_deserialized() const { return m_selection; } | ||||
| 	const Selection& 				selection_deserialized() const { return m_selection; } | ||||
| 
 | ||||
| //protected:
 | ||||
| 	template<typename T, typename T_AS> ObjectID save_mutable_object(const T &object); | ||||
| 	template<typename T> ObjectID save_immutable_object(std::shared_ptr<const T> &object); | ||||
| 	template<typename T> ObjectID save_immutable_object(std::shared_ptr<const T> &object, bool optional); | ||||
| 	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> std::shared_ptr<const T> load_immutable_object(const Slic3r::ObjectID id, bool optional); | ||||
| 	template<typename T, typename T_AS> void load_mutable_object(const Slic3r::ObjectID id, T &target); | ||||
| 
 | ||||
| #ifdef SLIC3R_UNDOREDO_DEBUG | ||||
|  | @ -394,6 +534,7 @@ public: | |||
| 		if (m_active_snapshot_time > m_snapshots.back().timestamp) | ||||
| 			out += ">>>\n"; | ||||
| 		out += "Current time: " + std::to_string(m_current_time) + "\n"; | ||||
| 		out += "Total memory occupied: " + std::to_string(this->memsize()) + "\n"; | ||||
| 		return out; | ||||
| 	} | ||||
| 	void print() const { | ||||
|  | @ -406,9 +547,10 @@ public: | |||
| #ifndef NDEBUG | ||||
| 	bool valid() const { | ||||
| 		assert(! m_snapshots.empty()); | ||||
| 		assert(m_snapshots.back().is_topmost()); | ||||
| 		auto it = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time)); | ||||
| 		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()); | ||||
| 		assert(m_active_snapshot_time <= m_snapshots.back().timestamp); | ||||
| 		for (auto it = m_objects.begin(); it != m_objects.end(); ++ it) | ||||
| 			assert(it->second->valid()); | ||||
| 		return true; | ||||
|  | @ -430,6 +572,9 @@ private: | |||
| 	} | ||||
| 	void 							collect_garbage(); | ||||
| 
 | ||||
| 	// Maximum memory allowed to be occupied by the Undo / Redo stack. If the limit is exceeded,
 | ||||
| 	// least recently used snapshots will be released.
 | ||||
| 	size_t 													m_memory_limit; | ||||
| 	// 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.
 | ||||
|  | @ -528,7 +673,11 @@ namespace cereal | |||
| 	// store just the ObjectID to this stream.
 | ||||
| 	template <class T> void save(BinaryOutputArchive &ar, const std::shared_ptr<const T> &ptr) | ||||
| 	{ | ||||
| 		ar(cereal::get_user_data<Slic3r::UndoRedo::StackImpl>(ar).save_immutable_object<T>(const_cast<std::shared_ptr<const T>&>(ptr))); | ||||
| 		ar(cereal::get_user_data<Slic3r::UndoRedo::StackImpl>(ar).save_immutable_object<T>(const_cast<std::shared_ptr<const T>&>(ptr), false)); | ||||
| 	} | ||||
| 	template <class T> void save_optional(BinaryOutputArchive &ar, const std::shared_ptr<const T> &ptr) | ||||
| 	{ | ||||
| 		ar(cereal::get_user_data<Slic3r::UndoRedo::StackImpl>(ar).save_immutable_object<T>(const_cast<std::shared_ptr<const T>&>(ptr), true)); | ||||
| 	} | ||||
| 
 | ||||
| 	// Load ObjectBase derived class from the Undo / Redo stack as a separate object
 | ||||
|  | @ -538,7 +687,14 @@ namespace cereal | |||
| 		Slic3r::UndoRedo::StackImpl &stack = cereal::get_user_data<Slic3r::UndoRedo::StackImpl>(ar); | ||||
| 		size_t id; | ||||
| 		ar(id); | ||||
| 		ptr = stack.load_immutable_object<T>(Slic3r::ObjectID(id)); | ||||
| 		ptr = stack.load_immutable_object<T>(Slic3r::ObjectID(id), false); | ||||
| 	} | ||||
| 	template <class T> void load_optional(BinaryInputArchive &ar, std::shared_ptr<const T> &ptr) | ||||
| 	{ | ||||
| 		Slic3r::UndoRedo::StackImpl &stack = cereal::get_user_data<Slic3r::UndoRedo::StackImpl>(ar); | ||||
| 		size_t id; | ||||
| 		ar(id); | ||||
| 		ptr = stack.load_immutable_object<T>(Slic3r::ObjectID(id), true); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
|  | @ -583,14 +739,16 @@ template<typename T, typename T_AS> ObjectID StackImpl::save_mutable_object(cons | |||
| 	return object.id(); | ||||
| } | ||||
| 
 | ||||
| template<typename T> ObjectID StackImpl::save_immutable_object(std::shared_ptr<const T> &object) | ||||
| template<typename T> ObjectID StackImpl::save_immutable_object(std::shared_ptr<const T> &object, bool optional) | ||||
| { | ||||
| 	// 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.emplace_hint(it_object_history, object_id, std::unique_ptr<ImmutableObjectHistory<T>>(new ImmutableObjectHistory<T>(object))); | ||||
| 		it_object_history = m_objects.emplace_hint(it_object_history, object_id, std::unique_ptr<ImmutableObjectHistory<T>>(new ImmutableObjectHistory<T>(object, optional))); | ||||
| 	else | ||||
| 		assert(it_object_history->second.get()->is_optional() == optional); | ||||
| 	// Then save the interval.
 | ||||
| 	static_cast<ImmutableObjectHistory<T>*>(it_object_history->second.get())->save(m_active_snapshot_time, m_current_time); | ||||
| 	return object_id; | ||||
|  | @ -603,13 +761,16 @@ template<typename T> T* StackImpl::load_mutable_object(const Slic3r::ObjectID id | |||
| 	return target; | ||||
| } | ||||
| 
 | ||||
| template<typename T> std::shared_ptr<const T> StackImpl::load_immutable_object(const Slic3r::ObjectID id) | ||||
| template<typename T> std::shared_ptr<const T> StackImpl::load_immutable_object(const Slic3r::ObjectID id, bool optional) | ||||
| { | ||||
| 	// 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()); | ||||
| 	assert(optional || it_object_history != m_objects.end()); | ||||
| 	if (it_object_history == m_objects.end()) | ||||
| 		return std::shared_ptr<const T>(); | ||||
| 	auto *object_history = static_cast<ImmutableObjectHistory<T>*>(it_object_history->second.get()); | ||||
| 	assert(object_history->has_snapshot(m_active_snapshot_time)); | ||||
| 	object_history->restore_optional(); | ||||
| 	return object_history->shared_ptr(*this); | ||||
| } | ||||
| 
 | ||||
|  | @ -627,7 +788,7 @@ template<typename T, typename T_AS> void StackImpl::load_mutable_object(const Sl | |||
| } | ||||
| 
 | ||||
| // 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, const Slic3r::GUI::GLGizmosManager& gizmos) | ||||
| void StackImpl::take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos, Slic3r::PrinterTechnology printer_technology) | ||||
| { | ||||
| 	// Release old snapshot data.
 | ||||
| 	assert(m_active_snapshot_time <= m_current_time); | ||||
|  | @ -647,11 +808,11 @@ void StackImpl::take_snapshot(const std::string& snapshot_name, const Slic3r::Mo | |||
| 	this->save_mutable_object<Selection, Selection>(m_selection); | ||||
|     this->save_mutable_object<Slic3r::GUI::GLGizmosManager, Slic3r::GUI::GLGizmosManager>(gizmos); | ||||
|     // Save the snapshot info.
 | ||||
| 	m_snapshots.emplace_back(snapshot_name, m_current_time ++, model.id().id); | ||||
| 	m_snapshots.emplace_back(snapshot_name, m_current_time ++, model.id().id, printer_technology); | ||||
| 	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); | ||||
| 	m_snapshots.emplace_back(topmost_snapshot_name, m_active_snapshot_time, 0, printer_technology); | ||||
| 	// Release empty objects from the history.
 | ||||
| 	this->collect_garbage(); | ||||
| 	assert(this->valid()); | ||||
|  | @ -697,7 +858,7 @@ bool StackImpl::has_redo_snapshot() const | |||
| 	return ++ it != m_snapshots.end(); | ||||
| } | ||||
| 
 | ||||
| bool StackImpl::undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selection, Slic3r::GUI::GLGizmosManager& gizmos, size_t time_to_load) | ||||
| bool StackImpl::undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection, Slic3r::GUI::GLGizmosManager &gizmos, PrinterTechnology printer_technology, size_t time_to_load) | ||||
| { | ||||
| 	assert(this->valid()); | ||||
| 	if (time_to_load == SIZE_MAX) { | ||||
|  | @ -708,9 +869,10 @@ bool StackImpl::undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selecti | |||
| 	} | ||||
| 	assert(time_to_load < m_active_snapshot_time); | ||||
| 	assert(std::binary_search(m_snapshots.begin(), m_snapshots.end(), Snapshot(time_to_load))); | ||||
| 	bool new_snapshot_taken = false; | ||||
| 	if (m_active_snapshot_time == m_snapshots.back().timestamp && ! m_snapshots.back().is_topmost_captured()) { | ||||
| 		// The current state is temporary. The current state needs to be captured to be redoable.
 | ||||
|         this->take_snapshot(topmost_snapsnot_name, model, selection, gizmos); | ||||
|         this->take_snapshot(topmost_snapshot_name, model, selection, gizmos, printer_technology); | ||||
|         // The line above entered another topmost_snapshot_name.
 | ||||
| 		assert(m_snapshots.back().is_topmost()); | ||||
| 		assert(! m_snapshots.back().is_topmost_captured()); | ||||
|  | @ -720,8 +882,15 @@ bool StackImpl::undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selecti | |||
| 		//-- m_current_time;
 | ||||
| 		assert(m_snapshots.back().is_topmost()); | ||||
| 		assert(m_snapshots.back().is_topmost_captured()); | ||||
| 		new_snapshot_taken = true; | ||||
| 	} | ||||
|     this->load_snapshot(time_to_load, model, gizmos); | ||||
| 	if (new_snapshot_taken) { | ||||
| 		// Release old snapshots if the memory allocated due to capturing the top most state is excessive.
 | ||||
| 		// Don't release the snapshots here, release them first after the scene and background processing gets updated, as this will release some references
 | ||||
| 		// to the shared TriangleMeshes.
 | ||||
| 		//this->release_least_recently_used();
 | ||||
| 	} | ||||
| #ifdef SLIC3R_UNDOREDO_DEBUG | ||||
| 	std::cout << "After undo" << std::endl; | ||||
|  	this->print(); | ||||
|  | @ -762,18 +931,107 @@ void StackImpl::collect_garbage() | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| void StackImpl::release_least_recently_used() | ||||
| { | ||||
| 	assert(this->valid()); | ||||
| 	size_t current_memsize = this->memsize(); | ||||
| #ifdef SLIC3R_UNDOREDO_DEBUG | ||||
| 	bool released = false; | ||||
| #endif | ||||
| 	// First try to release the optional immutable data (for example the convex hulls),
 | ||||
| 	// or the shared vertices of triangle meshes.
 | ||||
| 	for (auto it = m_objects.begin(); current_memsize > m_memory_limit && it != m_objects.end();) { | ||||
| 		const void *ptr = it->second->immutable_object_ptr(); | ||||
| 		size_t mem_released = it->second->release_optional(); | ||||
| 		if (it->second->empty()) { | ||||
| 			if (ptr != nullptr) | ||||
| 				// Release the immutable object from the ptr to ObjectID map.
 | ||||
| 				m_shared_ptr_to_object_id.erase(ptr); | ||||
| 			mem_released += it->second->memsize(); | ||||
| 			it = m_objects.erase(it); | ||||
| 		} else | ||||
| 			++ it; | ||||
| 		assert(current_memsize >= mem_released); | ||||
| 		if (current_memsize >= mem_released) | ||||
| 			current_memsize -= mem_released; | ||||
| 		else | ||||
| 			current_memsize = 0; | ||||
| 	} | ||||
| 	while (current_memsize > m_memory_limit && m_snapshots.size() >= 3) { | ||||
| 		// From which side to remove a snapshot?
 | ||||
| 		assert(m_snapshots.front().timestamp < m_active_snapshot_time); | ||||
| 		size_t mem_released = 0; | ||||
| 		if (m_snapshots[1].timestamp == m_active_snapshot_time) { | ||||
| 			// Remove the last snapshot.
 | ||||
| #if 0 | ||||
| 			for (auto it = m_objects.begin(); it != m_objects.end();) { | ||||
| 				mem_released += it->second->release_after_timestamp(m_snapshots.back().timestamp); | ||||
| 				if (it->second->empty()) { | ||||
| 					if (it->second->immutable_object_ptr() != nullptr) | ||||
| 						// Release the immutable object from the ptr to ObjectID map.
 | ||||
| 						m_shared_ptr_to_object_id.erase(it->second->immutable_object_ptr()); | ||||
| 					mem_released += it->second->memsize(); | ||||
| 					it = m_objects.erase(it); | ||||
| 				} else | ||||
| 					++ it; | ||||
| 			} | ||||
| 			m_snapshots.pop_back(); | ||||
| 			m_snapshots.back().name = topmost_snapshot_name; | ||||
| #else | ||||
| 			// Rather don't release the last snapshot as it will be very confusing to the user
 | ||||
| 			// as of why he cannot jump to the top most state. The Undo / Redo stack maximum size
 | ||||
| 			// should be set low enough to accomodate for the top most snapshot.
 | ||||
| 			break; | ||||
| #endif | ||||
| 		} else { | ||||
| 			// Remove the first snapshot.
 | ||||
| 			for (auto it = m_objects.begin(); it != m_objects.end();) { | ||||
| 				mem_released += it->second->release_before_timestamp(m_snapshots[1].timestamp); | ||||
| 				if (it->second->empty()) { | ||||
| 					if (it->second->immutable_object_ptr() != nullptr) | ||||
| 						// Release the immutable object from the ptr to ObjectID map.
 | ||||
| 						m_shared_ptr_to_object_id.erase(it->second->immutable_object_ptr()); | ||||
| 					mem_released += it->second->memsize(); | ||||
| 					it = m_objects.erase(it); | ||||
| 				} else | ||||
| 					++ it; | ||||
| 			} | ||||
| 			m_snapshots.erase(m_snapshots.begin()); | ||||
| 		} | ||||
| 		assert(current_memsize >= mem_released); | ||||
| 		if (current_memsize >= mem_released) | ||||
| 			current_memsize -= mem_released; | ||||
| 		else | ||||
| 			current_memsize = 0; | ||||
| #ifdef SLIC3R_UNDOREDO_DEBUG | ||||
| 		released = true; | ||||
| #endif | ||||
| 	} | ||||
| 	assert(this->valid()); | ||||
| #ifdef SLIC3R_UNDOREDO_DEBUG | ||||
| 	std::cout << "After release_least_recently_used" << std::endl; | ||||
|  	this->print(); | ||||
| #endif /* SLIC3R_UNDOREDO_DEBUG */ | ||||
| } | ||||
| 
 | ||||
| // Wrappers of the private implementation.
 | ||||
| Stack::Stack() : pimpl(new StackImpl()) {} | ||||
| Stack::~Stack() {} | ||||
| void Stack::take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos) { pimpl->take_snapshot(snapshot_name, model, selection, gizmos); } | ||||
| void Stack::set_memory_limit(size_t memsize) { pimpl->set_memory_limit(memsize); } | ||||
| size_t Stack::memsize() const { return pimpl->memsize(); } | ||||
| void Stack::release_least_recently_used() { pimpl->release_least_recently_used(); } | ||||
| void Stack::take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos, Slic3r::PrinterTechnology printer_technology) | ||||
| 	{ pimpl->take_snapshot(snapshot_name, model, selection, gizmos, printer_technology); } | ||||
| 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, Slic3r::GUI::GLGizmosManager& gizmos, size_t time_to_load) { return pimpl->undo(model, selection, gizmos, time_to_load); } | ||||
| bool Stack::undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selection, Slic3r::GUI::GLGizmosManager& gizmos, PrinterTechnology printer_technology, size_t time_to_load)  | ||||
| 	{ return pimpl->undo(model, selection, gizmos, printer_technology, time_to_load); } | ||||
| bool Stack::redo(Slic3r::Model& model, Slic3r::GUI::GLGizmosManager& gizmos, size_t time_to_load) { return pimpl->redo(model, gizmos, 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(); } | ||||
| bool Stack::temp_snapshot_active() const { return pimpl->temp_snapshot_active(); } | ||||
| 
 | ||||
| } // namespace UndoRedo
 | ||||
| } // namespace Slic3r
 | ||||
|  |  | |||
|  | @ -12,6 +12,7 @@ | |||
| namespace Slic3r { | ||||
| 
 | ||||
| class Model; | ||||
| enum PrinterTechnology : unsigned char; | ||||
| 
 | ||||
| namespace GUI { | ||||
| 	class Selection; | ||||
|  | @ -23,11 +24,13 @@ namespace UndoRedo { | |||
| struct Snapshot | ||||
| { | ||||
| 	Snapshot(size_t timestamp) : timestamp(timestamp) {} | ||||
| 	Snapshot(const std::string &name, size_t timestamp, size_t model_id) : name(name), timestamp(timestamp), model_id(model_id) {} | ||||
| 	Snapshot(const std::string &name, size_t timestamp, size_t model_id, Slic3r::PrinterTechnology printer_technology) : | ||||
| 		name(name), timestamp(timestamp), model_id(model_id), printer_technology(printer_technology) {} | ||||
| 	 | ||||
| 	std::string name; | ||||
| 	size_t 		timestamp; | ||||
| 	size_t 		model_id; | ||||
| 	std::string 		name; | ||||
| 	size_t 				timestamp; | ||||
| 	size_t 				model_id; | ||||
| 	PrinterTechnology 	printer_technology; | ||||
| 
 | ||||
| 	bool		operator< (const Snapshot &rhs) const { return this->timestamp < rhs.timestamp; } | ||||
| 	bool		operator==(const Snapshot &rhs) const { return this->timestamp == rhs.timestamp; } | ||||
|  | @ -56,8 +59,17 @@ public: | |||
| 	Stack(); | ||||
| 	~Stack(); | ||||
| 
 | ||||
| 	// Set maximum memory threshold. If the threshold is exceeded, least recently used snapshots are released.
 | ||||
| 	void set_memory_limit(size_t memsize); | ||||
| 
 | ||||
| 	// Estimate size of the RAM consumed by the Undo / Redo stack.
 | ||||
| 	size_t memsize() const; | ||||
| 
 | ||||
| 	// Release least recently used snapshots up to the memory limit set above.
 | ||||
| 	void release_least_recently_used(); | ||||
| 
 | ||||
| 	// Store the current application state onto the Undo / Redo stack, remove all snapshots after m_active_snapshot_time.
 | ||||
|     void take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos); | ||||
|     void take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos, Slic3r::PrinterTechnology printer_technology); | ||||
| 
 | ||||
| 	// To be queried to enable / disable the Undo / Redo buttons at the UI.
 | ||||
| 	bool has_undo_snapshot() const; | ||||
|  | @ -65,7 +77,7 @@ public: | |||
| 
 | ||||
| 	// 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, Slic3r::GUI::GLGizmosManager& gizmos, size_t time_to_load = SIZE_MAX); | ||||
|     bool undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selection, Slic3r::GUI::GLGizmosManager& gizmos, PrinterTechnology printer_technology, 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, Slic3r::GUI::GLGizmosManager& gizmos, size_t time_to_load = SIZE_MAX); | ||||
|  | @ -79,6 +91,9 @@ public: | |||
| 	// 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; | ||||
| 	// Temporary snapshot is active if the topmost snapshot is active and it has not been captured yet.
 | ||||
| 	// In that case the Undo action will capture the last snapshot.
 | ||||
| 	bool   temp_snapshot_active() 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
	
	 Enrico Turri
						Enrico Turri