From 3a74e7ab69fab1080f4245bfbf972cfe0b851129 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 17 Jul 2019 15:48:53 +0200 Subject: [PATCH 1/7] WIP: Undo / Redo memory limiting by releasing the least recently used snapshots. Memory limit set to 10% of physical system memory. --- src/admesh/stl.h | 8 ++ src/libslic3r/TriangleMesh.cpp | 6 + src/libslic3r/TriangleMesh.hpp | 2 + src/libslic3r/Utils.hpp | 2 + src/libslic3r/utils.cpp | 82 +++++++++++- src/slic3r/GUI/GUI_App.cpp | 4 +- src/slic3r/GUI/GUI_App.hpp | 4 +- src/slic3r/GUI/GUI_ObjectList.cpp | 16 +-- src/slic3r/GUI/Plater.cpp | 112 ++++++++++------ src/slic3r/GUI/Plater.hpp | 38 +++++- src/slic3r/Utils/UndoRedo.cpp | 207 ++++++++++++++++++++++++++---- src/slic3r/Utils/UndoRedo.hpp | 12 ++ 12 files changed, 411 insertions(+), 82 deletions(-) diff --git a/src/admesh/stl.h b/src/admesh/stl.h index 2ac6c7fd2..9b1146f8d 100644 --- a/src/admesh/stl.h +++ b/src/admesh/stl.h @@ -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 facet_start; std::vector 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 indices; std::vector vertices; //FIXME add normals once we get rid of the stl_file from TriangleMesh completely. diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index fbfff90fb..d782bc5ac 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -607,6 +607,12 @@ 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; +} + void TriangleMeshSlicer::init(const TriangleMesh *_mesh, throw_on_cancel_callback_type throw_on_cancel) { mesh = _mesh; diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index 5dd2597a5..d2b688638 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -67,6 +67,8 @@ 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. + size_t memsize() const; stl_file stl; indexed_triangle_set its; diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp index 3b30e981c..09e24a475 100644 --- a/src/libslic3r/Utils.hpp +++ b/src/libslic3r/Utils.hpp @@ -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); diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp index 519763731..f9a044338 100644 --- a/src/libslic3r/utils.cpp +++ b/src/libslic3r/utils.cpp @@ -7,10 +7,15 @@ #include #ifdef WIN32 -#include -#include + #include + #include #else -#include + #include + #include + #include + #ifdef BSD + #include + #endif #endif #include @@ -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 diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 8a376c3a3..b4e7bc2b2 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -534,7 +534,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(), @@ -546,7 +546,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(), diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index e69503ff8..2f0e8bcf3 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -121,8 +121,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 cb, const std::string& err); void persist_window_geometry(wxTopLevelWindow *window, bool default_maximized = false); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 9e681257c..3f3256c42 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -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 { @@ -3531,7 +3523,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 +3535,6 @@ void ObjectList::update_after_undo_redo() ++obj_idx; } - allow_snapshots(); - #ifndef __WXOSX__ selection_changed(); #endif /* __WXOSX__ */ diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index f5f245471..11055af11 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -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 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(); @@ -1634,6 +1647,8 @@ struct Plater::priv return; assert(this->m_prevent_snapshots >= 0); this->undo_redo_stack.take_snapshot(snapshot_name, model, view3D->get_canvas3d()->get_selection()); + this->undo_redo_stack.release_least_recently_used(); + 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(); @@ -1734,7 +1749,7 @@ private: void update_fff_scene(); void update_sla_scene(); - void update_after_undo_redo(); + void update_after_undo_redo(bool temp_snapshot_was_taken = false); // path to project file stored with no extension wxString m_project_filename; @@ -1888,7 +1903,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); @@ -1901,7 +1916,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); @@ -2459,7 +2474,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(); @@ -2467,7 +2485,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); @@ -2663,7 +2681,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) @@ -2903,7 +2921,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(); @@ -2939,7 +2957,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(); @@ -3603,7 +3621,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& 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(); @@ -3611,8 +3629,9 @@ int Plater::priv::get_active_snapshot_index() void Plater::priv::undo() { + bool temp_snapshot_was_taken = this->undo_redo_stack.temp_snapshot_active(); if (this->undo_redo_stack.undo(model, this->view3D->get_canvas3d()->get_selection())) - this->update_after_undo_redo(); + this->update_after_undo_redo(temp_snapshot_was_taken); } void Plater::priv::redo() @@ -3623,8 +3642,9 @@ void Plater::priv::redo() void Plater::priv::undo_to(size_t time_to_load) { + bool temp_snapshot_was_taken = this->undo_redo_stack.temp_snapshot_active(); if (this->undo_redo_stack.undo(model, this->view3D->get_canvas3d()->get_selection(), time_to_load)) - this->update_after_undo_redo(); + this->update_after_undo_redo(temp_snapshot_was_taken); } void Plater::priv::redo_to(size_t time_to_load) @@ -3633,10 +3653,16 @@ void Plater::priv::redo_to(size_t time_to_load) this->update_after_undo_redo(); } -void Plater::priv::update_after_undo_redo() +void Plater::priv::update_after_undo_redo(bool /* temp_snapshot_was_taken */) { this->view3D->get_canvas3d()->get_selection().clear(); - this->update(false); // update volumes from the deserializd model + // 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); @@ -3644,6 +3670,8 @@ void Plater::priv::update_after_undo_redo() //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 @@ -3683,10 +3711,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); } @@ -3710,13 +3740,28 @@ void Plater::add_model() if (input_files.empty()) return; - this->take_snapshot(_(L("Add object(s)"))); + std::vector paths; + for (const auto &file : input_files) + paths.push_back(into_path(file)); - std::vector 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() @@ -3769,17 +3814,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(); @@ -3815,7 +3858,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(); @@ -3851,16 +3894,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 @@ -3884,7 +3924,7 @@ void Plater::cut(size_t obj_idx, size_t instance_idx, coordf_t z, bool keep_uppe return; } - this->take_snapshot(_(L("Cut"))); + Plater::TakeSnapshot snapshot(this, _(L("Cut"))); wxBusyCursor wait; const auto new_objects = object->cut(instance_idx, z, keep_upper, keep_lower, rotate_lower); diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index e73f88ef3..a4ced64a0 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -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 p; + void suppress_snapshots(); + void allow_snapshots(); + friend class SuppressBackgroundProcessingUpdate; }; diff --git a/src/slic3r/Utils/UndoRedo.cpp b/src/slic3r/Utils/UndoRedo.cpp index 058062502..6910fa87b 100644 --- a/src/slic3r/Utils/UndoRedo.cpp +++ b/src/slic3r/Utils/UndoRedo.cpp @@ -18,6 +18,7 @@ #include #include +#include #include @@ -28,11 +29,11 @@ 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. @@ -53,9 +54,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; @@ -75,8 +79,15 @@ public: // 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; + + // 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. @@ -96,21 +107,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: @@ -138,6 +182,18 @@ public: bool is_immutable() const override { return true; } 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. @@ -230,7 +286,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; } @@ -240,6 +297,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); @@ -270,6 +334,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) { @@ -356,7 +429,17 @@ 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))), 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; + } // The Undo / Redo stack is being initialized with an empty model and an empty selection. // The first snapshot cannot be removed. @@ -370,13 +453,15 @@ public: bool has_redo_snapshot() const; bool undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection, size_t jump_to_time); bool redo(Slic3r::Model &model, size_t jump_to_time); + void release_least_recently_used(); // Snapshot history (names with timestamps). const std::vector& 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 ObjectID save_mutable_object(const T &object); @@ -400,6 +485,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 { @@ -412,9 +498,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; @@ -436,6 +523,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. @@ -655,7 +745,7 @@ void StackImpl::take_snapshot(const std::string &snapshot_name, const Slic3r::Mo 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); // Release empty objects from the history. this->collect_garbage(); assert(this->valid()); @@ -710,9 +800,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); + this->take_snapshot(topmost_snapshot_name, model, selection); // The line above entered another topmost_snapshot_name. assert(m_snapshots.back().is_topmost()); assert(! m_snapshots.back().is_topmost_captured()); @@ -722,8 +813,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); + 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(); @@ -764,10 +862,70 @@ 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 + 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. + 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 { + // 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) { pimpl->take_snapshot(snapshot_name, model, selection); } +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) { pimpl->take_snapshot(snapshot_name, model, selection); } 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, size_t time_to_load) { return pimpl->undo(model, selection, time_to_load); } @@ -776,6 +934,7 @@ const Selection& Stack::selection_deserialized() const { return pimpl->selection const std::vector& 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 diff --git a/src/slic3r/Utils/UndoRedo.hpp b/src/slic3r/Utils/UndoRedo.hpp index f8bfda08c..606b8de74 100644 --- a/src/slic3r/Utils/UndoRedo.hpp +++ b/src/slic3r/Utils/UndoRedo.hpp @@ -55,6 +55,15 @@ 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); @@ -78,6 +87,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. From cc1338ce6ab60470586626660240667965cb9523 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 17 Jul 2019 16:00:09 +0200 Subject: [PATCH 2/7] Fix after merge --- src/slic3r/Utils/UndoRedo.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/Utils/UndoRedo.cpp b/src/slic3r/Utils/UndoRedo.cpp index 3ed7bd05e..51a811350 100644 --- a/src/slic3r/Utils/UndoRedo.cpp +++ b/src/slic3r/Utils/UndoRedo.cpp @@ -803,7 +803,7 @@ bool StackImpl::undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selecti bool new_snapshot_taken = false; if (m_active_snapshot_time == m_snapshots.back().timestamp && ! m_snapshots.back().is_topmost_captured()) { // The current state is temporary. The current state needs to be captured to be redoable. - this->take_snapshot(topmost_snapsnot_name, model, selection, gizmos); + this->take_snapshot(topmost_snapshot_name, model, selection, gizmos); // The line above entered another topmost_snapshot_name. assert(m_snapshots.back().is_topmost()); assert(! m_snapshots.back().is_topmost_captured()); From cd95b52dcd0c12f5885abfea415ef8e05be37aba Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 18 Jul 2019 11:51:06 +0200 Subject: [PATCH 3/7] Undo / Redo memory conservation strategy: Release recoverable data starting from the objects of lowest ObjectID. (convex hulls are recoverable as well as the indexed triangle sets inside the TriangleMeshes or the triangle connectivity information). Now the top most snapshot (the temp one taken before Undo jump) will never be released. --- src/admesh/connect.cpp | 2 + src/libslic3r/Model.hpp | 24 ++++++- src/libslic3r/TriangleMesh.cpp | 28 ++++++++ src/libslic3r/TriangleMesh.hpp | 6 +- src/slic3r/Utils/UndoRedo.cpp | 115 ++++++++++++++++++++++++++++++--- 5 files changed, 162 insertions(+), 13 deletions(-) diff --git a/src/admesh/connect.cpp b/src/admesh/connect.cpp index b86ec5055..d6de6ce6a 100644 --- a/src/admesh/connect.cpp +++ b/src/admesh/connect.cpp @@ -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; diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 7551bd8cb..37741a1f1 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -526,8 +526,23 @@ private: ModelVolume() : ObjectBase(-1), config(-1), object(nullptr) { assert(this->id().invalid()); assert(this->config.id().invalid()); } - template void serialize(Archive &ar) { - ar(name, config, m_mesh, m_type, m_material_id, m_convex_hull, m_transformation, m_is_splittable); + template 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 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); } }; @@ -747,4 +762,9 @@ void check_model_ids_equal(const Model &model1, const Model &model2); } // namespace Slic3r +namespace cereal +{ + template struct specialize {}; +} + #endif /* slic3r_Model_hpp_ */ diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index d782bc5ac..c2fcb11bd 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -613,6 +613,34 @@ size_t TriangleMesh::memsize() const 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; diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index d2b688638..81390b79b 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -67,8 +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. + // 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; diff --git a/src/slic3r/Utils/UndoRedo.cpp b/src/slic3r/Utils/UndoRedo.cpp index 51a811350..10873ea89 100644 --- a/src/slic3r/Utils/UndoRedo.cpp +++ b/src/slic3r/Utils/UndoRedo.cpp @@ -25,6 +25,12 @@ #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 { @@ -74,6 +80,9 @@ 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. @@ -85,6 +94,10 @@ public: // Release all data after the given timestamp. For the ImmutableObjectHistory, the shared pointer is NOT released. // Return the amount of memory released. virtual size_t release_after_timestamp(size_t timestamp) = 0; + // Release all 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; @@ -175,11 +188,13 @@ template class ImmutableObjectHistory : public ObjectHistory { public: - ImmutableObjectHistory(std::shared_ptr shared_object) : m_shared_object(shared_object) {} + ImmutableObjectHistory(std::shared_ptr 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. @@ -216,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(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(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& shared_ptr(StackImpl &stack); @@ -240,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 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; }; @@ -375,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(); @@ -430,7 +483,7 @@ 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. // 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))), m_active_snapshot_time(0), m_current_time(0) {} + 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; } @@ -461,9 +514,9 @@ public: //protected: template ObjectID save_mutable_object(const T &object); - template ObjectID save_immutable_object(std::shared_ptr &object); + template ObjectID save_immutable_object(std::shared_ptr &object, bool optional); template T* load_mutable_object(const Slic3r::ObjectID id); - template std::shared_ptr load_immutable_object(const Slic3r::ObjectID id); + template std::shared_ptr load_immutable_object(const Slic3r::ObjectID id, bool optional); template void load_mutable_object(const Slic3r::ObjectID id, T &target); #ifdef SLIC3R_UNDOREDO_DEBUG @@ -620,7 +673,11 @@ namespace cereal // store just the ObjectID to this stream. template void save(BinaryOutputArchive &ar, const std::shared_ptr &ptr) { - ar(cereal::get_user_data(ar).save_immutable_object(const_cast&>(ptr))); + ar(cereal::get_user_data(ar).save_immutable_object(const_cast&>(ptr), false)); + } + template void save_optional(BinaryOutputArchive &ar, const std::shared_ptr &ptr) + { + ar(cereal::get_user_data(ar).save_immutable_object(const_cast&>(ptr), true)); } // Load ObjectBase derived class from the Undo / Redo stack as a separate object @@ -630,7 +687,14 @@ namespace cereal Slic3r::UndoRedo::StackImpl &stack = cereal::get_user_data(ar); size_t id; ar(id); - ptr = stack.load_immutable_object(Slic3r::ObjectID(id)); + ptr = stack.load_immutable_object(Slic3r::ObjectID(id), false); + } + template void load_optional(BinaryInputArchive &ar, std::shared_ptr &ptr) + { + Slic3r::UndoRedo::StackImpl &stack = cereal::get_user_data(ar); + size_t id; + ar(id); + ptr = stack.load_immutable_object(Slic3r::ObjectID(id), true); } } @@ -675,14 +739,16 @@ template ObjectID StackImpl::save_mutable_object(cons return object.id(); } -template ObjectID StackImpl::save_immutable_object(std::shared_ptr &object) +template ObjectID StackImpl::save_immutable_object(std::shared_ptr &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>(new ImmutableObjectHistory(object))); + it_object_history = m_objects.emplace_hint(it_object_history, object_id, std::unique_ptr>(new ImmutableObjectHistory(object, optional))); + else + assert(it_object_history->second.get()->is_optional() == optional); // Then save the interval. static_cast*>(it_object_history->second.get())->save(m_active_snapshot_time, m_current_time); return object_id; @@ -695,13 +761,16 @@ template T* StackImpl::load_mutable_object(const Slic3r::ObjectID id return target; } -template std::shared_ptr StackImpl::load_immutable_object(const Slic3r::ObjectID id) +template std::shared_ptr 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(); auto *object_history = static_cast*>(it_object_history->second.get()); assert(object_history->has_snapshot(m_active_snapshot_time)); + object_history->restore_optional(); return object_history->shared_ptr(*this); } @@ -869,12 +938,32 @@ void StackImpl::release_least_recently_used() #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()) { @@ -888,6 +977,12 @@ void StackImpl::release_least_recently_used() } 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();) { From 69ef6cf8067a2f10eb51e1570b1f9784557e734a Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 18 Jul 2019 12:07:50 +0200 Subject: [PATCH 4/7] Fix for compilation on clang: Forward declarations of templates. --- src/libslic3r/Model.hpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 37741a1f1..c026863ec 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -17,6 +17,13 @@ #include #include +namespace cereal { + class BinaryInputArchive; + class BinaryOutputArchive; + template void load_optional(BinaryInputArchive &ar, std::shared_ptr &ptr); + template void save_optional(BinaryOutputArchive &ar, const std::shared_ptr &ptr); +} + namespace Slic3r { class Model; From 76b1fbc5bf7ae42a0ea826afc6a6d5ae070f32ec Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 18 Jul 2019 16:32:04 +0200 Subject: [PATCH 5/7] Eliminate some igl warnings on msvc --- src/libslic3r/SLA/SLAAutoSupports.cpp | 2 +- src/libslic3r/SLA/SLASupportTreeIGL.cpp | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/SLA/SLAAutoSupports.cpp b/src/libslic3r/SLA/SLAAutoSupports.cpp index 91b7dfe5d..78efd0806 100644 --- a/src/libslic3r/SLA/SLAAutoSupports.cpp +++ b/src/libslic3r/SLA/SLAAutoSupports.cpp @@ -386,7 +386,7 @@ static inline std::vector poisson_disk_from_samples(const std::vector #include #include #include +#ifdef _MSC_VER +#pragma warning(pop) +#endif #include From a0ea96968d9097ad2f329e1e34bf207d955bed04 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 18 Jul 2019 17:41:47 +0200 Subject: [PATCH 6/7] Storing the active printer technology onto the Undo / Redo stack, remembering the last selected Printer profile for the SLA and FDM technologies separately, and activating them on Undo / Redo. When switching the technologies, user is asked whether to discard the modified profiles or not. --- src/libslic3r/Model.cpp | 25 ++++ src/libslic3r/Model.hpp | 6 + src/slic3r/GUI/GUI.cpp | 3 +- src/slic3r/GUI/GUI_App.cpp | 12 +- src/slic3r/GUI/GUI_App.hpp | 4 +- src/slic3r/GUI/GUI_ObjectList.cpp | 15 +- src/slic3r/GUI/GUI_ObjectList.hpp | 1 - src/slic3r/GUI/GUI_ObjectManipulation.cpp | 8 +- src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 2 +- src/slic3r/GUI/Plater.cpp | 146 +++++++++---------- src/slic3r/GUI/Tab.cpp | 2 +- src/slic3r/Utils/UndoRedo.cpp | 20 +-- src/slic3r/Utils/UndoRedo.hpp | 14 +- 14 files changed, 141 insertions(+), 119 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 949f82c0a..66532e9ea 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -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) diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index c026863ec..2b4a6d978 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -761,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); diff --git a/src/slic3r/GUI/GUI.cpp b/src/slic3r/GUI/GUI.cpp index 9a641c7c0..8f41ed5a3 100644 --- a/src/slic3r/GUI/GUI.cpp +++ b/src/slic3r/GUI/GUI.cpp @@ -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" + diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index b4e7bc2b2..02290e83d 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -854,7 +854,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(); @@ -868,8 +868,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; @@ -944,9 +948,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 diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 2f0e8bcf3..8d0bcfe2f 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -139,7 +139,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(); @@ -158,7 +158,7 @@ public: ObjectList* obj_list(); ObjectLayers* obj_layers(); Plater* plater(); - std::vector *model_objects(); + Model& model(); AppConfig* app_config{ nullptr }; PresetBundle* preset_bundle{ nullptr }; diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 3f3256c42..f7abb4128 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2603,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 @@ -3080,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() { diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 2d7e9f5f1..5f5d5d625 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -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); diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index fcb047139..308b3f208 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -25,7 +25,7 @@ static double get_volume_min_z(const GLVolume* volume) const Transform3f& world_matrix = volume->world_matrix().cast(); // 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. diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp index cb996a104..5a42cbd31 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp @@ -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; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 833ba3ee2..8027906a9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -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; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 7c7488c6a..559145eba 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1646,16 +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--; } @@ -1749,10 +1751,13 @@ private: void update_fff_scene(); void update_sla_scene(); + void undo_redo_to(std::vector::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); @@ -2102,66 +2107,22 @@ std::vector Plater::priv::load_files(const std::vector& 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) @@ -3629,28 +3590,58 @@ int Plater::priv::get_active_snapshot_index() void Plater::priv::undo() { - bool temp_snapshot_was_taken = this->undo_redo_stack.temp_snapshot_active(); - 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(temp_snapshot_was_taken); + const std::vector &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 &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) { - bool temp_snapshot_was_taken = this->undo_redo_stack.temp_snapshot_active(); - 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(temp_snapshot_was_taken); + const std::vector &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::undo_redo_to(std::vector::const_iterator it_snapshot) +{ + 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 */) @@ -3669,6 +3660,13 @@ void Plater::priv::update_after_undo_redo(bool /* temp_snapshot_was_taken */) 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? @@ -4230,7 +4228,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) { @@ -4240,7 +4238,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) { diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 7ab564beb..b52f9dd46 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -2904,7 +2904,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" + diff --git a/src/slic3r/Utils/UndoRedo.cpp b/src/slic3r/Utils/UndoRedo.cpp index 10873ea89..c4a082fb6 100644 --- a/src/slic3r/Utils/UndoRedo.cpp +++ b/src/slic3r/Utils/UndoRedo.cpp @@ -495,12 +495,12 @@ public: } // Store the current application state onto the Undo / Redo stack, remove all snapshots after m_active_snapshot_time. - void take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos); + 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 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(); @@ -788,7 +788,7 @@ template 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); @@ -808,11 +808,11 @@ void StackImpl::take_snapshot(const std::string& snapshot_name, const Slic3r::Mo this->save_mutable_object(m_selection); this->save_mutable_object(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_snapshot_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()); @@ -858,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) { @@ -872,7 +872,7 @@ bool StackImpl::undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selecti bool new_snapshot_taken = false; if (m_active_snapshot_time == m_snapshots.back().timestamp && ! m_snapshots.back().is_topmost_captured()) { // The current state is temporary. The current state needs to be captured to be redoable. - this->take_snapshot(topmost_snapshot_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()); @@ -1020,10 +1020,12 @@ Stack::~Stack() {} 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) { pimpl->take_snapshot(snapshot_name, model, selection, gizmos); } +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(); } diff --git a/src/slic3r/Utils/UndoRedo.hpp b/src/slic3r/Utils/UndoRedo.hpp index 6785cd740..00ab55822 100644 --- a/src/slic3r/Utils/UndoRedo.hpp +++ b/src/slic3r/Utils/UndoRedo.hpp @@ -23,11 +23,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; } @@ -66,7 +68,7 @@ public: 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; @@ -74,7 +76,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); From 4049f33609858bc2f6884f3e8a1b4c3ec9c87f7d Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 18 Jul 2019 18:19:40 +0200 Subject: [PATCH 7/7] Fix of osx builds --- src/libslic3r/Config.hpp | 2 +- src/slic3r/Utils/UndoRedo.hpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index 844287efb..7d96f6cf3 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -93,7 +93,7 @@ enum ConfigOptionMode { comExpert }; -enum PrinterTechnology +enum PrinterTechnology : unsigned char { // Fused Filament Fabrication ptFFF, diff --git a/src/slic3r/Utils/UndoRedo.hpp b/src/slic3r/Utils/UndoRedo.hpp index 00ab55822..96b539f18 100644 --- a/src/slic3r/Utils/UndoRedo.hpp +++ b/src/slic3r/Utils/UndoRedo.hpp @@ -12,6 +12,7 @@ namespace Slic3r { class Model; +enum PrinterTechnology : unsigned char; namespace GUI { class Selection;