WIP: Undo / Redo memory limiting by releasing the least recently
used snapshots. Memory limit set to 10% of physical system memory.
This commit is contained in:
parent
4865240a9c
commit
3a74e7ab69
12 changed files with 411 additions and 82 deletions
|
@ -127,6 +127,10 @@ struct stl_file {
|
||||||
this->stats.reset();
|
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_facet> facet_start;
|
||||||
std::vector<stl_neighbors> neighbors_start;
|
std::vector<stl_neighbors> neighbors_start;
|
||||||
// Statistics
|
// Statistics
|
||||||
|
@ -139,6 +143,10 @@ struct indexed_triangle_set
|
||||||
|
|
||||||
void clear() { indices.clear(); vertices.clear(); }
|
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_triangle_vertex_indices> indices;
|
||||||
std::vector<stl_vertex> vertices;
|
std::vector<stl_vertex> vertices;
|
||||||
//FIXME add normals once we get rid of the stl_file from TriangleMesh completely.
|
//FIXME add normals once we get rid of the stl_file from TriangleMesh completely.
|
||||||
|
|
|
@ -607,6 +607,12 @@ void TriangleMesh::require_shared_vertices()
|
||||||
BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - end";
|
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)
|
void TriangleMeshSlicer::init(const TriangleMesh *_mesh, throw_on_cancel_callback_type throw_on_cancel)
|
||||||
{
|
{
|
||||||
mesh = _mesh;
|
mesh = _mesh;
|
||||||
|
|
|
@ -67,6 +67,8 @@ public:
|
||||||
size_t facets_count() const { return this->stl.stats.number_of_facets; }
|
size_t facets_count() const { return this->stl.stats.number_of_facets; }
|
||||||
bool empty() const { return this->facets_count() == 0; }
|
bool empty() const { return this->facets_count() == 0; }
|
||||||
bool is_splittable() const;
|
bool is_splittable() const;
|
||||||
|
// Estimate of the memory occupied by this structure.
|
||||||
|
size_t memsize() const;
|
||||||
|
|
||||||
stl_file stl;
|
stl_file stl;
|
||||||
indexed_triangle_set its;
|
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).
|
// The string is non-empty only if the loglevel >= info (3).
|
||||||
extern std::string log_memory_info();
|
extern std::string log_memory_info();
|
||||||
extern void disable_multi_threading();
|
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.
|
// Set a path with GUI resource files.
|
||||||
void set_var_dir(const std::string &path);
|
void set_var_dir(const std::string &path);
|
||||||
|
|
|
@ -7,10 +7,15 @@
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
#ifdef WIN32
|
#ifdef WIN32
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <psapi.h>
|
#include <psapi.h>
|
||||||
#else
|
#else
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/param.h>
|
||||||
|
#ifdef BSD
|
||||||
|
#include <sys/sysctl.h>
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <boost/log/core.hpp>
|
#include <boost/log/core.hpp>
|
||||||
|
@ -467,4 +472,75 @@ std::string log_memory_info()
|
||||||
}
|
}
|
||||||
#endif
|
#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
|
}; // namespace Slic3r
|
||||||
|
|
|
@ -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();
|
input_file.Clear();
|
||||||
wxFileDialog dialog(parent ? parent : GetTopWindow(),
|
wxFileDialog dialog(parent ? parent : GetTopWindow(),
|
||||||
|
@ -546,7 +546,7 @@ void GUI_App::load_project(wxWindow *parent, wxString& input_file)
|
||||||
input_file = dialog.GetPath();
|
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();
|
input_files.Clear();
|
||||||
wxFileDialog dialog(parent ? parent : GetTopWindow(),
|
wxFileDialog dialog(parent ? parent : GetTopWindow(),
|
||||||
|
|
|
@ -121,8 +121,8 @@ public:
|
||||||
void recreate_GUI();
|
void recreate_GUI();
|
||||||
void system_info();
|
void system_info();
|
||||||
void keyboard_shortcuts();
|
void keyboard_shortcuts();
|
||||||
void load_project(wxWindow *parent, wxString& input_file);
|
void load_project(wxWindow *parent, wxString& input_file) const;
|
||||||
void import_model(wxWindow *parent, wxArrayString& input_files);
|
void import_model(wxWindow *parent, wxArrayString& input_files) const;
|
||||||
static bool catch_error(std::function<void()> cb, const std::string& err);
|
static bool catch_error(std::function<void()> cb, const std::string& err);
|
||||||
|
|
||||||
void persist_window_geometry(wxTopLevelWindow *window, bool default_maximized = false);
|
void persist_window_geometry(wxTopLevelWindow *window, bool default_maximized = false);
|
||||||
|
|
|
@ -71,9 +71,6 @@ static void take_snapshot(const wxString& snapshot_name)
|
||||||
wxGetApp().plater()->take_snapshot(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) :
|
ObjectList::ObjectList(wxWindow* parent) :
|
||||||
wxDataViewCtrl(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxDV_MULTIPLE),
|
wxDataViewCtrl(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxDV_MULTIPLE),
|
||||||
m_parent(parent)
|
m_parent(parent)
|
||||||
|
@ -2406,8 +2403,7 @@ void ObjectList::remove()
|
||||||
|
|
||||||
wxDataViewItem parent = wxDataViewItem(0);
|
wxDataViewItem parent = wxDataViewItem(0);
|
||||||
|
|
||||||
take_snapshot(_(L("Delete Selected")));
|
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Delete Selected")));
|
||||||
suppress_snapshots();
|
|
||||||
|
|
||||||
for (auto& item : sels)
|
for (auto& item : sels)
|
||||||
{
|
{
|
||||||
|
@ -2429,8 +2425,6 @@ void ObjectList::remove()
|
||||||
|
|
||||||
if (parent)
|
if (parent)
|
||||||
select_item(parent);
|
select_item(parent);
|
||||||
|
|
||||||
allow_snapshots();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ObjectList::del_layer_range(const t_layer_height_range& range)
|
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 };
|
t_layer_height_range new_range = { midl_layer, next_range.second };
|
||||||
|
|
||||||
take_snapshot(_(L("Add New Layers Range")));
|
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Add New Layers Range")));
|
||||||
suppress_snapshots();
|
|
||||||
|
|
||||||
// create new 2 layers instead of deleted one
|
// 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 };
|
new_range = { current_range.second, midl_layer };
|
||||||
ranges[new_range] = get_default_layer_config(obj_idx);
|
ranges[new_range] = get_default_layer_config(obj_idx);
|
||||||
add_layer_item(new_range, layers_item, layer_idx);
|
add_layer_item(new_range, layers_item, layer_idx);
|
||||||
allow_snapshots();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -3531,7 +3523,7 @@ void ObjectList::update_after_undo_redo()
|
||||||
m_prevent_list_events = true;
|
m_prevent_list_events = true;
|
||||||
m_prevent_canvas_selection_update = 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.
|
// Unselect all objects before deleting them, so that no change of selection is emitted during deletion.
|
||||||
this->UnselectAll();
|
this->UnselectAll();
|
||||||
|
@ -3543,8 +3535,6 @@ void ObjectList::update_after_undo_redo()
|
||||||
++obj_idx;
|
++obj_idx;
|
||||||
}
|
}
|
||||||
|
|
||||||
allow_snapshots();
|
|
||||||
|
|
||||||
#ifndef __WXOSX__
|
#ifndef __WXOSX__
|
||||||
selection_changed();
|
selection_changed();
|
||||||
#endif /* __WXOSX__ */
|
#endif /* __WXOSX__ */
|
||||||
|
|
|
@ -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)
|
bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &filenames)
|
||||||
{
|
{
|
||||||
plater->take_snapshot(_(L("Load Files")));
|
|
||||||
|
|
||||||
std::vector<fs::path> paths;
|
std::vector<fs::path> paths;
|
||||||
|
|
||||||
for (const auto &filename : filenames) {
|
for (const auto &filename : filenames) {
|
||||||
fs::path path(into_path(filename));
|
fs::path path(into_path(filename));
|
||||||
|
|
||||||
if (std::regex_match(path.string(), pattern_drop)) {
|
if (std::regex_match(path.string(), pattern_drop)) {
|
||||||
paths.push_back(std::move(path));
|
paths.push_back(std::move(path));
|
||||||
} else {
|
} 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
|
// 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)
|
// (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(Plater *q, MainFrame *main_frame);
|
||||||
~priv();
|
~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(const std::string& direction);
|
||||||
void select_view_3D(const std::string& name);
|
void select_view_3D(const std::string& name);
|
||||||
void select_next_view_3D();
|
void select_next_view_3D();
|
||||||
|
@ -1634,6 +1647,8 @@ struct Plater::priv
|
||||||
return;
|
return;
|
||||||
assert(this->m_prevent_snapshots >= 0);
|
assert(this->m_prevent_snapshots >= 0);
|
||||||
this->undo_redo_stack.take_snapshot(snapshot_name, model, view3D->get_canvas3d()->get_selection());
|
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())); }
|
void take_snapshot(const wxString& snapshot_name) { this->take_snapshot(std::string(snapshot_name.ToUTF8().data())); }
|
||||||
int get_active_snapshot_index();
|
int get_active_snapshot_index();
|
||||||
|
@ -1734,7 +1749,7 @@ private:
|
||||||
|
|
||||||
void update_fff_scene();
|
void update_fff_scene();
|
||||||
void update_sla_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
|
// path to project file stored with no extension
|
||||||
wxString m_project_filename;
|
wxString m_project_filename;
|
||||||
|
@ -1888,7 +1903,7 @@ Plater::priv::~priv()
|
||||||
delete config;
|
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
|
// the following line, when enabled, causes flickering on NVIDIA graphics cards
|
||||||
// wxWindowUpdateLocker freeze_guard(q);
|
// wxWindowUpdateLocker freeze_guard(q);
|
||||||
|
@ -1901,7 +1916,7 @@ void Plater::priv::update(bool force_full_scene_refresh)
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int update_status = 0;
|
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()
|
// Update the SLAPrint from the current Model, so that the reload_scene()
|
||||||
// pulls the correct data.
|
// pulls the correct data.
|
||||||
update_status = this->update_background_process(false);
|
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)
|
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);
|
model.delete_object(obj_idx);
|
||||||
update();
|
update();
|
||||||
object_list_changed();
|
object_list_changed();
|
||||||
|
@ -2467,7 +2485,7 @@ void Plater::priv::delete_object_from_model(size_t obj_idx)
|
||||||
|
|
||||||
void Plater::priv::reset()
|
void Plater::priv::reset()
|
||||||
{
|
{
|
||||||
this->take_snapshot(_(L("Reset Project")));
|
Plater::TakeSnapshot snapshot(q, _(L("Reset Project")));
|
||||||
|
|
||||||
set_project_filename(wxEmptyString);
|
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.")));
|
Slic3r::GUI::warning_catcher(q, _(L("The selected object couldn't be split because it contains only one part.")));
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
this->take_snapshot(_(L("Split to Objects")));
|
Plater::TakeSnapshot snapshot(q, _(L("Split to Objects")));
|
||||||
|
|
||||||
unsigned int counter = 1;
|
unsigned int counter = 1;
|
||||||
for (ModelObject* m : new_objects)
|
for (ModelObject* m : new_objects)
|
||||||
|
@ -2903,7 +2921,7 @@ void Plater::priv::update_sla_scene()
|
||||||
|
|
||||||
void Plater::priv::reload_from_disk()
|
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 &selection = get_selection();
|
||||||
const auto obj_orig_idx = selection.get_object_idx();
|
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)
|
if (obj_idx < 0)
|
||||||
return;
|
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);
|
fix_model_by_win10_sdk_gui(*model.objects[obj_idx], vol_idx);
|
||||||
this->update();
|
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()
|
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 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));
|
const auto it = std::lower_bound(ss_stack.begin(), ss_stack.end(), UndoRedo::Snapshot(active_snapshot_time));
|
||||||
return it - ss_stack.begin();
|
return it - ss_stack.begin();
|
||||||
|
@ -3611,8 +3629,9 @@ int Plater::priv::get_active_snapshot_index()
|
||||||
|
|
||||||
void Plater::priv::undo()
|
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()))
|
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()
|
void Plater::priv::redo()
|
||||||
|
@ -3623,8 +3642,9 @@ void Plater::priv::redo()
|
||||||
|
|
||||||
void Plater::priv::undo_to(size_t time_to_load)
|
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))
|
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)
|
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();
|
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->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)
|
//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_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 state of the manipulators?
|
||||||
//FIXME what about the focus? Cursor in the side panel?
|
//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
|
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()
|
void Plater::load_project()
|
||||||
{
|
{
|
||||||
this->take_snapshot(_(L("Load Project")));
|
// Ask user for a project file name.
|
||||||
|
|
||||||
wxString input_file;
|
wxString input_file;
|
||||||
wxGetApp().load_project(this, 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);
|
load_project(input_file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3710,13 +3740,28 @@ void Plater::add_model()
|
||||||
if (input_files.empty())
|
if (input_files.empty())
|
||||||
return;
|
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;
|
wxString snapshot_label;
|
||||||
for (const auto &file : input_files) {
|
assert(! paths.empty());
|
||||||
input_paths.push_back(into_path(file));
|
if (paths.size() == 1) {
|
||||||
}
|
snapshot_label = _(L("Import Object"));
|
||||||
load_files(input_paths, true, false);
|
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()
|
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()
|
void Plater::remove_selected()
|
||||||
{
|
{
|
||||||
this->take_snapshot(_(L("Delete Selected Objects")));
|
Plater::TakeSnapshot snapshot(this, _(L("Delete Selected Objects")));
|
||||||
this->suppress_snapshots();
|
|
||||||
this->p->view3D->delete_selected();
|
this->p->view3D->delete_selected();
|
||||||
this->allow_snapshots();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Plater::increase_instances(size_t num)
|
void Plater::increase_instances(size_t num)
|
||||||
{
|
{
|
||||||
if (! can_increase_instances()) { return; }
|
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();
|
int obj_idx = p->get_selected_object_idx();
|
||||||
|
|
||||||
|
@ -3815,7 +3858,7 @@ void Plater::decrease_instances(size_t num)
|
||||||
{
|
{
|
||||||
if (! can_decrease_instances()) { return; }
|
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();
|
int obj_idx = p->get_selected_object_idx();
|
||||||
|
|
||||||
|
@ -3851,16 +3894,13 @@ void Plater::set_number_of_copies(/*size_t num*/)
|
||||||
if (num < 0)
|
if (num < 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this->take_snapshot(wxString::Format(_(L("Set numbers of copies to %d")), num));
|
Plater::TakeSnapshot snapshot(this, wxString::Format(_(L("Set numbers of copies to %d")), num));
|
||||||
this->suppress_snapshots();
|
|
||||||
|
|
||||||
int diff = (int)num - (int)model_object->instances.size();
|
int diff = (int)num - (int)model_object->instances.size();
|
||||||
if (diff > 0)
|
if (diff > 0)
|
||||||
increase_instances(diff);
|
increase_instances(diff);
|
||||||
else if (diff < 0)
|
else if (diff < 0)
|
||||||
decrease_instances(-diff);
|
decrease_instances(-diff);
|
||||||
|
|
||||||
this->allow_snapshots();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Plater::is_selection_empty() const
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this->take_snapshot(_(L("Cut")));
|
Plater::TakeSnapshot snapshot(this, _(L("Cut")));
|
||||||
|
|
||||||
wxBusyCursor wait;
|
wxBusyCursor wait;
|
||||||
const auto new_objects = object->cut(instance_idx, z, keep_upper, keep_lower, rotate_lower);
|
const auto new_objects = object->cut(instance_idx, z, keep_upper, keep_lower, rotate_lower);
|
||||||
|
|
|
@ -186,8 +186,6 @@ public:
|
||||||
|
|
||||||
void take_snapshot(const std::string &snapshot_name);
|
void take_snapshot(const std::string &snapshot_name);
|
||||||
void take_snapshot(const wxString &snapshot_name);
|
void take_snapshot(const wxString &snapshot_name);
|
||||||
void suppress_snapshots();
|
|
||||||
void allow_snapshots();
|
|
||||||
void undo();
|
void undo();
|
||||||
void redo();
|
void redo();
|
||||||
void undo_to(int selection);
|
void undo_to(int selection);
|
||||||
|
@ -235,10 +233,46 @@ public:
|
||||||
|
|
||||||
const Camera& get_camera() const;
|
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:
|
private:
|
||||||
struct priv;
|
struct priv;
|
||||||
std::unique_ptr<priv> p;
|
std::unique_ptr<priv> p;
|
||||||
|
|
||||||
|
void suppress_snapshots();
|
||||||
|
void allow_snapshots();
|
||||||
|
|
||||||
friend class SuppressBackgroundProcessingUpdate;
|
friend class SuppressBackgroundProcessingUpdate;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
#include <cereal/archives/adapters.hpp>
|
#include <cereal/archives/adapters.hpp>
|
||||||
|
|
||||||
#include <libslic3r/ObjectID.hpp>
|
#include <libslic3r/ObjectID.hpp>
|
||||||
|
#include <libslic3r/Utils.hpp>
|
||||||
|
|
||||||
#include <boost/foreach.hpp>
|
#include <boost/foreach.hpp>
|
||||||
|
|
||||||
|
@ -28,11 +29,11 @@
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
namespace UndoRedo {
|
namespace UndoRedo {
|
||||||
|
|
||||||
static std::string topmost_snapsnot_name = "@@@ Topmost @@@";
|
static std::string topmost_snapshot_name = "@@@ Topmost @@@";
|
||||||
|
|
||||||
bool Snapshot::is_topmost() const
|
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.
|
// 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_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; }
|
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 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; }
|
void extend_end(size_t new_end) { assert(new_end >= m_end); m_end = new_end; }
|
||||||
|
|
||||||
|
size_t memsize() const { return sizeof(this); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
size_t m_begin;
|
size_t m_begin;
|
||||||
size_t m_end;
|
size_t m_end;
|
||||||
|
@ -75,8 +79,15 @@ public:
|
||||||
// If the history is empty, the ObjectHistory object could be released.
|
// If the history is empty, the ObjectHistory object could be released.
|
||||||
virtual bool empty() = 0;
|
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.
|
// 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
|
#ifdef SLIC3R_UNDOREDO_DEBUG
|
||||||
// Human readable debug information.
|
// Human readable debug information.
|
||||||
|
@ -96,21 +107,54 @@ public:
|
||||||
// If the history is empty, the ObjectHistory object could be released.
|
// If the history is empty, the ObjectHistory object could be released.
|
||||||
bool empty() override { return m_history.empty(); }
|
bool empty() override { return m_history.empty(); }
|
||||||
|
|
||||||
// Release all data after the given timestamp. The shared pointer is NOT released.
|
// Release all data before the given timestamp. For the ImmutableObjectHistory, the shared pointer is NOT released.
|
||||||
void release_after_timestamp(size_t timestamp) override {
|
size_t release_before_timestamp(size_t timestamp) override {
|
||||||
assert(! m_history.empty());
|
size_t mem_released = 0;
|
||||||
assert(this->valid());
|
if (! m_history.empty()) {
|
||||||
// it points to an interval which either starts with timestamp, or follows the timestamp.
|
assert(this->valid());
|
||||||
auto it = std::lower_bound(m_history.begin(), m_history.end(), T(timestamp, timestamp));
|
// it points to an interval which either starts with timestamp, or follows the timestamp.
|
||||||
if (it != m_history.begin()) {
|
auto it = std::lower_bound(m_history.begin(), m_history.end(), T(timestamp, timestamp));
|
||||||
auto it_prev = it;
|
// Find the first iterator with begin() < timestamp.
|
||||||
-- it_prev;
|
if (it == m_history.end())
|
||||||
assert(it_prev->begin() < timestamp);
|
-- it;
|
||||||
// Trim the last interval with timestamp.
|
while (it != m_history.begin() && it->begin() >= timestamp)
|
||||||
it_prev->trim_end(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());
|
return mem_released;
|
||||||
assert(this->valid());
|
}
|
||||||
|
|
||||||
|
// 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:
|
protected:
|
||||||
|
@ -138,6 +182,18 @@ public:
|
||||||
bool is_immutable() const override { return true; }
|
bool is_immutable() const override { return true; }
|
||||||
const void* immutable_object_ptr() const { return (const void*)m_shared_object.get(); }
|
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) {
|
void save(size_t active_snapshot_time, size_t current_time) {
|
||||||
assert(m_history.empty() || m_history.back().end() <= active_snapshot_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.
|
// 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; }
|
const Interval& interval() const { return m_interval; }
|
||||||
size_t begin() const { return m_interval.begin(); }
|
size_t begin() const { return m_interval.begin(); }
|
||||||
size_t end() const { return m_interval.end(); }
|
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); }
|
void extend_end(size_t timestamp) { m_interval.extend_end(timestamp); }
|
||||||
|
|
||||||
bool operator<(const MutableHistoryInterval& rhs) const { return m_interval < rhs.m_interval; }
|
bool operator<(const MutableHistoryInterval& rhs) const { return m_interval < rhs.m_interval; }
|
||||||
|
@ -240,6 +297,13 @@ public:
|
||||||
size_t size() const { return m_data->size; }
|
size_t size() const { return m_data->size; }
|
||||||
size_t refcnt() const { return m_data->refcnt; }
|
size_t refcnt() const { return m_data->refcnt; }
|
||||||
bool matches(const std::string& data) { return m_data->matches(data); }
|
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:
|
private:
|
||||||
MutableHistoryInterval(const MutableHistoryInterval &rhs);
|
MutableHistoryInterval(const MutableHistoryInterval &rhs);
|
||||||
|
@ -270,6 +334,15 @@ public:
|
||||||
bool is_mutable() const override { return true; }
|
bool is_mutable() const override { return true; }
|
||||||
bool is_immutable() const override { return false; }
|
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) {
|
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);
|
assert(m_history.empty() || m_history.back().end() <= active_snapshot_time);
|
||||||
if (m_history.empty() || m_history.back().end() < active_snapshot_time) {
|
if (m_history.empty() || m_history.back().end() < active_snapshot_time) {
|
||||||
|
@ -356,7 +429,17 @@ class StackImpl
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
// Stack needs to be initialized. An empty stack is not valid, there must be a "New Project" status stored at the beginning.
|
// 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 Undo / Redo stack is being initialized with an empty model and an empty selection.
|
||||||
// The first snapshot cannot be removed.
|
// The first snapshot cannot be removed.
|
||||||
|
@ -370,13 +453,15 @@ public:
|
||||||
bool has_redo_snapshot() const;
|
bool has_redo_snapshot() const;
|
||||||
bool undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection, size_t jump_to_time);
|
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);
|
bool redo(Slic3r::Model &model, size_t jump_to_time);
|
||||||
|
void release_least_recently_used();
|
||||||
|
|
||||||
// Snapshot history (names with timestamps).
|
// Snapshot history (names with timestamps).
|
||||||
const std::vector<Snapshot>& snapshots() const { return m_snapshots; }
|
const std::vector<Snapshot>& snapshots() const { return m_snapshots; }
|
||||||
// Timestamp of the active snapshot.
|
// Timestamp of the active snapshot.
|
||||||
size_t active_snapshot_time() const { return m_active_snapshot_time; }
|
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:
|
//protected:
|
||||||
template<typename T, typename T_AS> ObjectID save_mutable_object(const T &object);
|
template<typename T, typename T_AS> ObjectID save_mutable_object(const T &object);
|
||||||
|
@ -400,6 +485,7 @@ public:
|
||||||
if (m_active_snapshot_time > m_snapshots.back().timestamp)
|
if (m_active_snapshot_time > m_snapshots.back().timestamp)
|
||||||
out += ">>>\n";
|
out += ">>>\n";
|
||||||
out += "Current time: " + std::to_string(m_current_time) + "\n";
|
out += "Current time: " + std::to_string(m_current_time) + "\n";
|
||||||
|
out += "Total memory occupied: " + std::to_string(this->memsize()) + "\n";
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
void print() const {
|
void print() const {
|
||||||
|
@ -412,9 +498,10 @@ public:
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
bool valid() const {
|
bool valid() const {
|
||||||
assert(! m_snapshots.empty());
|
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));
|
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(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)
|
for (auto it = m_objects.begin(); it != m_objects.end(); ++ it)
|
||||||
assert(it->second->valid());
|
assert(it->second->valid());
|
||||||
return true;
|
return true;
|
||||||
|
@ -436,6 +523,9 @@ private:
|
||||||
}
|
}
|
||||||
void collect_garbage();
|
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)
|
// Each individual object (Model, ModelObject, ModelInstance, ModelVolume, Selection, TriangleMesh)
|
||||||
// is stored with its own history, referenced by the ObjectID. Immutable objects do not provide
|
// 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.
|
// 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;
|
m_active_snapshot_time = m_current_time;
|
||||||
// Save snapshot info of the last "current" aka "top most" state, that is only being serialized
|
// 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.
|
// 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.
|
// Release empty objects from the history.
|
||||||
this->collect_garbage();
|
this->collect_garbage();
|
||||||
assert(this->valid());
|
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(time_to_load < m_active_snapshot_time);
|
||||||
assert(std::binary_search(m_snapshots.begin(), m_snapshots.end(), Snapshot(time_to_load)));
|
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()) {
|
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.
|
// 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.
|
// The line above entered another topmost_snapshot_name.
|
||||||
assert(m_snapshots.back().is_topmost());
|
assert(m_snapshots.back().is_topmost());
|
||||||
assert(! m_snapshots.back().is_topmost_captured());
|
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;
|
//-- m_current_time;
|
||||||
assert(m_snapshots.back().is_topmost());
|
assert(m_snapshots.back().is_topmost());
|
||||||
assert(m_snapshots.back().is_topmost_captured());
|
assert(m_snapshots.back().is_topmost_captured());
|
||||||
|
new_snapshot_taken = true;
|
||||||
}
|
}
|
||||||
this->load_snapshot(time_to_load, model);
|
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
|
#ifdef SLIC3R_UNDOREDO_DEBUG
|
||||||
std::cout << "After undo" << std::endl;
|
std::cout << "After undo" << std::endl;
|
||||||
this->print();
|
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.
|
// Wrappers of the private implementation.
|
||||||
Stack::Stack() : pimpl(new StackImpl()) {}
|
Stack::Stack() : pimpl(new StackImpl()) {}
|
||||||
Stack::~Stack() {}
|
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_undo_snapshot() const { return pimpl->has_undo_snapshot(); }
|
||||||
bool Stack::has_redo_snapshot() const { return pimpl->has_redo_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); }
|
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<Snapshot>& Stack::snapshots() const { return pimpl->snapshots(); }
|
const std::vector<Snapshot>& Stack::snapshots() const { return pimpl->snapshots(); }
|
||||||
size_t Stack::active_snapshot_time() const { return pimpl->active_snapshot_time(); }
|
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 UndoRedo
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
|
|
|
@ -55,6 +55,15 @@ public:
|
||||||
Stack();
|
Stack();
|
||||||
~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.
|
// Store the current application state onto the Undo / Redo stack, remove all snapshots after m_active_snapshot_time.
|
||||||
void take_snapshot(const std::string &snapshot_name, const Slic3r::Model &model, const Slic3r::GUI::Selection &selection);
|
void 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 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.
|
// 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;
|
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
|
// 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.
|
// into the list of GLVolume pointers once the 3D scene is updated.
|
||||||
|
|
Loading…
Reference in a new issue