FuzzySkin hole + Fix: Painted artifacts and with Sacrificial layer issues (#12479)

This commit is contained in:
Ian Bassi
2026-05-08 08:22:49 -03:00
committed by GitHub
parent 8876c0b0df
commit c9413bb4ef
27 changed files with 242 additions and 44 deletions

View File

@@ -13121,6 +13121,9 @@ msgstr ""
msgid "Contour"
msgstr ""
msgid "Hole"
msgstr ""
msgid "Contour and hole"
msgstr ""

View File

@@ -15232,6 +15232,9 @@ msgstr "Només pintat"
msgid "Contour"
msgstr "Contorn"
msgid "Hole"
msgstr "Forat"
msgid "Contour and hole"
msgstr "Contorn i forat"

View File

@@ -14821,6 +14821,9 @@ msgstr "Painted only"
msgid "Contour"
msgstr "Obrys"
msgid "Hole"
msgstr "Otvor"
msgid "Contour and hole"
msgstr "Obrys a otvor"

View File

@@ -15357,6 +15357,9 @@ msgstr "Nur lackiert"
msgid "Contour"
msgstr "Kontur"
msgid "Hole"
msgstr "Loch"
msgid "Contour and hole"
msgstr "Kontur und Loch"

View File

@@ -13403,6 +13403,9 @@ msgstr ""
msgid "Contour"
msgstr ""
msgid "Hole"
msgstr ""
msgid "Contour and hole"
msgstr ""

View File

@@ -15294,6 +15294,9 @@ msgstr "Solo pintado"
msgid "Contour"
msgstr "Contorno"
msgid "Hole"
msgstr "Orificio"
msgid "Contour and hole"
msgstr "Contorno y orificio"

View File

@@ -15433,6 +15433,9 @@ msgstr "Peint uniquement"
msgid "Contour"
msgstr "Contour"
msgid "Hole"
msgstr "Trou"
msgid "Contour and hole"
msgstr "Contour et trou"

View File

@@ -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"

View File

@@ -15319,6 +15319,9 @@ msgstr "Solo verniciato"
msgid "Contour"
msgstr "Contorno"
msgid "Hole"
msgstr "Foro"
msgid "Contour and hole"
msgstr "Contorno e foro"

View File

@@ -14093,6 +14093,9 @@ msgstr "塗装のみ"
msgid "Contour"
msgstr "輪郭"
msgid "Hole"
msgstr "穴"
msgid "Contour and hole"
msgstr "輪郭と穴"

View File

@@ -14240,6 +14240,9 @@ msgstr "도색만"
msgid "Contour"
msgstr "윤곽"
msgid "Hole"
msgstr "구멍"
msgid "Contour and hole"
msgstr "윤곽 및 구멍"

View File

@@ -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ė"

View File

@@ -15043,6 +15043,9 @@ msgstr "Alleen geverfd"
msgid "Contour"
msgstr "Contour"
msgid "Hole"
msgstr "Gat"
msgid "Contour and hole"
msgstr "Contour en gat"

View File

@@ -14539,6 +14539,9 @@ msgstr "Tylko malowane"
msgid "Contour"
msgstr "Kontur"
msgid "Hole"
msgstr "Otwory"
msgid "Contour and hole"
msgstr "Kontur i otwory"

View File

@@ -15186,6 +15186,9 @@ msgstr "Somente pintado"
msgid "Contour"
msgstr "Contorno"
msgid "Hole"
msgstr "Furo"
msgid "Contour and hole"
msgstr "Contorno e furo"

View File

@@ -15501,6 +15501,9 @@ msgstr "Вручную"
msgid "Contour"
msgstr "Контур"
msgid "Hole"
msgstr "Отверстия"
msgid "Contour and hole"
msgstr "Контур и отверстия"

View File

@@ -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"

View File

@@ -13339,6 +13339,9 @@ msgstr "Sadece boyalı"
msgid "Contour"
msgstr "Kontur"
msgid "Hole"
msgstr "Delik"
msgid "Contour and hole"
msgstr "Kontur ve delik"

View File

@@ -14536,6 +14536,9 @@ msgstr "Тільки пофарбовані"
msgid "Contour"
msgstr "Контур"
msgid "Hole"
msgstr "Отвір"
msgid "Contour and hole"
msgstr "Контур та отвір"

View File

@@ -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ỗ"

View File

@@ -13894,6 +13894,9 @@ msgstr "仅涂漆"
msgid "Contour"
msgstr "轮廓"
msgid "Hole"
msgstr "孔"
msgid "Contour and hole"
msgstr "轮廓和孔"

View File

@@ -14006,6 +14006,9 @@ msgstr "僅塗漆"
msgid "Contour"
msgstr "輪廓"
msgid "Hole"
msgstr "孔洞"
msgid "Contour and hole"
msgstr "輪廓和孔"

View File

@@ -418,7 +418,13 @@ void group_region_by_fuzzify(PerimeterGenerator& g)
g.has_fuzzy_skin = false;
g.has_fuzzy_hole = false;
std::unordered_map<FuzzySkinConfig, SurfacesPtr> regions;
struct ConfigSurfaces {
FuzzySkinConfig config;
SurfacesPtr surfaces;
};
std::vector<ConfigSurfaces> 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<MergedFuzzyRegion> collect_merged_fuzzy_regions(const std::vector<std::pair<FuzzySkinConfig, ExPolygons>>& 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<MergedFuzzyRegion> 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({&region.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<std::pair<const FuzzySkinConfig&, const ExPolygons&>> 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<std::pair<const FuzzySkinConfig&, const ExPolygons&>> 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);

View File

@@ -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());
}
}
}
}
}

View File

@@ -103,7 +103,8 @@ public:
bool has_fuzzy_skin = false;
bool has_fuzzy_hole = false;
std::unordered_map<FuzzySkinConfig, ExPolygons> regions_by_fuzzify;
// Preserve construction order so overlap precedence remains deterministic.
std::vector<std::pair<FuzzySkinConfig, ExPolygons>> regions_by_fuzzify;
PerimeterGenerator(
// Input:

View File

@@ -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<FuzzySkinType>::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"));

View File

@@ -39,6 +39,7 @@ enum GCodeFlavor : unsigned char {
enum class FuzzySkinType {
None,
External,
Hole,
All,
AllWalls,
Disabled_fuzzy,