diff --git a/localization/i18n/OrcaSlicer.pot b/localization/i18n/OrcaSlicer.pot index 05728b977d..33d9dbb407 100644 --- a/localization/i18n/OrcaSlicer.pot +++ b/localization/i18n/OrcaSlicer.pot @@ -13121,6 +13121,9 @@ msgstr "" msgid "Contour" msgstr "" +msgid "Hole" +msgstr "" + msgid "Contour and hole" msgstr "" diff --git a/localization/i18n/ca/OrcaSlicer_ca.po b/localization/i18n/ca/OrcaSlicer_ca.po index 7960f651a6..768d46bd79 100644 --- a/localization/i18n/ca/OrcaSlicer_ca.po +++ b/localization/i18n/ca/OrcaSlicer_ca.po @@ -15232,6 +15232,9 @@ msgstr "Només pintat" msgid "Contour" msgstr "Contorn" +msgid "Hole" +msgstr "Forat" + msgid "Contour and hole" msgstr "Contorn i forat" diff --git a/localization/i18n/cs/OrcaSlicer_cs.po b/localization/i18n/cs/OrcaSlicer_cs.po index 719189a6fd..6755d2502f 100644 --- a/localization/i18n/cs/OrcaSlicer_cs.po +++ b/localization/i18n/cs/OrcaSlicer_cs.po @@ -14821,6 +14821,9 @@ msgstr "Painted only" msgid "Contour" msgstr "Obrys" +msgid "Hole" +msgstr "Otvor" + msgid "Contour and hole" msgstr "Obrys a otvor" diff --git a/localization/i18n/de/OrcaSlicer_de.po b/localization/i18n/de/OrcaSlicer_de.po index 8614a865d5..26a85806e3 100644 --- a/localization/i18n/de/OrcaSlicer_de.po +++ b/localization/i18n/de/OrcaSlicer_de.po @@ -15357,6 +15357,9 @@ msgstr "Nur lackiert" msgid "Contour" msgstr "Kontur" +msgid "Hole" +msgstr "Loch" + msgid "Contour and hole" msgstr "Kontur und Loch" diff --git a/localization/i18n/en/OrcaSlicer_en.po b/localization/i18n/en/OrcaSlicer_en.po index 96d1f5c588..1103146ce0 100644 --- a/localization/i18n/en/OrcaSlicer_en.po +++ b/localization/i18n/en/OrcaSlicer_en.po @@ -13403,6 +13403,9 @@ msgstr "" msgid "Contour" msgstr "" +msgid "Hole" +msgstr "" + msgid "Contour and hole" msgstr "" diff --git a/localization/i18n/es/OrcaSlicer_es.po b/localization/i18n/es/OrcaSlicer_es.po index 142ada0d46..c52a64f72a 100644 --- a/localization/i18n/es/OrcaSlicer_es.po +++ b/localization/i18n/es/OrcaSlicer_es.po @@ -15294,6 +15294,9 @@ msgstr "Solo pintado" msgid "Contour" msgstr "Contorno" +msgid "Hole" +msgstr "Orificio" + msgid "Contour and hole" msgstr "Contorno y orificio" diff --git a/localization/i18n/fr/OrcaSlicer_fr.po b/localization/i18n/fr/OrcaSlicer_fr.po index 3e285b8c4a..c7e9d3f354 100644 --- a/localization/i18n/fr/OrcaSlicer_fr.po +++ b/localization/i18n/fr/OrcaSlicer_fr.po @@ -15433,6 +15433,9 @@ msgstr "Peint uniquement" msgid "Contour" msgstr "Contour" +msgid "Hole" +msgstr "Trou" + msgid "Contour and hole" msgstr "Contour et trou" diff --git a/localization/i18n/hu/OrcaSlicer_hu.po b/localization/i18n/hu/OrcaSlicer_hu.po index 80d440642e..d4660b0ec6 100644 --- a/localization/i18n/hu/OrcaSlicer_hu.po +++ b/localization/i18n/hu/OrcaSlicer_hu.po @@ -15122,6 +15122,9 @@ msgstr "Csak festett" msgid "Contour" msgstr "Kontúr" +msgid "Hole" +msgstr "Lyuk" + msgid "Contour and hole" msgstr "Kontúr és lyuk" diff --git a/localization/i18n/it/OrcaSlicer_it.po b/localization/i18n/it/OrcaSlicer_it.po index b33ae653af..f49ac5711a 100644 --- a/localization/i18n/it/OrcaSlicer_it.po +++ b/localization/i18n/it/OrcaSlicer_it.po @@ -15319,6 +15319,9 @@ msgstr "Solo verniciato" msgid "Contour" msgstr "Contorno" +msgid "Hole" +msgstr "Foro" + msgid "Contour and hole" msgstr "Contorno e foro" diff --git a/localization/i18n/ja/OrcaSlicer_ja.po b/localization/i18n/ja/OrcaSlicer_ja.po index 7a3d446f82..0b1d6d4f4a 100644 --- a/localization/i18n/ja/OrcaSlicer_ja.po +++ b/localization/i18n/ja/OrcaSlicer_ja.po @@ -14093,6 +14093,9 @@ msgstr "塗装のみ" msgid "Contour" msgstr "輪郭" +msgid "Hole" +msgstr "穴" + msgid "Contour and hole" msgstr "輪郭と穴" diff --git a/localization/i18n/ko/OrcaSlicer_ko.po b/localization/i18n/ko/OrcaSlicer_ko.po index ec0168dd4c..85d1925bf6 100644 --- a/localization/i18n/ko/OrcaSlicer_ko.po +++ b/localization/i18n/ko/OrcaSlicer_ko.po @@ -14240,6 +14240,9 @@ msgstr "도색만" msgid "Contour" msgstr "윤곽" +msgid "Hole" +msgstr "구멍" + msgid "Contour and hole" msgstr "윤곽 및 구멍" diff --git a/localization/i18n/lt/OrcaSlicer_lt.po b/localization/i18n/lt/OrcaSlicer_lt.po index 877e0693c8..0245eb9b3d 100644 --- a/localization/i18n/lt/OrcaSlicer_lt.po +++ b/localization/i18n/lt/OrcaSlicer_lt.po @@ -15043,6 +15043,9 @@ msgstr "Tik dažytas" msgid "Contour" msgstr "Kontūras" +msgid "Hole" +msgstr "Skylė" + msgid "Contour and hole" msgstr "Kontūras ir skylė" diff --git a/localization/i18n/nl/OrcaSlicer_nl.po b/localization/i18n/nl/OrcaSlicer_nl.po index 7266a862b7..1dec100c62 100644 --- a/localization/i18n/nl/OrcaSlicer_nl.po +++ b/localization/i18n/nl/OrcaSlicer_nl.po @@ -15043,6 +15043,9 @@ msgstr "Alleen geverfd" msgid "Contour" msgstr "Contour" +msgid "Hole" +msgstr "Gat" + msgid "Contour and hole" msgstr "Contour en gat" diff --git a/localization/i18n/pl/OrcaSlicer_pl.po b/localization/i18n/pl/OrcaSlicer_pl.po index b991f18681..fefb91e288 100644 --- a/localization/i18n/pl/OrcaSlicer_pl.po +++ b/localization/i18n/pl/OrcaSlicer_pl.po @@ -14539,6 +14539,9 @@ msgstr "Tylko malowane" msgid "Contour" msgstr "Kontur" +msgid "Hole" +msgstr "Otwory" + msgid "Contour and hole" msgstr "Kontur i otwory" diff --git a/localization/i18n/pt_BR/OrcaSlicer_pt_BR.po b/localization/i18n/pt_BR/OrcaSlicer_pt_BR.po index 4c2bc0d138..47c06c471c 100644 --- a/localization/i18n/pt_BR/OrcaSlicer_pt_BR.po +++ b/localization/i18n/pt_BR/OrcaSlicer_pt_BR.po @@ -15186,6 +15186,9 @@ msgstr "Somente pintado" msgid "Contour" msgstr "Contorno" +msgid "Hole" +msgstr "Furo" + msgid "Contour and hole" msgstr "Contorno e furo" diff --git a/localization/i18n/ru/OrcaSlicer_ru.po b/localization/i18n/ru/OrcaSlicer_ru.po index c344999ab3..4998ad9034 100644 --- a/localization/i18n/ru/OrcaSlicer_ru.po +++ b/localization/i18n/ru/OrcaSlicer_ru.po @@ -15501,6 +15501,9 @@ msgstr "Вручную" msgid "Contour" msgstr "Контур" +msgid "Hole" +msgstr "Отверстия" + msgid "Contour and hole" msgstr "Контур и отверстия" diff --git a/localization/i18n/sv/OrcaSlicer_sv.po b/localization/i18n/sv/OrcaSlicer_sv.po index 4a7e8b3acc..d3665b412b 100644 --- a/localization/i18n/sv/OrcaSlicer_sv.po +++ b/localization/i18n/sv/OrcaSlicer_sv.po @@ -14883,6 +14883,9 @@ msgstr "Endast målad" msgid "Contour" msgstr "Kontur" +msgid "Hole" +msgstr "Hål" + msgid "Contour and hole" msgstr "Kontur och hål" diff --git a/localization/i18n/tr/OrcaSlicer_tr.po b/localization/i18n/tr/OrcaSlicer_tr.po index 84238e9b72..1739550e07 100644 --- a/localization/i18n/tr/OrcaSlicer_tr.po +++ b/localization/i18n/tr/OrcaSlicer_tr.po @@ -13339,6 +13339,9 @@ msgstr "Sadece boyalı" msgid "Contour" msgstr "Kontur" +msgid "Hole" +msgstr "Delik" + msgid "Contour and hole" msgstr "Kontur ve delik" diff --git a/localization/i18n/uk/OrcaSlicer_uk.po b/localization/i18n/uk/OrcaSlicer_uk.po index 56737be7e8..99466c46dc 100644 --- a/localization/i18n/uk/OrcaSlicer_uk.po +++ b/localization/i18n/uk/OrcaSlicer_uk.po @@ -14536,6 +14536,9 @@ msgstr "Тільки пофарбовані" msgid "Contour" msgstr "Контур" +msgid "Hole" +msgstr "Отвір" + msgid "Contour and hole" msgstr "Контур та отвір" diff --git a/localization/i18n/vi/OrcaSlicer_vi.po b/localization/i18n/vi/OrcaSlicer_vi.po index 5ece6f7a7b..c48d08f9cb 100644 --- a/localization/i18n/vi/OrcaSlicer_vi.po +++ b/localization/i18n/vi/OrcaSlicer_vi.po @@ -14730,6 +14730,9 @@ msgstr "Chỉ được sơn" msgid "Contour" msgstr "Đường viền" +msgid "Hole" +msgstr "Lỗ" + msgid "Contour and hole" msgstr "Đường viền và lỗ" diff --git a/localization/i18n/zh_CN/OrcaSlicer_zh_CN.po b/localization/i18n/zh_CN/OrcaSlicer_zh_CN.po index c1af59fa3c..85e2f49a05 100644 --- a/localization/i18n/zh_CN/OrcaSlicer_zh_CN.po +++ b/localization/i18n/zh_CN/OrcaSlicer_zh_CN.po @@ -13894,6 +13894,9 @@ msgstr "仅涂漆" msgid "Contour" msgstr "轮廓" +msgid "Hole" +msgstr "孔" + msgid "Contour and hole" msgstr "轮廓和孔" diff --git a/localization/i18n/zh_TW/OrcaSlicer_zh_TW.po b/localization/i18n/zh_TW/OrcaSlicer_zh_TW.po index 035ef1d0e2..12fc980c8f 100644 --- a/localization/i18n/zh_TW/OrcaSlicer_zh_TW.po +++ b/localization/i18n/zh_TW/OrcaSlicer_zh_TW.po @@ -14006,6 +14006,9 @@ msgstr "僅塗漆" msgid "Contour" msgstr "輪廓" +msgid "Hole" +msgstr "孔洞" + msgid "Contour and hole" msgstr "輪廓和孔" diff --git a/src/libslic3r/Feature/FuzzySkin/FuzzySkin.cpp b/src/libslic3r/Feature/FuzzySkin/FuzzySkin.cpp index 36535c2ffe..11e2d081d2 100644 --- a/src/libslic3r/Feature/FuzzySkin/FuzzySkin.cpp +++ b/src/libslic3r/Feature/FuzzySkin/FuzzySkin.cpp @@ -418,7 +418,13 @@ void group_region_by_fuzzify(PerimeterGenerator& g) g.has_fuzzy_skin = false; g.has_fuzzy_hole = false; - std::unordered_map regions; + struct ConfigSurfaces { + FuzzySkinConfig config; + SurfacesPtr surfaces; + }; + + std::vector regions; + regions.reserve(g.compatible_regions->size()); for (auto region : *g.compatible_regions) { const auto& region_config = region->region().config(); const FuzzySkinConfig cfg{region_config.fuzzy_skin, @@ -434,26 +440,36 @@ void group_region_by_fuzzify(PerimeterGenerator& g) region_config.fuzzy_skin_ripple_offset, region_config.fuzzy_skin_layers_between_ripple_offset, g.layer_id}; - auto& surfaces = regions[cfg]; + + auto it = std::find_if(regions.begin(), regions.end(), [&cfg](const ConfigSurfaces& item) { + return item.config == cfg; + }); + if (it == regions.end()) { + regions.push_back({cfg, {}}); + it = regions.end() - 1; + } + + auto& surfaces = it->surfaces; for (const auto& surface : region->slices.surfaces) { surfaces.push_back(&surface); } - if (cfg.type != FuzzySkinType::None && cfg.type != FuzzySkinType::Disabled_fuzzy) { + if (should_fuzzify(cfg, g.layer_id, 0, true)) { g.has_fuzzy_skin = true; - if (cfg.type != FuzzySkinType::External) { - g.has_fuzzy_hole = true; - } + } + if (should_fuzzify(cfg, g.layer_id, 0, false)) { + g.has_fuzzy_hole = true; } } if (regions.size() == 1) { // optimization - g.regions_by_fuzzify[regions.begin()->first] = {}; + g.regions_by_fuzzify.push_back({regions.front().config, {}}); return; } - for (auto& it : regions) { - g.regions_by_fuzzify[it.first] = offset_ex(it.second, ClipperSafetyOffset); + g.regions_by_fuzzify.reserve(regions.size()); + for (const auto& region : regions) { + g.regions_by_fuzzify.push_back({region.config, offset_ex(region.surfaces, ClipperSafetyOffset)}); } } @@ -469,12 +485,79 @@ bool should_fuzzify(const FuzzySkinConfig& config, const int layer_id, const siz return false; } - const bool fuzzify_contours = loop_idx == 0 || fuzziy_type == FuzzySkinType::AllWalls; - const bool fuzzify_holes = fuzzify_contours && (fuzziy_type == FuzzySkinType::All || fuzziy_type == FuzzySkinType::AllWalls); + const bool fuzzify_contours = (loop_idx == 0 && fuzziy_type != FuzzySkinType::Hole) || fuzziy_type == FuzzySkinType::AllWalls; + const bool fuzzify_holes = (fuzziy_type == FuzzySkinType::Hole || fuzziy_type == FuzzySkinType::All || fuzziy_type == FuzzySkinType::AllWalls) + && (loop_idx == 0 || fuzziy_type == FuzzySkinType::AllWalls); return is_contour ? fuzzify_contours : fuzzify_holes; } +struct MergedFuzzyRegion { + const FuzzySkinConfig *config; + ExPolygons expolygons; +}; + +// Compare whether two configs produce the same fuzzy effect (ignoring type/first_layer +// which only control which loops get fuzzified, not the noise itself). +static bool same_fuzzy_effect(const FuzzySkinConfig& a, const FuzzySkinConfig& b) +{ + return a.thickness == b.thickness + && a.point_distance == b.point_distance + && a.noise_type == b.noise_type + && a.noise_scale == b.noise_scale + && a.noise_octaves == b.noise_octaves + && a.noise_persistence == b.noise_persistence + && a.mode == b.mode + && a.ripples_per_layer == b.ripples_per_layer + && a.ripple_offset == b.ripple_offset + && a.layers_between_ripple_offset == b.layers_between_ripple_offset; +} + +static std::vector collect_merged_fuzzy_regions(const std::vector>& regions, + const int layer_id, + const size_t loop_idx, + const bool is_contour) +{ + // Merge regions that produce identical fuzzy effects (differ only in type). + // When the style (e.g. External) and a painted region (All) both fuzzify this loop + // with the same noise parameters, merging their ExPolygons avoids splitting the + // perimeter at the painted boundary — eliminating discontinuity artifacts. + std::vector merged_regions; + merged_regions.reserve(regions.size()); + for (const auto& region : regions) { + if (!should_fuzzify(region.first, layer_id, loop_idx, is_contour)) { + continue; + } + + bool merged = false; + for (auto& merged_region : merged_regions) { + if (same_fuzzy_effect(*merged_region.config, region.first)) { + if (merged_region.expolygons.empty()) { + // Already full coverage, nothing to add. + } else if (region.second.empty()) { + merged_region.expolygons.clear(); + } else { + append(merged_region.expolygons, region.second); + } + merged = true; + break; + } + } + + if (!merged) { + merged_regions.push_back({®ion.first, region.second}); + } + } + + for (auto& merged_region : merged_regions) { + if (!merged_region.expolygons.empty()) { + merged_region.expolygons = union_ex(merged_region.expolygons); + } + } + + return merged_regions; +} + Polygon apply_fuzzy_skin(const Polygon& polygon, const PerimeterGenerator& perimeter_generator, const size_t loop_idx, const bool is_contour) { Polygon fuzzified; @@ -493,22 +576,30 @@ Polygon apply_fuzzy_skin(const Polygon& polygon, const PerimeterGenerator& perim return fuzzified; } - // Find all affective regions - std::vector> fuzzified_regions; - fuzzified_regions.reserve(regions.size()); - for (const auto& region : regions) { - if (should_fuzzify(region.first, perimeter_generator.layer_id, loop_idx, is_contour)) { - fuzzified_regions.emplace_back(region.first, region.second); - } - } - if (fuzzified_regions.empty()) { + // Merge regions that produce identical fuzzy effects (differ only in type). + // When the style (e.g. External) and a painted region (All) both fuzzify this loop + // with the same noise parameters, merging their ExPolygons avoids splitting the + // perimeter at the painted boundary — eliminating discontinuity artifacts. + auto merged_regions = collect_merged_fuzzy_regions(regions, perimeter_generator.layer_id, loop_idx, is_contour); + if (merged_regions.empty()) { return polygon; } + // Fast path: single merged region — apply directly without splitting + if (merged_regions.size() == 1) { + const auto& mr = merged_regions.front(); + if (mr.expolygons.empty()) { + fuzzified = polygon; + fuzzy_polyline(fuzzified.points, true, slice_z, *mr.config); + return fuzzified; + } + // Fall through to split_line with a single region below + } + #ifdef DEBUG_FUZZY { int i = 0; - for (const auto& r : fuzzified_regions) { + for (const auto& r : merged_regions) { BoundingBox bbox = get_extents(perimeter_generator.slices->surfaces); bbox.offset(scale_(1.)); ::Slic3r::SVG svg(debug_out_path("fuzzy_traverse_loops_%d_%d_%d_region_%d.svg", perimeter_generator.layer_id, @@ -517,18 +608,26 @@ Polygon apply_fuzzy_skin(const Polygon& polygon, const PerimeterGenerator& perim bbox); svg.draw_outline(perimeter_generator.slices->surfaces); svg.draw_outline(polygon, "green"); - svg.draw(r.second, "red", 0.5); - svg.draw_outline(r.second, "red"); + svg.draw(r.expolygons, "red", 0.5); + svg.draw_outline(r.expolygons, "red"); svg.Close(); i++; } } #endif + // Make each region's ExPolygons exclusive so overlapping regions don't double-fuzz + // the same perimeter section. Later regions in the list take priority over earlier ones + // in overlapping areas (matching modifier precedence order). + for (size_t i = 0; i < merged_regions.size(); ++i) + for (size_t j = i + 1; j < merged_regions.size(); ++j) + if (!merged_regions[i].expolygons.empty() && !merged_regions[j].expolygons.empty()) + merged_regions[i].expolygons = diff_ex(merged_regions[i].expolygons, merged_regions[j].expolygons); + // Split the loops into lines with different config, and fuzzy them separately fuzzified = polygon; - for (const auto& r : fuzzified_regions) { - auto splitted = Algorithm::split_line(fuzzified, r.second, true); + for (const auto& r : merged_regions) { + auto splitted = Algorithm::split_line(fuzzified, r.expolygons, true); if (splitted.empty()) { // No intersection, skip continue; @@ -537,7 +636,7 @@ Polygon apply_fuzzy_skin(const Polygon& polygon, const PerimeterGenerator& perim // Fuzzy splitted polygon if (std::all_of(splitted.begin(), splitted.end(), [](const Algorithm::SplitLineJunction& j) { return j.clipped; })) { // The entire polygon is fuzzified - fuzzy_polyline(fuzzified.points, true, slice_z, r.first); + fuzzy_polyline(fuzzified.points, true, slice_z, *r.config); } else { // Start from a non-clipped junction so wrapped clipped segments do // not need an artificial reconnection across the seam. @@ -554,7 +653,7 @@ Polygon apply_fuzzy_skin(const Polygon& polygon, const PerimeterGenerator& perim const auto fuzzy_current_segment = [&segment, &fuzzified, &r, slice_z]() { fuzzified.points.push_back(segment.front()); const auto back = segment.back(); - fuzzy_polyline(segment, false, slice_z, r.first); + fuzzy_polyline(segment, false, slice_z, *r.config); fuzzified.points.insert(fuzzified.points.end(), segment.begin(), segment.end()); fuzzified.points.push_back(back); segment.clear(); @@ -593,20 +692,23 @@ void apply_fuzzy_skin(Arachne::ExtrusionLine* extrusion, const PerimeterGenerato if (fuzzify) fuzzy_extrusion_line(extrusion->junctions, slice_z, config); } else { - // Find all affective regions - std::vector> fuzzified_regions; - fuzzified_regions.reserve(regions.size()); - for (const auto& region : regions) { - if (should_fuzzify(region.first, perimeter_generator.layer_id, extrusion->inset_idx, is_contour)) { - fuzzified_regions.emplace_back(region.first, region.second); + // Merge regions that produce identical fuzzy effects (differ only in type). + // When the style (e.g. External) and a painted region (All) both fuzzify this loop + // with the same noise parameters, merging avoids splitting the perimeter at the + // painted boundary — eliminating discontinuity artifacts. + auto merged_regions = collect_merged_fuzzy_regions(regions, perimeter_generator.layer_id, extrusion->inset_idx, is_contour); + if (!merged_regions.empty()) { + + // Fast path: single merged region — apply directly without splitting + if (merged_regions.size() == 1 && merged_regions.front().expolygons.empty()) { + fuzzy_extrusion_line(extrusion->junctions, slice_z, *merged_regions.front().config); + return; } - } - if (!fuzzified_regions.empty()) { - + #ifdef DEBUG_FUZZY { int i = 0; - for (const auto& r : fuzzified_regions) { + for (const auto& r : merged_regions) { BoundingBox bbox = get_extents(perimeter_generator.slices->surfaces); bbox.offset(scale_(1.)); ::Slic3r::SVG svg(debug_out_path("fuzzy_traverse_loops_%d_%d_%d_region_%d.svg", perimeter_generator.layer_id, @@ -623,17 +725,25 @@ void apply_fuzzy_skin(Arachne::ExtrusionLine* extrusion, const PerimeterGenerato svg.draw_outline(perimeter_generator.slices->surfaces); svg.draw_outline(extrusion_polygon, "green"); - svg.draw(r.second, "red", 0.5); - svg.draw_outline(r.second, "red"); + svg.draw(r.expolygons, "red", 0.5); + svg.draw_outline(r.expolygons, "red"); svg.Close(); i++; } } #endif + // Make each region's ExPolygons exclusive so overlapping regions don't double-fuzz + // the same perimeter section. Later regions in the list take priority over earlier ones + // in overlapping areas. + for (size_t i = 0; i < merged_regions.size(); ++i) + for (size_t j = i + 1; j < merged_regions.size(); ++j) + if (!merged_regions[i].expolygons.empty() && !merged_regions[j].expolygons.empty()) + merged_regions[i].expolygons = diff_ex(merged_regions[i].expolygons, merged_regions[j].expolygons); + // Split the loops into lines with different config, and fuzzy them separately - for (const auto& r : fuzzified_regions) { - const auto splitted = Algorithm::split_line(*extrusion, r.second, false); + for (const auto& r : merged_regions) { + const auto splitted = Algorithm::split_line(*extrusion, r.expolygons, false); if (splitted.empty()) { // No intersection, skip continue; @@ -642,7 +752,7 @@ void apply_fuzzy_skin(Arachne::ExtrusionLine* extrusion, const PerimeterGenerato // Fuzzy splitted extrusion if (std::all_of(splitted.begin(), splitted.end(), [](const Algorithm::SplitLineJunction& j) { return j.clipped; })) { // The entire polygon is fuzzified - fuzzy_extrusion_line(extrusion->junctions, slice_z, r.first); + fuzzy_extrusion_line(extrusion->junctions, slice_z, *r.config); continue; } else { const auto current_ext = extrusion->junctions; @@ -655,7 +765,7 @@ void apply_fuzzy_skin(Arachne::ExtrusionLine* extrusion, const PerimeterGenerato const auto front = segment.front(); const auto back = segment.back(); - fuzzy_extrusion_line(segment, slice_z, r.first, false); + fuzzy_extrusion_line(segment, slice_z, *r.config, false); // Orca: only add non fuzzy point if it's not in the extrusion closing point. if (!extrusion->junctions.empty() && extrusion->junctions.front().p != front.p) { extrusion->junctions.push_back(front); diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index 16a9efe812..67959a508a 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -250,6 +250,23 @@ void Layer::make_perimeters() //BBS: Separate fill_no_overlap (*l)->fill_no_overlap_expolygons = intersection_ex((*l)->slices.surfaces, fill_no_overlap); } + + // When counterbore hole bridging (chbFilled) is active, process_no_bridge may + // create fill surfaces that extend beyond all region slices (e.g. by clearing + // holes in the bridge expolygon). These "extra" fills are lost during the + // intersection-based splitting above. Recover them and assign to the first + // merged region so the sacrificial bridge layer is not broken. + if (layerm_config->region().config().counterbore_hole_bridging.value != chbNone) { + Polygons all_region_slices_p; + for (LayerRegion *l : layerms) + polygons_append(all_region_slices_p, to_polygons(l->slices.surfaces)); + ExPolygons extra_fill = diff_ex(fill_surfaces.surfaces, all_region_slices_p, ApplySafetyOffset::Yes); + if (!extra_fill.empty()) { + append(layerms.front()->fill_expolygons, extra_fill); + layerms.front()->fill_expolygons = union_ex(layerms.front()->fill_expolygons); + layerms.front()->fill_surfaces.append(std::move(extra_fill), fill_surfaces.surfaces.front()); + } + } } } } diff --git a/src/libslic3r/PerimeterGenerator.hpp b/src/libslic3r/PerimeterGenerator.hpp index 08ba854d06..9ccf8c4d8c 100644 --- a/src/libslic3r/PerimeterGenerator.hpp +++ b/src/libslic3r/PerimeterGenerator.hpp @@ -103,7 +103,8 @@ public: bool has_fuzzy_skin = false; bool has_fuzzy_hole = false; - std::unordered_map regions_by_fuzzify; + // Preserve construction order so overlap precedence remains deterministic. + std::vector> regions_by_fuzzify; PerimeterGenerator( // Input: diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 73cf66954b..ac22ab89cc 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -172,6 +172,7 @@ CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PowerLossRecoveryMode) static t_config_enum_values s_keys_map_FuzzySkinType { { "none", int(FuzzySkinType::None) }, { "external", int(FuzzySkinType::External) }, + { "hole", int(FuzzySkinType::Hole) }, { "all", int(FuzzySkinType::All) }, { "allwalls", int(FuzzySkinType::AllWalls)}, { "disabled_fuzzy", int(FuzzySkinType::Disabled_fuzzy)} @@ -3354,11 +3355,13 @@ void PrintConfigDef::init_fff_params() def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); def->enum_values.push_back("none"); def->enum_values.push_back("external"); + def->enum_values.push_back("hole"); def->enum_values.push_back("all"); def->enum_values.push_back("allwalls"); def->enum_values.push_back("disabled_fuzzy"); def->enum_labels.push_back(L("Painted only")); def->enum_labels.push_back(L("Contour")); + def->enum_labels.push_back(L("Hole")); def->enum_labels.push_back(L("Contour and hole")); def->enum_labels.push_back(L("All walls")); def->enum_labels.push_back(L("Disabled")); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 8ab37ddfde..e81e0eb79a 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -39,6 +39,7 @@ enum GCodeFlavor : unsigned char { enum class FuzzySkinType { None, External, + Hole, All, AllWalls, Disabled_fuzzy,