From e14ba9d43102b566b811febbf7a28920704e9093 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Tue, 23 Apr 2024 13:45:34 +0200 Subject: [PATCH] SPE-2063: Determine correctly which extruders are used when the object is painted by the multi-material painting gizmo. During the serialization of TriangleSelector and also during reading serialized painting data from 3MF, we cache all used states in the painted triangle mesh. Based on this information, we can quickly determine which extruders are used and which don't. --- src/libslic3r/Model.cpp | 5 ++++- src/libslic3r/Model.hpp | 6 ++--- src/libslic3r/PrintApply.cpp | 17 +++++++++++--- src/libslic3r/TriangleSelector.cpp | 36 ++++++++++++++++++++++++++++++ src/libslic3r/TriangleSelector.hpp | 23 +++++++++++++++++-- 5 files changed, 78 insertions(+), 9 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 22f3a4b132..d6b83c283b 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -2095,6 +2095,7 @@ void FacetsAnnotation::set_triangle_from_string(int triangle_id, const std::stri assert(m_data.triangles_to_split.empty() || m_data.triangles_to_split.back().triangle_idx < triangle_id); m_data.triangles_to_split.emplace_back(triangle_id, int(m_data.bitstream.size())); + const size_t bitstream_start_idx = m_data.bitstream.size(); for (auto it = str.crbegin(); it != str.crend(); ++it) { const char ch = *it; int dec = 0; @@ -2106,9 +2107,11 @@ 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) + for (int i = 0; i < 4; ++i) m_data.bitstream.insert(m_data.bitstream.end(), bool(dec & (1 << i))); } + + m_data.update_used_states(bitstream_start_idx); } // Test whether the two models contain the same number of ModelObjects with the same set of IDs diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 67fb7ac517..214842d3e5 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -664,9 +664,9 @@ enum class ConversionType : int { class FacetsAnnotation final : public ObjectWithTimestamp { 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 TriangleSelector::TriangleSplittingData &get_data() const throw() { return m_data; } + 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 TriangleSelector::TriangleSplittingData &get_data() const noexcept { return m_data; } bool set(const TriangleSelector& selector); indexed_triangle_set get_facets(const ModelVolume& mv, TriangleStateType type) const; indexed_triangle_set get_facets_strict(const ModelVolume& mv, TriangleStateType type) const; diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp index 1ac2c72304..03e1dff23f 100644 --- a/src/libslic3r/PrintApply.cpp +++ b/src/libslic3r/PrintApply.cpp @@ -1375,9 +1375,20 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ if (const auto &volumes = print_object.model_object()->volumes; num_extruders > 1 && std::find_if(volumes.begin(), volumes.end(), [](const ModelVolume *v) { return ! v->mm_segmentation_facets.empty(); }) != volumes.end()) { - //FIXME be more specific! Don't enumerate extruders that are not used for painting! - painting_extruders.assign(num_extruders, 0); - std::iota(painting_extruders.begin(), painting_extruders.end(), 1); + + std::array(TriangleStateType::Count)> used_facet_states{}; + for (const ModelVolume *volume : volumes) { + const std::vector &volume_used_facet_states = volume->mm_segmentation_facets.get_data().used_states; + + assert(volume_used_facet_states.size() == used_facet_states.size()); + for (size_t state_idx = 0; state_idx < std::min(volume_used_facet_states.size(), used_facet_states.size()); ++state_idx) + used_facet_states[state_idx] |= volume_used_facet_states[state_idx]; + } + + for (size_t state_idx = static_cast(TriangleStateType::Extruder1); state_idx < used_facet_states.size(); ++state_idx) { + if (used_facet_states[state_idx]) + painting_extruders.emplace_back(state_idx); + } } if (model_object_status.print_object_regions_status == ModelObjectStatus::PrintObjectRegionsStatus::Valid) { // Verify that the trafo for regions & volume bounding boxes thus for regions is still applicable. diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp index 02070dfe70..c8c03fcfbe 100644 --- a/src/libslic3r/TriangleSelector.cpp +++ b/src/libslic3r/TriangleSelector.cpp @@ -1560,6 +1560,9 @@ TriangleSelector::TriangleSplittingData TriangleSelector::serialize() const { } else { // In case this is leaf, we better save information about its state. int n = int(tr.get_state()); + if (n < static_cast(TriangleStateType::Count)) + data.used_states[n] = true; + if (n >= 3) { assert(n <= 16); if (n <= 16) { @@ -1694,6 +1697,39 @@ void TriangleSelector::deserialize(const TriangleSplittingData &data, bool needs } } +void TriangleSelector::TriangleSplittingData::update_used_states(const size_t bitstream_start_idx) { + assert(bitstream_start_idx < this->bitstream.size()); + assert(!this->bitstream.empty() && this->bitstream.size() != bitstream_start_idx); + assert((this->bitstream.size() - bitstream_start_idx) % 4 == 0); + + if (this->bitstream.empty() || this->bitstream.size() == bitstream_start_idx) + return; + + size_t nibble_idx = bitstream_start_idx; + + auto read_next_nibble = [&data_bitstream = std::as_const(this->bitstream), &nibble_idx]() -> uint8_t { + assert(nibble_idx + 3 < data_bitstream.size()); + uint8_t code = 0; + for (size_t bit_idx = 0; bit_idx < 4; ++bit_idx) + code |= data_bitstream[nibble_idx++] << bit_idx; + return code; + }; + + while (nibble_idx < this->bitstream.size()) { + const uint8_t code = read_next_nibble(); + + if (const bool is_split = (code & 0b11) != 0; is_split) + continue; + + const uint8_t facet_state = (code & 0b1100) == 0b1100 ? read_next_nibble() + 3 : code >> 2; + assert(facet_state < this->used_states.size()); + if (facet_state >= this->used_states.size()) + continue; + + this->used_states[facet_state] = true; + } +} + // Lightweight variant of deserialization, which only tests whether a face of test_state exists. bool TriangleSelector::has_facets(const TriangleSplittingData &data, const TriangleStateType test_state) { // Depth-first queue of a number of unvisited children. diff --git a/src/libslic3r/TriangleSelector.hpp b/src/libslic3r/TriangleSelector.hpp index 75fe12383b..0e88fe3a7c 100644 --- a/src/libslic3r/TriangleSelector.hpp +++ b/src/libslic3r/TriangleSelector.hpp @@ -228,15 +228,31 @@ public: std::vector triangles_to_split; // Bit stream containing splitting information. std::vector bitstream; + // Array indicating which triangle state types are used (encoded inside bitstream). + std::vector used_states { std::vector(static_cast(TriangleStateType::Count), false) }; TriangleSplittingData() = default; - friend bool operator==(const TriangleSplittingData &lhs, const TriangleSplittingData &rhs) { return lhs.triangles_to_split == rhs.triangles_to_split && lhs.bitstream == rhs.bitstream; } + friend bool operator==(const TriangleSplittingData &lhs, const TriangleSplittingData &rhs) { + return lhs.triangles_to_split == rhs.triangles_to_split + && lhs.bitstream == rhs.bitstream + && lhs.used_states == rhs.used_states; + } + friend bool operator!=(const TriangleSplittingData &lhs, const TriangleSplittingData &rhs) { return !(lhs == rhs); } + // Reset all used states before they are recomputed based on the bitstream. + void reset_used_states() { + used_states.resize(static_cast(TriangleStateType::Count), false); + std::fill(used_states.begin(), used_states.end(), false); + } + + // Update used states based on the bitstream. It just iterated over the bitstream from the bitstream_start_idx till the end. + void update_used_states(size_t bitstream_start_idx); + private: friend class cereal::access; - template void serialize(Archive &ar) { ar(triangles_to_split, bitstream); } + template void serialize(Archive &ar) { ar(triangles_to_split, bitstream, used_states); } }; std::pair, std::vector> precompute_all_neighbors() const; @@ -302,6 +318,9 @@ public: // Load serialized data. Assumes that correct mesh is loaded. void deserialize(const TriangleSplittingData &data, bool needs_reset = true); + // Extract all used facet states from the given TriangleSplittingData. + static std::vector extract_used_facet_states(const TriangleSplittingData &data); + // For all triangles, remove the flag indicating that the triangle was selected by seed fill. void seed_fill_unselect_all_triangles();