diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 96136db395e..ad63a21ee61 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -3293,7 +3293,7 @@ bool FacetsAnnotation::has_facets(const ModelVolume& mv, EnforcerBlockerType typ bool FacetsAnnotation::set(const TriangleSelector& selector) { - std::pair>, std::vector> sel_map = selector.serialize(); + TriangleSelector::TriangleSplittingData sel_map = selector.serialize(); if (sel_map != m_data) { m_data = std::move(sel_map); this->touch(); @@ -3304,8 +3304,8 @@ bool FacetsAnnotation::set(const TriangleSelector& selector) void FacetsAnnotation::reset() { - m_data.first.clear(); - m_data.second.clear(); + m_data.triangles_to_split.clear(); + m_data.bitstream.clear(); this->touch(); } @@ -3316,15 +3316,15 @@ std::string FacetsAnnotation::get_triangle_as_string(int triangle_idx) const { std::string out; - 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; + auto triangle_it = std::lower_bound(m_data.triangles_to_split.begin(), m_data.triangles_to_split.end(), triangle_idx, [](const TriangleSelector::TriangleBitStreamMapping &l, const int r) { return l.triangle_idx < r; }); + if (triangle_it != m_data.triangles_to_split.end() && triangle_it->triangle_idx == triangle_idx) { + int offset = triangle_it->bitstream_start_idx; + int end = ++ triangle_it == m_data.triangles_to_split.end() ? int(m_data.bitstream.size()) : triangle_it->bitstream_start_idx; while (offset < end) { int next_code = 0; for (int i=3; i>=0; --i) { next_code = next_code << 1; - next_code |= int(m_data.second[offset + i]); + next_code |= int(m_data.bitstream[offset + i]); } offset += 4; @@ -3341,9 +3341,10 @@ 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()); - assert(m_data.first.empty() || m_data.first.back().first < triangle_id); - m_data.first.emplace_back(triangle_id, int(m_data.second.size())); + 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; @@ -3355,14 +3356,16 @@ 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) - m_data.second.insert(m_data.second.end(), bool(dec & (1 << 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); } bool FacetsAnnotation::equals(const FacetsAnnotation &other) const { - const std::pair>, std::vector>& data = other.get_data(); + const auto& data = other.get_data(); return (m_data == data); } diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 927ab47f9ba..9fc315778fa 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -16,6 +16,7 @@ #include "enum_bitmask.hpp" #include "TextConfiguration.hpp" #include "EmbossShape.hpp" +#include "TriangleSelector.hpp" //BBS: add bbs 3mf #include "Format/bbs_3mf.hpp" @@ -704,31 +705,6 @@ class ModelObject final : public ObjectBase void update_min_max_z(); }; -enum class EnforcerBlockerType : int8_t { - // Maximum is 3. The value is serialized in TriangleSelector into 2 bits. - NONE = 0, - ENFORCER = 1, - BLOCKER = 2, - // Maximum is 15. The value is serialized in TriangleSelector into 6 bits using a 2 bit prefix code. - Extruder1 = ENFORCER, - Extruder2 = BLOCKER, - Extruder3, - Extruder4, - Extruder5, - Extruder6, - Extruder7, - Extruder8, - Extruder9, - Extruder10, - Extruder11, - Extruder12, - Extruder13, - Extruder14, - Extruder15, - Extruder16, - ExtruderMax = Extruder16 -}; - enum class ConversionType : int { CONV_TO_INCH, CONV_FROM_INCH, @@ -745,9 +721,9 @@ enum class En3mfType : 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 std::pair>, std::vector>& 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, EnforcerBlockerType type) const; // BBS @@ -755,7 +731,7 @@ class FacetsAnnotation final : public ObjectWithTimestamp { void set_enforcer_block_type_limit(const ModelVolume& mv, EnforcerBlockerType max_type); indexed_triangle_set get_facets_strict(const ModelVolume& mv, EnforcerBlockerType type) const; bool has_facets(const ModelVolume& mv, EnforcerBlockerType type) const; - bool empty() const { return m_data.first.empty(); } + bool empty() const { return m_data.triangles_to_split.empty(); } // Following method clears the config and increases its timestamp, so the deleted // state is considered changed from perspective of the undo/redo stack. @@ -765,11 +741,11 @@ class FacetsAnnotation final : public ObjectWithTimestamp { 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); } + void reserve(int n_triangles) { m_data.triangles_to_split.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(); } + void shrink_to_fit() { m_data.triangles_to_split.shrink_to_fit(); m_data.bitstream.shrink_to_fit(); } bool equals(const FacetsAnnotation &other) const; private: @@ -796,7 +772,7 @@ class FacetsAnnotation final : public ObjectWithTimestamp { ar(cereal::base_class(this), m_data); } - std::pair>, std::vector> m_data; + TriangleSelector::TriangleSplittingData m_data; // To access set_new_unique_id() when copy / pasting a ModelVolume. friend class ModelVolume; diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp index b6fd173c036..232c86b9ec5 100644 --- a/src/libslic3r/PrintApply.cpp +++ b/src/libslic3r/PrintApply.cpp @@ -1498,11 +1498,22 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ } std::vector painting_extruders; if (const auto &volumes = print_object.model_object()->volumes; - num_extruders > 1 && + num_extruders > 1 && std::find_if(volumes.begin(), volumes.end(), [](const ModelVolume *v) { return ! v->mmu_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(EnforcerBlockerType::ExtruderMax)> used_facet_states{}; + for (const ModelVolume *volume : volumes) { + const std::vector &volume_used_facet_states = volume->mmu_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(EnforcerBlockerType::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 329e50f3291..e7c59b7119b 100644 --- a/src/libslic3r/TriangleSelector.cpp +++ b/src/libslic3r/TriangleSelector.cpp @@ -1622,8 +1622,7 @@ void TriangleSelector::get_seed_fill_contour_recursive(const int facet_idx, cons } } -std::pair>, std::vector> TriangleSelector::serialize() const -{ +TriangleSelector::TriangleSplittingData 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): // leaf triangle: xx = EnforcerBlockerType (Only values 0, 1, and 2. Value 3 is used as an indicator for additional 4 bits.), yy = 0 @@ -1639,7 +1638,7 @@ std::pair>, std::vector> TriangleSelector: // (std::function calls using a pointer, while this implementation calls directly). struct Serializer { const TriangleSelector* triangle_selector; - std::pair>, std::vector> data; + TriangleSplittingData data; void serialize(int facet_idx) { const Triangle& tr = triangle_selector->m_triangles[facet_idx]; @@ -1648,8 +1647,8 @@ std::pair>, std::vector> TriangleSelector: int split_sides = tr.number_of_split_sides(); assert(split_sides >= 0 && split_sides <= 3); - data.second.push_back(split_sides & 0b01); - data.second.push_back(split_sides & 0b10); + data.bitstream.push_back(split_sides & 0b01); + data.bitstream.push_back(split_sides & 0b10); if (split_sides) { // If this triangle is split, save which side is split (in case @@ -1657,8 +1656,8 @@ std::pair>, std::vector> TriangleSelector: // be ignored for 3-side split. assert(tr.is_split() && split_sides > 0); assert(tr.special_side() >= 0 && tr.special_side() <= 3); - data.second.push_back(tr.special_side() & 0b01); - data.second.push_back(tr.special_side() & 0b10); + data.bitstream.push_back(tr.special_side() & 0b01); + data.bitstream.push_back(tr.special_side() & 0b10); // Now save all children. // Serialized in reverse order for compatibility with PrusaSlicer 2.3.1. for (int child_idx = split_sides; child_idx >= 0; -- child_idx) @@ -1666,45 +1665,48 @@ std::pair>, std::vector> TriangleSelector: } else { // In case this is leaf, we better save information about its state. int n = int(tr.get_state()); + if (n < static_cast(EnforcerBlockerType::ExtruderMax)) + data.used_states[n] = true; + if (n >= 3) { assert(n <= 16); if (n <= 16) { // Store "11" plus 4 bits of (n-3). - data.second.insert(data.second.end(), { true, true }); + data.bitstream.insert(data.bitstream.end(), { true, true }); n -= 3; for (size_t bit_idx = 0; bit_idx < 4; ++bit_idx) - data.second.push_back(n & (uint64_t(0b0001) << bit_idx)); + data.bitstream.push_back(n & (uint64_t(0b0001) << bit_idx)); } } else { // Simple case, compatible with PrusaSlicer 2.3.1 and older for storing paint on supports and seams. // Store 2 bits of n. - data.second.push_back(n & 0b01); - data.second.push_back(n & 0b10); + data.bitstream.push_back(n & 0b01); + data.bitstream.push_back(n & 0b10); } } } } out { this }; - out.data.first.reserve(m_orig_size_indices); + out.data.triangles_to_split.reserve(m_orig_size_indices); for (int i=0; i>, std::vector> &data, bool needs_reset, EnforcerBlockerType max_ebt) +void TriangleSelector::deserialize(const TriangleSplittingData& data, bool needs_reset, EnforcerBlockerType max_ebt) { if (needs_reset) reset(); // dump any current state - for (auto [triangle_id, ibit] : data.first) { + for (auto [triangle_id, ibit] : data.triangles_to_split) { if (triangle_id >= int(m_triangles.size())) { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "array bound:error:triangle_id >= int(m_triangles.size())"; return; @@ -1712,7 +1714,7 @@ void TriangleSelector::deserialize(const std::pair parents; - for (auto [triangle_id, ibit] : data.first) { + for (auto [triangle_id, ibit] : data.triangles_to_split) { assert(triangle_id < int(m_triangles.size())); - assert(ibit < int(data.second.size())); + assert(ibit < int(data.bitstream.size())); auto next_nibble = [&data, &ibit = ibit]() { int n = 0; for (int i = 0; i < 4; ++ i) - n |= data.second[ibit ++] << i; + n |= data.bitstream[ibit ++] << i; return n; }; @@ -1811,21 +1813,53 @@ void TriangleSelector::deserialize(const std::pairbitstream.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 std::pair>, std::vector> &data, const EnforcerBlockerType test_state) -{ +bool TriangleSelector::has_facets(const TriangleSplittingData &data, const EnforcerBlockerType test_state) { // Depth-first queue of a number of unvisited children. // Kept outside of the loop to avoid re-allocating inside the loop. std::vector parents_children; parents_children.reserve(64); - for (const std::pair &triangle_id_and_ibit : data.first) { - int ibit = triangle_id_and_ibit.second; - assert(ibit < int(data.second.size())); + for (const TriangleBitStreamMapping &triangle_id_and_ibit : data.triangles_to_split) { + int ibit = triangle_id_and_ibit.bitstream_start_idx; + assert(ibit < int(data.bitstream.size())); auto next_nibble = [&data, &ibit = ibit]() { int n = 0; for (int i = 0; i < 4; ++ i) - n |= data.second[ibit ++] << i; + n |= data.bitstream[ibit ++] << i; return n; }; // < 0 -> negative of a number of children diff --git a/src/libslic3r/TriangleSelector.hpp b/src/libslic3r/TriangleSelector.hpp index b73283c0e1c..e22a551baf5 100644 --- a/src/libslic3r/TriangleSelector.hpp +++ b/src/libslic3r/TriangleSelector.hpp @@ -7,10 +7,34 @@ #include #include "Point.hpp" #include "TriangleMesh.hpp" -#include "libslic3r/Model.hpp" namespace Slic3r { +enum class EnforcerBlockerType : int8_t { + // Maximum is 3. The value is serialized in TriangleSelector into 2 bits. + NONE = 0, + ENFORCER = 1, + BLOCKER = 2, + // Maximum is 15. The value is serialized in TriangleSelector into 6 bits using a 2 bit prefix code. + Extruder1 = ENFORCER, + Extruder2 = BLOCKER, + Extruder3, + Extruder4, + Extruder5, + Extruder6, + Extruder7, + Extruder8, + Extruder9, + Extruder10, + Extruder11, + Extruder12, + Extruder13, + Extruder14, + Extruder15, + Extruder16, + ExtruderMax = Extruder16 +}; + // Following class holds information about selected triangles. It also has power // to recursively subdivide the triangles and make the selection finer. class TriangleSelector @@ -208,6 +232,56 @@ class TriangleSelector } }; + struct TriangleBitStreamMapping + { + // Index of the triangle to which we assign the bitstream containing splitting information. + int triangle_idx = -1; + // Index of the first bit of the bitstream assigned to this triangle. + int bitstream_start_idx = -1; + + TriangleBitStreamMapping() = default; + explicit TriangleBitStreamMapping(int triangleIdx, int bitstreamStartIdx) : triangle_idx(triangleIdx), bitstream_start_idx(bitstreamStartIdx) {} + + friend bool operator==(const TriangleBitStreamMapping &lhs, const TriangleBitStreamMapping &rhs) { return lhs.triangle_idx == rhs.triangle_idx && lhs.bitstream_start_idx == rhs.bitstream_start_idx; } + friend bool operator!=(const TriangleBitStreamMapping &lhs, const TriangleBitStreamMapping &rhs) { return !(lhs == rhs); } + + private: + friend class cereal::access; + template void serialize(Archive &ar) { ar(triangle_idx, bitstream_start_idx); } + }; + + struct TriangleSplittingData { + // Vector of triangles and its indexes to the bitstream. + 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(EnforcerBlockerType::ExtruderMax), 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 + && 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(EnforcerBlockerType::ExtruderMax), 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, used_states); } + }; + std::pair, std::vector> precompute_all_neighbors() const; void precompute_all_neighbors_recursive(int facet_idx, const Vec3i32 &neighbors, const Vec3i32 &neighbors_propagated, std::vector &neighbors_out, std::vector &neighbors_normal_out) const; @@ -247,7 +321,7 @@ class TriangleSelector bool force_reselection = false); // force reselection of the triangle mesh even in cases that mouse is pointing on the selected triangle bool has_facets(EnforcerBlockerType state) const; - static bool has_facets(const std::pair>, std::vector> &data, EnforcerBlockerType test_state); + static bool has_facets(const TriangleSplittingData &data, EnforcerBlockerType test_state); int num_facets(EnforcerBlockerType state) const; // Get facets at a given state. Don't triangulate T-joints. indexed_triangle_set get_facets(EnforcerBlockerType state) const; @@ -270,10 +344,15 @@ class TriangleSelector // 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; + TriangleSplittingData serialize() const; // Load serialized data. Assumes that correct mesh is loaded. - void deserialize(const std::pair>, std::vector>& data, bool needs_reset = true, EnforcerBlockerType max_ebt = EnforcerBlockerType::ExtruderMax); + void deserialize(const TriangleSplittingData& data, + bool needs_reset = true, + EnforcerBlockerType max_ebt = EnforcerBlockerType::ExtruderMax); + + // 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(); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 450bf768f24..5ce506951bf 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1354,7 +1354,7 @@ void Sidebar::update_presets(Preset::Type preset_type) for (size_t i = 0; i < filament_cnt; i++) p->combos_filament[i]->update(); - dynamic_filament_list.update(); + update_dynamic_filament_list(); break; } @@ -1637,7 +1637,7 @@ void Sidebar::on_filaments_change(size_t num_filaments) Layout(); p->m_panel_filament_title->Refresh(); update_ui_from_settings(); - dynamic_filament_list.update(); + update_dynamic_filament_list(); } void Sidebar::add_filament() { @@ -1818,7 +1818,7 @@ void Sidebar::sync_ams_list() c->update(); wxGetApp().get_tab(Preset::TYPE_FILAMENT)->select_preset(wxGetApp().preset_bundle->filament_presets[0]); wxGetApp().preset_bundle->export_selections(*wxGetApp().app_config); - dynamic_filament_list.update(); + update_dynamic_filament_list(); // Expand filament list p->m_panel_filament_content->SetMaxSize({-1, -1}); // BBS:Synchronized consumables information @@ -1854,6 +1854,12 @@ void Sidebar::show_SEMM_buttons(bool bshow) Layout(); } +void Sidebar::update_dynamic_filament_list() +{ + dynamic_filament_list.update(); + dynamic_filament_list_1_based.update(); +} + ObjectList* Sidebar::obj_list() { // BBS @@ -6535,7 +6541,7 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt) wxGetApp().preset_bundle->set_filament_preset(idx, preset_name); wxGetApp().plater()->update_project_dirty_from_presets(); wxGetApp().preset_bundle->export_selections(*wxGetApp().app_config); - dynamic_filament_list.update(); + sidebar->update_dynamic_filament_list(); bool flag_is_change = is_support_filament(idx); if (flag != flag_is_change) { sidebar->auto_calc_flushing_volumes(idx); @@ -12794,7 +12800,7 @@ void Plater::on_config_change(const DynamicPrintConfig &config) if (update_filament_colors_in_full_config()) { p->sidebar->obj_list()->update_filament_colors(); - dynamic_filament_list.update(); + p->sidebar->update_dynamic_filament_list(); continue; } } @@ -12851,9 +12857,9 @@ void Plater::on_config_change(const DynamicPrintConfig &config) bed_shape_changed = true; update_scheduled = true; } - // BBS - else if (opt_key == "support_interface_filament" || - opt_key == "support_filament") { + // Orca: update when *_filament changed + else if (opt_key == "support_interface_filament" || opt_key == "support_filament" || opt_key == "wall_filament" || + opt_key == "sparse_infill_filament" || opt_key == "solid_infill_filament") { update_scheduled = true; } } diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 5e5c88e7e61..16396631a85 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -150,6 +150,7 @@ class Sidebar : public wxPanel void sync_ams_list(); // Orca void show_SEMM_buttons(bool bshow); + void update_dynamic_filament_list(); ObjectList* obj_list(); ObjectSettings* obj_settings();