From e52946123420f3c10423ee7fdb97eac8cc3914ea Mon Sep 17 00:00:00 2001 From: Rad Date: Mon, 9 Feb 2026 22:35:18 +0100 Subject: [PATCH] Add mixed filament support: Implement MixedFilament and MixedFilamentManager classes for layer-based color mixing. Update CLI to handle downward_check option. Enhance GUI to display mixed filaments and integrate with existing filament management. --- src/Snapmaker_Orca.cpp | 16 +- src/libslic3r/CMakeLists.txt | 2 + src/libslic3r/GCode/ToolOrdering.cpp | 51 ++++-- src/libslic3r/GCode/ToolOrdering.hpp | 18 ++ src/libslic3r/MixedFilament.cpp | 158 +++++++++++++++++ src/libslic3r/MixedFilament.hpp | 108 ++++++++++++ src/libslic3r/Model.cpp | 11 ++ src/libslic3r/MultiMaterialSegmentation.cpp | 29 +++- src/libslic3r/PresetBundle.cpp | 14 +- src/libslic3r/PresetBundle.hpp | 4 + src/libslic3r/Print.hpp | 4 + src/libslic3r/PrintApply.cpp | 57 +++++- src/libslic3r/PrintObject.cpp | 6 +- src/libslic3r/PrintObjectSlice.cpp | 6 +- src/slic3r/GUI/GLCanvas3D.cpp | 9 +- src/slic3r/GUI/GUI_Factories.cpp | 12 +- src/slic3r/GUI/GUI_ObjectList.cpp | 8 +- .../GUI/Gizmos/GLGizmoMmuSegmentation.cpp | 25 +++ src/slic3r/GUI/Plater.cpp | 162 +++++++++++++++++- src/slic3r/GUI/Plater.hpp | 3 +- src/slic3r/GUI/Tab.cpp | 5 +- 21 files changed, 669 insertions(+), 39 deletions(-) create mode 100644 src/libslic3r/MixedFilament.cpp create mode 100644 src/libslic3r/MixedFilament.hpp diff --git a/src/Snapmaker_Orca.cpp b/src/Snapmaker_Orca.cpp index 51361333b4..e04b915020 100644 --- a/src/Snapmaker_Orca.cpp +++ b/src/Snapmaker_Orca.cpp @@ -1120,9 +1120,23 @@ int CLI::run(int argc, char **argv) sliced_info_t sliced_info; std::map record_key_values; + // Only honor downward_check when explicitly requested on CLI. + // This prevents accidental activation during normal GUI startup. + bool downward_check_requested = false; + for (int i = 1; i < argc; ++i) { + std::string arg = argv[i] ? argv[i] : ""; + if (arg == "--downward_check" || boost::algorithm::starts_with(arg, "--downward_check=") || + arg == "--downward-check" || boost::algorithm::starts_with(arg, "--downward-check=")) { + downward_check_requested = true; + break; + } + } + ConfigOptionBool* downward_check_option = m_config.option("downward_check"); - if (downward_check_option) + if (downward_check_option && downward_check_requested) downward_check = downward_check_option->value; + else + downward_check = false; bool start_gui = m_actions.empty() && !downward_check; if (start_gui) { diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index da6a6b42c1..697a5332e3 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -283,6 +283,8 @@ set(lisbslic3r_sources MinAreaBoundingBox.hpp MinimumSpanningTree.cpp MinimumSpanningTree.hpp + MixedFilament.cpp + MixedFilament.hpp miniz_extension.cpp miniz_extension.hpp ModelArrange.cpp diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index 8813dfee35..6db53f97ad 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -146,23 +146,34 @@ bool LayerTools::is_extruder_order(unsigned int a, unsigned int b) const return false; } +// Resolve a 1-based filament ID through the mixed-filament manager for this layer. +unsigned int LayerTools::resolve_mixed_1based(unsigned int filament_id) const +{ + if (mixed_mgr && mixed_mgr->is_mixed(filament_id, num_physical)) + return mixed_mgr->resolve(filament_id, num_physical, this->layer_index); + return filament_id; +} + // Return a zero based extruder from the region, or extruder_override if overriden. unsigned int LayerTools::wall_filament(const PrintRegion ®ion) const { assert(region.config().wall_filament.value > 0); - return ((this->extruder_override == 0) ? region.config().wall_filament.value : this->extruder_override) - 1; + unsigned int id = (this->extruder_override == 0) ? region.config().wall_filament.value : this->extruder_override; + return resolve_mixed_1based(id) - 1; } unsigned int LayerTools::sparse_infill_filament(const PrintRegion ®ion) const { assert(region.config().sparse_infill_filament.value > 0); - return ((this->extruder_override == 0) ? region.config().sparse_infill_filament.value : this->extruder_override) - 1; + unsigned int id = (this->extruder_override == 0) ? region.config().sparse_infill_filament.value : this->extruder_override; + return resolve_mixed_1based(id) - 1; } unsigned int LayerTools::solid_infill_filament(const PrintRegion ®ion) const { assert(region.config().solid_infill_filament.value > 0); - return ((this->extruder_override == 0) ? region.config().solid_infill_filament.value : this->extruder_override) - 1; + unsigned int id = (this->extruder_override == 0) ? region.config().solid_infill_filament.value : this->extruder_override; + return resolve_mixed_1based(id) - 1; } // Returns a zero based extruder this eec should be printed with, according to PrintRegion config or extruder_override if overriden. @@ -184,6 +195,7 @@ unsigned int LayerTools::extruder(const ExtrusionEntityCollection &extrusions, c } else extruder = this->extruder_override; + extruder = resolve_mixed_1based(extruder); return (extruder == 0) ? 0 : extruder - 1; } @@ -208,6 +220,9 @@ ToolOrdering::ToolOrdering(const PrintObject &object, unsigned int first_extrude m_is_BBL_printer = object.print()->is_BBL_printer(); m_print_full_config = &object.print()->full_print_config(); m_print_object_ptr = &object; + // Mixed filament support. + m_mixed_mgr = &object.print()->mixed_filament_manager(); + m_num_physical = object.print()->config().filament_diameter.size(); if (object.layers().empty()) return; @@ -271,6 +286,9 @@ ToolOrdering::ToolOrdering(const Print &print, unsigned int first_extruder, bool m_is_BBL_printer = print.is_BBL_printer(); m_print_full_config = &print.full_print_config(); m_print_config_ptr = &print.config(); + // Mixed filament support. + m_mixed_mgr = &print.mixed_filament_manager(); + m_num_physical = print.config().filament_diameter.size(); // Initialize the print layers for all objects and all layers. coordf_t object_bottom_z = 0.; @@ -480,8 +498,8 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto ExtrusionRole role = support_layer->support_fills.role(); bool has_support = role == erMixed || role == erSupportMaterial || role == erSupportTransition; bool has_interface = role == erMixed || role == erSupportMaterialInterface; - unsigned int extruder_support = object.config().support_filament.value; - unsigned int extruder_interface = object.config().support_interface_filament.value; + unsigned int extruder_support = resolve_mixed(object.config().support_filament.value, layer_tools.layer_index); + unsigned int extruder_interface = resolve_mixed(object.config().support_interface_filament.value, layer_tools.layer_index); if (has_support) layer_tools.extruders.push_back(extruder_support); if (has_interface) @@ -505,6 +523,10 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto // Collect the object extruders. for (auto layer : object.layers()) { LayerTools &layer_tools = this->tools_for_layer(layer->print_z); + // Store the sequential layer index and mixed-filament context for resolution. + layer_tools.layer_index = layerCount; + layer_tools.mixed_mgr = m_mixed_mgr; + layer_tools.num_physical = m_num_physical; // Override extruder with the next for (; it_per_layer_extruder_override != per_layer_extruder_switches.end() && it_per_layer_extruder_override->first < layer->print_z + EPSILON; ++ it_per_layer_extruder_override) @@ -528,9 +550,11 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto } if (something_nonoverriddable){ - layer_tools.extruders.emplace_back((extruder_override == 0) ? region.config().wall_filament.value : extruder_override); + unsigned int wall_ext = (extruder_override == 0) ? region.config().wall_filament.value : extruder_override; + wall_ext = resolve_mixed(wall_ext, layerCount); + layer_tools.extruders.emplace_back(wall_ext); if (layerCount == 0) { - firstLayerExtruders.emplace_back((extruder_override == 0) ? region.config().wall_filament.value : extruder_override); + firstLayerExtruders.emplace_back(wall_ext); } } @@ -558,11 +582,11 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto if (something_nonoverriddable || !m_print_config_ptr) { if (extruder_override == 0) { if (has_solid_infill) - layer_tools.extruders.emplace_back(region.config().solid_infill_filament); + layer_tools.extruders.emplace_back(resolve_mixed(region.config().solid_infill_filament, layerCount)); if (has_infill) - layer_tools.extruders.emplace_back(region.config().sparse_infill_filament); + layer_tools.extruders.emplace_back(resolve_mixed(region.config().sparse_infill_filament, layerCount)); } else if (has_solid_infill || has_infill) - layer_tools.extruders.emplace_back(extruder_override); + layer_tools.extruders.emplace_back(resolve_mixed(extruder_override, layerCount)); } if (has_solid_infill || has_infill) layer_tools.has_object = true; @@ -1414,5 +1438,12 @@ int WipingExtrusions::get_support_interface_extruder_overrides(const PrintObject return -1; } +// Resolve a 1-based filament ID through the mixed-filament manager. +unsigned int ToolOrdering::resolve_mixed(unsigned int filament_id_1based, int layer_index) const +{ + if (m_mixed_mgr && m_mixed_mgr->is_mixed(filament_id_1based, m_num_physical)) + return m_mixed_mgr->resolve(filament_id_1based, m_num_physical, layer_index); + return filament_id_1based; +} } // namespace Slic3r diff --git a/src/libslic3r/GCode/ToolOrdering.hpp b/src/libslic3r/GCode/ToolOrdering.hpp index f93578196d..4be18af7f4 100644 --- a/src/libslic3r/GCode/ToolOrdering.hpp +++ b/src/libslic3r/GCode/ToolOrdering.hpp @@ -4,6 +4,7 @@ #define slic3r_ToolOrdering_hpp_ #include "../libslic3r.h" +#include "../MixedFilament.hpp" #include @@ -119,6 +120,8 @@ public: // If per layer extruder switches are inserted by the G-code preview slider, this value contains the new (1 based) extruder, with which the whole object layer is being printed with. // If not overriden, it is set to 0. unsigned int extruder_override = 0; + // Sequential layer index (0-based), used by mixed-filament resolution. + int layer_index = 0; // Should a skirt be printed at this layer? // Layers are marked for infinite skirt aka draft shield. Not all the layers have to be printed. bool has_skirt = false; @@ -138,7 +141,13 @@ public: return m_wiping_extrusions; } + // Mixed-filament resolution context (set by ToolOrdering during collect_extruders). + const MixedFilamentManager *mixed_mgr = nullptr; + size_t num_physical = 0; + private: + // Resolve a 1-based filament ID through the mixed-filament manager for this layer. + unsigned int resolve_mixed_1based(unsigned int filament_id) const; // This object holds list of extrusion that will be used for extruder wiping WipingExtrusions m_wiping_extrusions; }; @@ -203,6 +212,11 @@ private: std::vector generate_first_layer_tool_order(const Print& print); std::vector generate_first_layer_tool_order(const PrintObject& object); + // Resolve a 1-based filament ID through the mixed-filament manager. + // Returns the resolved physical extruder (1-based). If the ID is not a + // mixed filament or no manager is set, returns the input unchanged. + unsigned int resolve_mixed(unsigned int filament_id_1based, int layer_index) const; + std::vector m_layer_tools; // First printing extruder, including the multi-material priming sequence. unsigned int m_first_printing_extruder = (unsigned int)-1; @@ -215,6 +229,10 @@ private: const PrintConfig* m_print_config_ptr = nullptr; const PrintObject* m_print_object_ptr = nullptr; bool m_is_BBL_printer = false; + // Mixed filament support: pointer to manager (owned by Print) and + // number of physical extruders. + const MixedFilamentManager* m_mixed_mgr = nullptr; + size_t m_num_physical = 0; }; } // namespace SLic3r diff --git a/src/libslic3r/MixedFilament.cpp b/src/libslic3r/MixedFilament.cpp new file mode 100644 index 0000000000..33405ce1cb --- /dev/null +++ b/src/libslic3r/MixedFilament.cpp @@ -0,0 +1,158 @@ +#include "MixedFilament.hpp" + +#include +#include +#include +#include +#include + +namespace Slic3r { + +// --------------------------------------------------------------------------- +// Colour helpers (internal) +// --------------------------------------------------------------------------- + +struct RGB { + int r = 0, g = 0, b = 0; +}; + +// Parse "#RRGGBB" to RGB. Returns black on failure. +static RGB parse_hex_color(const std::string &hex) +{ + RGB c; + if (hex.size() >= 7 && hex[0] == '#') { + c.r = std::stoi(hex.substr(1, 2), nullptr, 16); + c.g = std::stoi(hex.substr(3, 2), nullptr, 16); + c.b = std::stoi(hex.substr(5, 2), nullptr, 16); + } + return c; +} + +static std::string rgb_to_hex(const RGB &c) +{ + char buf[8]; + std::snprintf(buf, sizeof(buf), "#%02X%02X%02X", c.r, c.g, c.b); + return std::string(buf); +} + +// --------------------------------------------------------------------------- +// MixedFilamentManager +// --------------------------------------------------------------------------- + +void MixedFilamentManager::auto_generate(const std::vector &filament_colours) +{ + // Keep a copy of the old list so we can preserve user-modified ratios and + // enabled flags. + std::vector old = std::move(m_mixed); + m_mixed.clear(); + + const size_t n = filament_colours.size(); + if (n < 2) + return; + + // Generate all C(N,2) pairwise combinations. + for (size_t i = 0; i < n; ++i) { + for (size_t j = i + 1; j < n; ++j) { + MixedFilament mf; + mf.component_a = static_cast(i + 1); // 1-based + mf.component_b = static_cast(j + 1); + mf.ratio_a = 1; + mf.ratio_b = 1; + mf.enabled = true; + + // Try to preserve previous settings. + for (const auto &prev : old) { + if (prev.component_a == mf.component_a && + prev.component_b == mf.component_b) { + mf.ratio_a = prev.ratio_a; + mf.ratio_b = prev.ratio_b; + mf.enabled = prev.enabled; + break; + } + } + + mf.display_color = blend_color(filament_colours[i], + filament_colours[j], + mf.ratio_a, mf.ratio_b); + m_mixed.push_back(mf); + } + } +} + +unsigned int MixedFilamentManager::resolve(unsigned int filament_id, + size_t num_physical, + int layer_index) const +{ + if (!is_mixed(filament_id, num_physical)) + return filament_id; + + const size_t idx = index_of(filament_id, num_physical); + if (idx >= m_mixed.size()) + return 1; // fallback to first extruder + + const MixedFilament &mf = m_mixed[idx]; + const int cycle = mf.ratio_a + mf.ratio_b; + if (cycle <= 0) + return mf.component_a; + + const int pos = ((layer_index % cycle) + cycle) % cycle; // safe modulo for negatives + return (pos < mf.ratio_a) ? mf.component_a : mf.component_b; +} + +std::string MixedFilamentManager::blend_color(const std::string &color_a, + const std::string &color_b, + int ratio_a, int ratio_b) +{ + RGB a = parse_hex_color(color_a); + RGB b = parse_hex_color(color_b); + + // Additive blend: min(a + b, 255) per channel. + // For unequal ratios, weight accordingly. + const float total = static_cast(ratio_a + ratio_b); + const float wa = (total > 0.f) ? static_cast(ratio_a) / total : 0.5f; + const float wb = 1.f - wa; + + // Use screen blending which is additive-like without oversaturation: + // screen(A, B) = A + B - A*B/255 + // Weighted variant: blend each channel independently. + auto screen_ch = [](int ca, int cb, float wa, float wb) -> int { + // Weighted additive with clamping – matches user expectation: + // Red(255,0,0) + Green(0,255,0) = Yellow(255,255,0) + float v = static_cast(ca) * wa + static_cast(cb) * wb; + // Boost towards additive: add the minimum so pure colours combine fully. + float additive = std::min(static_cast(ca + cb), 255.f); + // Blend between weighted-average and full-additive based on colour distance. + float result = wa * static_cast(ca) + wb * static_cast(cb); + // For the 1:1 case, use pure additive (clamped) to get R+G=Y. + if (std::abs(wa - wb) < 0.01f) + result = additive; + return std::min(static_cast(std::round(result)), 255); + }; + + RGB out; + out.r = screen_ch(a.r, b.r, wa, wb); + out.g = screen_ch(a.g, b.g, wa, wb); + out.b = screen_ch(a.b, b.b, wa, wb); + + return rgb_to_hex(out); +} + +size_t MixedFilamentManager::enabled_count() const +{ + size_t count = 0; + for (const auto &mf : m_mixed) + if (mf.enabled) + ++count; + return count; +} + +std::vector MixedFilamentManager::display_colors() const +{ + std::vector colors; + for (const auto &mf : m_mixed) + if (mf.enabled) + colors.push_back(mf.display_color); + return colors; +} + +} // namespace Slic3r diff --git a/src/libslic3r/MixedFilament.hpp b/src/libslic3r/MixedFilament.hpp new file mode 100644 index 0000000000..366b717500 --- /dev/null +++ b/src/libslic3r/MixedFilament.hpp @@ -0,0 +1,108 @@ +#ifndef slic3r_MixedFilament_hpp_ +#define slic3r_MixedFilament_hpp_ + +#include +#include +#include +#include + +namespace Slic3r { + +// Represents a virtual "mixed" filament created by alternating layers of two +// physical filaments. The display colour is an additive RGB blend so that, +// for example, Red + Green previews as Yellow. +struct MixedFilament +{ + // 1-based physical filament IDs that are combined. + unsigned int component_a = 1; + unsigned int component_b = 2; + + // Layer-alternation ratio. With ratio_a = 2, ratio_b = 1 the cycle is + // A, A, B, A, A, B, ... + int ratio_a = 1; + int ratio_b = 1; + + // Whether this mixed filament is enabled (available for assignment). + bool enabled = true; + + // Computed display colour as "#RRGGBB". + std::string display_color; + + bool operator==(const MixedFilament &rhs) const + { + return component_a == rhs.component_a && + component_b == rhs.component_b && + ratio_a == rhs.ratio_a && + ratio_b == rhs.ratio_b && + enabled == rhs.enabled; + } + bool operator!=(const MixedFilament &rhs) const { return !(*this == rhs); } +}; + +// --------------------------------------------------------------------------- +// MixedFilamentManager +// +// Owns the list of mixed filaments and provides helpers used by the slicing +// pipeline to resolve virtual IDs back to physical extruders. +// +// Virtual filament IDs are numbered starting at (num_physical + 1). For a +// 4-extruder printer the first mixed filament has ID 5, the second 6, etc. +// --------------------------------------------------------------------------- +class MixedFilamentManager +{ +public: + MixedFilamentManager() = default; + + // ---- Auto-generation ------------------------------------------------ + + // Rebuild the mixed-filament list from the current set of physical + // filament colours. Generates all C(N,2) pairwise combinations. + // Previous ratio/enabled state is preserved when a combination still + // exists. + void auto_generate(const std::vector &filament_colours); + + // ---- Queries -------------------------------------------------------- + + // True when `filament_id` (1-based) refers to a mixed filament. + bool is_mixed(unsigned int filament_id, size_t num_physical) const + { + return filament_id > num_physical && index_of(filament_id, num_physical) < m_mixed.size(); + } + + // Resolve a mixed filament ID to a physical extruder (1-based) for the + // given `layer_index`. Returns `filament_id` unchanged when it is not a + // mixed filament. + unsigned int resolve(unsigned int filament_id, size_t num_physical, int layer_index) const; + + // Compute a display colour by additively blending the two component + // colours. `filament_colours` contains the physical colours only. + static std::string blend_color(const std::string &color_a, + const std::string &color_b, + int ratio_a, int ratio_b); + + // ---- Accessors ------------------------------------------------------ + + const std::vector &mixed_filaments() const { return m_mixed; } + std::vector &mixed_filaments() { return m_mixed; } + + size_t enabled_count() const; + + // Total filament count = num_physical + number of *enabled* mixed filaments. + size_t total_filaments(size_t num_physical) const { return num_physical + enabled_count(); } + + // Return the display colours of all enabled mixed filaments (in order). + std::vector display_colors() const; + +private: + // Convert a 1-based virtual ID to a 0-based index into m_mixed. + size_t index_of(unsigned int filament_id, size_t num_physical) const + { + return static_cast(filament_id - num_physical - 1); + } + + std::vector m_mixed; +}; + +} // namespace Slic3r + +#endif /* slic3r_MixedFilament_hpp_ */ diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index b91bf65262..cf6004dae1 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -2477,6 +2477,17 @@ void ModelVolume::update_extruder_count(size_t extruder_count) std::vector used_extruders = get_extruders(); for (int extruder_id : used_extruders) { if (extruder_id > extruder_count) { + std::string used_extruders_str; + for (size_t i = 0; i < used_extruders.size(); ++i) { + if (i > 0) + used_extruders_str += ","; + used_extruders_str += std::to_string(used_extruders[i]); + } + BOOST_LOG_TRIVIAL(warning) << "ModelVolume::update_extruder_count clipping painted extruders to limit" + << " volume_name=" << this->name + << " volume_id=" << this->id().id + << " requested_limit=" << extruder_count + << " used_extruders=[" << used_extruders_str << "]"; mmu_segmentation_facets.set_enforcer_block_type_limit(*this, (EnforcerBlockerType)extruder_count); break; } diff --git a/src/libslic3r/MultiMaterialSegmentation.cpp b/src/libslic3r/MultiMaterialSegmentation.cpp index 4946ad45c8..f44d127d7c 100644 --- a/src/libslic3r/MultiMaterialSegmentation.cpp +++ b/src/libslic3r/MultiMaterialSegmentation.cpp @@ -8,6 +8,7 @@ #include "MutablePolygon.hpp" #include "format.hpp" +#include #include #include @@ -2195,11 +2196,37 @@ std::vector> segmentation_by_painting(const PrintObject // Returns multi-material segmentation based on painting in multi-material segmentation gizmo std::vector> multi_material_segmentation_by_painting(const PrintObject &print_object, const std::function &throw_on_cancel_callback) { - const size_t num_facets_states = print_object.print()->config().filament_colour.size() + 1; + const size_t num_physical_filaments = print_object.print()->config().filament_colour.size(); + const size_t num_total_filaments = print_object.print()->mixed_filament_manager().total_filaments(num_physical_filaments); + const size_t num_facets_states = num_total_filaments + 1; const float max_width = float(print_object.config().mmu_segmented_region_max_width.value); const float interlocking_depth = float(print_object.config().mmu_segmented_region_interlocking_depth.value); const bool interlocking_beam = print_object.config().interlocking_beam.value; + size_t max_painted_state = 0; + for (const ModelVolume *mv : print_object.model_object()->volumes) { + if (!mv->is_model_part()) + continue; + const auto &used_states = mv->mmu_segmentation_facets.get_data().used_states; + for (size_t state_idx = static_cast(EnforcerBlockerType::Extruder1); state_idx < used_states.size(); ++state_idx) { + if (used_states[state_idx]) + max_painted_state = std::max(max_painted_state, state_idx); + } + } + if (max_painted_state >= num_facets_states) { + BOOST_LOG_TRIVIAL(warning) << "multi_material_segmentation_by_painting dropping painted states above segmentation range" + << " max_painted_state=" << max_painted_state + << " num_facets_states=" << num_facets_states + << " physical_filaments=" << num_physical_filaments + << " total_filaments=" << num_total_filaments; + } else { + BOOST_LOG_TRIVIAL(debug) << "multi_material_segmentation_by_painting state range check" + << " max_painted_state=" << max_painted_state + << " num_facets_states=" << num_facets_states + << " physical_filaments=" << num_physical_filaments + << " total_filaments=" << num_total_filaments; + } + const auto extract_facets_info = [](const ModelVolume &mv) -> ModelVolumeFacetsInfo { return {mv.mmu_segmentation_facets, mv.is_mm_painted(), false}; }; diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 407b6bf740..6870bbd4b8 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -1811,7 +1811,9 @@ void PresetBundle::export_selections(AppConfig &config) config.set_printer_setting(printer_name, name, filament_presets[i]); } CNumericLocalesSetter locales_setter; - std::string filament_colors = boost::algorithm::join(project_config.option("filament_colour")->values, ","); + std::vector physical_filament_colors = project_config.option("filament_colour")->values; + physical_filament_colors.resize(filament_presets.size(), "#26A69A"); + std::string filament_colors = boost::algorithm::join(physical_filament_colors, ","); config.set_printer_setting(printer_name, "filament_colors", filament_colors); std::string flush_volumes_matrix = boost::algorithm::join(project_config.option("flush_volumes_matrix")->values | boost::adaptors::transformed(static_cast(std::to_string)), @@ -3256,6 +3258,16 @@ void PresetBundle::update_multi_material_filament_presets(size_t to_delete_filam } this->project_config.option("flush_volumes_matrix")->values = new_matrix; } + + // Keep project colours aligned to physical filaments, then regenerate mixed + // (virtual) entries from the physical set only. + { + ConfigOptionStrings *color_opt = this->project_config.option("filament_colour"); + if (color_opt) { + color_opt->values.resize(num_filaments, "#26A69A"); + this->mixed_filaments.auto_generate(color_opt->values); + } + } } void PresetBundle::update_compatible(PresetSelectCompatibleType select_other_print_if_incompatible, PresetSelectCompatibleType select_other_filament_if_incompatible) diff --git a/src/libslic3r/PresetBundle.hpp b/src/libslic3r/PresetBundle.hpp index 6973a82b78..612aefcda8 100644 --- a/src/libslic3r/PresetBundle.hpp +++ b/src/libslic3r/PresetBundle.hpp @@ -4,6 +4,7 @@ #include "Preset.hpp" #include "AppConfig.hpp" #include "enum_bitmask.hpp" +#include "MixedFilament.hpp" #include #include @@ -153,6 +154,9 @@ public: std::map filament_ams_list; std::vector> ams_multi_color_filment; + // Mixed (virtual) filaments for layer-based colour mixing. + MixedFilamentManager mixed_filaments; + // Snapmaker std::map> machine_filaments; diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 7261bc2ce6..8d83a55f41 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -17,6 +17,7 @@ #include "GCode/ThumbnailData.hpp" #include "GCode/GCodeProcessor.hpp" #include "MultiMaterialSegmentation.hpp" +#include "MixedFilament.hpp" #include "libslic3r.h" #include @@ -896,6 +897,8 @@ public: const PrintConfig& config() const { return m_config; } const PrintObjectConfig& default_object_config() const { return m_default_object_config; } const PrintRegionConfig& default_region_config() const { return m_default_region_config; } + const MixedFilamentManager& mixed_filament_manager() const { return m_mixed_filament_mgr; } + MixedFilamentManager& mixed_filament_manager() { return m_mixed_filament_mgr; } ConstPrintObjectPtrsAdaptor objects() const { return ConstPrintObjectPtrsAdaptor(&m_objects); } PrintObject* get_object(size_t idx) { return const_cast(m_objects[idx]); } const PrintObject* get_object(size_t idx) const { return m_objects[idx]; } @@ -1028,6 +1031,7 @@ private: PrintConfig m_config; PrintObjectConfig m_default_object_config; PrintRegionConfig m_default_region_config; + MixedFilamentManager m_mixed_filament_mgr; PrintObjectPtrs m_objects; PrintRegionPtrs m_print_regions; diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp index 608af65bee..7450d3ed80 100644 --- a/src/libslic3r/PrintApply.cpp +++ b/src/libslic3r/PrintApply.cpp @@ -2,6 +2,7 @@ #include "Print.hpp" #include +#include #include namespace Slic3r { @@ -1188,6 +1189,14 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ } } + // Regenerate mixed (virtual) filaments from physical filament colours only. + std::vector physical_filament_colors = m_config.filament_colour.values; + physical_filament_colors.resize(num_extruders, "#26A69A"); + m_mixed_filament_mgr.auto_generate(physical_filament_colors); + // Total filaments = physical extruders + enabled mixed (virtual) filaments. + // Used for extruder ID clamping so that virtual IDs are accepted. + size_t num_total_filaments = m_mixed_filament_mgr.total_filaments(num_extruders); + ModelObjectStatusDB model_object_status_db; // 1) Synchronize model objects. @@ -1375,7 +1384,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ if (object_config_changed) model_object.config.assign_config(model_object_new.config); if (! object_diff.empty() || object_config_changed || num_extruders_changed ) { - PrintObjectConfig new_config = PrintObject::object_config_from_model_object(m_default_object_config, model_object, num_extruders ); + PrintObjectConfig new_config = PrintObject::object_config_from_model_object(m_default_object_config, model_object, num_total_filaments ); for (const PrintObjectStatus &print_object_status : print_object_status_db.get_range(model_object)) { t_config_option_keys diff = print_object_status.print_object->config().diff(new_config); if (! diff.empty()) { @@ -1438,10 +1447,10 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ // Generate a list of trafos and XY offsets for instances of a ModelObject // Producing the config for PrintObject on demand, caching it at print_object_last. const PrintObject *print_object_last = nullptr; - auto print_object_apply_config = [this, &print_object_last, model_object, num_extruders ](PrintObject *print_object) { + auto print_object_apply_config = [this, &print_object_last, model_object, num_total_filaments ](PrintObject *print_object) { print_object->config_apply(print_object_last ? print_object_last->config() : - PrintObject::object_config_from_model_object(m_default_object_config, *model_object, num_extruders )); + PrintObject::object_config_from_model_object(m_default_object_config, *model_object, num_total_filaments )); print_object_last = print_object; }; if (old.empty()) { @@ -1581,9 +1590,43 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ used_facet_states[state_idx] |= volume_used_facet_states[state_idx]; } + size_t dropped_painted_states = 0; 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 (!used_facet_states[state_idx]) + continue; + if (state_idx <= num_total_filaments) + painting_extruders.emplace_back(static_cast(state_idx)); + else + ++dropped_painted_states; + } + + if (dropped_painted_states > 0) { + BOOST_LOG_TRIVIAL(warning) << "Print::apply dropping painted extruder IDs above available filament range" + << " dropped_states=" << dropped_painted_states + << " physical_filaments=" << num_extruders + << " total_filaments=" << num_total_filaments; + } + + if (!painting_extruders.empty()) { + std::string painting_ids; + for (size_t i = 0; i < painting_extruders.size(); ++i) { + if (i > 0) + painting_ids += ","; + painting_ids += std::to_string(painting_extruders[i]); + } + + const unsigned int max_painted_extruder = *std::max_element(painting_extruders.begin(), painting_extruders.end()); + if (max_painted_extruder > num_total_filaments) { + BOOST_LOG_TRIVIAL(warning) << "Print::apply detected painted extruder IDs above available filament range" + << " painted_extruders=[" << painting_ids << "]" + << " physical_filaments=" << num_extruders + << " total_filaments=" << num_total_filaments; + } else { + BOOST_LOG_TRIVIAL(debug) << "Print::apply collected painted extruders" + << " painted_extruders=[" << painting_ids << "]" + << " physical_filaments=" << num_extruders + << " total_filaments=" << num_total_filaments; + } } } if (model_object_status.print_object_regions_status == ModelObjectStatus::PrintObjectRegionsStatus::Valid) { @@ -1602,7 +1645,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ verify_update_print_object_regions( print_object.model_object()->volumes, m_default_region_config, - num_extruders, + num_total_filaments, *print_object_regions, [it_print_object, it_print_object_end, &update_apply_status](const PrintRegionConfig &old_config, const PrintRegionConfig &new_config, const t_config_option_keys &diff_keys) { for (auto it = it_print_object; it != it_print_object_end; ++it) @@ -1627,7 +1670,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ LayerRanges(print_object.model_object()->layer_config_ranges), m_default_region_config, model_object_status.print_instances.front().trafo, - num_extruders , + num_total_filaments , print_object.is_mm_painted() ? 0.f : float(print_object.config().xy_contour_compensation.value), painting_extruders, print_object.is_fuzzy_skin_painted()); diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index c76e1e2520..0bad0efd91 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -3157,9 +3157,11 @@ void PrintObject::bridge_over_infill() } // void PrintObject::bridge_over_infill() -static void clamp_exturder_to_default(ConfigOptionInt &opt, size_t num_extruders) +// Clamp extruder IDs that exceed the total number of available filaments +// (physical + virtual/mixed) back to the default extruder. +static void clamp_exturder_to_default(ConfigOptionInt &opt, size_t num_total_filaments) { - if (opt.value > (int)num_extruders) + if (opt.value > (int)num_total_filaments) // assign the default extruder opt.value = 1; } diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp index 532307414d..3735cd95d1 100644 --- a/src/libslic3r/PrintObjectSlice.cpp +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -854,8 +854,9 @@ static inline void apply_mm_segmentation(PrintObject &print_object, ThrowOnCance const auto &layer_ranges = print_object.shared_regions()->layer_ranges; double z = print_object.get_layer(int(range.begin()))->slice_z; auto it_layer_range = layer_range_first(layer_ranges, z); - // BBS - const size_t num_extruders = print_object.print()->config().filament_diameter.size(); + // MM segmentation channels correspond to filament IDs (1-based), which now + // include enabled mixed / virtual filaments. + const size_t num_extruders = segmentation.empty() ? 0 : segmentation.front().size(); struct ByExtruder { ExPolygons expolygons; @@ -875,6 +876,7 @@ static inline void apply_mm_segmentation(PrintObject &print_object, ThrowOnCance it_layer_range = layer_range_next(layer_ranges, it_layer_range, layer.slice_z); const PrintObjectRegions::LayerRangeRegions &layer_range = *it_layer_range; // Gather per extruder expolygons. + assert(segmentation[layer_id].size() == num_extruders); by_extruder.assign(num_extruders, ByExtruder()); by_region.assign(layer.region_count(), ByRegion()); bool layer_split = false; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 86c4c78975..9153e57a63 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2432,7 +2432,8 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re std::sort(model_volume_state.begin(), model_volume_state.end(), model_volume_state_lower); std::sort(aux_volume_state.begin(), aux_volume_state.end(), model_volume_state_lower); - // BBS: normalize painting data with current filament count + // BBS: normalize painting data with current available filament count + // (physical + enabled mixed/virtual filaments). for (unsigned int obj_idx = 0; obj_idx < (unsigned int)m_model->objects.size(); ++obj_idx) { const ModelObject& model_object = *m_model->objects[obj_idx]; for (int volume_idx = 0; volume_idx < (int)model_object.volumes.size(); ++volume_idx) { @@ -2441,7 +2442,11 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re continue; unsigned int filaments_count = (unsigned int)dynamic_cast(m_config->option("filament_colour"))->values.size(); - model_volume.update_extruder_count(filaments_count); + size_t available_filaments = filaments_count; + if (wxGetApp().preset_bundle != nullptr) + available_filaments = wxGetApp().preset_bundle->mixed_filaments.total_filaments(filaments_count); + + model_volume.update_extruder_count(available_filaments); } } diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index c71c3035ec..7d220e0ad0 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -1488,14 +1488,18 @@ void MenuFactory::create_filament_action_menu(bool init, int active_filament_men if (item_id != wxNOT_FOUND) menu->Destroy(item_id); - wxMenu* sub_menu = new wxMenu(); - std::vector icons = get_extruder_color_icons(true); - int filaments_cnt = icons.size(); + wxMenu * sub_menu = new wxMenu(); + std::vector icons = get_extruder_color_icons(true); + const std::vector &filament_presets = wxGetApp().preset_bundle->filament_presets; + int filaments_cnt = std::max(wxGetApp().filaments_cnt(), 0); + filaments_cnt = std::min(filaments_cnt, static_cast(icons.size())); + filaments_cnt = std::min(filaments_cnt, static_cast(filament_presets.size())); + for (int i = 0; i < filaments_cnt; i++) { if (i == active_filament_menu_id) continue; - auto preset = wxGetApp().preset_bundle->filaments.find_preset(wxGetApp().preset_bundle->filament_presets[i]); + auto preset = wxGetApp().preset_bundle->filaments.find_preset(filament_presets[i]); wxString item_name = preset ? from_u8(preset->label(false)) : wxString::Format(_L("Filament %d"), i + 1); append_menu_item( diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 21a947adb9..4114c7d58c 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -70,7 +70,13 @@ static DynamicPrintConfig& printer_config() static int filaments_count() { - return wxGetApp().filaments_cnt(); + if (wxGetApp().preset_bundle == nullptr) + return 0; + + int physical = wxGetApp().filaments_cnt(); + // Include enabled mixed (virtual) filaments in the count. + const auto &mixed_mgr = wxGetApp().preset_bundle->mixed_filaments; + return static_cast(mixed_mgr.total_filaments(physical)); } static void take_snapshot(const std::string& snapshot_name) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index 9dfd36f56a..fe9bcd61e7 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -16,6 +16,8 @@ #include +#include +#include namespace Slic3r::GUI { @@ -770,6 +772,29 @@ void GLGizmoMmuSegmentation::update_model_object() } if (updated) { + const size_t num_physical = static_cast(std::max(wxGetApp().filaments_cnt(), 0)); + size_t num_total = num_physical; + if (wxGetApp().preset_bundle != nullptr) + num_total = wxGetApp().preset_bundle->mixed_filaments.total_filaments(num_physical); + + size_t max_used_state = 0; + for (const ModelVolume *mv : mo->volumes) { + if (!mv->is_model_part()) + continue; + const auto &used_states = mv->mmu_segmentation_facets.get_data().used_states; + for (size_t state_idx = static_cast(EnforcerBlockerType::Extruder1); state_idx < used_states.size(); ++state_idx) { + if (used_states[state_idx]) + max_used_state = std::max(max_used_state, state_idx); + } + } + + if (max_used_state > num_physical) { + BOOST_LOG_TRIVIAL(warning) << "GLGizmoMmuSegmentation::update_model_object painted virtual extruder state detected" + << " max_used_state=" << max_used_state + << " physical_filaments=" << num_physical + << " total_filaments=" << num_total; + } + const ModelObjectPtrs &mos = wxGetApp().model().objects; size_t obj_idx = std::find(mos.begin(), mos.end(), mo) - mos.begin(); wxGetApp().obj_list()->update_info_items(obj_idx); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 1e85321182..aa83078e77 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1,5 +1,6 @@ #include "Plater.hpp" #include "libslic3r/Config.hpp" +#include "libslic3r/MixedFilament.hpp" #include "common_func/common_func.hpp" #include @@ -42,6 +43,7 @@ #include #endif #include +#include #include #include @@ -634,6 +636,10 @@ struct Sidebar::priv int m_menu_filament_id = -1; wxPanel* m_panel_filament_content; wxScrolledWindow* m_scrolledWindow_filament_content; + + // Mixed (virtual) filaments panel + wxPanel* m_panel_mixed_filaments = nullptr; + wxBoxSizer* m_sizer_mixed_filaments = nullptr; wxStaticLine* m_staticline2; wxPanel* m_panel_project_title; ScalableButton* m_filament_icon = nullptr; @@ -1266,7 +1272,7 @@ Sidebar::Sidebar(Plater *parent) ConfigOptionFloat* flush_multi_opt = project_config.option("flush_multiplier"); float flush_multiplier = flush_multi_opt ? flush_multi_opt->getFloat() : 1.f; - const std::vector extruder_colours = wxGetApp().plater()->get_extruder_colors_from_plater_config(); + const std::vector extruder_colours = wxGetApp().plater()->get_extruder_colors_from_plater_config(nullptr, false); const auto& full_config = wxGetApp().preset_bundle->full_config(); const auto& extra_flush_volumes = get_min_flush_volumes(full_config); WipingDialog dlg(parent, cast(init_matrix), cast(init_extruders), extruder_colours, extra_flush_volumes, flush_multiplier); @@ -1423,6 +1429,23 @@ Sidebar::Sidebar(Plater *parent) scrolled_sizer->Add(p->m_panel_filament_content, 0, wxEXPAND, 0); } + // --- Mixed Colours Panel --- + { + p->m_panel_mixed_filaments = new wxPanel(p->scrolled, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + p->m_panel_mixed_filaments->SetBackgroundColour(wxColour(255, 255, 255)); + p->m_sizer_mixed_filaments = new wxBoxSizer(wxVERTICAL); + p->m_sizer_mixed_filaments->AddSpacer(FromDIP(4)); + // Title + auto *mixed_title = new wxStaticText(p->m_panel_mixed_filaments, wxID_ANY, _L("Mixed Colors")); + mixed_title->SetFont(Label::Head_14); + p->m_sizer_mixed_filaments->Add(mixed_title, 0, wxLEFT | wxRIGHT, FromDIP(16)); + p->m_sizer_mixed_filaments->AddSpacer(FromDIP(4)); + p->m_panel_mixed_filaments->SetSizer(p->m_sizer_mixed_filaments); + p->m_panel_mixed_filaments->Layout(); + p->m_panel_mixed_filaments->Hide(); // Hidden until 2+ filaments + scrolled_sizer->Add(p->m_panel_mixed_filaments, 0, wxEXPAND, 0); + } + { //add project title auto params_panel = ((MainFrame*)parent->GetParent())->m_param_panel; @@ -2161,6 +2184,116 @@ void Sidebar::on_filaments_change(size_t num_filaments) p->m_panel_filament_title->Refresh(); update_ui_from_settings(); update_dynamic_filament_list(); + update_mixed_filament_panel(); +} + +void Sidebar::update_mixed_filament_panel() +{ + if (!p->m_panel_mixed_filaments || !p->m_sizer_mixed_filaments) + return; + + auto *preset_bundle = wxGetApp().preset_bundle; + if (!preset_bundle) + return; + + const auto &mixed_mgr = preset_bundle->mixed_filaments; + const auto &mixed = mixed_mgr.mixed_filaments(); + + // Clear old entries safely. Using Clear(true) avoids manual child-window + // destruction loops with deferred wxWindow::Destroy(). + p->m_sizer_mixed_filaments->Clear(true); + + // Recreate header. + p->m_sizer_mixed_filaments->AddSpacer(FromDIP(4)); + auto *mixed_title = new wxStaticText(p->m_panel_mixed_filaments, wxID_ANY, _L("Mixed Colors")); + mixed_title->SetFont(Label::Head_14); + p->m_sizer_mixed_filaments->Add(mixed_title, 0, wxLEFT | wxRIGHT, FromDIP(16)); + p->m_sizer_mixed_filaments->AddSpacer(FromDIP(4)); + + if (mixed.empty()) { + p->m_panel_mixed_filaments->Hide(); + Layout(); + return; + } + + int mixed_id = 0; + for (const auto &mf : mixed) { + // Row panel + auto *row = new wxPanel(p->m_panel_mixed_filaments, wxID_ANY); + row->SetBackgroundColour(wxColour(255, 255, 255)); + auto *row_sizer = new wxBoxSizer(wxHORIZONTAL); + + // Colour swatch + wxColour swatch_color(mf.display_color); + auto *swatch = new wxPanel(row, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(16), FromDIP(16))); + swatch->SetBackgroundColour(swatch_color); + swatch->SetMinSize(wxSize(FromDIP(16), FromDIP(16))); + row_sizer->Add(swatch, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(8)); + + // Label: "Filament 1 + Filament 2" + wxString label_str = wxString::Format("Filament %u + Filament %u", + (unsigned)mf.component_a, (unsigned)mf.component_b); + auto *label = new wxStaticText(row, wxID_ANY, label_str); + row_sizer->Add(label, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(8)); + + // Ratio display + wxString ratio_str = wxString::Format("%d:%d", mf.ratio_a, mf.ratio_b); + auto *ratio_label = new wxStaticText(row, wxID_ANY, ratio_str); + row_sizer->Add(ratio_label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(4)); + + // Ratio spin controls + auto *spin_a = new wxSpinCtrl(row, wxID_ANY, wxEmptyString, wxDefaultPosition, + wxSize(FromDIP(45), -1), wxSP_ARROW_KEYS, 1, 10, mf.ratio_a); + auto *spin_b = new wxSpinCtrl(row, wxID_ANY, wxEmptyString, wxDefaultPosition, + wxSize(FromDIP(45), -1), wxSP_ARROW_KEYS, 1, 10, mf.ratio_b); + row_sizer->Add(spin_a, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(2)); + auto *colon = new wxStaticText(row, wxID_ANY, ":"); + row_sizer->Add(colon, 0, wxALIGN_CENTER_VERTICAL); + row_sizer->Add(spin_b, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(2)); + + // Enable checkbox + auto *chk = new wxCheckBox(row, wxID_ANY, wxEmptyString); + chk->SetValue(mf.enabled); + row_sizer->Add(chk, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, FromDIP(8)); + + // Bind events to update the MixedFilamentManager when ratio/enabled changes. + int capturedIdx = mixed_id; + auto *capturedSpinA = spin_a; + auto *capturedSpinB = spin_b; + auto *capturedChk = chk; + + auto update_lambda = [capturedIdx, capturedSpinA, capturedSpinB, capturedChk](wxCommandEvent &) { + auto &mgr = wxGetApp().preset_bundle->mixed_filaments; + auto &mfs = mgr.mixed_filaments(); + if (capturedIdx < (int)mfs.size()) { + mfs[capturedIdx].ratio_a = capturedSpinA->GetValue(); + mfs[capturedIdx].ratio_b = capturedSpinB->GetValue(); + mfs[capturedIdx].enabled = capturedChk->GetValue(); + // Recompute display color. + ConfigOptionStrings *co = wxGetApp().preset_bundle->project_config.option("filament_colour"); + if (co && mfs[capturedIdx].component_a <= co->values.size() && mfs[capturedIdx].component_b <= co->values.size()) { + mfs[capturedIdx].display_color = Slic3r::MixedFilamentManager::blend_color( + co->values[mfs[capturedIdx].component_a - 1], + co->values[mfs[capturedIdx].component_b - 1], + mfs[capturedIdx].ratio_a, mfs[capturedIdx].ratio_b); + } + } + }; + spin_a->Bind(wxEVT_SPINCTRL, update_lambda); + spin_b->Bind(wxEVT_SPINCTRL, update_lambda); + chk->Bind(wxEVT_CHECKBOX, update_lambda); + + row->SetSizer(row_sizer); + p->m_sizer_mixed_filaments->Add(row, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(8)); + p->m_sizer_mixed_filaments->AddSpacer(FromDIP(2)); + + ++mixed_id; + } + + p->m_sizer_mixed_filaments->AddSpacer(FromDIP(8)); + p->m_panel_mixed_filaments->Show(); + p->m_panel_mixed_filaments->Layout(); + Layout(); } void Sidebar::add_filament() { @@ -2845,7 +2978,7 @@ void Sidebar::auto_calc_flushing_volumes(const int modify_id) int m_max_flush_volume = Slic3r::g_max_flush_volume; unsigned int m_number_of_extruders = (int)(sqrt(init_matrix.size()) + 0.001); - const std::vector extruder_colours = wxGetApp().plater()->get_extruder_colors_from_plater_config(); + const std::vector extruder_colours = wxGetApp().plater()->get_extruder_colors_from_plater_config(nullptr, false); std::vector> multi_colours; // Support for multi-color filament @@ -8377,6 +8510,10 @@ void Plater::priv::on_filament_color_changed(wxCommandEvent &event) if (wxGetApp().app_config->get("auto_calculate") == "true") { sidebar->auto_calc_flushing_volumes(modify_id); } + + // Regenerate mixed filaments and update the sidebar panel. + wxGetApp().preset_bundle->update_multi_material_filament_presets(); + sidebar->update_mixed_filament_panel(); } void Plater::priv::install_network_plugin(wxCommandEvent &event) @@ -14238,9 +14375,13 @@ void Plater::on_filaments_change(size_t num_filaments) part_plate->update_first_layer_print_sequence(num_filaments); } + size_t total_filaments = num_filaments; + if (wxGetApp().preset_bundle != nullptr) + total_filaments = wxGetApp().preset_bundle->mixed_filaments.total_filaments(num_filaments); + for (ModelObject* mo : wxGetApp().model().objects) { for (ModelVolume* mv : mo->volumes) { - mv->update_extruder_count(num_filaments); + mv->update_extruder_count(total_filaments); } } } @@ -14442,17 +14583,30 @@ void Plater::on_activate() } // Get vector of extruder colors considering filament color, if extruder color is undefined. -std::vector Plater::get_extruder_colors_from_plater_config(const GCodeProcessorResult* const result) const +std::vector Plater::get_extruder_colors_from_plater_config(const GCodeProcessorResult* const result, bool include_mixed) const { if (wxGetApp().is_gcode_viewer() && result != nullptr) return result->extruder_colors; else { + if (wxGetApp().preset_bundle == nullptr) + return {}; + const Slic3r::DynamicPrintConfig* config = &wxGetApp().preset_bundle->project_config; std::vector filament_colors; if (!config->has("filament_colour")) // in case of a SLA print return filament_colors; filament_colors = (config->option("filament_colour"))->values; + const size_t num_physical = static_cast(std::max(wxGetApp().filaments_cnt(), 0)); + filament_colors.resize(num_physical, "#26A69A"); + + if (include_mixed) { + // Append display colours for enabled mixed (virtual) filaments. + const auto &mixed_mgr = wxGetApp().preset_bundle->mixed_filaments; + for (const auto &dc : mixed_mgr.display_colors()) + filament_colors.push_back(dc); + } + return filament_colors; } } diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 9bd596c98a..25f188c99a 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -156,6 +156,7 @@ public: void edit_filament(); void on_filaments_delete(size_t filament_id); + void update_mixed_filament_panel(); // BBS void on_bed_type_change(BedType bed_type); void load_ams_list(std::string const & device, MachineObject* obj); @@ -499,7 +500,7 @@ public: void force_print_bed_update(); // On activating the parent window. void on_activate(); - std::vector get_extruder_colors_from_plater_config(const GCodeProcessorResult* const result = nullptr) const; + std::vector get_extruder_colors_from_plater_config(const GCodeProcessorResult* const result = nullptr, bool include_mixed = true) const; std::vector get_colors_for_color_print(const GCodeProcessorResult* const result = nullptr) const; void update_menus(); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 4fd9e4ce45..ee5890d3da 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1702,7 +1702,7 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value) // -1 means caculate all auto update_flush_volume = [](int idx = -1) { if (idx < 0) { - size_t filament_size = wxGetApp().plater()->get_extruder_colors_from_plater_config().size(); + size_t filament_size = wxGetApp().plater()->get_extruder_colors_from_plater_config(nullptr, false).size(); for (size_t i = 0; i < filament_size; ++i) wxGetApp().plater()->sidebar().auto_calc_flushing_volumes(i); } @@ -5347,8 +5347,7 @@ bool Tab::select_preset(std::string preset_name, bool delete_current /*=false*/, if (m_type == Preset::TYPE_PRINTER && wxGetApp().app_config->get_bool("remember_printer_config")) { if (preset_name.find("Snapmaker U1") != std::string::npos) { // 在 update_selections() 改变耗材数量之前先保存旧数量和颜色 - size_t old_filament_count = m_preset_bundle->filament_presets.size(); - std::vector old_filament_colors = wxGetApp().plater()->get_extruder_colors_from_plater_config(); + std::vector old_filament_colors = wxGetApp().plater()->get_extruder_colors_from_plater_config(nullptr, false); std::vector old_filament_presets = m_preset_bundle->filament_presets; m_preset_bundle->update_selections(*wxGetApp().app_config);