From 49ab2f9ac52a704470a76f7b891a0142fb5f034b Mon Sep 17 00:00:00 2001 From: Rad Date: Tue, 10 Feb 2026 03:10:31 +0100 Subject: [PATCH] Update README and implement dithering features: Change download link in README, add mixed filament management enhancements including filament removal and layer height adjustments for dithering in the MixedFilamentManager. Introduce new configuration options for dithering in the PrintConfig and GUI, and ensure proper handling of mixed filament states during filament deletion. --- README.md | 7 +- src/libslic3r/GCode/ToolOrdering.cpp | 96 +++++++++- src/libslic3r/GCode/ToolOrdering.hpp | 8 + src/libslic3r/MixedFilament.cpp | 21 +++ src/libslic3r/MixedFilament.hpp | 5 + src/libslic3r/Model.cpp | 23 +++ src/libslic3r/Model.hpp | 4 + src/libslic3r/PresetBundle.cpp | 58 ++++++ src/libslic3r/PresetBundle.hpp | 4 + src/libslic3r/Print.cpp | 4 +- src/libslic3r/Print.hpp | 5 +- src/libslic3r/PrintApply.cpp | 7 + src/libslic3r/PrintConfig.cpp | 38 ++++ src/libslic3r/PrintConfig.hpp | 4 + src/libslic3r/PrintObject.cpp | 261 ++++++++++++++++++++++++++- src/libslic3r/PrintObjectSlice.cpp | 2 +- src/libslic3r/TriangleSelector.cpp | 17 +- src/libslic3r/TriangleSelector.hpp | 3 +- src/slic3r/GUI/GUI.cpp | 12 +- src/slic3r/GUI/OptionsGroup.cpp | 65 ++++++- src/slic3r/GUI/Plater.cpp | 39 +++- src/slic3r/GUI/Tab.cpp | 7 + 22 files changed, 655 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 75d99f4b36..df55a89b8c 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ # Download ### Stable Release -📥 **[Download the Latest Stable Release](https://github.com/Snapmaker/OrcaSlicer/releases/latest)** +📥 **[Download the Latest Stable Release](https://github.com/ratdoux/OrcaSlicer-FullSpectrum/releases)** Visit our GitHub Releases page for the latest stable version of Full Spectrum, recommended for most users. # Features @@ -145,7 +145,10 @@ resolution: 0.1 Orca Slicer was originally forked from Bambu Studio, it was previously known as BambuStudio-SoftFever. Bambu Studio is forked from [PrusaSlicer](https://github.com/prusa3d/PrusaSlicer) by Prusa Research, which is from [Slic3r](https://github.com/Slic3r/Slic3r) by Alessandro Ranellucci and the RepRap community. Orca Slicer incorporates a lot of features from SuperSlicer by @supermerill -Orca Slicer's logo is designed by community member Justin Levine(@freejstnalxndr) +Orca Slicer's logo is designed by community member Justin Levine(@freejstnalxndr) + +## Acknowledgements +Special thanks to [u/Aceman11100](https://www.reddit.com/user/Aceman11100/) for the inspiration and idea behind the mixed-color filament feature! # License diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index 6db53f97ad..889c3258fe 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include @@ -24,6 +25,40 @@ namespace Slic3r { const static bool g_wipe_into_objects = false; +namespace { + +unsigned int resolve_mixed_with_layer_heights(const MixedFilamentManager *mixed_mgr, + size_t num_physical, + unsigned int filament_id_1based, + int layer_index, + float layer_height_a, + float layer_height_b, + float base_layer_height) +{ + if (!(mixed_mgr && mixed_mgr->is_mixed(filament_id_1based, num_physical))) + return filament_id_1based; + + if (layer_height_a > 0.f || layer_height_b > 0.f) { + const float safe_base = std::max(0.01f, base_layer_height); + const int ratio_a = std::max(1, int(std::lround((layer_height_a > 0.f ? layer_height_a : safe_base) / safe_base))); + const int ratio_b = std::max(1, int(std::lround((layer_height_b > 0.f ? layer_height_b : safe_base) / safe_base))); + const int cycle = ratio_a + ratio_b; + + if (cycle > 0) { + const size_t idx = static_cast(filament_id_1based - num_physical - 1); + const auto &mixed = mixed_mgr->mixed_filaments(); + if (idx < mixed.size()) { + const int pos = ((layer_index % cycle) + cycle) % cycle; + return pos < ratio_a ? mixed[idx].component_a : mixed[idx].component_b; + } + } + } + + return mixed_mgr->resolve(filament_id_1based, num_physical, layer_index); +} + +} // namespace + // Shortest hamilton path problem static std::vector solve_extruder_order(const std::vector>& wipe_volumes, std::vector all_extruders, std::optional start_extruder_id) @@ -149,9 +184,13 @@ bool LayerTools::is_extruder_order(unsigned int a, unsigned int b) const // 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 resolve_mixed_with_layer_heights(mixed_mgr, + num_physical, + filament_id, + this->layer_index, + mixed_layer_height_a, + mixed_layer_height_b, + mixed_base_layer_height); } // Return a zero based extruder from the region, or extruder_override if overriden. @@ -223,6 +262,7 @@ ToolOrdering::ToolOrdering(const PrintObject &object, unsigned int first_extrude // Mixed filament support. m_mixed_mgr = &object.print()->mixed_filament_manager(); m_num_physical = object.print()->config().filament_diameter.size(); + update_mixed_layer_height_settings(); if (object.layers().empty()) return; @@ -289,6 +329,7 @@ ToolOrdering::ToolOrdering(const Print &print, unsigned int first_extruder, bool // Mixed filament support. m_mixed_mgr = &print.mixed_filament_manager(); m_num_physical = print.config().filament_diameter.size(); + update_mixed_layer_height_settings(); // Initialize the print layers for all objects and all layers. coordf_t object_bottom_z = 0.; @@ -363,6 +404,33 @@ ToolOrdering::ToolOrdering(const Print &print, unsigned int first_extruder, bool this->mark_skirt_layers(print.config(), max_layer_height); } +void ToolOrdering::update_mixed_layer_height_settings() +{ + const PrintConfig *cfg = m_print_config_ptr; + if (cfg == nullptr && m_print_object_ptr != nullptr) + cfg = &m_print_object_ptr->print()->config(); + + m_mixed_layer_height_a = 0.f; + m_mixed_layer_height_b = 0.f; + if (m_print_full_config != nullptr && + m_print_full_config->has("mixed_color_layer_height_a") && + m_print_full_config->has("mixed_color_layer_height_b")) { + m_mixed_layer_height_a = float(m_print_full_config->opt_float("mixed_color_layer_height_a")); + m_mixed_layer_height_b = float(m_print_full_config->opt_float("mixed_color_layer_height_b")); + } else if (cfg != nullptr) { + m_mixed_layer_height_a = cfg->mixed_color_layer_height_a.value; + m_mixed_layer_height_b = cfg->mixed_color_layer_height_b.value; + } + + float base_height = 0.2f; + if (m_print_object_ptr != nullptr) + base_height = float(m_print_object_ptr->config().layer_height.value); + else if (m_print_full_config != nullptr && m_print_full_config->has("layer_height")) + base_height = float(m_print_full_config->opt_float("layer_height")); + + m_mixed_base_layer_height = std::max(0.01f, base_height); +} + static void apply_first_layer_order(const DynamicPrintConfig* config, std::vector& tool_order) { const ConfigOptionInts* first_layer_print_sequence_op = config->option("first_layer_print_sequence"); if (first_layer_print_sequence_op) { @@ -492,6 +560,14 @@ void ToolOrdering::initialize_layers(std::vector &zs) // Collect extruders reuqired to print layers. void ToolOrdering::collect_extruders(const PrintObject &object, const std::vector> &per_layer_extruder_switches) { + for (LayerTools &layer_tools : m_layer_tools) { + layer_tools.mixed_mgr = m_mixed_mgr; + layer_tools.num_physical = m_num_physical; + layer_tools.mixed_layer_height_a = m_mixed_layer_height_a; + layer_tools.mixed_layer_height_b = m_mixed_layer_height_b; + layer_tools.mixed_base_layer_height = m_mixed_base_layer_height; + } + // Collect the support extruders. for (auto support_layer : object.support_layers()) { LayerTools &layer_tools = this->tools_for_layer(support_layer->print_z); @@ -524,9 +600,7 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto 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; + layer_tools.layer_index = layerCount; // 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) @@ -1441,9 +1515,13 @@ int WipingExtrusions::get_support_interface_extruder_overrides(const PrintObject // 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; + return resolve_mixed_with_layer_heights(m_mixed_mgr, + m_num_physical, + filament_id_1based, + layer_index, + m_mixed_layer_height_a, + m_mixed_layer_height_b, + m_mixed_base_layer_height); } } // namespace Slic3r diff --git a/src/libslic3r/GCode/ToolOrdering.hpp b/src/libslic3r/GCode/ToolOrdering.hpp index 4be18af7f4..7b457f635b 100644 --- a/src/libslic3r/GCode/ToolOrdering.hpp +++ b/src/libslic3r/GCode/ToolOrdering.hpp @@ -144,6 +144,10 @@ public: // Mixed-filament resolution context (set by ToolOrdering during collect_extruders). const MixedFilamentManager *mixed_mgr = nullptr; size_t num_physical = 0; + // Optional mixed-layer cadence override from print settings. + float mixed_layer_height_a = 0.f; + float mixed_layer_height_b = 0.f; + float mixed_base_layer_height = 0.2f; private: // Resolve a 1-based filament ID through the mixed-filament manager for this layer. @@ -211,6 +215,7 @@ private: // BBS std::vector generate_first_layer_tool_order(const Print& print); std::vector generate_first_layer_tool_order(const PrintObject& object); + void update_mixed_layer_height_settings(); // Resolve a 1-based filament ID through the mixed-filament manager. // Returns the resolved physical extruder (1-based). If the ID is not a @@ -233,6 +238,9 @@ private: // number of physical extruders. const MixedFilamentManager* m_mixed_mgr = nullptr; size_t m_num_physical = 0; + float m_mixed_layer_height_a = 0.f; + float m_mixed_layer_height_b = 0.f; + float m_mixed_base_layer_height = 0.2f; }; } // namespace SLic3r diff --git a/src/libslic3r/MixedFilament.cpp b/src/libslic3r/MixedFilament.cpp index 33405ce1cb..52983b6f92 100644 --- a/src/libslic3r/MixedFilament.cpp +++ b/src/libslic3r/MixedFilament.cpp @@ -79,6 +79,27 @@ void MixedFilamentManager::auto_generate(const std::vector &filamen } } +void MixedFilamentManager::remove_physical_filament(unsigned int deleted_filament_id) +{ + if (deleted_filament_id == 0 || m_mixed.empty()) + return; + + std::vector filtered; + filtered.reserve(m_mixed.size()); + for (MixedFilament mf : m_mixed) { + if (mf.component_a == deleted_filament_id || mf.component_b == deleted_filament_id) + continue; + + if (mf.component_a > deleted_filament_id) + --mf.component_a; + if (mf.component_b > deleted_filament_id) + --mf.component_b; + + filtered.emplace_back(std::move(mf)); + } + m_mixed = std::move(filtered); +} + unsigned int MixedFilamentManager::resolve(unsigned int filament_id, size_t num_physical, int layer_index) const diff --git a/src/libslic3r/MixedFilament.hpp b/src/libslic3r/MixedFilament.hpp index 366b717500..676ff8d49f 100644 --- a/src/libslic3r/MixedFilament.hpp +++ b/src/libslic3r/MixedFilament.hpp @@ -61,6 +61,11 @@ public: // exists. void auto_generate(const std::vector &filament_colours); + // Remove a physical filament (1-based ID) from the mixed list. + // Any mixed filament that contains the removed component is deleted. + // Remaining component IDs are shifted down to stay aligned with physical IDs. + void remove_physical_filament(unsigned int deleted_filament_id); + // ---- Queries -------------------------------------------------------- // True when `filament_id` (1-based) refers to a mixed filament. diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index cf6004dae1..7d99da78d5 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -2507,6 +2507,20 @@ void ModelVolume::update_extruder_count_when_delete_filament(size_t extruder_cou } } +void ModelVolume::remap_extruder_ids(size_t extruder_count, const EnforcerBlockerStateMap &state_map) +{ + std::vector used_extruders = get_extruders(); + for (int extruder_id : used_extruders) { + if (extruder_id <= 0) + continue; + if (extruder_id >= int(state_map.size()) || extruder_id > int(extruder_count) || + state_map[size_t(extruder_id)] != EnforcerBlockerType(extruder_id)) { + mmu_segmentation_facets.remap_enforcer_block_types(*this, EnforcerBlockerType(extruder_count), state_map); + break; + } + } +} + void ModelVolume::center_geometry_after_creation(bool update_source_offset) { Vec3d shift = this->mesh().bounding_box().center(); @@ -3411,6 +3425,15 @@ void FacetsAnnotation::set_enforcer_block_type_limit(const ModelVolume& mv, this->set(selector); } +void FacetsAnnotation::remap_enforcer_block_types(const ModelVolume& mv, + EnforcerBlockerType max_type, + const EnforcerBlockerStateMap &state_map) +{ + TriangleSelector selector(mv.mesh()); + selector.deserialize(m_data, false, max_type, EnforcerBlockerType::NONE, EnforcerBlockerType::NONE, &state_map); + this->set(selector); +} + indexed_triangle_set FacetsAnnotation::get_facets_strict(const ModelVolume& mv, EnforcerBlockerType type) const { TriangleSelector selector(mv.mesh()); diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index bf3d129bdf..1555119379 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -738,6 +738,9 @@ public: EnforcerBlockerType max_type, EnforcerBlockerType to_delete_filament = EnforcerBlockerType::NONE, EnforcerBlockerType replace_filament = EnforcerBlockerType::NONE); + void remap_enforcer_block_types(const ModelVolume& mv, + EnforcerBlockerType max_type, + const EnforcerBlockerStateMap &state_map); 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.triangles_to_split.empty(); } @@ -919,6 +922,7 @@ public: std::vector get_extruders() const; void update_extruder_count(size_t extruder_count); void update_extruder_count_when_delete_filament(size_t extruder_count, size_t filament_id, int replace_filament_id = -1); + void remap_extruder_ids(size_t extruder_count, const EnforcerBlockerStateMap &state_map); // Split this volume, append the result to the object owning this volume. // Return the number of volumes created from this one. diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 6870bbd4b8..09301959b0 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -3231,6 +3231,10 @@ void PresetBundle::update_multi_material_filament_presets(size_t to_delete_filam #else size_t num_filaments = this->filament_presets.size(); #endif + const bool deleting_filament = (to_delete_filament_id != size_t(-1)); + const size_t old_num_filaments = deleting_filament ? (num_filaments + 1) : num_filaments; + const std::vector old_mixed = this->mixed_filaments.mixed_filaments(); + m_last_filament_id_remap.clear(); // Now verify if flush_volumes_matrix has proper size (it is used to deduce number of extruders in wipe tower generator): std::vector old_matrix = this->project_config.option("flush_volumes_matrix")->values; @@ -3259,6 +3263,12 @@ void PresetBundle::update_multi_material_filament_presets(size_t to_delete_filam this->project_config.option("flush_volumes_matrix")->values = new_matrix; } + // Keep mixed (virtual) combinations in sync with physical filament deletion. + // Mixed entries containing the deleted physical filament are removed, while + // remaining component IDs are shifted. + if (deleting_filament) + this->mixed_filaments.remove_physical_filament(unsigned(to_delete_filament_id + 1)); + // Keep project colours aligned to physical filaments, then regenerate mixed // (virtual) entries from the physical set only. { @@ -3268,6 +3278,54 @@ void PresetBundle::update_multi_material_filament_presets(size_t to_delete_filam this->mixed_filaments.auto_generate(color_opt->values); } } + + // Build old->new filament ID remap for painted facet data normalization. + if (deleting_filament) { + const unsigned int deleted_1based = unsigned(to_delete_filament_id + 1); + + size_t old_enabled_mixed = 0; + for (const auto &mf : old_mixed) + if (mf.enabled) + ++old_enabled_mixed; + + const size_t old_total_filaments = old_num_filaments + old_enabled_mixed; + m_last_filament_id_remap.assign(old_total_filaments + 1, 0); + + for (unsigned int old_id = 1; old_id <= unsigned(old_num_filaments); ++old_id) { + if (old_id == deleted_1based) + m_last_filament_id_remap[old_id] = 0; + else + m_last_filament_id_remap[old_id] = old_id > deleted_1based ? old_id - 1 : old_id; + } + + std::map, unsigned int> new_pair_to_id; + unsigned int next_virtual_id = unsigned(num_filaments + 1); + for (const auto &mf : this->mixed_filaments.mixed_filaments()) { + if (!mf.enabled) + continue; + new_pair_to_id[{mf.component_a, mf.component_b}] = next_virtual_id++; + } + + unsigned int old_virtual_id = unsigned(old_num_filaments + 1); + for (const auto &mf : old_mixed) { + if (!mf.enabled) + continue; + + unsigned int a = mf.component_a; + unsigned int b = mf.component_b; + if (a == deleted_1based || b == deleted_1based) { + m_last_filament_id_remap[old_virtual_id] = 0; + } else { + if (a > deleted_1based) + --a; + if (b > deleted_1based) + --b; + auto it = new_pair_to_id.find({a, b}); + m_last_filament_id_remap[old_virtual_id] = (it == new_pair_to_id.end()) ? 0 : it->second; + } + ++old_virtual_id; + } + } } 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 612aefcda8..ea524106f3 100644 --- a/src/libslic3r/PresetBundle.hpp +++ b/src/libslic3r/PresetBundle.hpp @@ -253,6 +253,9 @@ public: // Read out the number of extruders from an active printer preset, // update size and content of filament_presets. void update_multi_material_filament_presets(size_t to_delete_filament_id = size_t(-1)); + // Mapping generated during the latest filament deletion. + // Index is old 1-based filament ID, value is new 1-based filament ID (0 = removed). + const std::vector& last_filament_id_remap() const { return m_last_filament_id_remap; } // Update the is_compatible flag of all print and filament presets depending on whether they are marked // as compatible with the currently selected printer (and print in case of filament presets). @@ -324,6 +327,7 @@ private: bool validation_mode = false; std::string vendor_to_validate = ""; int m_errors = 0; + std::vector m_last_filament_id_remap; }; diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 2011a58636..01016d8ad5 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -245,6 +245,8 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n || opt_key == "filament_shrinkage_compensation_z" || opt_key == "resolution" || opt_key == "precise_z_height" + || opt_key == "dithering_z_step_size" + || opt_key == "dithering_step_painted_zones_only" // Spiral Vase forces different kind of slicing than the normal model: // In Spiral Vase mode, holes are closed and only the largest area contour is kept at each layer. // Therefore toggling the Spiral Vase on / off requires complete reslicing. @@ -1130,7 +1132,7 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* layer_height_profiles.assign(m_objects.size(), std::vector()); std::vector &profile = layer_height_profiles[print_object_idx]; if (profile.empty()) - PrintObject::update_layer_height_profile(*print_object.model_object(), print_object.slicing_parameters(), profile); + PrintObject::update_layer_height_profile(*print_object.model_object(), print_object.slicing_parameters(), profile, &print_object); return profile; }; diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 8d83a55f41..df789c2596 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -410,7 +410,10 @@ public: // Initialize the layer_height_profile from the model_object's layer_height_profile, from model_object's layer height table, or from slicing parameters. // Returns true, if the layer_height_profile was changed. - static bool update_layer_height_profile(const ModelObject &model_object, const SlicingParameters &slicing_parameters, std::vector &layer_height_profile); + static bool update_layer_height_profile(const ModelObject &model_object, + const SlicingParameters &slicing_parameters, + std::vector &layer_height_profile, + const PrintObject *print_object = nullptr); // Collect the slicing parameters, to be used by variable layer thickness algorithm, // by the interactive layer height editor and by the printing process itself. diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp index 7450d3ed80..fa619c8a09 100644 --- a/src/libslic3r/PrintApply.cpp +++ b/src/libslic3r/PrintApply.cpp @@ -1092,6 +1092,13 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ new_full_config.option("print_settings_id", true); new_full_config.option("filament_settings_id", true); new_full_config.option("printer_settings_id", true); + // Ensure newly introduced dithering keys are present so in-session updates are detected. + new_full_config.option("dithering_z_step_size", true); + new_full_config.option("dithering_step_painted_zones_only", true); + m_config.option("dithering_z_step_size", true); + m_config.option("dithering_step_painted_zones_only", true); + m_default_object_config.option("dithering_z_step_size", true); + m_default_object_config.option("dithering_step_painted_zones_only", true); // BBS int used_filaments = this->extruders(true).size(); diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 4bcb6be574..9dd9c750b0 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -4120,6 +4120,44 @@ void PrintConfigDef::init_fff_params() def->height = 6; def->mode = comAdvanced; def->set_default_value(new ConfigOptionStrings()); + + def = this->add("mixed_color_layer_height_a", coFloat); + def->label = L("Dithering cadence height A"); + def->category = L("Others"); + def->tooltip = L("Layer height contribution of component A for dithering virtual filaments. " + "Set to 0 to use normal 1-layer A / 1-layer B alternation."); + def->sidetext = "mm"; + def->min = 0.; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0.0)); + + def = this->add("mixed_color_layer_height_b", coFloat); + def->label = L("Dithering cadence height B"); + def->category = L("Others"); + def->tooltip = L("Layer height contribution of component B for dithering virtual filaments. " + "Set to 0 to use normal 1-layer A / 1-layer B alternation."); + def->sidetext = "mm"; + def->min = 0.; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0.0)); + + def = this->add("dithering_z_step_size", coFloat); + def->label = L("Dithering Z step size"); + def->category = L("Others"); + def->tooltip = L("Layer height used in Z zones painted with dithering (mixed virtual filaments). " + "Set to 0 to keep normal layer height in those zones."); + def->sidetext = "mm"; + def->min = 0.; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0.0)); + + def = this->add("dithering_step_painted_zones_only", coBool); + def->label = L("Use step size in painted zones only"); + def->category = L("Others"); + def->tooltip = L("When enabled, dithering Z step size is applied only where mixed filament is painted. " + "Unpainted zones keep their original layer height."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(true)); def = this->add("printer_model", coString); def->label = L("Printer type"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 33c21a98da..7fb49aa982 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -1351,6 +1351,10 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( ((ConfigOptionBool, ooze_prevention)) ((ConfigOptionString, filename_format)) ((ConfigOptionStrings, post_process)) + ((ConfigOptionFloat, mixed_color_layer_height_a)) + ((ConfigOptionFloat, mixed_color_layer_height_b)) + ((ConfigOptionFloat, dithering_z_step_size)) + ((ConfigOptionBool, dithering_step_painted_zones_only)) ((ConfigOptionString, printer_model)) ((ConfigOptionFloat, resolution)) ((ConfigOptionFloats, retraction_minimum_travel)) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 0bad0efd91..308f1b5545 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -946,6 +946,8 @@ bool PrintObject::invalidate_state_by_config_options( steps.emplace_back(posPerimeters); } else if ( opt_key == "layer_height" + || opt_key == "dithering_z_step_size" + || opt_key == "dithering_step_painted_zones_only" || opt_key == "mmu_segmented_region_max_width" || opt_key == "mmu_segmented_region_interlocking_depth" || opt_key == "raft_layers" @@ -3331,10 +3333,263 @@ std::vector PrintObject::object_extruders() const return extruders; } -bool PrintObject::update_layer_height_profile(const ModelObject &model_object, const SlicingParameters &slicing_parameters, std::vector &layer_height_profile) +namespace { + +struct LayerHeightRangeOverride { + coordf_t lo { 0.f }; + coordf_t hi { 0.f }; + coordf_t height { 0.f }; +}; + +static void sort_and_merge_layer_ranges(std::vector &ranges) +{ + if (ranges.empty()) + return; + + std::sort(ranges.begin(), ranges.end(), [](const t_layer_height_range &a, const t_layer_height_range &b) { + return a.first < b.first || (a.first == b.first && a.second < b.second); + }); + + std::vector merged; + merged.reserve(ranges.size()); + for (const t_layer_height_range &range : ranges) { + if (range.second <= range.first + EPSILON) + continue; + + if (merged.empty() || range.first > merged.back().second + EPSILON) { + merged.emplace_back(range); + } else { + merged.back().second = std::max(merged.back().second, range.second); + } + } + ranges = std::move(merged); +} + +static std::vector collect_mixed_painted_z_ranges(const PrintObject &print_object, coordf_t object_height) +{ + std::vector mixed_ranges; + if (object_height <= EPSILON) + return mixed_ranges; + + const Print *print = print_object.print(); + if (print == nullptr) + return mixed_ranges; + + const size_t num_physical = print->config().filament_colour.size(); + const size_t num_total = print->mixed_filament_manager().total_filaments(num_physical); + if (num_total <= num_physical) + return mixed_ranges; + + const size_t max_state = std::min(num_total, size_t(EnforcerBlockerType::ExtruderMax)); + const Transform3d object_to_print = print_object.trafo_centered(); + + for (const ModelVolume *mv : print_object.model_object()->volumes) { + if (mv == nullptr || !mv->is_model_part() || mv->mmu_segmentation_facets.empty()) + continue; + + const auto &used_states = mv->mmu_segmentation_facets.get_data().used_states; + if (used_states.empty()) + continue; + + const Transform3d volume_to_print = object_to_print * mv->get_matrix(); + constexpr coordf_t thin_band = 0.01f; + for (size_t state_idx = num_physical + 1; state_idx <= max_state; ++state_idx) { + if (state_idx >= used_states.size() || !used_states[state_idx]) + continue; + + const auto facets = mv->mmu_segmentation_facets.get_facets_strict(*mv, static_cast(state_idx)); + if (facets.indices.empty() || facets.vertices.empty()) + continue; + + for (const auto &face : facets.indices) { + double tri_z_min = DBL_MAX; + double tri_z_max = -DBL_MAX; + for (int i = 0; i < 3; ++i) { + const size_t vertex_idx = size_t(face[i]); + if (vertex_idx >= facets.vertices.size()) + continue; + const Vec3d p = volume_to_print * facets.vertices[vertex_idx].cast(); + tri_z_min = std::min(tri_z_min, p.z()); + tri_z_max = std::max(tri_z_max, p.z()); + } + + if (tri_z_min == DBL_MAX || tri_z_max == -DBL_MAX) + continue; + + coordf_t lo = std::max(0.f, coordf_t(tri_z_min)); + coordf_t hi = std::min(object_height, coordf_t(tri_z_max)); + if (hi <= lo + EPSILON) { + const coordf_t center = std::max(0.f, std::min(object_height, lo)); + lo = std::max(0.f, center - thin_band * 0.5f); + hi = std::min(object_height, center + thin_band * 0.5f); + } + if (lo + EPSILON < hi) + mixed_ranges.emplace_back(lo, hi); + } + } + } + + sort_and_merge_layer_ranges(mixed_ranges); + return mixed_ranges; +} + +static std::vector base_layer_height_overrides(const t_layer_config_ranges &ranges, coordf_t object_height) +{ + std::vector out; + out.reserve(ranges.size()); + + coordf_t last_hi = 0.f; + for (const auto &[range, config] : ranges) { + coordf_t lo = std::max(range.first, last_hi); + coordf_t hi = std::min(range.second, object_height); + if (lo + EPSILON >= hi) + continue; + + const ConfigOption *layer_height_opt = config.option("layer_height"); + if (layer_height_opt == nullptr) + continue; + + out.push_back({ lo, hi, coordf_t(layer_height_opt->getFloat()) }); + last_hi = hi; + } + return out; +} + +static bool contains_z(const std::vector &ranges, coordf_t z) +{ + for (const t_layer_height_range &range : ranges) { + if (z + EPSILON < range.first) + break; + if (z + EPSILON >= range.first && z < range.second - EPSILON) + return true; + } + return false; +} + +static bool get_override_height(const std::vector &ranges, coordf_t z, coordf_t &height_out) +{ + for (const LayerHeightRangeOverride &range : ranges) { + if (z + EPSILON < range.lo) + break; + if (z + EPSILON >= range.lo && z < range.hi - EPSILON) { + height_out = range.height; + return true; + } + } + return false; +} + +static t_layer_config_ranges layer_ranges_with_dithering(const t_layer_config_ranges &base_ranges_map, + coordf_t object_height, + coordf_t default_layer_height, + const std::vector &mixed_ranges, + coordf_t dithering_step) +{ + if (object_height <= EPSILON || mixed_ranges.empty() || dithering_step <= EPSILON) + return base_ranges_map; + + const std::vector base_ranges = base_layer_height_overrides(base_ranges_map, object_height); + + std::vector boundaries; + boundaries.reserve(2 + base_ranges.size() * 2 + mixed_ranges.size() * 2); + boundaries.emplace_back(0.f); + boundaries.emplace_back(object_height); + for (const LayerHeightRangeOverride &range : base_ranges) { + boundaries.emplace_back(range.lo); + boundaries.emplace_back(range.hi); + } + for (const t_layer_height_range &range : mixed_ranges) { + boundaries.emplace_back(range.first); + boundaries.emplace_back(range.second); + } + + std::sort(boundaries.begin(), boundaries.end()); + boundaries.erase(std::unique(boundaries.begin(), boundaries.end(), [](coordf_t a, coordf_t b) { + return std::abs(a - b) <= EPSILON; + }), + boundaries.end()); + + std::vector merged_ranges; + merged_ranges.reserve(boundaries.size()); + for (size_t i = 1; i < boundaries.size(); ++i) { + const coordf_t lo = boundaries[i - 1]; + const coordf_t hi = boundaries[i]; + if (hi <= lo + EPSILON) + continue; + + const coordf_t z_mid = 0.5f * (lo + hi); + + coordf_t target_height = default_layer_height; + coordf_t base_height = 0.f; + if (contains_z(mixed_ranges, z_mid)) { + target_height = dithering_step; + } else if (get_override_height(base_ranges, z_mid, base_height)) { + target_height = base_height; + } + + if (std::abs(target_height - default_layer_height) <= EPSILON) + continue; + + if (!merged_ranges.empty() && + std::abs(merged_ranges.back().height - target_height) <= EPSILON && + std::abs(merged_ranges.back().hi - lo) <= EPSILON) { + merged_ranges.back().hi = hi; + } else { + merged_ranges.push_back({ lo, hi, target_height }); + } + } + + t_layer_config_ranges out; + for (const LayerHeightRangeOverride &range : merged_ranges) { + if (range.hi <= range.lo + EPSILON) + continue; + ModelConfig cfg; + cfg.set_key_value("layer_height", new ConfigOptionFloat(range.height)); + out.emplace(t_layer_height_range(range.lo, range.hi), std::move(cfg)); + } + return out; +} + +} // namespace + +bool PrintObject::update_layer_height_profile(const ModelObject &model_object, + const SlicingParameters &slicing_parameters, + std::vector &layer_height_profile, + const PrintObject *print_object) { bool updated = false; + const t_layer_config_ranges *ranges_to_use = &model_object.layer_config_ranges; + t_layer_config_ranges dithering_ranges; + if (print_object != nullptr && print_object->print() != nullptr) { + const DynamicPrintConfig &full_cfg = print_object->print()->full_print_config(); + coordf_t dithering_step = coordf_t(print_object->print()->config().dithering_z_step_size.value); + bool painted_zones_only = print_object->print()->config().dithering_step_painted_zones_only.value; + if (full_cfg.has("dithering_z_step_size")) + dithering_step = coordf_t(full_cfg.opt_float("dithering_z_step_size")); + if (full_cfg.has("dithering_step_painted_zones_only")) + painted_zones_only = full_cfg.opt_bool("dithering_step_painted_zones_only"); + + if (dithering_step > EPSILON) { + const coordf_t object_height = slicing_parameters.object_print_z_uncompensated_height(); + std::vector mixed_ranges; + if (painted_zones_only) + mixed_ranges = collect_mixed_painted_z_ranges(*print_object, object_height); + else if (object_height > EPSILON) + mixed_ranges.emplace_back(0.f, object_height); + + if (!mixed_ranges.empty()) { + dithering_ranges = layer_ranges_with_dithering(model_object.layer_config_ranges, + object_height, + slicing_parameters.layer_height, + mixed_ranges, + dithering_step); + ranges_to_use = &dithering_ranges; + } + } + } + const bool has_dithering_ranges = (ranges_to_use != &model_object.layer_config_ranges); + if (layer_height_profile.empty()) { // use the constructor because the assignement is crashing on ASAN OsX layer_height_profile = std::vector(model_object.layer_height_profile.get()); @@ -3352,9 +3607,9 @@ bool PrintObject::update_layer_height_profile(const ModelObject &model_object, c std::abs(layer_height_profile[layer_height_profile.size() - 2] - slicing_parameters.object_print_z_uncompensated_max + slicing_parameters.object_print_z_min) > 1e-3)) layer_height_profile.clear(); - if (layer_height_profile.empty() || layer_height_profile[1] != slicing_parameters.first_object_layer_height) { + if (layer_height_profile.empty() || layer_height_profile[1] != slicing_parameters.first_object_layer_height || has_dithering_ranges) { //layer_height_profile = layer_height_profile_adaptive(slicing_parameters, model_object.layer_config_ranges, model_object.volumes); - layer_height_profile = layer_height_profile_from_ranges(slicing_parameters, model_object.layer_config_ranges); + layer_height_profile = layer_height_profile_from_ranges(slicing_parameters, *ranges_to_use); // The layer height profile is already compressed. updated = true; } diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp index 3735cd95d1..9ab8ad226a 100644 --- a/src/libslic3r/PrintObjectSlice.cpp +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -793,7 +793,7 @@ void PrintObject::slice() //BBS: add flag to reload scene for shell rendering m_print->set_status(5, L("Slicing mesh"), PrintBase::SlicingStatus::RELOAD_SCENE); std::vector layer_height_profile; - this->update_layer_height_profile(*this->model_object(), m_slicing_params, layer_height_profile); + this->update_layer_height_profile(*this->model_object(), m_slicing_params, layer_height_profile, this); m_print->throw_if_canceled(); m_typed_slices = false; this->clear_layers(); diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp index 30fca733b7..b12b0bbc51 100644 --- a/src/libslic3r/TriangleSelector.cpp +++ b/src/libslic3r/TriangleSelector.cpp @@ -1724,7 +1724,8 @@ void TriangleSelector::deserialize(const TriangleSplittingData& data, bool needs_reset, EnforcerBlockerType max_ebt, EnforcerBlockerType to_delete_filament, - EnforcerBlockerType replace_filament) + EnforcerBlockerType replace_filament, + const EnforcerBlockerStateMap* state_map) { if (needs_reset) reset(); // dump any current state @@ -1790,11 +1791,15 @@ void TriangleSelector::deserialize(const TriangleSplittingData& data, } } - - if (state == to_delete_filament) - state = replace_filament; - else if (to_delete_filament != EnforcerBlockerType::NONE && state != EnforcerBlockerType::NONE) { - state = state > to_delete_filament ? EnforcerBlockerType((int) state - 1) : state; + if (state_map != nullptr && state != EnforcerBlockerType::NONE) { + const size_t state_idx = static_cast(state); + state = state_idx < state_map->size() ? (*state_map)[state_idx] : EnforcerBlockerType::NONE; + } else { + if (state == to_delete_filament) + state = replace_filament; + else if (to_delete_filament != EnforcerBlockerType::NONE && state != EnforcerBlockerType::NONE) { + state = state > to_delete_filament ? EnforcerBlockerType((int) state - 1) : state; + } } if (state > max_ebt) { diff --git a/src/libslic3r/TriangleSelector.hpp b/src/libslic3r/TriangleSelector.hpp index b7a536e932..37f4b6c3b1 100644 --- a/src/libslic3r/TriangleSelector.hpp +++ b/src/libslic3r/TriangleSelector.hpp @@ -360,7 +360,8 @@ public: bool needs_reset = true, EnforcerBlockerType max_ebt = EnforcerBlockerType::ExtruderMax, EnforcerBlockerType to_delete_filament = EnforcerBlockerType::NONE, - EnforcerBlockerType replace_filament = EnforcerBlockerType::NONE); + EnforcerBlockerType replace_filament = EnforcerBlockerType::NONE, + const EnforcerBlockerStateMap *state_map = nullptr); // Extract all used facet states from the given TriangleSplittingData. static std::vector extract_used_facet_states(const TriangleSplittingData &data); diff --git a/src/slic3r/GUI/GUI.cpp b/src/slic3r/GUI/GUI.cpp index 5b6b619e86..31c33c6e11 100644 --- a/src/slic3r/GUI/GUI.cpp +++ b/src/slic3r/GUI/GUI.cpp @@ -108,13 +108,21 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt { try{ - if (config.def()->get(opt_key)->type == coBools && config.def()->get(opt_key)->nullable) { + const ConfigOptionDef *opt_def = config.def()->get(opt_key); + if (opt_def == nullptr) + throw Slic3r::RuntimeError("Unknown config option key: " + opt_key); + + // Some older presets may not carry newly introduced keys. Ensure the + // option exists before mutating it through typed accessors below. + if (!config.has(opt_key)) + config.set_key_value(opt_key, opt_def->create_default_option()); + + if (opt_def->type == coBools && opt_def->nullable) { ConfigOptionBoolsNullable* vec_new = new ConfigOptionBoolsNullable{ boost::any_cast(value) }; config.option(opt_key)->set_at(vec_new, opt_index, 0); return; } - const ConfigOptionDef *opt_def = config.def()->get(opt_key); switch (opt_def->type) { case coFloatOrPercent:{ std::string str = boost::any_cast(value); diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index ba6ff14f37..5330ccf40d 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -981,10 +981,29 @@ boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config case coPercents: case coFloats: case coFloat:{ - double val = opt->type == coFloats ? + double val = 0.0; + if (config.has(opt_key)) { + val = opt->type == coFloats ? config.opt_float(opt_key, idx) : opt->type == coFloat ? config.opt_float(opt_key) : config.option(opt_key)->get_at(idx); + } else if (opt->default_value) { + if (opt->type == coFloats) { + const auto *defaults = dynamic_cast(opt->default_value.get()); + if (defaults != nullptr && !defaults->values.empty()) { + const size_t safe_idx = std::min(idx, defaults->values.size() - 1); + val = defaults->values[safe_idx]; + } + } else if (opt->type == coFloat) { + val = opt->default_value->getFloat(); + } else { + const auto *defaults = dynamic_cast(opt->default_value.get()); + if (defaults != nullptr && !defaults->values.empty()) { + const size_t safe_idx = std::min(idx, defaults->values.size() - 1); + val = defaults->values[safe_idx]; + } + } + } ret = double_to_string(val); } break; @@ -1009,10 +1028,29 @@ boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config ret = from_u8(config.opt_string(opt_key, static_cast(idx))); break; case coBool: - ret = config.opt_bool(opt_key); + if (config.has(opt_key)) { + ret = config.opt_bool(opt_key); + } else if (opt->default_value) { + const auto *defaults = dynamic_cast(opt->default_value.get()); + ret = defaults != nullptr ? defaults->value : false; + } else { + ret = false; + } break; case coBools: - ret = config.opt_bool(opt_key, idx); + if (config.has(opt_key)) { + ret = config.opt_bool(opt_key, idx); + } else if (opt->default_value) { + const auto *defaults = dynamic_cast(opt->default_value.get()); + if (defaults != nullptr && !defaults->values.empty()) { + const size_t safe_idx = std::min(idx, defaults->values.size() - 1); + ret = defaults->values[safe_idx]; + } else { + ret = false; + } + } else { + ret = false; + } break; case coInt: ret = config.opt_int(opt_key); @@ -1139,10 +1177,27 @@ boost::any ConfigOptionsGroup::get_config_value2(const DynamicPrintConfig& confi ret = config.opt_string(opt_key, static_cast(idx)); break; case coBool: - ret = config.opt_bool(opt_key); + if (config.has(opt_key)) + ret = config.opt_bool(opt_key); + else if (opt->default_value) { + const auto *defaults = dynamic_cast(opt->default_value.get()); + ret = defaults != nullptr ? defaults->value : false; + } else + ret = false; break; case coBools: - ret = static_cast(config.opt_bool(opt_key, idx)); + if (config.has(opt_key)) + ret = static_cast(config.opt_bool(opt_key, idx)); + else if (opt->default_value) { + const auto *defaults = dynamic_cast(opt->default_value.get()); + if (defaults != nullptr && !defaults->values.empty()) { + const size_t safe_idx = std::min(idx, defaults->values.size() - 1); + ret = static_cast(defaults->values[safe_idx]); + } else { + ret = static_cast(false); + } + } else + ret = static_cast(false); break; case coInt: ret = config.opt_int(opt_key); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index aa83078e77..2121b88da1 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1436,7 +1436,7 @@ Sidebar::Sidebar(Plater *parent) 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")); + auto *mixed_title = new wxStaticText(p->m_panel_mixed_filaments, wxID_ANY, _L("Dithering")); 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)); @@ -2205,7 +2205,7 @@ void Sidebar::update_mixed_filament_panel() // 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")); + auto *mixed_title = new wxStaticText(p->m_panel_mixed_filaments, wxID_ANY, _L("Dithering")); 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)); @@ -2355,6 +2355,7 @@ void Sidebar::on_filaments_delete(size_t filament_id) p->m_panel_filament_title->Refresh(); update_ui_from_settings(); dynamic_filament_list.update(); + update_mixed_filament_panel(); } void Sidebar::edit_filament() { @@ -14317,10 +14318,40 @@ void Plater::on_filaments_delete(size_t num_filaments, size_t filament_id, int r }*/ // update mmu info + PresetBundle *preset_bundle = wxGetApp().preset_bundle; + + size_t total_filaments = num_filaments; + if (preset_bundle != nullptr) { + const size_t current_num_physical = preset_bundle->filament_presets.size(); + total_filaments = preset_bundle->mixed_filaments.total_filaments(current_num_physical); + } + + EnforcerBlockerStateMap state_map; + for (size_t i = 0; i < state_map.size(); ++i) + state_map[i] = EnforcerBlockerType(i); + + const bool can_use_remap = replace_filament_id < 0; + static const std::vector empty_remap; + const std::vector &id_remap = + preset_bundle != nullptr ? preset_bundle->last_filament_id_remap() : empty_remap; + if (can_use_remap && !id_remap.empty()) { + for (size_t i = 1; i < state_map.size(); ++i) { + const unsigned int mapped = i < id_remap.size() ? id_remap[i] : 0; + if (mapped == 0 || mapped >= state_map.size() || mapped > total_filaments) + state_map[i] = EnforcerBlockerType::NONE; + else + state_map[i] = EnforcerBlockerType(mapped); + } + } + for (ModelObject* mo : wxGetApp().model().objects) { for (ModelVolume* mv : mo->volumes) { - mv->update_extruder_count_when_delete_filament(num_filaments, filament_id + 1, - replace_filament_id + 1); // this function is 1 base + if (can_use_remap && !id_remap.empty()) { + mv->remap_extruder_ids(total_filaments, state_map); + } else { + mv->update_extruder_count_when_delete_filament(total_filaments, filament_id + 1, + replace_filament_id + 1); // this function is 1 base + } } } diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index ee5890d3da..9a8224beb9 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -2488,6 +2488,13 @@ optgroup->append_single_option_line("skirt_loops", "others_settings_skirt#loops" optgroup->append_single_option_line("timelapse_type", "others_settings_special_mode#timelapse"); + // Use default (no icon) here to avoid runtime bitmap load failures. + optgroup = page->new_optgroup(L("Dithering")); + optgroup->append_single_option_line("mixed_color_layer_height_a", "others_settings_mixed_colors#layer-height-a"); + optgroup->append_single_option_line("mixed_color_layer_height_b", "others_settings_mixed_colors#layer-height-b"); + optgroup->append_single_option_line("dithering_z_step_size", "others_settings_mixed_colors#z-step-size"); + optgroup->append_single_option_line("dithering_step_painted_zones_only", "others_settings_mixed_colors#painted-zones-only"); + optgroup = page->new_optgroup(L("Fuzzy Skin"), L"fuzzy_skin"); optgroup->append_single_option_line("fuzzy_skin", "others_settings_fuzzy_skin"); optgroup->append_single_option_line("fuzzy_skin_mode", "others_settings_fuzzy_skin#fuzzy-skin-mode");