diff --git a/src/libslic3r/MixedFilament.cpp b/src/libslic3r/MixedFilament.cpp index 2dcd5d28dc..72a914802f 100644 --- a/src/libslic3r/MixedFilament.cpp +++ b/src/libslic3r/MixedFilament.cpp @@ -1321,6 +1321,43 @@ unsigned int MixedFilamentManager::resolve_perimeter(unsigned int filament_id, return resolve(filament_id, num_physical, layer_index, layer_print_z, layer_height, force_height_weighted); } +unsigned int MixedFilamentManager::effective_painted_region_filament_id(unsigned int filament_id, + size_t num_physical, + int layer_index, + float layer_print_z, + float layer_height, + float layer_height_a, + float layer_height_b, + float base_layer_height) const +{ + const int mixed_idx = mixed_index_from_filament_id(filament_id, num_physical); + if (mixed_idx < 0) + return filament_id; + + const MixedFilament &mf = m_mixed[size_t(mixed_idx)]; + if (mf.distribution_mode == int(MixedFilament::SameLayerPointillisme)) + return filament_id; + + const std::string normalized_pattern = normalize_manual_pattern(mf.manual_pattern); + if (normalized_pattern.find(',') != std::string::npos) + return filament_id; + + const bool is_custom_mixed = mf.custom; + if (!is_custom_mixed && (layer_height_a > 0.f || layer_height_b > 0.f)) { + const float safe_base = std::max(0.01f, base_layer_height); + const int ratio_a = std::max(1, int(std::lround((layer_height_a > 0.f ? layer_height_a : safe_base) / safe_base))); + const int ratio_b = std::max(1, int(std::lround((layer_height_b > 0.f ? layer_height_b : safe_base) / safe_base))); + const int cycle = ratio_a + ratio_b; + + if (cycle > 0) { + const int pos = ((layer_index % cycle) + cycle) % cycle; + return pos < ratio_a ? mf.component_a : mf.component_b; + } + } + + return resolve(filament_id, num_physical, layer_index, layer_print_z, layer_height); +} + std::vector MixedFilamentManager::ordered_perimeter_extruders(unsigned int filament_id, size_t num_physical, int layer_index, diff --git a/src/libslic3r/MixedFilament.hpp b/src/libslic3r/MixedFilament.hpp index 167a008255..8421beab7d 100644 --- a/src/libslic3r/MixedFilament.hpp +++ b/src/libslic3r/MixedFilament.hpp @@ -179,6 +179,18 @@ public: float layer_print_z = 0.f, float layer_height = 0.f, bool force_height_weighted = false) const; + // Resolve the filament ID that should own painted regions on this layer. + // Modes that require virtual identity later in G-code generation keep the + // original mixed ID; ordinary mixed rows collapse to the current physical + // extruder so adjacent same-tool regions can merge. + unsigned int effective_painted_region_filament_id(unsigned int filament_id, + size_t num_physical, + int layer_index, + float layer_print_z = 0.f, + float layer_height = 0.f, + float layer_height_a = 0.f, + float layer_height_b = 0.f, + float base_layer_height = 0.2f) const; std::vector ordered_perimeter_extruders(unsigned int filament_id, size_t num_physical, int layer_index, diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index cc81973868..c6fb321ecf 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -75,6 +75,7 @@ static std::vector s_project_options { "mixed_filament_height_upper_bound", "mixed_filament_advanced_dithering", "mixed_filament_surface_indentation", + "mixed_filament_region_collapse", "mixed_filament_definitions", "mixed_color_layer_height_a", "mixed_color_layer_height_b", diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 9f1585ab7f..b90df40089 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -297,6 +297,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n || opt_key == "mixed_filament_height_upper_bound" || opt_key == "mixed_filament_advanced_dithering" || opt_key == "mixed_filament_surface_indentation" + || opt_key == "mixed_filament_region_collapse" || opt_key == "mixed_filament_definitions" // Spiral Vase forces different kind of slicing than the normal model: // In Spiral Vase mode, holes are closed and only the largest area contour is kept at each layer. diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp index efcf985141..d53b359929 100644 --- a/src/libslic3r/PrintApply.cpp +++ b/src/libslic3r/PrintApply.cpp @@ -1112,18 +1112,22 @@ static inline void append_unique_painted_extruder(std::vector &pai painting_extruders.emplace_back(extruder_id); } -static void append_same_layer_component_extruders(const MixedFilamentManager &mixed_mgr, - unsigned int state_id, - size_t num_physical_extruders, - std::vector &painting_extruders) +static void append_mixed_component_extruders(const MixedFilamentManager &mixed_mgr, + unsigned int state_id, + size_t num_physical_extruders, + std::vector &painting_extruders) { if (state_id <= num_physical_extruders) return; const MixedFilament *mixed_row = mixed_mgr.mixed_filament_from_id(state_id, num_physical_extruders); - if (mixed_row == nullptr || !mixed_row->enabled || mixed_row->distribution_mode != int(MixedFilament::SameLayerPointillisme)) + if (mixed_row == nullptr || !mixed_row->enabled) return; + // Pre-create painted target regions for every physical filament a mixed row + // may resolve to. apply_mm_segmentation can then collapse ordinary mixed + // channels onto the active physical tool for a layer without losing the + // destination region. append_unique_painted_extruder(painting_extruders, mixed_row->component_a, num_physical_extruders); append_unique_painted_extruder(painting_extruders, mixed_row->component_b, num_physical_extruders); @@ -1177,6 +1181,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ new_full_config.option("mixed_filament_pointillism_pixel_size", true); new_full_config.option("mixed_filament_pointillism_line_gap", true); new_full_config.option("mixed_filament_surface_indentation", true); + new_full_config.option("mixed_filament_region_collapse", true); new_full_config.option("mixed_filament_definitions", true); m_config.option("dithering_z_step_size", true); m_config.option("dithering_local_z_mode", true); @@ -1188,6 +1193,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ m_config.option("mixed_filament_pointillism_pixel_size", true); m_config.option("mixed_filament_pointillism_line_gap", true); m_config.option("mixed_filament_surface_indentation", true); + m_config.option("mixed_filament_region_collapse", true); m_config.option("mixed_filament_definitions", true); m_default_object_config.option("dithering_z_step_size", true); m_default_object_config.option("dithering_local_z_mode", true); @@ -1199,6 +1205,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ m_default_object_config.option("mixed_filament_pointillism_pixel_size", true); m_default_object_config.option("mixed_filament_pointillism_line_gap", true); m_default_object_config.option("mixed_filament_surface_indentation", true); + m_default_object_config.option("mixed_filament_region_collapse", true); m_default_object_config.option("mixed_filament_definitions", true); // BBS int used_filaments = this->extruders(true).size(); @@ -1772,10 +1779,10 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ continue; if (state_idx <= num_total_filaments) { painting_extruders.emplace_back(static_cast(state_idx)); - append_same_layer_component_extruders(m_mixed_filament_mgr, - static_cast(state_idx), - num_extruders, - painting_extruders); + append_mixed_component_extruders(m_mixed_filament_mgr, + static_cast(state_idx), + num_extruders, + painting_extruders); } else ++dropped_painted_states; } diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 4f76874d4a..738c768d2d 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -4244,6 +4244,15 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(0.0)); + def = this->add("mixed_filament_region_collapse", coBool); + def->label = L("Collapse same-color mixed regions"); + def->category = L("Others"); + def->tooltip = L("Merge ordinary mixed-filament painted regions into a single area when they resolve to the same physical filament on a layer.\n\n" + "This improves continuity for adjacent same-color areas. Local Z dithering turns this off automatically when enabled, but you may turn it back on manually.\n\n" + "Experimental with Local Z dithering."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(true)); + def = this->add("mixed_filament_definitions", coString); def->label = L("Mixed filament custom definitions"); def->tooltip = L("Serialized custom mixed filament rows.\n\n" diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index aac44bad62..10514744d7 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -1363,6 +1363,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( ((ConfigOptionFloat, mixed_filament_pointillism_pixel_size)) ((ConfigOptionFloat, mixed_filament_pointillism_line_gap)) ((ConfigOptionFloat, mixed_filament_surface_indentation)) + ((ConfigOptionBool, mixed_filament_region_collapse)) ((ConfigOptionString, mixed_filament_definitions)) ((ConfigOptionFloat, dithering_z_step_size)) ((ConfigOptionBool, dithering_local_z_mode)) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 0256e03226..e4a3d877a9 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -950,6 +950,7 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "dithering_z_step_size" || opt_key == "dithering_local_z_mode" || opt_key == "dithering_step_painted_zones_only" + || opt_key == "mixed_filament_region_collapse" || opt_key == "mmu_segmented_region_max_width" || opt_key == "mmu_segmented_region_interlocking_depth" || opt_key == "raft_layers" @@ -973,6 +974,7 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "mixed_filament_height_upper_bound" || opt_key == "mixed_filament_advanced_dithering" || opt_key == "mixed_filament_surface_indentation" + || opt_key == "mixed_filament_region_collapse" || opt_key == "mixed_filament_definitions") { // Mixed filament gradient controls affect layer cadence and virtual // tool distribution, so force a re-slice prompt like other diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp index 5e177757b4..c2a7130701 100644 --- a/src/libslic3r/PrintObjectSlice.cpp +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -2947,9 +2947,21 @@ template static inline void apply_mm_segmentation(PrintObject &print_object, std::vector> segmentation, ThrowOnCancel throw_on_cancel) { assert(segmentation.size() == print_object.layer_count()); + const PrintConfig &print_cfg = print_object.print()->config(); + const DynamicPrintConfig &full_cfg = print_object.print()->full_print_config(); + const size_t num_physical = print_cfg.filament_diameter.size(); + const coordf_t preferred_a = float_from_full_config(full_cfg, "mixed_color_layer_height_a", + coordf_t(print_cfg.mixed_color_layer_height_a.value)); + const coordf_t preferred_b = float_from_full_config(full_cfg, "mixed_color_layer_height_b", + coordf_t(print_cfg.mixed_color_layer_height_b.value)); + const coordf_t base_height = std::max(0.01f, coordf_t(print_object.config().layer_height.value)); + const bool collapse_mixed_regions = + bool_from_full_config(full_cfg, "mixed_filament_region_collapse", print_cfg.mixed_filament_region_collapse.value); + const MixedFilamentManager &mixed_mgr = print_object.print()->mixed_filament_manager(); + tbb::parallel_for( tbb::blocked_range(0, segmentation.size(), std::max(segmentation.size() / 128, size_t(1))), - [&print_object, &segmentation, throw_on_cancel](const tbb::blocked_range &range) { + [&print_object, &segmentation, &mixed_mgr, num_physical, preferred_a, preferred_b, base_height, collapse_mixed_regions, throw_on_cancel](const tbb::blocked_range &range) { const auto &layer_ranges = print_object.shared_regions()->layer_ranges; double z = print_object.get_layer(int(range.begin()))->slice_z; auto it_layer_range = layer_range_first(layer_ranges, z); @@ -2982,7 +2994,20 @@ static inline void apply_mm_segmentation(PrintObject &print_object, std::vector< size_t missing_target_regions = 0; std::vector missing_target_extruders; for (size_t extruder_id = 0; extruder_id < num_extruders; ++ extruder_id) { - ByExtruder ®ion = by_extruder[extruder_id]; + const unsigned int channel_id = unsigned(extruder_id + 1); + const unsigned int effective_filament_id = collapse_mixed_regions ? + mixed_mgr.effective_painted_region_filament_id(channel_id, + num_physical, + int(layer_id), + float(layer.print_z), + float(layer.height), + float(preferred_a), + float(preferred_b), + float(base_height)) : + channel_id; + const size_t effective_idx = + effective_filament_id >= 1 && effective_filament_id <= num_extruders ? size_t(effective_filament_id - 1) : extruder_id; + ByExtruder ®ion = by_extruder[effective_idx]; append(region.expolygons, std::move(segmentation[layer_id][extruder_id])); if (! region.expolygons.empty()) { region.bbox = get_extents(region.expolygons); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 9267c1ad3e..291e2b4604 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1491,6 +1491,21 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value) update_wiping_button_visibility(); } + if (opt_key == "dithering_local_z_mode" && + boost::any_cast(value) && + (!m_config->has("mixed_filament_region_collapse") || + m_config->option("mixed_filament_region_collapse") == nullptr || + m_config->opt_bool("mixed_filament_region_collapse"))) { + change_opt_value(*m_config, "mixed_filament_region_collapse", boost::any(false)); + if (m_type == Preset::TYPE_PRINT) { + DynamicPrintConfig &project_cfg = wxGetApp().preset_bundle->project_config; + project_cfg.set_key_value("mixed_filament_region_collapse", new ConfigOptionBool(false)); + } + if (Field *field = this->get_field("mixed_filament_region_collapse")) + field->set_value(boost::any(false), false); + update_dirty(); + } + if (opt_key == "single_extruder_multi_material" ){ const auto bSEMM = m_config->opt_bool("single_extruder_multi_material"); @@ -1791,6 +1806,7 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value) opt_key == "mixed_filament_pointillism_pixel_size" || opt_key == "mixed_filament_pointillism_line_gap" || opt_key == "mixed_filament_surface_indentation" || + opt_key == "mixed_filament_region_collapse" || opt_key == "dithering_z_step_size" || opt_key == "dithering_local_z_mode" || opt_key == "dithering_step_painted_zones_only" || @@ -2528,6 +2544,7 @@ optgroup->append_single_option_line("skirt_loops", "others_settings_skirt#loops" optgroup->append_single_option_line("mixed_filament_pointillism_pixel_size"); optgroup->append_single_option_line("mixed_filament_pointillism_line_gap"); optgroup->append_single_option_line("mixed_filament_surface_indentation"); + optgroup->append_single_option_line("mixed_filament_region_collapse"); optgroup->append_single_option_line("dithering_z_step_size"); optgroup->append_single_option_line("dithering_local_z_mode"); optgroup->append_single_option_line("dithering_step_painted_zones_only"); diff --git a/tests/libslic3r/test_mixed_filament.cpp b/tests/libslic3r/test_mixed_filament.cpp index 1849a5748d..06683054b8 100644 --- a/tests/libslic3r/test_mixed_filament.cpp +++ b/tests/libslic3r/test_mixed_filament.cpp @@ -221,6 +221,41 @@ TEST_CASE("Mixed filament perimeter resolver uses grouped manual patterns by ins CHECK(ordered_layer1[1] == 1); } +TEST_CASE("Mixed filament painted-region resolver collapses ordinary mixed rows to the active physical extruder", "[MixedFilament]") +{ + const std::vector colors = {"#FF0000", "#00FF00"}; + + MixedFilamentManager mgr; + mgr.add_custom_filament(1, 2, 50, colors); + REQUIRE(mgr.mixed_filaments().size() == 1); + + MixedFilament &row = mgr.mixed_filaments().front(); + row.ratio_a = 1; + row.ratio_b = 1; + row.manual_pattern.clear(); + row.distribution_mode = int(MixedFilament::Simple); + + CHECK(mgr.effective_painted_region_filament_id(3, 2, 0) == 1); + CHECK(mgr.effective_painted_region_filament_id(3, 2, 1) == 2); +} + +TEST_CASE("Mixed filament painted-region resolver preserves virtual channels for grouped and same-layer modes", "[MixedFilament]") +{ + const std::vector colors = {"#00FFFF", "#FF00FF"}; + + MixedFilamentManager mgr; + mgr.add_custom_filament(1, 2, 50, colors); + REQUIRE(mgr.mixed_filaments().size() == 1); + + MixedFilament &row = mgr.mixed_filaments().front(); + row.manual_pattern = MixedFilamentManager::normalize_manual_pattern("12,21"); + CHECK(mgr.effective_painted_region_filament_id(3, 2, 0) == 3); + + row.manual_pattern.clear(); + row.distribution_mode = int(MixedFilament::SameLayerPointillisme); + CHECK(mgr.effective_painted_region_filament_id(3, 2, 0) == 3); +} + TEST_CASE("ExtrusionPath copies preserve inset index", "[MixedFilament]") { ExtrusionPath src(erPerimeter);