diff --git a/xs/src/libslic3r/ClipperUtils.cpp b/xs/src/libslic3r/ClipperUtils.cpp index 7625cba49a9..b04a018a731 100644 --- a/xs/src/libslic3r/ClipperUtils.cpp +++ b/xs/src/libslic3r/ClipperUtils.cpp @@ -278,7 +278,9 @@ ClipperLib::Paths _offset(const Slic3r::ExPolygon &expolygon, const float delta, // 3) Subtract holes from the contours. ClipperLib::Paths output; - { + if (holes.empty()) { + output = std::move(contours); + } else { ClipperLib::Clipper clipper; clipper.Clear(); clipper.AddPaths(contours, ClipperLib::ptSubject, true); @@ -291,26 +293,22 @@ ClipperLib::Paths _offset(const Slic3r::ExPolygon &expolygon, const float delta, return output; } -// This is a safe variant of the polygon offset, tailored for a single ExPolygon: -// a single polygon with multiple non-overlapping holes. -// Each contour and hole is offsetted separately, then the holes are subtracted from the outer contours. +// This is a safe variant of the polygons offset, tailored for multiple ExPolygons. +// It is required, that the input expolygons do not overlap and that the holes of each ExPolygon don't intersect with their respective outer contours. +// Each ExPolygon is offsetted separately, then the offsetted ExPolygons are united. ClipperLib::Paths _offset(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType, double miterLimit) { -// printf("new ExPolygon offset\n"); const float delta_scaled = delta * float(CLIPPER_OFFSET_SCALE); - ClipperLib::Paths contours; - ClipperLib::Paths holes; - contours.reserve(expolygons.size()); - { - size_t n_holes = 0; - for (size_t i = 0; i < expolygons.size(); ++ i) - n_holes += expolygons[i].holes.size(); - holes.reserve(n_holes); - } - + // Offsetted ExPolygons before they are united. + ClipperLib::Paths contours_cummulative; + contours_cummulative.reserve(expolygons.size()); + // How many non-empty offsetted expolygons were actually collected into contours_cummulative? + // If only one, then there is no need to do a final union. + size_t expolygons_collected = 0; for (Slic3r::ExPolygons::const_iterator it_expoly = expolygons.begin(); it_expoly != expolygons.end(); ++ it_expoly) { // 1) Offset the outer contour. + ClipperLib::Paths contours; { ClipperLib::Path input = Slic3rMultiPoint_to_ClipperPath(it_expoly->contour); scaleClipperPolygon(input); @@ -320,37 +318,81 @@ ClipperLib::Paths _offset(const Slic3r::ExPolygons &expolygons, const float delt else co.MiterLimit = miterLimit; co.AddPath(input, joinType, ClipperLib::etClosedPolygon); - ClipperLib::Paths out; - co.Execute(out, delta_scaled); - contours.insert(contours.end(), out.begin(), out.end()); + co.Execute(contours, delta_scaled); } + if (contours.empty()) + // No need to try to offset the holes. + continue; + + if (it_expoly->holes.empty()) { + // No need to subtract holes from the offsetted expolygon, we are done. + contours_cummulative.insert(contours_cummulative.end(), contours.begin(), contours.end()); + ++ expolygons_collected; + } else { + // 2) Offset the holes one by one, collect the offsetted holes. + ClipperLib::Paths holes; + { + for (Polygons::const_iterator it_hole = it_expoly->holes.begin(); it_hole != it_expoly->holes.end(); ++ it_hole) { + ClipperLib::Path input = Slic3rMultiPoint_to_ClipperPath_reversed(*it_hole); + scaleClipperPolygon(input); + ClipperLib::ClipperOffset co; + if (joinType == jtRound) + co.ArcTolerance = miterLimit * double(CLIPPER_OFFSET_SCALE); + else + co.MiterLimit = miterLimit; + co.AddPath(input, joinType, ClipperLib::etClosedPolygon); + ClipperLib::Paths out; + co.Execute(out, - delta_scaled); + holes.insert(holes.end(), out.begin(), out.end()); + } + } - // 2) Offset the holes one by one, collect the results. - { - for (Polygons::const_iterator it_hole = it_expoly->holes.begin(); it_hole != it_expoly->holes.end(); ++ it_hole) { - ClipperLib::Path input = Slic3rMultiPoint_to_ClipperPath_reversed(*it_hole); - scaleClipperPolygon(input); - ClipperLib::ClipperOffset co; - if (joinType == jtRound) - co.ArcTolerance = miterLimit * double(CLIPPER_OFFSET_SCALE); - else - co.MiterLimit = miterLimit; - co.AddPath(input, joinType, ClipperLib::etClosedPolygon); - ClipperLib::Paths out; - co.Execute(out, - delta_scaled); - holes.insert(holes.end(), out.begin(), out.end()); + // 3) Subtract holes from the contours. + if (holes.empty()) { + // No hole remaining after an offset. Just copy the outer contour. + contours_cummulative.insert(contours_cummulative.end(), contours.begin(), contours.end()); + ++ expolygons_collected; + } else if (delta < 0) { + // Negative offset. There is a chance, that the offsetted hole intersects the outer contour. + // Subtract the offsetted holes from the offsetted contours. + ClipperLib::Clipper clipper; + clipper.Clear(); + clipper.AddPaths(contours, ClipperLib::ptSubject, true); + clipper.AddPaths(holes, ClipperLib::ptClip, true); + ClipperLib::Paths output; + clipper.Execute(ClipperLib::ctDifference, output, ClipperLib::pftNonZero, ClipperLib::pftNonZero); + if (! output.empty()) { + contours_cummulative.insert(contours_cummulative.end(), output.begin(), output.end()); + ++ expolygons_collected; + } else { + // The offsetted holes have eaten up the offsetted outer contour. + } + } else { + // Positive offset. As long as the Clipper offset does what one expects it to do, the offsetted hole will have a smaller + // area than the original hole or even disappear, therefore there will be no new intersections. + // Just collect the reversed holes. + contours_cummulative.reserve(contours.size() + holes.size()); + contours_cummulative.insert(contours_cummulative.end(), contours.begin(), contours.end()); + // Reverse the holes in place. + for (size_t i = 0; i < holes.size(); ++ i) + std::reverse(holes[i].begin(), holes[i].end()); + contours_cummulative.insert(contours_cummulative.end(), holes.begin(), holes.end()); + ++ expolygons_collected; } } } - // 3) Subtract holes from the contours. + // 4) Unite the offsetted expolygons. ClipperLib::Paths output; - { + if (expolygons_collected > 1 && delta > 0) { + // There is a chance that the outwards offsetted expolygons may intersect. Perform a union. ClipperLib::Clipper clipper; - clipper.Clear(); - clipper.AddPaths(contours, ClipperLib::ptSubject, true); - clipper.AddPaths(holes, ClipperLib::ptClip, true); - clipper.Execute(ClipperLib::ctDifference, output, ClipperLib::pftNonZero, ClipperLib::pftNonZero); + clipper.Clear(); + clipper.AddPaths(contours_cummulative, ClipperLib::ptSubject, true); + clipper.Execute(ClipperLib::ctUnion, output, ClipperLib::pftNonZero, ClipperLib::pftNonZero); + } else { + // Negative offset. The shrunk expolygons shall not mutually intersect. Just copy the output. + output = std::move(contours_cummulative); } // 4) Unscale the output. diff --git a/xs/src/libslic3r/LayerRegion.cpp b/xs/src/libslic3r/LayerRegion.cpp index f0063114a99..5536baf743d 100644 --- a/xs/src/libslic3r/LayerRegion.cpp +++ b/xs/src/libslic3r/LayerRegion.cpp @@ -302,8 +302,10 @@ LayerRegion::process_external_surfaces(const Layer* lower_layer) polygons_append(polys, STDMOVE(s1)); for (size_t j = i + 1; j < top.size(); ++ j) { Surface &s2 = top[j]; - if (! s2.empty() && surfaces_could_merge(s1, s2)) + if (! s2.empty() && surfaces_could_merge(s1, s2)) { polygons_append(polys, STDMOVE(s2)); + s2.clear(); + } } if (s1.surface_type == stTop) // Trim the top surfaces by the bottom surfaces. This gives the priority to the bottom surfaces. @@ -326,8 +328,10 @@ LayerRegion::process_external_surfaces(const Layer* lower_layer) polygons_append(polys, STDMOVE(s1)); for (size_t j = i + 1; j < internal.size(); ++ j) { Surface &s2 = internal[j]; - if (! s2.empty() && surfaces_could_merge(s1, s2)) + if (! s2.empty() && surfaces_could_merge(s1, s2)) { polygons_append(polys, STDMOVE(s2)); + s2.clear(); + } } ExPolygons new_expolys = diff_ex(polys, new_polygons); polygons_append(new_polygons, to_polygons(new_expolys)); diff --git a/xs/src/libslic3r/Surface.hpp b/xs/src/libslic3r/Surface.hpp index adf71cc068b..9ad86d0ed2d 100644 --- a/xs/src/libslic3r/Surface.hpp +++ b/xs/src/libslic3r/Surface.hpp @@ -57,6 +57,7 @@ class Surface operator Polygons() const; double area() const; bool empty() const { return expolygon.empty(); } + void clear() { expolygon.clear(); } bool is_solid() const; bool is_external() const; bool is_internal() const;