From 95f4690e253d3d393036321144c131edbfacdbd6 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 10 Jun 2021 09:26:01 +0200 Subject: [PATCH] Support / seam / MMU painting serialization / deserialization: Changed the serialization structure std::map> to a significantly more compact std::pair>, std::vector> Such change shall significantly improve efficiency of Undo / Redo stack. --- src/libslic3r/Format/3mf.cpp | 7 ++++- src/libslic3r/Model.cpp | 28 ++++++++++---------- src/libslic3r/Model.hpp | 14 +++++++--- src/libslic3r/TriangleSelector.cpp | 42 +++++++++++++++--------------- src/libslic3r/TriangleSelector.hpp | 8 +++--- 5 files changed, 56 insertions(+), 43 deletions(-) diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index 1b59448fd0..fbf27c5488 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -1899,6 +1899,9 @@ namespace Slic3r { volume->calculate_convex_hull(); // recreate custom supports, seam and mmu segmentation from previously loaded attribute + volume->supported_facets.reserve(triangles_count); + volume->seam_facets.reserve(triangles_count); + volume->mmu_segmentation_facets.reserve(triangles_count); for (unsigned i=0; immu_segmentation_facets.set_triangle_from_string(i, geometry.mmu_segmentation[index]); } - + volume->supported_facets.shrink_to_fit(); + volume->seam_facets.shrink_to_fit(); + volume->mmu_segmentation_facets.shrink_to_fit(); // apply the remaining volume's metadata for (const Metadata& metadata : volume_data.metadata) { diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 7893455dae..bf330cf21a 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1952,9 +1952,9 @@ indexed_triangle_set FacetsAnnotation::get_facets(const ModelVolume& mv, Enforce bool FacetsAnnotation::set(const TriangleSelector& selector) { - std::map> sel_map = selector.serialize(); + std::pair>, std::vector> sel_map = selector.serialize(); if (sel_map != m_data) { - m_data = sel_map; + m_data = std::move(sel_map); this->touch(); return true; } @@ -1963,7 +1963,8 @@ bool FacetsAnnotation::set(const TriangleSelector& selector) void FacetsAnnotation::clear() { - m_data.clear(); + m_data.first.clear(); + m_data.second.clear(); this->reset_timestamp(); } @@ -1974,15 +1975,15 @@ std::string FacetsAnnotation::get_triangle_as_string(int triangle_idx) const { std::string out; - auto triangle_it = m_data.find(triangle_idx); - if (triangle_it != m_data.end()) { - const std::vector& code = triangle_it->second; - int offset = 0; - while (offset < int(code.size())) { + auto triangle_it = std::lower_bound(m_data.first.begin(), m_data.first.end(), triangle_idx, [](const std::pair &l, const int r) { return l.first < r; }); + if (triangle_it != m_data.first.end() && triangle_it->first == triangle_idx) { + int offset = triangle_it->second; + int end = ++ triangle_it == m_data.first.end() ? int(m_data.second.size()) : triangle_it->second; + while (offset < end) { int next_code = 0; for (int i=3; i>=0; --i) { next_code = next_code << 1; - next_code |= int(code[offset + i]); + next_code |= int(m_data.second[offset + i]); } offset += 4; @@ -1999,8 +2000,8 @@ std::string FacetsAnnotation::get_triangle_as_string(int triangle_idx) const void FacetsAnnotation::set_triangle_from_string(int triangle_id, const std::string& str) { assert(! str.empty()); - m_data[triangle_id] = std::vector(); // zero current state or create new - std::vector& code = m_data[triangle_id]; + assert(m_data.first.empty() || m_data.first.back().first < triangle_id); + m_data.first.emplace_back(triangle_id, int(m_data.second.size())); for (auto it = str.crbegin(); it != str.crend(); ++it) { const char ch = *it; @@ -2013,9 +2014,8 @@ void FacetsAnnotation::set_triangle_from_string(int triangle_id, const std::stri assert(false); // Convert to binary and append into code. - for (int i=0; i<4; ++i) { - code.insert(code.end(), bool(dec & (1 << i))); - } + for (int i=0; i<4; ++i) + m_data.second.insert(m_data.second.end(), bool(dec & (1 << i))); } } diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 1d4de4783f..c6a54d5c61 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -512,13 +512,21 @@ public: // Assign the content if the timestamp differs, don't assign an ObjectID. void assign(const FacetsAnnotation& rhs) { if (! this->timestamp_matches(rhs)) { m_data = rhs.m_data; this->copy_timestamp(rhs); } } void assign(FacetsAnnotation&& rhs) { if (! this->timestamp_matches(rhs)) { m_data = std::move(rhs.m_data); this->copy_timestamp(rhs); } } - const std::map>& get_data() const throw() { return m_data; } + const std::pair>, std::vector>& get_data() const throw() { return m_data; } bool set(const TriangleSelector& selector); indexed_triangle_set get_facets(const ModelVolume& mv, EnforcerBlockerType type) const; - bool empty() const { return m_data.empty(); } + bool empty() const { return m_data.first.empty(); } void clear(); + + // Serialize triangle into string, for serialization into 3MF/AMF. std::string get_triangle_as_string(int i) const; + + // Before deserialization, reserve space for n_triangles. + void reserve(int n_triangles) { m_data.first.reserve(n_triangles); } + // Deserialize triangles one by one, with strictly increasing triangle_id. void set_triangle_from_string(int triangle_id, const std::string& str); + // After deserializing the last triangle, shrink data to fit. + void shrink_to_fit() { m_data.first.shrink_to_fit(); m_data.second.shrink_to_fit(); } private: // Constructors to be only called by derived classes. @@ -544,7 +552,7 @@ private: ar(cereal::base_class(this), m_data); } - std::map> m_data; + std::pair>, std::vector> m_data; // To access set_new_unique_id() when copy / pasting a ModelVolume. friend class ModelVolume; diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp index 13c6c4816b..8f07d3bca9 100644 --- a/src/libslic3r/TriangleSelector.cpp +++ b/src/libslic3r/TriangleSelector.cpp @@ -593,7 +593,7 @@ indexed_triangle_set TriangleSelector::get_facets(EnforcerBlockerType state) con -std::map> TriangleSelector::serialize() const +std::pair>, std::vector> TriangleSelector::serialize() const { // Each original triangle of the mesh is assigned a number encoding its state // or how it is split. Each triangle is encoded by 4 bits (xxyy) or 8 bits (zzzzxxyy): @@ -605,27 +605,27 @@ std::map> TriangleSelector::serialize() const // The function returns a map from original triangle indices to // stream of bits encoding state and offsprings. - std::map> out; + std::pair>, std::vector> out; + out.first.reserve(m_orig_size_indices); for (int i=0; i data; // complete encoding of this mesh triangle - int stored_triangles = 0; // how many have been already encoded + // Store index of the first bit assigned to ith triangle. + out.first.emplace_back(i, int(out.second.size())); std::function serialize_recursive; - serialize_recursive = [this, &serialize_recursive, &stored_triangles, &data](int facet_idx) { + serialize_recursive = [this, &serialize_recursive, &out](int facet_idx) { const Triangle& tr = m_triangles[facet_idx]; // Always save number of split sides. It is zero for unsplit triangles. int split_sides = tr.number_of_split_sides(); assert(split_sides >= 0 && split_sides <= 3); - //data |= (split_sides << (stored_triangles * 4)); - data.push_back(split_sides & 0b01); - data.push_back(split_sides & 0b10); + out.second.push_back(split_sides & 0b01); + out.second.push_back(split_sides & 0b10); if (tr.is_split()) { // If this triangle is split, save which side is split (in case @@ -633,9 +633,8 @@ std::map> TriangleSelector::serialize() const // be ignored for 3-side split. assert(split_sides > 0); assert(tr.special_side() >= 0 && tr.special_side() <= 3); - data.push_back(tr.special_side() & 0b01); - data.push_back(tr.special_side() & 0b10); - ++stored_triangles; + out.second.push_back(tr.special_side() & 0b01); + out.second.push_back(tr.special_side() & 0b10); // Now save all children. for (int child_idx=0; child_idx<=split_sides; ++child_idx) serialize_recursive(tr.children[child_idx]); @@ -643,32 +642,33 @@ std::map> TriangleSelector::serialize() const // In case this is leaf, we better save information about its state. assert(int(tr.get_state()) <= 15); if (3 <= int(tr.get_state()) && int(tr.get_state()) <= 15) { - data.insert(data.end(), {true, true}); + out.second.insert(out.second.end(), {true, true}); for (size_t bit_idx = 0; bit_idx < 4; ++bit_idx) { size_t bit_mask = uint64_t(0b0001) << bit_idx; - data.push_back((int(tr.get_state()) - 3) & bit_mask); + out.second.push_back((int(tr.get_state()) - 3) & bit_mask); } } else { - data.push_back(int(tr.get_state()) & 0b01); - data.push_back(int(tr.get_state()) & 0b10); + out.second.push_back(int(tr.get_state()) & 0b01); + out.second.push_back(int(tr.get_state()) & 0b10); } - ++stored_triangles; } }; serialize_recursive(i); - out[i] = data; } + // May be stored onto Undo / Redo stack, thus conserve memory. + out.first.shrink_to_fit(); + out.second.shrink_to_fit(); return out; } -void TriangleSelector::deserialize(const std::map> data, const EnforcerBlockerType init_state) +void TriangleSelector::deserialize(const std::pair>, std::vector> &data, const EnforcerBlockerType init_state) { reset(init_state); // dump any current state - for (const auto& [triangle_id, code] : data) { + for (const auto [triangle_id, first_bit] : data.first) { assert(triangle_id < int(m_triangles.size())); - assert(! code.empty()); + assert(first_bit < data.second.size()); int processed_nibbles = 0; struct ProcessingInfo { int facet_id = 0; @@ -689,7 +689,7 @@ void TriangleSelector::deserialize(const std::map> data, for (int i = 3; i >= 0; --i) { next_code[nibble_idx] = next_code[nibble_idx] << 1; - next_code[nibble_idx] |= int(code[4 * processed_nibbles + i]); + next_code[nibble_idx] |= int(data.second[first_bit + 4 * processed_nibbles + i]); } ++processed_nibbles; diff --git a/src/libslic3r/TriangleSelector.hpp b/src/libslic3r/TriangleSelector.hpp index 383aab4809..4fd454055c 100644 --- a/src/libslic3r/TriangleSelector.hpp +++ b/src/libslic3r/TriangleSelector.hpp @@ -54,12 +54,12 @@ public: // Remove all unnecessary data. void garbage_collect(); - // Store the division trees in compact form (a long stream of - // bits for each triangle of the original mesh). - std::map> serialize() const; + // Store the division trees in compact form (a long stream of bits for each triangle of the original mesh). + // First vector contains pairs of (triangle index, first bit in the second vector). + std::pair>, std::vector> serialize() const; // Load serialized data. Assumes that correct mesh is loaded. - void deserialize(const std::map> data, const EnforcerBlockerType init_state = EnforcerBlockerType{0}); + void deserialize(const std::pair>, std::vector> &data, const EnforcerBlockerType init_state = EnforcerBlockerType{0}); // For all triangles, remove the flag indicating that the triangle was selected by seed fill. void seed_fill_unselect_all_triangles();