From d4947f3cbebdd443c4febef20a017b96b2870742 Mon Sep 17 00:00:00 2001 From: Rad Date: Tue, 24 Mar 2026 15:35:11 +0100 Subject: [PATCH] Add explicit infill filament override setting with per-layer control Introduce enable_infill_filament_override toggle to replace the implicit behavior where sparse_infill_filament != wall_filament implied an override. Add infill_filament_use_base_first_layers and infill_filament_use_base_last_layers to keep the base filament on boundary layers before switching to the infill override filament. Move the sparse infill filament selector into the infill section of the UI and gate its visibility behind the new toggle. Also fixes: filament deletion now correctly updates wall/infill/solid filament assignments, LayerRegion::flow() resolves nozzle diameter from the effective extruder per role, and mixed filament preview ratio calculation is improved for edge cases (0% and 100%). --- .gitignore | 3 +- src/libslic3r/Fill/Fill.cpp | 2 +- src/libslic3r/GCode.cpp | 46 ++++++--- src/libslic3r/GCode/ToolOrdering.cpp | 66 +++++++++---- src/libslic3r/GCode/ToolOrdering.hpp | 2 + src/libslic3r/Layer.hpp | 1 + src/libslic3r/LayerRegion.cpp | 55 ++++++++++- src/libslic3r/Preset.cpp | 2 +- src/libslic3r/PrintConfig.cpp | 39 ++++++++ src/libslic3r/PrintConfig.hpp | 3 + src/libslic3r/PrintObject.cpp | 31 +++++- src/libslic3r/PrintRegion.cpp | 32 +++++- src/slic3r/GUI/ConfigManipulation.cpp | 13 ++- src/slic3r/GUI/GLCanvas3D.cpp | 19 +++- src/slic3r/GUI/GUI_Factories.cpp | 2 +- src/slic3r/GUI/GUI_ObjectList.cpp | 8 +- src/slic3r/GUI/PartPlate.cpp | 74 +++++++++----- src/slic3r/GUI/Plater.cpp | 137 +++++++++++++++++--------- src/slic3r/GUI/PresetHints.cpp | 2 +- src/slic3r/GUI/Tab.cpp | 42 ++++++-- 20 files changed, 440 insertions(+), 139 deletions(-) diff --git a/.gitignore b/.gitignore index c05638770b..66ffcb3ae1 100644 --- a/.gitignore +++ b/.gitignore @@ -41,4 +41,5 @@ resources/profiles/user/default deps_src/build/ .claude/ .omc -nul \ No newline at end of file +nul +.codex_tmp \ No newline at end of file diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index e8294ea673..88bced4642 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -1082,7 +1082,7 @@ std::vector group_fills(const Layer &layer, LockRegionParam &lock_p } if (internal_solid_fill == nullptr) { // Produce another solid fill. - params.extruder = layerm.region().extruder(frSolidInfill); + params.extruder = layerm.extruder(frSolidInfill); const auto top_pattern = layerm.region().config().top_surface_pattern; if(top_pattern == ipMonotonic || top_pattern == ipMonotonicLine) params.pattern = top_pattern; diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 565e464166..889fcc73cd 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -4767,15 +4767,32 @@ LayerResult GCode::process_layer(const Print& print, size_t pointillism_path_split_segments = 0; size_t pointillism_path_split_fallbacks = 0; - auto configured_filament_id_1based = [&layer_tools](const ExtrusionEntityCollection& entities, const PrintRegion& region) -> unsigned int { - if (layer_tools.extruder_override != 0) - return layer_tools.extruder_override; - if (entities.has_infill()) { - if (entities.has_solid_infill()) - return region.config().solid_infill_filament.value; - return region.config().sparse_infill_filament.value; + auto configured_filament_id_1based = [&layer_tools](const GCode::ObjectByExtruder::Island::Region::Type entity_type, + const ExtrusionEntityCollection& entities, + const PrintRegion& region) -> unsigned int { + if (entity_type == GCode::ObjectByExtruder::Island::Region::INFILL) { + const ExtrusionRole role = entities.entities.empty() ? erNone : entities.entities.front()->role(); + if (role == erSolidInfill && std::abs(region.config().sparse_infill_density.value - 100.) < EPSILON) + return layer_tools.sparse_infill_filament(region) + 1; + if (is_solid_infill(role)) + return layer_tools.solid_infill_filament(region) + 1; + return layer_tools.sparse_infill_filament(region) + 1; } - return region.config().wall_filament.value; + return layer_tools.wall_filament(region) + 1; + }; + + auto configured_extruder_id = [&layer_tools](const GCode::ObjectByExtruder::Island::Region::Type entity_type, + const ExtrusionEntityCollection& entities, + const PrintRegion& region) -> int { + if (entity_type == GCode::ObjectByExtruder::Island::Region::INFILL) { + const ExtrusionRole role = entities.entities.empty() ? erNone : entities.entities.front()->role(); + if (role == erSolidInfill && std::abs(region.config().sparse_infill_density.value - 100.) < EPSILON) + return int(layer_tools.sparse_infill_filament(region)); + if (is_solid_infill(role)) + return int(layer_tools.solid_infill_filament(region)); + return int(layer_tools.sparse_infill_filament(region)); + } + return int(layer_tools.wall_filament(region)); }; auto pointillism_sequence_for_filament = [&](unsigned int filament_id_1based) -> const std::vector* { @@ -4798,8 +4815,9 @@ LayerResult GCode::process_layer(const Print& print, return inserted.first->second.empty() ? nullptr : &inserted.first->second; }; auto grouped_manual_pattern_mixed_filament_id = - [&layer_tools, &configured_filament_id_1based](const ExtrusionEntityCollection& entities, - const PrintRegion& region) -> unsigned int { + [&layer_tools, &configured_filament_id_1based](const GCode::ObjectByExtruder::Island::Region::Type entity_type, + const ExtrusionEntityCollection& entities, + const PrintRegion& region) -> unsigned int { if (layer_tools.mixed_mgr == nullptr || layer_tools.num_physical == 0) return 0; @@ -4813,7 +4831,7 @@ LayerResult GCode::process_layer(const Print& print, return normalized_pattern.find(',') != std::string::npos; }; - const unsigned int configured_filament_id = configured_filament_id_1based(entities, region); + const unsigned int configured_filament_id = configured_filament_id_1based(entity_type, entities, region); if (has_grouped_pattern(configured_filament_id)) return configured_filament_id; return 0; @@ -5239,7 +5257,7 @@ LayerResult GCode::process_layer(const Print& print, local_z_clipped_collections.emplace_back(std::move(clipped_base)); } - const unsigned int configured_filament_id = configured_filament_id_1based(*filtered_extrusions, region); + const unsigned int configured_filament_id = configured_filament_id_1based(entity_type, *filtered_extrusions, region); const std::vector* pointillism_sequence = is_anything_overridden ? nullptr : pointillism_sequence_for_filament(configured_filament_id); if (pointillism_sequence != nullptr) { @@ -5283,14 +5301,14 @@ LayerResult GCode::process_layer(const Print& print, } // This extrusion is part of certain Region, which tells us which extruder should be used for it: - int correct_extruder_id = layer_tools.extruder(*filtered_extrusions, region); + int correct_extruder_id = configured_extruder_id(entity_type, *filtered_extrusions, region); if (!is_anything_overridden && entity_type == ObjectByExtruder::Island::Region::PERIMETERS && layer_tools.mixed_mgr != nullptr && layer_tools.num_physical > 0 && correct_extruder_id >= 0) { const unsigned int mixed_filament_id = - grouped_manual_pattern_mixed_filament_id(*filtered_extrusions, region); + grouped_manual_pattern_mixed_filament_id(entity_type, *filtered_extrusions, region); if (mixed_filament_id != 0) { std::vector> split_by_extruder; size_t bucket_count = 0; diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index cc6a78890c..b8bd078d7d 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -79,6 +79,36 @@ void append_unique_preserve_order(std::vector &dst, unsigned int v dst.emplace_back(value); } +bool internal_solid_infill_uses_sparse_filament(const PrintRegion ®ion, ExtrusionRole role) +{ + return role == erSolidInfill && std::abs(region.config().sparse_infill_density.value - 100.) < EPSILON; +} + +bool use_base_infill_filament(const LayerTools &layer_tools, const PrintRegion ®ion) +{ + const PrintRegionConfig &config = region.config(); + if (!config.enable_infill_filament_override.value) + return true; + if (layer_tools.object_layer_count <= 0) + return false; + + const int first_layers = std::max(0, config.infill_filament_use_base_first_layers.value); + const int last_layers = std::max(0, config.infill_filament_use_base_last_layers.value); + return layer_tools.layer_index < first_layers || layer_tools.layer_index >= layer_tools.object_layer_count - last_layers; +} + +unsigned int sparse_infill_filament_id_1based(const LayerTools &layer_tools, const PrintRegion ®ion) +{ + return use_base_infill_filament(layer_tools, region) ? region.config().wall_filament.value : region.config().sparse_infill_filament.value; +} + +unsigned int infill_filament_id_1based(const LayerTools &layer_tools, const PrintRegion ®ion, ExtrusionRole role) +{ + if (internal_solid_infill_uses_sparse_filament(region, role)) + return sparse_infill_filament_id_1based(layer_tools, region); + return is_solid_infill(role) ? region.config().solid_infill_filament.value : sparse_infill_filament_id_1based(layer_tools, region); +} + unsigned int grouped_manual_pattern_mixed_filament_id_for_layer(const LayerTools& layer_tools, unsigned int configured_filament_id_1based) { @@ -247,8 +277,8 @@ unsigned int LayerTools::wall_filament(const PrintRegion ®ion) const unsigned int LayerTools::sparse_infill_filament(const PrintRegion ®ion) const { - assert(region.config().sparse_infill_filament.value > 0); - unsigned int id = (this->extruder_override == 0) ? region.config().sparse_infill_filament.value : this->extruder_override; + assert(region.config().wall_filament.value > 0); + unsigned int id = (this->extruder_override == 0) ? sparse_infill_filament_id_1based(*this, region) : this->extruder_override; return resolve_mixed_1based(id) - 1; } @@ -269,10 +299,8 @@ unsigned int LayerTools::extruder(const ExtrusionEntityCollection &extrusions, c unsigned int extruder = 1; if (this->extruder_override == 0) { if (extrusions.has_infill()) { - if (extrusions.has_solid_infill()) - extruder = region.config().solid_infill_filament; - else - extruder = region.config().sparse_infill_filament; + const ExtrusionRole role = extrusions.entities.empty() ? erNone : extrusions.entities.front()->role(); + extruder = infill_filament_id_1based(*this, region, role); } else extruder = region.config().wall_filament.value; } else @@ -653,8 +681,9 @@ 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.layer_height = layer->height; + layer_tools.layer_index = layerCount; + layer_tools.object_layer_count = int(object.layers().size()); + layer_tools.layer_height = layer->height; // 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) @@ -712,17 +741,19 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto layer_tools.has_object = true; } - bool has_infill = false; - bool has_solid_infill = false; + bool has_sparse_infill = false; + bool has_solid_infill = false; bool something_nonoverriddable = false; for (const ExtrusionEntity *ee : layerm->fills.entities) { // fill represents infill extrusions of a single island. const auto *fill = dynamic_cast(ee); ExtrusionRole role = fill->entities.empty() ? erNone : fill->entities.front()->role(); - if (is_solid_infill(role)) + if (internal_solid_infill_uses_sparse_filament(region, role)) + has_sparse_infill = true; + else if (is_solid_infill(role)) has_solid_infill = true; else if (role != erNone) - has_infill = true; + has_sparse_infill = true; if (m_print_config_ptr) { if (! layer_tools.wiping_extrusions().is_overriddable_and_mark(*fill, *m_print_config_ptr, object, region)) @@ -737,18 +768,15 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto layerCount, float(layer->print_z), float(layer->height))); - if (has_infill) - layer_tools.extruders.emplace_back(resolve_mixed(region.config().sparse_infill_filament, - layerCount, - float(layer->print_z), - float(layer->height))); - } else if (has_solid_infill || has_infill) + if (has_sparse_infill) + layer_tools.extruders.emplace_back(layer_tools.sparse_infill_filament(region) + 1); + } else if (has_solid_infill || has_sparse_infill) layer_tools.extruders.emplace_back(resolve_mixed(extruder_override, layerCount, float(layer->print_z), float(layer->height))); } - if (has_solid_infill || has_infill) + if (has_solid_infill || has_sparse_infill) layer_tools.has_object = true; } layerCount++; diff --git a/src/libslic3r/GCode/ToolOrdering.hpp b/src/libslic3r/GCode/ToolOrdering.hpp index 2513511b2f..83eba3ecca 100644 --- a/src/libslic3r/GCode/ToolOrdering.hpp +++ b/src/libslic3r/GCode/ToolOrdering.hpp @@ -123,6 +123,8 @@ public: unsigned int extruder_override = 0; // Sequential layer index (0-based), used by mixed-filament resolution. int layer_index = 0; + // Total number of object layers for the current print object. + int object_layer_count = 0; // Actual layer height for this print_z where available. coordf_t layer_height = 0.; // Should a skirt be printed at this layer? diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index 1c325034cd..c4bca162cc 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -71,6 +71,7 @@ public: // (this collection contains only ExtrusionEntityCollection objects) ExtrusionEntityCollection fills; + unsigned int extruder(FlowRole role) const; Flow flow(FlowRole role) const; Flow flow(FlowRole role, double layer_height) const; Flow bridging_flow(FlowRole role, bool thick_bridge = false) const; diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index ec88905b96..937d5ccabd 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -1,6 +1,7 @@ #include "Layer.hpp" #include "BridgeDetector.hpp" #include "ClipperUtils.hpp" +#include "Exception.hpp" #include "Geometry.hpp" #include "PerimeterGenerator.hpp" #include "Print.hpp" @@ -9,6 +10,8 @@ #include "SVG.hpp" #include "Algorithm/RegionExpansion.hpp" +#include +#include #include #include @@ -17,6 +20,32 @@ namespace Slic3r { +namespace { + +bool use_base_infill_filament(const PrintRegionConfig &config, int layer_index, int layer_count) +{ + if (!config.enable_infill_filament_override.value) + return true; + if (layer_count <= 0) + return false; + + const int first_layers = std::max(0, config.infill_filament_use_base_first_layers.value); + const int last_layers = std::max(0, config.infill_filament_use_base_last_layers.value); + return layer_index < first_layers || layer_index >= layer_count - last_layers; +} + +} // namespace + +unsigned int LayerRegion::extruder(FlowRole role) const +{ + const PrintRegionConfig &config = this->region().config(); + if (role == frInfill) + return use_base_infill_filament(config, m_layer->id(), int(m_layer->object()->layers().size())) ? config.wall_filament.value : config.sparse_infill_filament.value; + if (role == frSolidInfill && std::abs(config.sparse_infill_density.value - 100.) < EPSILON) + return use_base_infill_filament(config, m_layer->id(), int(m_layer->object()->layers().size())) ? config.wall_filament.value : config.sparse_infill_filament.value; + return this->region().extruder(role); +} + Flow LayerRegion::flow(FlowRole role) const { return this->flow(role, m_layer->height); @@ -24,7 +53,29 @@ Flow LayerRegion::flow(FlowRole role) const Flow LayerRegion::flow(FlowRole role, double layer_height) const { - return m_region->flow(*m_layer->object(), role, layer_height, m_layer->id() == 0); + const PrintConfig &print_config = m_layer->object()->print()->config(); + ConfigOptionFloatOrPercent config_width; + if (m_layer->id() == 0 && print_config.initial_layer_line_width.value > 0) { + config_width = print_config.initial_layer_line_width; + } else if (role == frExternalPerimeter) { + config_width = m_region->config().outer_wall_line_width; + } else if (role == frPerimeter) { + config_width = m_region->config().inner_wall_line_width; + } else if (role == frInfill) { + config_width = m_region->config().sparse_infill_line_width; + } else if (role == frSolidInfill) { + config_width = m_region->config().internal_solid_infill_line_width; + } else if (role == frTopSolidInfill) { + config_width = m_region->config().top_surface_line_width; + } else { + throw Slic3r::InvalidArgument("Unknown role"); + } + + if (config_width.value == 0) + config_width = m_layer->object()->config().line_width; + + const auto nozzle_diameter = float(print_config.nozzle_diameter.get_at(this->extruder(role) - 1)); + return Flow::new_from_config_width(role, config_width, nozzle_diameter, float(layer_height)); } Flow LayerRegion::bridging_flow(FlowRole role, bool thick_bridge) const @@ -33,7 +84,7 @@ Flow LayerRegion::bridging_flow(FlowRole role, bool thick_bridge) const const PrintRegionConfig ®ion_config = region.config(); const PrintObject &print_object = *this->layer()->object(); Flow bridge_flow; - auto nozzle_diameter = float(print_object.print()->config().nozzle_diameter.get_at(region.extruder(role) - 1)); + auto nozzle_diameter = float(print_object.print()->config().nozzle_diameter.get_at(this->extruder(role) - 1)); if (thick_bridge) { // The old Slic3r way (different from all other slicers): Use rounded extrusions. // Get the configured nozzle_diameter for the extruder associated to the flow role requested. diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 68a273fafb..07dfc925f1 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -856,7 +856,7 @@ static std::vector s_Preset_print_options { "support_interface_pattern", "support_interface_spacing", "support_interface_loop_pattern", "support_top_z_distance", "support_on_build_plate_only","support_critical_regions_only", "bridge_no_support", "thick_bridges", "thick_internal_bridges","dont_filter_internal_bridges","enable_extra_bridge_layer", "max_bridge_length", "print_sequence", "print_order", "support_remove_small_overhang", "filename_format", "wall_filament", "support_bottom_z_distance", - "sparse_infill_filament", "solid_infill_filament", "support_filament", "support_interface_filament","support_interface_not_for_body", + "enable_infill_filament_override", "infill_filament_use_base_first_layers", "infill_filament_use_base_last_layers", "sparse_infill_filament", "solid_infill_filament", "support_filament", "support_interface_filament","support_interface_not_for_body", "ooze_prevention", "standby_temperature_delta", "preheat_time","delta_temperature","preheat_steps", "interface_shells", "line_width", "initial_layer_line_width", "inner_wall_line_width", "outer_wall_line_width", "sparse_infill_line_width", "internal_solid_infill_line_width", "skin_infill_line_width","skeleton_infill_line_width", diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 78e814053a..4f76874d4a 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -3321,6 +3321,31 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloatOrPercent(100., true)); + def = this->add("enable_infill_filament_override", coBool); + def->label = L("Override infill filament"); + def->category = L("Extruders"); + def->tooltip = L("Allow this print, object, or part to use a dedicated filament for sparse infill instead of inheriting its regular filament."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("infill_filament_use_base_first_layers", coInt); + def->label = L("Base infill on first layers"); + def->category = L("Extruders"); + def->tooltip = L("Keep using the regular object filament for this many bottom infill layers before switching to the infill override filament."); + def->sidetext = L("layers"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionInt(0)); + + def = this->add("infill_filament_use_base_last_layers", coInt); + def->label = L("Base infill on last layers"); + def->category = L("Extruders"); + def->tooltip = L("Keep using the regular object filament for this many top infill layers after switching back from the infill override filament."); + def->sidetext = L("layers"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionInt(0)); + def = this->add("sparse_infill_filament", coInt); def->gui_type = ConfigOptionDef::GUIType::i_enum_open; def->label = L("Infill"); @@ -7279,6 +7304,13 @@ void DynamicPrintConfig::normalize_fdm(int used_filaments) } } + if (!this->has("enable_infill_filament_override") && this->has("sparse_infill_filament")) { + const int wall_filament = this->has("wall_filament") ? this->option("wall_filament")->getInt() : 1; + const int sparse_infill_filament = this->option("sparse_infill_filament")->getInt(); + if (sparse_infill_filament > 0 && sparse_infill_filament != wall_filament) + this->opt("enable_infill_filament_override", true)->value = true; + } + if (this->has("wipe_tower_filament")) { // If invalid, replace with 0. int extruder = this->opt("wipe_tower_filament")->value; @@ -7360,6 +7392,13 @@ void DynamicPrintConfig::normalize_fdm_1() } } + if (!this->has("enable_infill_filament_override") && this->has("sparse_infill_filament")) { + const int wall_filament = this->has("wall_filament") ? this->option("wall_filament")->getInt() : 1; + const int sparse_infill_filament = this->option("sparse_infill_filament")->getInt(); + if (sparse_infill_filament > 0 && sparse_infill_filament != wall_filament) + this->opt("enable_infill_filament_override", true)->value = true; + } + if (!this->has("solid_infill_filament") && this->has("sparse_infill_filament")) this->option("solid_infill_filament", true)->setInt(this->option("sparse_infill_filament")->getInt()); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index c62066c99e..aac44bad62 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -1003,6 +1003,9 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionInt, fuzzy_skin_octaves)) ((ConfigOptionFloat, fuzzy_skin_persistence)) ((ConfigOptionFloat, gap_infill_speed)) + ((ConfigOptionBool, enable_infill_filament_override)) + ((ConfigOptionInt, infill_filament_use_base_first_layers)) + ((ConfigOptionInt, infill_filament_use_base_last_layers)) ((ConfigOptionInt, sparse_infill_filament)) ((ConfigOptionFloatOrPercent, sparse_infill_line_width)) ((ConfigOptionPercent, infill_wall_overlap)) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index c69193201b..0256e03226 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1086,6 +1086,9 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "bottom_shell_thickness" || opt_key == "top_shell_thickness" || opt_key == "minimum_sparse_infill_area" + || opt_key == "enable_infill_filament_override" + || opt_key == "infill_filament_use_base_first_layers" + || opt_key == "infill_filament_use_base_last_layers" || opt_key == "sparse_infill_filament" || opt_key == "solid_infill_filament" || opt_key == "sparse_infill_line_width" @@ -3198,6 +3201,7 @@ PrintObjectConfig PrintObject::object_config_from_model_object(const PrintObject } const std::string key_extruder { "extruder" }; +const std::string key_enable_infill_filament_override { "enable_infill_filament_override" }; static constexpr const std::initializer_list keys_extruders { "sparse_infill_filament"sv, "solid_infill_filament"sv, "wall_filament"sv }; static void apply_to_print_region_config(PrintRegionConfig &out, const DynamicPrintConfig &in) @@ -3211,10 +3215,23 @@ static void apply_to_print_region_config(PrintRegionConfig &out, const DynamicPr out.solid_infill_filament.value = extruder; out.wall_filament.value = extruder; } + const auto *opt_enable_infill_filament_override = in.opt(key_enable_infill_filament_override); + const auto *opt_sparse_infill_filament = in.opt("sparse_infill_filament"); + const bool allow_local_infill_filament_override = opt_enable_infill_filament_override != nullptr ? + opt_enable_infill_filament_override->value : + (opt_sparse_infill_filament != nullptr && + opt_sparse_infill_filament->value > 0 && + opt_sparse_infill_filament->value != out.wall_filament.value); + if (opt_enable_infill_filament_override != nullptr) + out.enable_infill_filament_override.value = opt_enable_infill_filament_override->value; + else if (opt_sparse_infill_filament != nullptr) + out.enable_infill_filament_override.value = allow_local_infill_filament_override; // 2) Copy the rest of the values. for (auto it = in.cbegin(); it != in.cend(); ++ it) - if (it->first != key_extruder) + if (it->first != key_extruder && it->first != key_enable_infill_filament_override) if (ConfigOption* my_opt = out.option(it->first, false); my_opt != nullptr) { + if (it->first == "sparse_infill_filament" && !allow_local_infill_filament_override) + continue; if (one_of(it->first, keys_extruders)) { // Ignore "default" extruders. int extruder = static_cast(it->second.get())->value; @@ -3296,6 +3313,8 @@ SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig &full print_config.apply(full_config, true); object_config.apply(full_config, true); default_region_config.apply(full_config, true); + default_region_config.enable_infill_filament_override.value = false; + default_region_config.sparse_infill_filament.value = default_region_config.wall_filament.value; // BBS size_t filament_extruders = print_config.filament_diameter.size(); object_config = object_config_from_model_object(object_config, model_object, filament_extruders); @@ -3310,6 +3329,9 @@ SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig &full object_extruders); for (const std::pair &range_and_config : model_object.layer_config_ranges) if (range_and_config.second.has("wall_filament") || + range_and_config.second.has("enable_infill_filament_override") || + range_and_config.second.has("infill_filament_use_base_first_layers") || + range_and_config.second.has("infill_filament_use_base_last_layers") || range_and_config.second.has("sparse_infill_filament") || range_and_config.second.has("solid_infill_filament")) PrintRegion::collect_object_printing_extruders( @@ -4278,9 +4300,10 @@ void PrintObject::combine_infill() // Limit the number of combined layers to the maximum height allowed by this regions' nozzle. //FIXME limit the layer height to max_layer_height - double nozzle_diameter = std::min( - this->print()->config().nozzle_diameter.get_at(region.config().sparse_infill_filament.value - 1), - this->print()->config().nozzle_diameter.get_at(region.config().solid_infill_filament.value - 1)); + double nozzle_diameter = this->print()->config().nozzle_diameter.get_at(region.config().wall_filament.value - 1); + if (region.config().enable_infill_filament_override.value) + nozzle_diameter = std::min(nozzle_diameter, this->print()->config().nozzle_diameter.get_at(region.config().sparse_infill_filament.value - 1)); + nozzle_diameter = std::min(nozzle_diameter, this->print()->config().nozzle_diameter.get_at(region.config().solid_infill_filament.value - 1)); //Orca: Limit combination of infill to up to infill_combination_max_layer_height const double infill_combination_max_layer_height = region.config().infill_combination_max_layer_height.get_abs_value(nozzle_diameter); diff --git a/src/libslic3r/PrintRegion.cpp b/src/libslic3r/PrintRegion.cpp index f3d5359f4c..c1e5e347dd 100644 --- a/src/libslic3r/PrintRegion.cpp +++ b/src/libslic3r/PrintRegion.cpp @@ -1,8 +1,24 @@ #include "Exception.hpp" #include "Print.hpp" +#include + namespace Slic3r { +namespace { + +unsigned int effective_sparse_infill_filament(const PrintRegionConfig &config) +{ + return config.enable_infill_filament_override.value ? config.sparse_infill_filament.value : config.wall_filament.value; +} + +bool internal_solid_infill_uses_sparse_filament(const PrintRegionConfig &config, FlowRole role) +{ + return role == frSolidInfill && std::abs(config.sparse_infill_density.value - 100.) < EPSILON; +} + +} // namespace + // 1-based extruder identifier for this region and role. unsigned int PrintRegion::extruder(FlowRole role) const { @@ -10,8 +26,10 @@ unsigned int PrintRegion::extruder(FlowRole role) const if (role == frPerimeter || role == frExternalPerimeter) extruder = m_config.wall_filament; else if (role == frInfill) - extruder = m_config.sparse_infill_filament; - else if (role == frSolidInfill || role == frTopSolidInfill) + extruder = effective_sparse_infill_filament(m_config); + else if (role == frSolidInfill) + extruder = internal_solid_infill_uses_sparse_filament(m_config, role) ? effective_sparse_infill_filament(m_config) : m_config.solid_infill_filament; + else if (role == frTopSolidInfill) extruder = m_config.solid_infill_filament; else throw Slic3r::InvalidArgument("Unknown role"); @@ -52,7 +70,7 @@ Flow PrintRegion::flow(const PrintObject &object, FlowRole role, double layer_he coordf_t PrintRegion::nozzle_dmr_avg(const PrintConfig &print_config) const { return (print_config.nozzle_diameter.get_at(m_config.wall_filament.value - 1) + - print_config.nozzle_diameter.get_at(m_config.sparse_infill_filament.value - 1) + + print_config.nozzle_diameter.get_at(effective_sparse_infill_filament(m_config) - 1) + print_config.nozzle_diameter.get_at(m_config.solid_infill_filament.value - 1)) / 3.; } @@ -70,10 +88,16 @@ void PrintRegion::collect_object_printing_extruders(const PrintConfig &print_con int i = std::max(0, extruder_id - 1); object_extruders.emplace_back((i >= num_extruders) ? 0 : i); }; + const bool use_base_infill_boundary_layers = + region_config.enable_infill_filament_override.value && + region_config.sparse_infill_density.value > 0 && + (region_config.infill_filament_use_base_first_layers.value > 0 || region_config.infill_filament_use_base_last_layers.value > 0); if (region_config.wall_loops.value > 0 || has_brim) emplace_extruder(region_config.wall_filament); + if (use_base_infill_boundary_layers) + emplace_extruder(region_config.wall_filament); if (region_config.sparse_infill_density.value > 0) - emplace_extruder(region_config.sparse_infill_filament); + emplace_extruder(int(effective_sparse_infill_filament(region_config))); if (region_config.top_shell_layers.value > 0 || region_config.bottom_shell_layers.value > 0) emplace_extruder(region_config.solid_infill_filament); } diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 103a15e53e..a9947da827 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -536,6 +536,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co PresetBundle *preset_bundle = wxGetApp().preset_bundle; auto gcflavor = preset_bundle->printers.get_edited_preset().config.option>("gcode_flavor")->value; + const bool bSEMM = preset_bundle->printers.get_edited_preset().config.opt_bool("single_extruder_multi_material"); bool have_volumetric_extrusion_rate_slope = config->option("max_volumetric_extrusion_rate_slope")->value > 0; float have_volumetric_extrusion_rate_slope_segment_length = config->option("max_volumetric_extrusion_rate_slope_segment_length")->value; @@ -556,10 +557,12 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co toggle_field(el, have_perimeters); bool have_infill = config->option("sparse_infill_density")->value > 0; + const bool show_infill_filament_override_toggle = have_infill && !bSEMM; // sparse_infill_filament uses the same logic as in Print::extruders() for (auto el : { "sparse_infill_pattern", "infill_combination", - "minimum_sparse_infill_area", "sparse_infill_filament", "infill_anchor_max","infill_shift_step","sparse_infill_rotate_template","symmetric_infill_y_axis"}) + "minimum_sparse_infill_area", "infill_anchor_max","infill_shift_step","sparse_infill_rotate_template","symmetric_infill_y_axis"}) toggle_line(el, have_infill); + toggle_line("enable_infill_filament_override", show_infill_filament_override_toggle); bool have_combined_infill = config->opt_bool("infill_combination") && have_infill; toggle_line("infill_combination_max_layer_height", have_combined_infill); @@ -748,8 +751,6 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co toggle_field("single_extruder_multi_material", !is_BBL_Printer); - auto bSEMM = preset_bundle->printers.get_edited_preset().config.opt_bool("single_extruder_multi_material"); - toggle_field("ooze_prevention", !bSEMM); bool have_ooze_prevention = config->opt_bool("ooze_prevention"); toggle_line("standby_temperature_delta", have_ooze_prevention); @@ -764,6 +765,12 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co for (auto el : {"wall_filament", "sparse_infill_filament", "solid_infill_filament", "wipe_tower_filament"}) toggle_line(el, !bSEMM); + const bool show_sparse_infill_filament = + show_infill_filament_override_toggle && config->opt_bool("enable_infill_filament_override"); + toggle_line("infill_filament_use_base_first_layers", show_sparse_infill_filament); + toggle_line("infill_filament_use_base_last_layers", show_sparse_infill_filament); + toggle_line("sparse_infill_filament", show_sparse_infill_filament); + bool purge_in_primetower = preset_bundle->printers.get_edited_preset().config.opt_bool("purge_in_prime_tower"); for (auto el : {"wipe_tower_rotation_angle", "wipe_tower_cone_angle", diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 9d6751da69..1e760b8f52 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -9390,8 +9390,9 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c if (is_selected_separate_extruder) { const PrintRegionConfig& cfg = layerm->region().config(); - if (cfg.wall_filament.value != m_selected_extruder || - cfg.sparse_infill_filament.value != m_selected_extruder || + const int effective_sparse_infill_filament = int(layerm->extruder(frInfill)); + if (cfg.wall_filament.value != m_selected_extruder && + effective_sparse_infill_filament != m_selected_extruder && cfg.solid_infill_filament.value != m_selected_extruder) continue; } @@ -9403,10 +9404,18 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c // fill represents infill extrusions of a single island. const auto *fill = dynamic_cast(ee); if (! fill->entities.empty()) + { + const int effective_sparse_infill_filament = int(layerm->extruder(frInfill)); _3DScene::extrusionentity_to_verts(*fill, float(layer->print_z), copy, - select_geometry(idx_layer, is_solid_infill(fill->entities.front()->role()) ? - layerm->region().config().solid_infill_filament : - layerm->region().config().sparse_infill_filament, 1)); + select_geometry(idx_layer, + (fill->entities.front()->role() == erSolidInfill && + std::abs(layerm->region().config().sparse_infill_density.value - 100.) < EPSILON) ? + int(layerm->extruder(frSolidInfill)) : + (is_solid_infill(fill->entities.front()->role()) ? + layerm->region().config().solid_infill_filament : + effective_sparse_infill_filament), + 1)); + } } } } diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index 92c26016fb..6bbaa672fd 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -145,7 +145,7 @@ std::map> SettingsFactory::PART_CAT }}, { L("Strength"), {{"wall_loops", "",1},{"top_shell_layers", L("Top Solid Layers"),1},{"top_shell_thickness", L("Top Minimum Shell Thickness"),1},{"top_surface_density", L("Top Surface Density"),1}, {"bottom_shell_layers", L("Bottom Solid Layers"),1}, {"bottom_shell_thickness", L("Bottom Minimum Shell Thickness"),1},{"bottom_surface_density", L("Bottom Surface Density"),1}, - {"sparse_infill_density", "",1},{"sparse_infill_pattern", "",1},{"lateral_lattice_angle_1", "",1},{"lateral_lattice_angle_2", "",1},{"infill_overhang_angle", "",1},{"infill_anchor", "",1},{"infill_anchor_max", "",1},{"top_surface_pattern", "",1},{"bottom_surface_pattern", "",1}, {"internal_solid_infill_pattern", "",1}, + {"sparse_infill_density", "",1},{"sparse_infill_pattern", "",1},{"enable_infill_filament_override", "",1},{"infill_filament_use_base_first_layers", "",1},{"infill_filament_use_base_last_layers", "",1},{"sparse_infill_filament", "",1},{"lateral_lattice_angle_1", "",1},{"lateral_lattice_angle_2", "",1},{"infill_overhang_angle", "",1},{"infill_anchor", "",1},{"infill_anchor_max", "",1},{"top_surface_pattern", "",1},{"bottom_surface_pattern", "",1}, {"internal_solid_infill_pattern", "",1}, {"align_infill_direction_to_model", "", 1}, {"extra_solid_infills", "", 1}, {"infill_combination", "",1}, {"infill_combination_max_layer_height", "",1}, {"infill_wall_overlap", "",1},{"top_bottom_infill_wall_overlap", "",1}, {"solid_infill_direction", "",1}, {"infill_direction", "",1}, {"bridge_angle", "",1}, {"internal_bridge_angle", "",1}, {"minimum_sparse_infill_area", "",1} diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 3ef372ca36..4c5c16e21e 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -701,7 +701,8 @@ void ObjectList::update_filament_values_for_items(const size_t filaments_count) } m_objects_model->SetExtruder(extruder, item); - static const char *keys[] = {"support_filament", "support_interface_filament"}; + static const char *keys[] = {"wall_filament", "sparse_infill_filament", "solid_infill_filament", + "support_filament", "support_interface_filament"}; for (auto key : keys) if (object->config.has(key) && object->config.opt_int(key) > filaments_count) object->config.erase(key); @@ -876,7 +877,8 @@ void ObjectList::update_filament_values_for_items_when_delete_filament(const siz } m_objects_model->SetExtruder(extruder, item); - static const char* keys[] = {"support_filament", "support_interface_filament"}; + static const char* keys[] = {"wall_filament", "sparse_infill_filament", "solid_infill_filament", + "support_filament", "support_interface_filament"}; for (auto key : keys) { if (object->config.has(key)) { if (object->config.opt_int(key) == filament_id + 1) @@ -903,7 +905,7 @@ void ObjectList::update_filament_values_for_items_when_delete_filament(const siz int new_value = object->volumes[id]->config.opt_int(key) > filament_id ? object->volumes[id]->config.opt_int(key) - 1 : object->volumes[id]->config.opt_int(key); - object->config.set_key_value(key, new ConfigOptionInt(new_value)); + object->volumes[id]->config.set_key_value(key, new ConfigOptionInt(new_value)); } } } diff --git a/src/slic3r/GUI/PartPlate.cpp b/src/slic3r/GUI/PartPlate.cpp index d4e3d5aad4..50d40412b2 100644 --- a/src/slic3r/GUI/PartPlate.cpp +++ b/src/slic3r/GUI/PartPlate.cpp @@ -75,6 +75,44 @@ namespace GUI { class Bed3D; +namespace { + +struct ResolvedInfillFilament +{ + int wall_filament = 1; + int sparse_infill_filament = 1; + bool override_enabled = false; +}; + +template +ResolvedInfillFilament resolve_infill_filament(const ConfigLike &config, int inherited_wall_filament, bool inherited_override_enabled, int inherited_sparse_infill_filament) +{ + ResolvedInfillFilament resolved; + resolved.wall_filament = inherited_wall_filament; + resolved.sparse_infill_filament = inherited_sparse_infill_filament; + resolved.override_enabled = inherited_override_enabled; + + if (const ConfigOption *wall_opt = config.option("wall_filament"); wall_opt != nullptr) + resolved.wall_filament = wall_opt->getInt(); + + const ConfigOption *sparse_opt = config.option("sparse_infill_filament"); + const ConfigOption *override_opt = config.option("enable_infill_filament_override"); + if (override_opt != nullptr) + resolved.override_enabled = override_opt->getBool(); + else if (sparse_opt != nullptr) + resolved.override_enabled = sparse_opt->getInt() != resolved.wall_filament; + + if (sparse_opt != nullptr) + resolved.sparse_infill_filament = sparse_opt->getInt(); + + if (!resolved.override_enabled) + resolved.sparse_infill_filament = resolved.wall_filament; + + return resolved; +} + +} // namespace + ColorRGBA PartPlate::SELECT_COLOR = { 0.2666f, 0.2784f, 0.2784f, 1.0f }; //{ 0.4196f, 0.4235f, 0.4235f, 1.0f }; ColorRGBA PartPlate::UNSELECT_COLOR = { 0.82f, 0.82f, 0.82f, 1.0f }; ColorRGBA PartPlate::UNSELECT_DARK_COLOR = { 0.384f, 0.384f, 0.412f, 1.0f }; @@ -1350,7 +1388,8 @@ std::vector PartPlate::get_extruders(bool conside_custom_gcode) const int glb_support_intf_extr = glb_config.opt_int("support_interface_filament"); int glb_support_extr = glb_config.opt_int("support_filament"); int glb_wall_extr = glb_config.opt_int("wall_filament"); - int glb_sparse_infill_extr = glb_config.opt_int("sparse_infill_filament"); + const ResolvedInfillFilament global_infill { glb_wall_extr, glb_wall_extr, false }; + int glb_sparse_infill_extr = glb_wall_extr; int glb_solid_infill_extr = glb_config.opt_int("solid_infill_filament"); bool glb_support = glb_config.opt_bool("enable_support"); glb_support |= glb_config.opt_int("raft_layers") > 0; @@ -1405,23 +1444,16 @@ std::vector PartPlate::get_extruders(bool conside_custom_gcode) const plate_extruders.push_back(glb_support_extr); } - int obj_wall_extr = 1; + int obj_wall_extr = glb_wall_extr; const ConfigOption* wall_opt = mo->config.option("wall_filament"); if (wall_opt != nullptr) obj_wall_extr = wall_opt->getInt(); if (obj_wall_extr != 1) plate_extruders.push_back(obj_wall_extr); - else if (glb_wall_extr != 1) - plate_extruders.push_back(glb_wall_extr); - int obj_sparse_infill_extr = 1; - const ConfigOption* sparse_infill_opt = mo->config.option("sparse_infill_filament"); - if (sparse_infill_opt != nullptr) - obj_sparse_infill_extr = sparse_infill_opt->getInt(); - if (obj_sparse_infill_extr != 1) - plate_extruders.push_back(obj_sparse_infill_extr); - else if (glb_sparse_infill_extr != 1) - plate_extruders.push_back(glb_sparse_infill_extr); + const ResolvedInfillFilament object_infill = resolve_infill_filament(mo->config, glb_wall_extr, global_infill.override_enabled, glb_sparse_infill_extr); + if (object_infill.override_enabled && object_infill.sparse_infill_filament != 1) + plate_extruders.push_back(object_infill.sparse_infill_filament); int obj_solid_infill_extr = 1; const ConfigOption* solid_infill_opt = mo->config.option("solid_infill_filament"); @@ -1463,7 +1495,8 @@ std::vector PartPlate::get_extruders_under_cli(bool conside_custom_gcode, D int glb_support_intf_extr = full_config.opt_int("support_interface_filament"); int glb_support_extr = full_config.opt_int("support_filament"); int glb_wall_extr = full_config.opt_int("wall_filament"); - int glb_sparse_infill_extr = full_config.opt_int("sparse_infill_filament"); + const ResolvedInfillFilament global_infill { glb_wall_extr, glb_wall_extr, false }; + int glb_sparse_infill_extr = glb_wall_extr; int glb_solid_infill_extr = full_config.opt_int("solid_infill_filament"); bool glb_support = full_config.opt_bool("enable_support"); @@ -1528,23 +1561,16 @@ std::vector PartPlate::get_extruders_under_cli(bool conside_custom_gcode, D else if (glb_support_extr != 0) plate_extruders.push_back(glb_support_extr); - int obj_wall_extr = 1; + int obj_wall_extr = glb_wall_extr; const ConfigOption* wall_opt = object->config.option("wall_filament"); if (wall_opt != nullptr) obj_wall_extr = wall_opt->getInt(); if (obj_wall_extr != 1) plate_extruders.push_back(obj_wall_extr); - else if (glb_wall_extr != 1) - plate_extruders.push_back(glb_wall_extr); - int obj_sparse_infill_extr = 1; - const ConfigOption* sparse_infill_opt = object->config.option("sparse_infill_filament"); - if (sparse_infill_opt != nullptr) - obj_sparse_infill_extr = sparse_infill_opt->getInt(); - if (obj_sparse_infill_extr != 1) - plate_extruders.push_back(obj_sparse_infill_extr); - else if (glb_sparse_infill_extr != 1) - plate_extruders.push_back(glb_sparse_infill_extr); + const ResolvedInfillFilament object_infill = resolve_infill_filament(object->config, glb_wall_extr, global_infill.override_enabled, glb_sparse_infill_extr); + if (object_infill.override_enabled && object_infill.sparse_infill_filament != 1) + plate_extruders.push_back(object_infill.sparse_infill_filament); int obj_solid_infill_extr = 1; const ConfigOption* solid_infill_opt = object->config.option("solid_infill_filament"); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 4ec5879f42..53c57a2b96 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -4638,7 +4638,7 @@ private: static std::vector decode_gradient_weights(const std::string &s, size_t n); static std::vector normalize_gradient_weights(const std::vector &w, size_t n); static std::string encode_gradient_weights(const std::vector &w); - static std::vector build_weighted_pair_sequence(unsigned int a, unsigned int b, int percent_b); + static std::vector build_weighted_pair_sequence(unsigned int a, unsigned int b, int percent_b, bool limit_cycle = false); static std::vector build_weighted_multi_sequence(const std::vector &ids, const std::vector &weights, size_t max_cycle_limit = 0); @@ -4891,31 +4891,90 @@ std::string MixedFilamentConfigPanel::encode_gradient_weights(const std::vector< return out.str(); } -std::vector MixedFilamentConfigPanel::build_weighted_pair_sequence(unsigned int a, unsigned int b, int percent_b) +namespace { + +std::pair effective_pair_preview_ratios(int percent_b) { - std::vector seq; - const int b_percent = std::clamp(percent_b, 0, 100); - int ratio_a = std::max(1, 100 - b_percent); - int ratio_b = std::max(1, b_percent); - const int g = std::gcd(ratio_a, ratio_b); - if (g > 1) { - ratio_a /= g; - ratio_b /= g; + const int mix_b = std::clamp(percent_b, 0, 100); + int ratio_a = 1; + int ratio_b = 0; + + if (mix_b >= 100) { + ratio_a = 0; + ratio_b = 1; + } else if (mix_b > 0) { + const int pct_b = mix_b; + const int pct_a = 100 - pct_b; + const bool b_is_major = pct_b >= pct_a; + const int major_pct = b_is_major ? pct_b : pct_a; + const int minor_pct = b_is_major ? pct_a : pct_b; + const int major_layers = + std::max(1, int(std::lround(double(major_pct) / double(std::max(1, minor_pct))))); + ratio_a = b_is_major ? 1 : major_layers; + ratio_b = b_is_major ? major_layers : 1; } + + if (ratio_a > 0 && ratio_b > 0) { + const int g = std::gcd(ratio_a, ratio_b); + if (g > 1) { + ratio_a /= g; + ratio_b /= g; + } + } + + return { std::max(0, ratio_a), std::max(0, ratio_b) }; +} + +std::vector build_effective_pair_preview_sequence(unsigned int component_a, + unsigned int component_b, + int percent_b, + bool limit_cycle) +{ + std::vector sequence; + if (component_a == 0 || component_b == 0 || component_a == component_b) + return sequence; + + auto [ratio_a, ratio_b] = effective_pair_preview_ratios(percent_b); constexpr int k_max_cycle = 24; - if (ratio_a + ratio_b > k_max_cycle) { + if (limit_cycle && ratio_a > 0 && ratio_b > 0 && ratio_a + ratio_b > k_max_cycle) { const double scale = double(k_max_cycle) / double(ratio_a + ratio_b); ratio_a = std::max(1, int(std::round(double(ratio_a) * scale))); ratio_b = std::max(1, int(std::round(double(ratio_b) * scale))); } + if (ratio_a == 0 && ratio_b == 0) + ratio_a = 1; + const int cycle = std::max(1, ratio_a + ratio_b); - seq.reserve(size_t(cycle)); + sequence.reserve(size_t(cycle)); for (int pos = 0; pos < cycle; ++pos) { const int b_before = (pos * ratio_b) / cycle; const int b_after = ((pos + 1) * ratio_b) / cycle; - seq.emplace_back((b_after > b_before) ? b : a); + sequence.emplace_back((b_after > b_before) ? component_b : component_a); } - return seq; + return sequence; +} + +std::string format_preview_sequence_percent(int count, int total) +{ + if (count <= 0 || total <= 0) + return ""; + + const double percent = 100.0 * double(count) / double(total); + const double rounded_tenths = std::round(percent * 10.0) / 10.0; + const double nearest_integer = std::round(rounded_tenths); + if (std::abs(rounded_tenths - nearest_integer) < 1e-6) + return wxString::Format("%d%%", int(nearest_integer)).ToStdString(); + return wxString::Format("%.1f%%", rounded_tenths).ToStdString(); +} + +} // namespace + +std::vector MixedFilamentConfigPanel::build_weighted_pair_sequence(unsigned int a, + unsigned int b, + int percent_b, + bool limit_cycle) +{ + return build_effective_pair_preview_sequence(a, b, percent_b, limit_cycle); } static void reduce_weight_counts_to_cycle_limit(std::vector &counts, size_t cycle_limit) @@ -5412,7 +5471,7 @@ std::string MixedFilamentConfigPanel::summarize_sequence(const std::vector selected_gradient_ids = decode_gradient_ids(m_mf.gradient_component_ids); if (preview_sequence.empty()) - preview_sequence = build_weighted_pair_sequence(m_mf.component_a, m_mf.component_b, preview_mix_b_percent); + preview_sequence = build_weighted_pair_sequence(m_mf.component_a, m_mf.component_b, preview_mix_b_percent, same_layer_mode); if (m_blend_selector && selected_gradient_ids.size() >= 3) { std::vector corner_colors; @@ -6203,7 +6262,8 @@ void MixedFilamentConfigPanel::update_preview() else initial_sequence = build_weighted_pair_sequence(m_mf.component_a, m_mf.component_b, - effective_local_z_preview_mix_b_percent(m_mf, m_preview_settings)); + effective_local_z_preview_mix_b_percent(m_mf, m_preview_settings), + same_layer_mode); if (m_blend_selector && initial_gradient_ids.size() >= 3) { std::vector corner_colors; @@ -6666,31 +6726,6 @@ void Sidebar::update_mixed_filament_panel(bool sync_manager) } return sequence; }; - auto build_weighted_pair_sequence = [](unsigned int component_a, unsigned int component_b, int mix_b_percent) { - std::vector sequence; - const int b_percent = std::clamp(mix_b_percent, 0, 100); - int ratio_a = std::max(1, 100 - b_percent); - int ratio_b = std::max(1, b_percent); - const int g = std::gcd(ratio_a, ratio_b); - if (g > 1) { - ratio_a /= g; - ratio_b /= g; - } - constexpr int k_max_cycle = 24; - if (ratio_a + ratio_b > k_max_cycle) { - const double scale = double(k_max_cycle) / double(ratio_a + ratio_b); - ratio_a = std::max(1, int(std::round(double(ratio_a) * scale))); - ratio_b = std::max(1, int(std::round(double(ratio_b) * scale))); - } - const int cycle = std::max(1, ratio_a + ratio_b); - sequence.reserve(size_t(cycle)); - for (int pos = 0; pos < cycle; ++pos) { - const int b_before = (pos * ratio_b) / cycle; - const int b_after = ((pos + 1) * ratio_b) / cycle; - sequence.emplace_back((b_after > b_before) ? component_b : component_a); - } - return sequence; - }; const bool height_weighted_mode = get_mixed_mode(false); int gradient_mode = height_weighted_mode ? 1 : 0; float lower_bound = std::max(0.01f, get_mixed_float("mixed_filament_height_lower_bound", 0.04f)); @@ -6776,8 +6811,7 @@ void Sidebar::update_mixed_filament_panel(bool sync_manager) return blended; }; auto build_entry_preview_sequence = [decode_manual_pattern_ids, decode_gradient_ids, decode_gradient_weights, - build_weighted_multi_sequence, - build_weighted_pair_sequence, preview_settings](const MixedFilament &entry) { + build_weighted_multi_sequence, preview_settings](const MixedFilament &entry) { const std::string normalized_pattern = MixedFilamentManager::normalize_manual_pattern(entry.manual_pattern); if (!normalized_pattern.empty()) return decode_manual_pattern_ids(normalized_pattern, entry.component_a, entry.component_b); @@ -6793,7 +6827,8 @@ void Sidebar::update_mixed_filament_panel(bool sync_manager) } const int effective_mix_b = MixedFilamentConfigPanel::effective_local_z_preview_mix_b_percent(entry, preview_settings); - return build_weighted_pair_sequence(entry.component_a, entry.component_b, effective_mix_b); + const bool same_layer_mode = entry.distribution_mode == int(MixedFilament::SameLayerPointillisme); + return build_effective_pair_preview_sequence(entry.component_a, entry.component_b, effective_mix_b, same_layer_mode); }; auto compute_entry_display_color = [num_physical, &physical_colors, blend_from_sequence, build_entry_preview_sequence](const MixedFilament &entry) { const std::vector sequence = build_entry_preview_sequence(entry); @@ -8908,7 +8943,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) "extruder_colour", "filament_colour", "material_colour", "printable_height", "printer_model", "printer_technology", // These values are necessary to construct SlicingParameters by the Canvas3D variable layer height editor. "layer_height", "initial_layer_print_height", "min_layer_height", "max_layer_height", - "brim_width", "wall_loops", "wall_filament", "sparse_infill_density", "sparse_infill_filament", "top_shell_layers", + "brim_width", "wall_loops", "wall_filament", "sparse_infill_density", "enable_infill_filament_override", "infill_filament_use_base_first_layers", "infill_filament_use_base_last_layers", "sparse_infill_filament", "top_shell_layers", "enable_support", "support_filament", "support_interface_filament", "support_top_z_distance", "support_bottom_z_distance", "raft_layers", "wipe_tower_rotation_angle", "wipe_tower_cone_angle", "wipe_tower_extra_spacing", "wipe_tower_extra_flow", "local_z_wipe_tower_purge_lines", "wipe_tower_max_purge_speed", @@ -19708,8 +19743,9 @@ void Plater::on_filaments_delete(size_t num_filaments, size_t filament_id, int r // update UI sidebar().on_filaments_delete(filament_id); - // update global support filament - static const char* keys[] = {"support_filament", "support_interface_filament"}; + // update global feature filament selections + static const char* keys[] = {"wall_filament", "sparse_infill_filament", "solid_infill_filament", + "support_filament", "support_interface_filament"}; for (auto key : keys) if (p->config->has(key)) { if (p->config->opt_int(key) == filament_id + 1) @@ -19986,6 +20022,9 @@ void Plater::on_config_change(const DynamicPrintConfig &config) } // Orca: update when *_filament changed else if (opt_key == "support_interface_filament" || opt_key == "support_filament" || opt_key == "wall_filament" || + opt_key == "enable_infill_filament_override" || + opt_key == "infill_filament_use_base_first_layers" || + opt_key == "infill_filament_use_base_last_layers" || opt_key == "sparse_infill_filament" || opt_key == "solid_infill_filament") { update_scheduled = true; } diff --git a/src/slic3r/GUI/PresetHints.cpp b/src/slic3r/GUI/PresetHints.cpp index c338049abc..483ffdc3e9 100644 --- a/src/slic3r/GUI/PresetHints.cpp +++ b/src/slic3r/GUI/PresetHints.cpp @@ -129,7 +129,7 @@ std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle return i <= 0 || i > num_extruders || idx_extruder == -1 || idx_extruder == i - 1; }; bool perimeter_extruder_active = feature_extruder_active(print_config.opt_int("wall_filament")); - bool infill_extruder_active = feature_extruder_active(print_config.opt_int("sparse_infill_filament")); + bool infill_extruder_active = feature_extruder_active(print_config.opt_int("wall_filament")); bool solid_infill_extruder_active = feature_extruder_active(print_config.opt_int("solid_infill_filament")); bool support_material_extruder_active = feature_extruder_active(print_config.opt_int("support_filament")); bool support_material_interface_extruder_active = feature_extruder_active(print_config.opt_int("support_interface_filament")); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index bb420ce16d..f17827be3f 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -2267,6 +2267,10 @@ void TabPrint::build() optgroup->append_single_option_line("sparse_infill_density", "strength_settings_infill#sparse-infill-density"); optgroup->append_single_option_line("fill_multiline", "strength_settings_infill#fill-multiline"); optgroup->append_single_option_line("sparse_infill_pattern", "strength_settings_infill#sparse-infill-pattern"); + optgroup->append_single_option_line("enable_infill_filament_override"); + optgroup->append_single_option_line("infill_filament_use_base_first_layers"); + optgroup->append_single_option_line("infill_filament_use_base_last_layers"); + optgroup->append_single_option_line("sparse_infill_filament", "multimaterial_settings_filament_for_features#infill"); optgroup->append_single_option_line("infill_direction", "strength_settings_infill#direction"); optgroup->append_single_option_line("sparse_infill_rotate_template", "strength_settings_infill_rotation_template_metalanguage"); optgroup->append_single_option_line("skin_infill_density", "strength_settings_patterns#locked-zag"); @@ -2457,7 +2461,6 @@ void TabPrint::build() optgroup = page->new_optgroup(L("Filament for Features"), L"param_filament_for_features"); optgroup->append_single_option_line("wall_filament", "multimaterial_settings_filament_for_features#walls"); - optgroup->append_single_option_line("sparse_infill_filament", "multimaterial_settings_filament_for_features#infill"); optgroup->append_single_option_line("solid_infill_filament", "multimaterial_settings_filament_for_features#solid-infill"); optgroup->append_single_option_line("wipe_tower_filament", "multimaterial_settings_filament_for_features#wipe-tower"); @@ -2710,6 +2713,31 @@ static std::vector substruct(std::vector const& l, std return t; } +static DynamicPrintConfig resolved_model_config_for_tab(const DynamicPrintConfig& config) +{ + DynamicPrintConfig resolved(config); + const auto* infill_override_opt = config.option("enable_infill_filament_override"); + const bool infill_override_enabled = infill_override_opt != nullptr && infill_override_opt->value; + + if (!infill_override_enabled && resolved.has("sparse_infill_filament")) + resolved.erase("sparse_infill_filament"); + + if (const auto* extruder_opt = config.option("extruder"); extruder_opt != nullptr && extruder_opt->value > 0) { + const int extruder = extruder_opt->value; + if (!resolved.has("wall_filament")) + resolved.set_key_value("wall_filament", new ConfigOptionInt(extruder)); + if (!resolved.has("sparse_infill_filament")) + resolved.set_key_value("sparse_infill_filament", new ConfigOptionInt(extruder)); + if (!resolved.has("solid_infill_filament")) + resolved.set_key_value("solid_infill_filament", new ConfigOptionInt(extruder)); + } + + if (!resolved.has("solid_infill_filament") && resolved.has("sparse_infill_filament")) + resolved.set_key_value("solid_infill_filament", new ConfigOptionInt(resolved.opt_int("sparse_infill_filament"))); + + return resolved; +} + TabPrintModel::TabPrintModel(ParamsPanel* parent, std::vector const & keys) : TabPrint(parent, Preset::TYPE_MODEL) , m_keys(intersect(Preset::print_options(), keys)) @@ -2778,19 +2806,19 @@ void TabPrintModel::update_model_config() m_null_keys.clear(); if (!m_object_configs.empty()) { DynamicPrintConfig const & global_config= *m_config; - DynamicPrintConfig const & local_config = m_object_configs.begin()->second->get(); + const DynamicPrintConfig local_config = resolved_model_config_for_tab(m_object_configs.begin()->second->get()); DynamicPrintConfig diff_config; std::vector all_keys = local_config.keys(); // at least one has these keys std::vector local_keys = intersect(m_keys, all_keys); // all equal on these keys if (m_object_configs.size() > 1) { std::vector global_keys = m_keys; // all equal with global on these keys for (auto & config : m_object_configs) { - auto equals = global_config.equal(config.second->get()); + const DynamicPrintConfig resolved_config = resolved_model_config_for_tab(config.second->get()); + auto equals = global_config.equal(resolved_config); global_keys = intersect(global_keys, equals); - diff_config.apply_only(config.second->get(), substruct(config.second->keys(), equals)); - if (&config.second->get() == &local_config) continue; - all_keys = concat(all_keys, config.second->keys()); - local_keys = intersect(local_keys, local_config.equal(config.second->get())); + diff_config.apply_only(resolved_config, substruct(resolved_config.keys(), equals)); + all_keys = concat(all_keys, resolved_config.keys()); + local_keys = intersect(local_keys, local_config.equal(resolved_config)); } all_keys = intersect(all_keys, m_keys); m_null_keys = substruct(substruct(all_keys, global_keys), local_keys);