Support / seam / MMU painting serialization / deserialization:

Changed the serialization structure
	std::map<int, std::vector<bool>>
to a significantly more compact
	std::pair<std::vector<std::pair<int, int>>, std::vector<bool>>
Such change shall significantly improve efficiency of Undo / Redo stack.
This commit is contained in:
Vojtech Bubnik 2021-06-10 09:26:01 +02:00
parent b59ff2c294
commit 95f4690e25
5 changed files with 56 additions and 43 deletions

View file

@ -1899,6 +1899,9 @@ namespace Slic3r {
volume->calculate_convex_hull(); volume->calculate_convex_hull();
// recreate custom supports, seam and mmu segmentation from previously loaded attribute // 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; i<triangles_count; ++i) { for (unsigned i=0; i<triangles_count; ++i) {
size_t index = src_start_id/3 + i; size_t index = src_start_id/3 + i;
assert(index < geometry.custom_supports.size()); assert(index < geometry.custom_supports.size());
@ -1911,7 +1914,9 @@ namespace Slic3r {
if (! geometry.mmu_segmentation[index].empty()) if (! geometry.mmu_segmentation[index].empty())
volume->mmu_segmentation_facets.set_triangle_from_string(i, geometry.mmu_segmentation[index]); volume->mmu_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 // apply the remaining volume's metadata
for (const Metadata& metadata : volume_data.metadata) { for (const Metadata& metadata : volume_data.metadata) {

View file

@ -1952,9 +1952,9 @@ indexed_triangle_set FacetsAnnotation::get_facets(const ModelVolume& mv, Enforce
bool FacetsAnnotation::set(const TriangleSelector& selector) bool FacetsAnnotation::set(const TriangleSelector& selector)
{ {
std::map<int, std::vector<bool>> sel_map = selector.serialize(); std::pair<std::vector<std::pair<int, int>>, std::vector<bool>> sel_map = selector.serialize();
if (sel_map != m_data) { if (sel_map != m_data) {
m_data = sel_map; m_data = std::move(sel_map);
this->touch(); this->touch();
return true; return true;
} }
@ -1963,7 +1963,8 @@ bool FacetsAnnotation::set(const TriangleSelector& selector)
void FacetsAnnotation::clear() void FacetsAnnotation::clear()
{ {
m_data.clear(); m_data.first.clear();
m_data.second.clear();
this->reset_timestamp(); this->reset_timestamp();
} }
@ -1974,15 +1975,15 @@ std::string FacetsAnnotation::get_triangle_as_string(int triangle_idx) const
{ {
std::string out; std::string out;
auto triangle_it = m_data.find(triangle_idx); auto triangle_it = std::lower_bound(m_data.first.begin(), m_data.first.end(), triangle_idx, [](const std::pair<int, int> &l, const int r) { return l.first < r; });
if (triangle_it != m_data.end()) { if (triangle_it != m_data.first.end() && triangle_it->first == triangle_idx) {
const std::vector<bool>& code = triangle_it->second; int offset = triangle_it->second;
int offset = 0; int end = ++ triangle_it == m_data.first.end() ? int(m_data.second.size()) : triangle_it->second;
while (offset < int(code.size())) { while (offset < end) {
int next_code = 0; int next_code = 0;
for (int i=3; i>=0; --i) { for (int i=3; i>=0; --i) {
next_code = next_code << 1; next_code = next_code << 1;
next_code |= int(code[offset + i]); next_code |= int(m_data.second[offset + i]);
} }
offset += 4; 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) void FacetsAnnotation::set_triangle_from_string(int triangle_id, const std::string& str)
{ {
assert(! str.empty()); assert(! str.empty());
m_data[triangle_id] = std::vector<bool>(); // zero current state or create new assert(m_data.first.empty() || m_data.first.back().first < triangle_id);
std::vector<bool>& code = m_data[triangle_id]; m_data.first.emplace_back(triangle_id, int(m_data.second.size()));
for (auto it = str.crbegin(); it != str.crend(); ++it) { for (auto it = str.crbegin(); it != str.crend(); ++it) {
const char ch = *it; const char ch = *it;
@ -2013,9 +2014,8 @@ void FacetsAnnotation::set_triangle_from_string(int triangle_id, const std::stri
assert(false); assert(false);
// Convert to binary and append into code. // Convert to binary and append into code.
for (int i=0; i<4; ++i) { for (int i=0; i<4; ++i)
code.insert(code.end(), bool(dec & (1 << i))); m_data.second.insert(m_data.second.end(), bool(dec & (1 << i)));
}
} }
} }

View file

@ -512,13 +512,21 @@ public:
// Assign the content if the timestamp differs, don't assign an ObjectID. // 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(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); } } void assign(FacetsAnnotation&& rhs) { if (! this->timestamp_matches(rhs)) { m_data = std::move(rhs.m_data); this->copy_timestamp(rhs); } }
const std::map<int, std::vector<bool>>& get_data() const throw() { return m_data; } const std::pair<std::vector<std::pair<int, int>>, std::vector<bool>>& get_data() const throw() { return m_data; }
bool set(const TriangleSelector& selector); bool set(const TriangleSelector& selector);
indexed_triangle_set get_facets(const ModelVolume& mv, EnforcerBlockerType type) const; 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(); void clear();
// Serialize triangle into string, for serialization into 3MF/AMF.
std::string get_triangle_as_string(int i) const; 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); 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: private:
// Constructors to be only called by derived classes. // Constructors to be only called by derived classes.
@ -544,7 +552,7 @@ private:
ar(cereal::base_class<ObjectWithTimestamp>(this), m_data); ar(cereal::base_class<ObjectWithTimestamp>(this), m_data);
} }
std::map<int, std::vector<bool>> m_data; std::pair<std::vector<std::pair<int, int>>, std::vector<bool>> m_data;
// To access set_new_unique_id() when copy / pasting a ModelVolume. // To access set_new_unique_id() when copy / pasting a ModelVolume.
friend class ModelVolume; friend class ModelVolume;

View file

@ -593,7 +593,7 @@ indexed_triangle_set TriangleSelector::get_facets(EnforcerBlockerType state) con
std::map<int, std::vector<bool>> TriangleSelector::serialize() const std::pair<std::vector<std::pair<int, int>>, std::vector<bool>> TriangleSelector::serialize() const
{ {
// Each original triangle of the mesh is assigned a number encoding its state // 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): // or how it is split. Each triangle is encoded by 4 bits (xxyy) or 8 bits (zzzzxxyy):
@ -605,27 +605,27 @@ std::map<int, std::vector<bool>> TriangleSelector::serialize() const
// The function returns a map from original triangle indices to // The function returns a map from original triangle indices to
// stream of bits encoding state and offsprings. // stream of bits encoding state and offsprings.
std::map<int, std::vector<bool>> out; std::pair<std::vector<std::pair<int, int>>, std::vector<bool>> out;
out.first.reserve(m_orig_size_indices);
for (int i=0; i<m_orig_size_indices; ++i) { for (int i=0; i<m_orig_size_indices; ++i) {
const Triangle& tr = m_triangles[i]; const Triangle& tr = m_triangles[i];
if (! tr.is_split() && tr.get_state() == EnforcerBlockerType::NONE) if (! tr.is_split() && tr.get_state() == EnforcerBlockerType::NONE)
continue; // no need to save anything, unsplit and unselected is default continue; // no need to save anything, unsplit and unselected is default
std::vector<bool> data; // complete encoding of this mesh triangle // Store index of the first bit assigned to ith triangle.
int stored_triangles = 0; // how many have been already encoded out.first.emplace_back(i, int(out.second.size()));
std::function<void(int)> serialize_recursive; std::function<void(int)> 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]; const Triangle& tr = m_triangles[facet_idx];
// Always save number of split sides. It is zero for unsplit triangles. // Always save number of split sides. It is zero for unsplit triangles.
int split_sides = tr.number_of_split_sides(); int split_sides = tr.number_of_split_sides();
assert(split_sides >= 0 && split_sides <= 3); assert(split_sides >= 0 && split_sides <= 3);
//data |= (split_sides << (stored_triangles * 4)); out.second.push_back(split_sides & 0b01);
data.push_back(split_sides & 0b01); out.second.push_back(split_sides & 0b10);
data.push_back(split_sides & 0b10);
if (tr.is_split()) { if (tr.is_split()) {
// If this triangle is split, save which side is split (in case // If this triangle is split, save which side is split (in case
@ -633,9 +633,8 @@ std::map<int, std::vector<bool>> TriangleSelector::serialize() const
// be ignored for 3-side split. // be ignored for 3-side split.
assert(split_sides > 0); assert(split_sides > 0);
assert(tr.special_side() >= 0 && tr.special_side() <= 3); assert(tr.special_side() >= 0 && tr.special_side() <= 3);
data.push_back(tr.special_side() & 0b01); out.second.push_back(tr.special_side() & 0b01);
data.push_back(tr.special_side() & 0b10); out.second.push_back(tr.special_side() & 0b10);
++stored_triangles;
// Now save all children. // Now save all children.
for (int child_idx=0; child_idx<=split_sides; ++child_idx) for (int child_idx=0; child_idx<=split_sides; ++child_idx)
serialize_recursive(tr.children[child_idx]); serialize_recursive(tr.children[child_idx]);
@ -643,32 +642,33 @@ std::map<int, std::vector<bool>> TriangleSelector::serialize() const
// In case this is leaf, we better save information about its state. // In case this is leaf, we better save information about its state.
assert(int(tr.get_state()) <= 15); assert(int(tr.get_state()) <= 15);
if (3 <= int(tr.get_state()) && 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) { for (size_t bit_idx = 0; bit_idx < 4; ++bit_idx) {
size_t bit_mask = uint64_t(0b0001) << 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 { } else {
data.push_back(int(tr.get_state()) & 0b01); out.second.push_back(int(tr.get_state()) & 0b01);
data.push_back(int(tr.get_state()) & 0b10); out.second.push_back(int(tr.get_state()) & 0b10);
} }
++stored_triangles;
} }
}; };
serialize_recursive(i); 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; return out;
} }
void TriangleSelector::deserialize(const std::map<int, std::vector<bool>> data, const EnforcerBlockerType init_state) void TriangleSelector::deserialize(const std::pair<std::vector<std::pair<int, int>>, std::vector<bool>> &data, const EnforcerBlockerType init_state)
{ {
reset(init_state); // dump any current 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(triangle_id < int(m_triangles.size()));
assert(! code.empty()); assert(first_bit < data.second.size());
int processed_nibbles = 0; int processed_nibbles = 0;
struct ProcessingInfo { struct ProcessingInfo {
int facet_id = 0; int facet_id = 0;
@ -689,7 +689,7 @@ void TriangleSelector::deserialize(const std::map<int, std::vector<bool>> data,
for (int i = 3; i >= 0; --i) { for (int i = 3; i >= 0; --i) {
next_code[nibble_idx] = next_code[nibble_idx] << 1; 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; ++processed_nibbles;

View file

@ -54,12 +54,12 @@ public:
// Remove all unnecessary data. // Remove all unnecessary data.
void garbage_collect(); void garbage_collect();
// Store the division trees in compact form (a long stream of // Store the division trees in compact form (a long stream of bits for each triangle of the original mesh).
// bits for each triangle of the original mesh). // First vector contains pairs of (triangle index, first bit in the second vector).
std::map<int, std::vector<bool>> serialize() const; std::pair<std::vector<std::pair<int, int>>, std::vector<bool>> serialize() const;
// Load serialized data. Assumes that correct mesh is loaded. // Load serialized data. Assumes that correct mesh is loaded.
void deserialize(const std::map<int, std::vector<bool>> data, const EnforcerBlockerType init_state = EnforcerBlockerType{0}); void deserialize(const std::pair<std::vector<std::pair<int, int>>, std::vector<bool>> &data, const EnforcerBlockerType init_state = EnforcerBlockerType{0});
// For all triangles, remove the flag indicating that the triangle was selected by seed fill. // For all triangles, remove the flag indicating that the triangle was selected by seed fill.
void seed_fill_unselect_all_triangles(); void seed_fill_unselect_all_triangles();