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);