fix: thin-walled holes classic walls flip the direction (#16051)

Co-authored-by: Ioannis Giannakas <59056762+igiannakas@users.noreply.github.com>
This commit is contained in:
Ian Bassi
2026-10-06 14:09:36 -03:00
committed by GitHub
co-authored by Ioannis Giannakas
parent 13d4a0d922
commit c1baacf8a0
2 changed files with 157 additions and 14 deletions
+19 -14
View File
@@ -61,6 +61,8 @@ public:
bool is_smaller_width_perimeter;
// Depth in the hierarchy. External perimeter has depth = 0. An external perimeter could be both a contour and a hole.
unsigned short depth;
// ORCA: an external hole perimeter touching the next outer wall all around, with nothing between them.
bool is_thin_wall_hole = false;
// Children contour, may be both CCW and CW oriented (outer contours or holes).
std::vector<PerimeterGeneratorLoop> children;
@@ -118,7 +120,7 @@ static bool detect_steep_overhang(const PrintRegionConfig *config,
}
static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perimeter_generator, const PerimeterGeneratorLoops &loops, ThickPolylines &thin_walls,
bool &steep_overhang_contour, bool &steep_overhang_hole, bool reverse_thin_wall_hole)
bool &steep_overhang_contour, bool &steep_overhang_hole)
{
// loops is an arrayref of ::Loop objects
// turn each one into an ExtrusionLoop object
@@ -285,24 +287,17 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime
} else {
const PerimeterGeneratorLoop &loop = loops[idx.first];
assert(thin_walls.empty());
const bool reverse_children_thin_wall_hole = loops.size() == 1 && loop.is_contour && loop.children.size() == 1 &&
(!loop.children.front().is_contour) && loop.children.front().children.empty();
ExtrusionEntityCollection children = traverse_loops(perimeter_generator, loop.children, thin_walls, steep_overhang_contour,
steep_overhang_hole, reverse_children_thin_wall_hole);
ExtrusionEntityCollection children = traverse_loops(perimeter_generator, loop.children, thin_walls, steep_overhang_contour, steep_overhang_hole);
out.entities.reserve(out.entities.size() + children.entities.size() + 1);
ExtrusionLoop *eloop = static_cast<ExtrusionLoop*>(coll.entities[idx.first]);
coll.entities[idx.first] = nullptr;
if ((perimeter_generator.config->wall_direction == WallDirection::CounterClockwise) == (loop.is_contour || reverse_thin_wall_hole))
// Orca: holes run against the contour, except thin wall holes, which run with it.
if ((perimeter_generator.config->wall_direction == WallDirection::CounterClockwise) == (loop.is_contour || loop.is_thin_wall_hole))
eloop->make_counter_clockwise();
else
eloop->make_clockwise();
// Orca: Reverse print order for thin wall holes.
if (reverse_thin_wall_hole) {
std::reverse(out.entities.begin(), out.entities.end());
}
eloop->inset_idx = loop.depth;
if (loop.is_contour) {
out.append(std::move(children.entities));
@@ -1499,6 +1494,8 @@ void PerimeterGenerator::process_classic()
coord_t min_spacing = coord_t(perimeter_spacing * (1 - INSET_OVERLAP_TOLERANCE));
coord_t ext_min_spacing = coord_t(ext_perimeter_spacing * (1 - INSET_OVERLAP_TOLERANCE));
bool has_gap_fill = this->config->gap_infill_speed.get_at(get_extruder_index(*print_config, this->config->outer_wall_filament_id - 1)) > 0;
// ORCA: Use the smaller width as the lower bound to avoid overestimating safe overlap
const double gap_fill_min_width = 0.2 * std::min(perimeter_width, ext_perimeter_width) * (1 - INSET_OVERLAP_TOLERANCE);
// BBS: this flow is for smaller external perimeter for small area
coord_t ext_min_spacing_smaller = coord_t(ext_perimeter_spacing * (1 - SMALLER_EXT_INSET_OVERLAP_TOLERANCE));
@@ -1693,6 +1690,15 @@ void PerimeterGenerator::process_classic()
last = std::move(offsets);
// ORCA: a thin wall hole has no room for gap fill or an inner wall anywhere beside its outer wall.
if (i == 0 && ! holes[0].empty()) {
const float room = float(0.5 * (ext_perimeter_spacing2 + gap_fill_min_width));
const Polygons reach = to_polygons(offset2_ex(last, -room, room + float(SCALED_EPSILON)));
for (PerimeterGeneratorLoop &hole : holes[0])
hole.is_thin_wall_hole = intersection_pl(Polylines{ hole.polygon.split_at_first_point() },
ClipperUtils::clip_clipper_polygons_with_subject_bbox(reach, get_extents(hole.polygon).inflated(SCALED_EPSILON))).empty();
}
//BBS: refer to superslicer
//store surface for top infill if only_one_wall_top
if (i == 0 && i!=loop_number && only_one_wall_top && !surface.is_bridge() && this->upper_slices != NULL) {
@@ -1819,7 +1825,7 @@ void PerimeterGenerator::process_classic()
steep_overhang_contour = true;
steep_overhang_hole = true;
}
ExtrusionEntityCollection entities = traverse_loops(*this, contours.front(), thin_walls, steep_overhang_contour, steep_overhang_hole, false);
ExtrusionEntityCollection entities = traverse_loops(*this, contours.front(), thin_walls, steep_overhang_contour, steep_overhang_hole);
// All walls are counter-clockwise initially, so we don't need to reorient it if that's what we want
if (config->overhang_reverse) {
reorient_perimeters(entities, steep_overhang_contour, steep_overhang_hole,
@@ -1946,8 +1952,7 @@ void PerimeterGenerator::process_classic()
// fill gaps
if (! gaps.empty()) { // collapse
// ORCA: Use the smaller width as the lower bound to avoid overestimating safe overlap
double min = 0.2 * std::min(perimeter_width, ext_perimeter_width) * (1 - INSET_OVERLAP_TOLERANCE);
double min = gap_fill_min_width;
double max = 2. * perimeter_spacing;
ExPolygons gaps_ex = diff_ex(
//FIXME offset2 would be enough and cheaper.