diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index 0769fb76de..a5cdfe5bc4 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -1727,9 +1727,12 @@ void PerimeterGenerator::add_infill_contour_for_arachne( ExPolygons infil // Orca: sacrificial bridge layer algorithm ported from SuperSlicer void PerimeterGenerator::process_no_bridge(Surfaces& all_surfaces, coord_t perimeter_spacing, coord_t ext_perimeter_width) { + + if (this->config->counterbore_hole_bridging == chbNone) + return; // return if counterbore hole is not enabled + //store surface for bridge infill to avoid unsupported perimeters (but the first one, this one is always good) - if (this->config->counterbore_hole_bridging != chbNone - && this->lower_slices != NULL && !this->lower_slices->empty()) { + if (this->lower_slices != NULL && !this->lower_slices->empty()) { const coordf_t bridged_infill_margin = scale_(BRIDGE_INFILL_MARGIN); for (size_t surface_idx = 0; surface_idx < all_surfaces.size(); surface_idx++) { @@ -1738,11 +1741,8 @@ void PerimeterGenerator::process_no_bridge(Surfaces& all_surfaces, coord_t perim //compute our unsupported surface ExPolygons unsupported = diff_ex(last, *this->lower_slices, ApplySafetyOffset::Yes); if (!unsupported.empty()) { - // remove small overhangs (when using chbFilled we need to be less aggressive in removing small overhangs, - // to avoid affecting bridging detection.) - const int outset_divisor = this->config->counterbore_hole_bridging.value == chbFilled ? 2 : 1; - ExPolygons unsupported_filtered = offset2_ex(unsupported, double(-perimeter_spacing), - double(perimeter_spacing) / outset_divisor); + //remove small overhangs + ExPolygons unsupported_filtered = offset2_ex(unsupported, double(-perimeter_spacing), double(perimeter_spacing)); if (!unsupported_filtered.empty()) { //to_draw.insert(to_draw.end(), last.begin(), last.end()); @@ -1759,13 +1759,18 @@ void PerimeterGenerator::process_no_bridge(Surfaces& all_surfaces, coord_t perim for (ExPolygon unsupported : unsupported_filtered) { BridgeDetector detector{ unsupported, lower_island.expolygons, - perimeter_spacing }; + perimeter_spacing / 4}; // Use a finer BridgeDetector. This affects coverage resolution, not extrusion spacing. + if (detector.detect_angle(Geometry::deg2rad(this->config->bridge_angle.value))) expolygons_append(bridgeable, union_ex(detector.coverage(-1, true))); } - if (!bridgeable.empty()) { - //check if we get everything or just the bridgeable area - if (/*this->config->counterbore_hole_bridging.value == chbNoPeri || */this->config->counterbore_hole_bridging.value == chbFilled) { + if (!bridgeable.empty() && !surface->expolygon.holes.empty()) { // keep out if cannot be bridged or no holes to bridge + const coordf_t bridge_anchor_offset = std::min({bridged_infill_margin, coordf_t(perimeter_spacing), coordf_t(ext_perimeter_width)}); + + // Handle filled vs partial counterbore bridging modes. + if (this->config->counterbore_hole_bridging.value == chbFilled) { + unsupported_filtered = offset_ex(unsupported_filtered, -perimeter_spacing); // shrink it to survive the strict bridge-candidate filter + //we bridge everything, even the not-bridgeable bits for (size_t i = 0; i < unsupported_filtered.size();) { ExPolygon& poly_unsupp = *(unsupported_filtered.begin() + i); @@ -1785,139 +1790,96 @@ void PerimeterGenerator::process_no_bridge(Surfaces& all_surfaces, coord_t perim unsupported_filtered.erase(unsupported_filtered.begin() + i); } } - unsupported_filtered = intersection_ex(last, - offset_ex(unsupported_filtered, 0.5 * double(bridged_infill_margin))); - if (this->config->counterbore_hole_bridging.value == chbFilled) { - for (ExPolygon& expol : unsupported_filtered) { - //check if the holes won't be covered by the upper layer - //TODO: if we want to do that, we must modify the geometry before making perimeters. - //if (this->upper_slices != nullptr && !this->upper_slices->expolygons.empty()) { - // for (Polygon &poly : expol.holes) poly.make_counter_clockwise(); - // float perimeterwidth = this->config->perimeters == 0 ? 0 : (this->ext_perimeter_flow.scaled_width() + (this->config->perimeters - 1) + this->perimeter_flow.scaled_spacing()); - // std::cout << "test upper slices with perimeterwidth=" << perimeterwidth << "=>" << offset_ex(this->upper_slices->expolygons, -perimeterwidth).size(); - // if (intersection(Polygons() = { expol.holes }, to_polygons(offset_ex(this->upper_slices->expolygons, -this->ext_perimeter_flow.scaled_width() / 2))).empty()) { - // std::cout << " EMPTY"; - // expol.holes.clear(); - // } else { - // } - // std::cout << "\n"; - //} else { - expol.holes.clear(); - //} - //detect inside volume - for (size_t surface_idx_other = 0; surface_idx_other < all_surfaces.size(); surface_idx_other++) { - if (surface_idx == surface_idx_other) continue; - if (intersection_ex(ExPolygons() = { expol }, ExPolygons() = { all_surfaces[surface_idx_other].expolygon }).size() > 0) { - //this means that other_surf was inside an expol holes - //as we removed them, we need to add a new one - ExPolygons new_poly = offset2_ex(ExPolygons{ all_surfaces[surface_idx_other].expolygon }, double(-bridged_infill_margin - perimeter_spacing), double(perimeter_spacing)); - if (new_poly.size() == 1) { - all_surfaces[surface_idx_other].expolygon = new_poly[0]; - expol.holes.push_back(new_poly[0].contour); + unsupported_filtered = offset_ex(unsupported_filtered, perimeter_spacing + bridge_anchor_offset); // restore it back to its original size and add anchor + unsupported_filtered = intersection_ex(last, unsupported_filtered); // clamp to the original surface, to avoid creating new unsupported areas + + for (ExPolygon& expol : unsupported_filtered) { + // Remove holes that need sacrificial fill, but keep holes + // whose wall is already supported by the lower layer. + const float hole_wall_width = float(ext_perimeter_width / 2); + for (size_t hole_idx = 0; hole_idx < expol.holes.size();) { + Polygon hole_area_contour = expol.holes[hole_idx]; + hole_area_contour.make_counter_clockwise(); + + const ExPolygons hole_area = { ExPolygon(hole_area_contour) }; + ExPolygons hole_wall_area = diff_ex( + offset_ex(hole_area_contour, hole_wall_width), + hole_area, + ApplySafetyOffset::Yes); + hole_wall_area = intersection_ex(hole_wall_area, ExPolygons{ expol }, ApplySafetyOffset::Yes); + if (!hole_wall_area.empty() && + intersection_ex(hole_wall_area, *this->lower_slices, ApplySafetyOffset::Yes).empty()) + expol.holes.erase(expol.holes.begin() + hole_idx); + // After erase(), the next hole shifts into the same index. So hole_idx + // must not be incremented, otherwise the next hole would be skipped. + else + ++hole_idx; // keep this hole, it won't be bridged, so we need to keep it as a hole + } + + //detect inside volume + for (size_t surface_idx_other = 0; surface_idx_other < all_surfaces.size(); surface_idx_other++) { + if (surface_idx == surface_idx_other) continue; + if (intersection_ex(ExPolygons() = { expol }, ExPolygons() = { all_surfaces[surface_idx_other].expolygon }).size() > 0) { + //this means that other_surf was inside an expol holes + //as we removed them, we need to add a new one + ExPolygons new_poly = offset2_ex(ExPolygons{ all_surfaces[surface_idx_other].expolygon }, double(-bridged_infill_margin - perimeter_spacing), double(perimeter_spacing)); + if (new_poly.size() == 1) { + all_surfaces[surface_idx_other].expolygon = new_poly[0]; + expol.holes.push_back(new_poly[0].contour); + expol.holes.back().make_clockwise(); + } else { + for (size_t idx = 0; idx < new_poly.size(); idx++) { + Surface new_surf = all_surfaces[surface_idx_other]; + new_surf.expolygon = new_poly[idx]; + all_surfaces.push_back(new_surf); + expol.holes.push_back(new_poly[idx].contour); expol.holes.back().make_clockwise(); - } else { - for (size_t idx = 0; idx < new_poly.size(); idx++) { - Surface new_surf = all_surfaces[surface_idx_other]; - new_surf.expolygon = new_poly[idx]; - all_surfaces.push_back(new_surf); - expol.holes.push_back(new_poly[idx].contour); - expol.holes.back().make_clockwise(); - } - all_surfaces.erase(all_surfaces.begin() + surface_idx_other); - if (surface_idx_other < surface_idx) { - surface_idx--; - surface = &all_surfaces[surface_idx]; - } - surface_idx_other--; } + all_surfaces.erase(all_surfaces.begin() + surface_idx_other); + if (surface_idx_other < surface_idx) { + surface_idx--; + surface = &all_surfaces[surface_idx]; + } + surface_idx_other--; } } } - } //TODO: add other polys as holes inside this one (-margin) - } else if (/*this->config->counterbore_hole_bridging.value == chbBridgesOverhangs || */this->config->counterbore_hole_bridging.value == chbBridges) { - // Partially bridged counterbore handling should not rewrite generic bridge islands - // because by doing so regular bridges will lose their overhang-wall perimeters. - if (surface->expolygon.holes.empty()) { - unsupported_filtered.clear(); // "Partially bridged" only applies to hole-bearing bridge islands. - continue; - } - //simplify to avoid most of artefacts from printing lines. - ExPolygons bridgeable_simplified; + } else { // if(this->config->counterbore_hole_bridging.value == chbBridges) + // Orca: Partial counterbore bridging is mask-based. Preserve the supported + // remainder (`last`) and use simplified BridgeDetector coverage to derive the + // bridgeable counterbore span. The span is grown from supported material, + // shrunk back, stripped from `last`, and expanded back. It is then prevented + // from intruding deeper into `last` than the explicit anchor overlap. + // Finally, add the allowed anchor band from `last` then remove the + // narrow hole-side wall contact, which must remain unbridgeable. + + last = diff_ex(last, unsupported_filtered, ApplySafetyOffset::Yes); + + ExPolygons bridgeable_filtered; for (ExPolygon& poly : bridgeable) { - poly.simplify(perimeter_spacing, &bridgeable_simplified); + poly.simplify(perimeter_spacing, &bridgeable_filtered); } - bridgeable_simplified = offset2_ex(bridgeable_simplified, -ext_perimeter_width, ext_perimeter_width); - //bridgeable_simplified = intersection_ex(bridgeable_simplified, unsupported_filtered); - //offset by perimeter spacing because the simplify may have reduced it a bit. - //it's not dangerous as it will be intersected by 'unsupported' later - //FIXME: add overlap in this->fill_surfaces->append - //FIXME: it overlap inside unsuppported not-bridgeable area! + bridgeable_filtered = opening_ex(bridgeable_filtered, ext_perimeter_width); - //bridgeable_simplified = offset2_ex(bridgeable_simplified, (double)-perimeter_spacing, (double)perimeter_spacing * 2); - //ExPolygons unbridgeable = offset_ex(diff_ex(unsupported, bridgeable_simplified), perimeter_spacing * 3 / 2); - //ExPolygons unbridgeable = intersection_ex(unsupported, diff_ex(unsupported_filtered, offset_ex(bridgeable_simplified, ext_perimeter_width / 2))); - //unbridgeable = offset2_ex(unbridgeable, -ext_perimeter_width, ext_perimeter_width); + // Get rid of coarseness of the resulted bridgeable area by using the original supported area as reference. + // This is to avoid keeping tiny bridgeable areas that are far from the supported area, or protrude into it. + bridgeable_filtered = union_ex(offset_ex(last, perimeter_spacing), bridgeable_filtered); + bridgeable_filtered = offset_ex(bridgeable_filtered, -perimeter_spacing); + bridgeable_filtered = diff_ex(bridgeable_filtered, last, ApplySafetyOffset::Yes); + bridgeable_filtered = opening_ex(bridgeable_filtered, perimeter_spacing); // filter noise from the diff_ex + bridgeable_filtered = offset_ex(bridgeable_filtered, perimeter_spacing); // restore the size to the original bridgeable area + // Safety measure: Keep the bridge mask from intruding deeper into the + // supported anchor region (`last`) than the explicit anchor overlap. + bridgeable_filtered = diff_ex(bridgeable_filtered, offset_ex(last, -bridge_anchor_offset)); - - // if (this->config->counterbore_hole_bridging.value == chbBridges) { - ExPolygons unbridgeable = unsupported_filtered; - for (ExPolygon& expol : unbridgeable) - expol.holes.clear(); - unbridgeable = diff_ex(unbridgeable, bridgeable_simplified); - unbridgeable = offset2_ex(unbridgeable, -ext_perimeter_width * 2, ext_perimeter_width * 2); - ExPolygons bridges_temp = offset2_ex(intersection_ex(last, diff_ex(unsupported_filtered, unbridgeable), ApplySafetyOffset::Yes), -ext_perimeter_width / 4, ext_perimeter_width / 4); - //remove the overhangs section from the surface polygons - ExPolygons reference = last; - last = diff_ex(last, unsupported_filtered); - //ExPolygons no_bridge = diff_ex(offset_ex(unbridgeable, ext_perimeter_width * 3 / 2), last); - //bridges_temp = diff_ex(bridges_temp, no_bridge); - coordf_t offset_to_do = bridged_infill_margin; - bool first = true; - unbridgeable = diff_ex(unbridgeable, offset_ex(bridges_temp, ext_perimeter_width)); - while (offset_to_do > ext_perimeter_width * 1.5) { - unbridgeable = offset2_ex(unbridgeable, -ext_perimeter_width / 4, ext_perimeter_width * 2.25, ClipperLib::jtSquare); - bridges_temp = diff_ex(bridges_temp, unbridgeable); - bridges_temp = offset_ex(bridges_temp, ext_perimeter_width, ClipperLib::jtMiter, 6.); - unbridgeable = diff_ex(unbridgeable, offset_ex(bridges_temp, ext_perimeter_width)); - offset_to_do -= ext_perimeter_width; - first = false; - } - unbridgeable = offset_ex(unbridgeable, ext_perimeter_width + offset_to_do, ClipperLib::jtSquare); - bridges_temp = diff_ex(bridges_temp, unbridgeable); - unsupported_filtered = offset_ex(bridges_temp, offset_to_do); - unsupported_filtered = intersection_ex(unsupported_filtered, reference); - - // Normalize anchor size for partial bridges: - // derive the bridge core first, then add a fixed overlap into support. - const coordf_t anchor_overlap = bridged_infill_margin; - ExPolygons bridge_core = diff_ex(unsupported_filtered, support, ApplySafetyOffset::Yes); - if (bridge_core.empty()) { - bridge_core = unsupported_filtered; - } - ExPolygons anchor_overlap_area = intersection_ex( - offset_ex(bridge_core, anchor_overlap), - support, - ApplySafetyOffset::Yes); - unsupported_filtered = union_ex(bridge_core, anchor_overlap_area); - unsupported_filtered = intersection_ex(unsupported_filtered, reference); - // } else { - // ExPolygons unbridgeable = intersection_ex(unsupported, diff_ex(unsupported_filtered, offset_ex(bridgeable_simplified, ext_perimeter_width / 2))); - // unbridgeable = offset2_ex(unbridgeable, -ext_perimeter_width, ext_perimeter_width); - // unsupported_filtered = unbridgeable; - - // ////put the bridge area inside the unsupported_filtered variable - // //unsupported_filtered = intersection_ex(last, - // // diff_ex( - // // offset_ex(bridgeable_simplified, (double)perimeter_spacing / 2), - // // unbridgeable - // // ) - // // ); - // } - } else { - unsupported_filtered.clear(); + ExPolygons bridge_anchor_areas = intersection_ex(last, offset_ex(unsupported_filtered, bridge_anchor_offset)); + unsupported_filtered = union_ex(bridgeable_filtered, bridge_anchor_areas); // add bridge anchor + unsupported_filtered = opening_ex(unsupported_filtered, bridge_anchor_offset); // remove anchor area from hole-side walls, it must remain unbridgeable + // TODO: Fix the case with thin outer walls around the bridge (1~2 walls) where classic wall + // might generate two walls in a tiny space or non at all if "Detect thin walls" is not activated } } else { unsupported_filtered.clear();