diff --git a/src/clipper/clipper.cpp b/src/clipper/clipper.cpp index e46a0797c..84d68b1e6 100644 --- a/src/clipper/clipper.cpp +++ b/src/clipper/clipper.cpp @@ -155,7 +155,20 @@ bool PolyNode::IsHole() const node = node->Parent; } return result; -} +} + +void PolyTree::RemoveOutermostPolygon() +{ + if (this->ChildCount() == 1 && this->Childs[0]->ChildCount() > 0) { + PolyNode *outerNode = this->Childs[0]; + this->Childs.reserve(outerNode->ChildCount()); + this->Childs[0] = outerNode->Childs[0]; + this->Childs[0]->Parent = outerNode->Parent; + for (int i = 1; i < outerNode->ChildCount(); ++i) + this->AddChild(*outerNode->Childs[i]); + } else + this->Clear(); +} //------------------------------------------------------------------------------ // Miscellaneous global functions @@ -3444,7 +3457,8 @@ void ClipperOffset::Execute(Paths& solution, double delta) clpr.AddPath(outer, ptSubject, true); clpr.ReverseSolution(true); clpr.Execute(ctUnion, solution, pftNegative, pftNegative); - if (solution.size() > 0) solution.erase(solution.begin()); + if (! solution.empty()) + solution.erase(solution.begin()); } } //------------------------------------------------------------------------------ @@ -3475,17 +3489,7 @@ void ClipperOffset::Execute(PolyTree& solution, double delta) clpr.ReverseSolution(true); clpr.Execute(ctUnion, solution, pftNegative, pftNegative); //remove the outer PolyNode rectangle ... - if (solution.ChildCount() == 1 && solution.Childs[0]->ChildCount() > 0) - { - PolyNode* outerNode = solution.Childs[0]; - solution.Childs.reserve(outerNode->ChildCount()); - solution.Childs[0] = outerNode->Childs[0]; - solution.Childs[0]->Parent = outerNode->Parent; - for (int i = 1; i < outerNode->ChildCount(); ++i) - solution.AddChild(*outerNode->Childs[i]); - } - else - solution.Clear(); + solution.RemoveOutermostPolygon(); } } //------------------------------------------------------------------------------ diff --git a/src/clipper/clipper.hpp b/src/clipper/clipper.hpp index 74e6601f9..b1dae3c24 100644 --- a/src/clipper/clipper.hpp +++ b/src/clipper/clipper.hpp @@ -180,6 +180,7 @@ public: PolyNode* GetFirst() const { return Childs.empty() ? nullptr : Childs.front(); } void Clear() { AllNodes.clear(); Childs.clear(); } int Total() const; + void RemoveOutermostPolygon(); private: PolyTree(const PolyTree &src) = delete; PolyTree& operator=(const PolyTree &src) = delete; @@ -521,6 +522,7 @@ public: double MiterLimit; double ArcTolerance; double ShortestEdgeLength; + private: Paths m_destPolys; Path m_srcPoly; @@ -528,6 +530,8 @@ private: std::vector m_normals; double m_delta, m_sinA, m_sin, m_cos; double m_miterLim, m_StepsPerRad; + // x: index of the lowest contour in m_polyNodes + // y: index of the lowest point in the lowest contour IntPoint m_lowest; PolyNode m_polyNodes; diff --git a/src/libslic3r/Brim.cpp b/src/libslic3r/Brim.cpp index db31975e3..69a9e87f9 100644 --- a/src/libslic3r/Brim.cpp +++ b/src/libslic3r/Brim.cpp @@ -177,7 +177,7 @@ static ExPolygons top_level_outer_brim_area(const Print &print append(brim_area_object, diff_ex(offset(ex_poly.contour, brim_width + brim_separation, ClipperLib::jtSquare), offset(ex_poly.contour, brim_separation, ClipperLib::jtSquare))); if (brim_type == BrimType::btOuterOnly || brim_type == BrimType::btNoBrim) - append(no_brim_area_object, offset_ex(ex_poly.holes, -no_brim_offset, ClipperLib::jtSquare)); + append(no_brim_area_object, shrink_ex(ex_poly.holes, no_brim_offset, ClipperLib::jtSquare)); if (brim_type == BrimType::btInnerOnly || brim_type == BrimType::btNoBrim) append(no_brim_area_object, diff_ex(offset(ex_poly.contour, no_brim_offset, ClipperLib::jtSquare), ex_poly.holes)); @@ -230,13 +230,13 @@ static ExPolygons inner_brim_area(const Print &print, } if (brim_type == BrimType::btInnerOnly || brim_type == BrimType::btOuterAndInner) - append(brim_area_object, diff_ex(offset_ex(ex_poly.holes, -brim_separation, ClipperLib::jtSquare), offset_ex(ex_poly.holes, -brim_width - brim_separation, ClipperLib::jtSquare))); + append(brim_area_object, diff_ex(shrink_ex(ex_poly.holes, brim_separation, ClipperLib::jtSquare), shrink_ex(ex_poly.holes, brim_width + brim_separation, ClipperLib::jtSquare))); if (brim_type == BrimType::btInnerOnly || brim_type == BrimType::btNoBrim) append(no_brim_area_object, diff_ex(offset(ex_poly.contour, no_brim_offset, ClipperLib::jtSquare), ex_poly.holes)); if (brim_type == BrimType::btOuterOnly || brim_type == BrimType::btNoBrim) - append(no_brim_area_object, offset_ex(ex_poly.holes, -no_brim_offset, ClipperLib::jtSquare)); + append(no_brim_area_object, shrink_ex(ex_poly.holes, no_brim_offset, ClipperLib::jtSquare)); append(holes_object, ex_poly.holes); } @@ -385,10 +385,10 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance size_t num_loops = size_t(floor(max_brim_width(print.objects()) / flow.spacing())); for (size_t i = 0; i < num_loops; ++i) { try_cancel(); - islands = offset(islands, float(flow.scaled_spacing()), ClipperLib::jtSquare); + islands = expand(islands, float(flow.scaled_spacing()), ClipperLib::jtSquare); for (Polygon &poly : islands) poly.douglas_peucker(SCALED_RESOLUTION); - polygons_append(loops, offset(islands, -0.5f * float(flow.scaled_spacing()))); + polygons_append(loops, shrink(islands, 0.5f * float(flow.scaled_spacing()))); } loops = union_pt_chained_outside_in(loops); diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index e8f87a6e3..5e54da684 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -117,15 +117,6 @@ Polylines PolyTreeToPolylines(ClipperLib::PolyTree &&polytree) return out; } -ExPolygons ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input) -{ - ClipperLib::Clipper clipper; - clipper.AddPaths(input, ClipperLib::ptSubject, true); - ClipperLib::PolyTree polytree; - clipper.Execute(ClipperLib::ctUnion, polytree, ClipperLib::pftEvenOdd, ClipperLib::pftEvenOdd); // offset results work with both EvenOdd and NonZero - return PolyTreeToExPolygons(std::move(polytree)); -} - #if 0 // Global test. bool has_duplicate_points(const ClipperLib::PolyTree &polytree) @@ -165,23 +156,78 @@ bool has_duplicate_points(const ClipperLib::PolyTree &polytree) } #endif -// Offset outside by 10um, one by one. -template -static ClipperLib::Paths safety_offset(PathsProvider &&paths) +template +TResult clipper_do( + const ClipperLib::ClipType clipType, + TSubj && subject, + TClip && clip, + const ClipperLib::PolyFillType fillType) +{ + ClipperLib::Clipper clipper; + clipper.AddPaths(std::forward(subject), ClipperLib::ptSubject, true); + clipper.AddPaths(std::forward(clip), ClipperLib::ptClip, true); + TResult retval; + clipper.Execute(clipType, retval, fillType, fillType); + return retval; +} + +template +TResult clipper_do( + const ClipperLib::ClipType clipType, + TSubj && subject, + TClip && clip, + const ClipperLib::PolyFillType fillType, + const ApplySafetyOffset do_safety_offset) +{ + // Safety offset only allowed on intersection and difference. + assert(do_safety_offset == ApplySafetyOffset::No || clipType != ClipperLib::ctUnion); + return do_safety_offset == ApplySafetyOffset::Yes ? + clipper_do(clipType, std::forward(subject), safety_offset(std::forward(clip)), fillType) : + clipper_do(clipType, std::forward(subject), std::forward(clip), fillType); +} + +template +TResult clipper_union( + TSubj && subject, + // fillType pftNonZero and pftPositive "should" produce the same result for "normalized with implicit union" set of polygons + const ClipperLib::PolyFillType fillType = ClipperLib::pftNonZero) +{ + ClipperLib::Clipper clipper; + clipper.AddPaths(std::forward(subject), ClipperLib::ptSubject, true); + TResult retval; + clipper.Execute(ClipperLib::ctUnion, retval, fillType, fillType); + return retval; +} + +// Perform union of input polygons using the positive rule, convert to ExPolygons. +//FIXME is there any benefit of not doing the boolean / using pftEvenOdd? +ExPolygons ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input, bool do_union) +{ + return PolyTreeToExPolygons(clipper_union(input, do_union ? ClipperLib::pftNonZero : ClipperLib::pftEvenOdd)); +} + +// Offset CCW contours outside, CW contours (holes) inside. +// Don't calculate union of the output paths. +template +static ClipperLib::Paths raw_offset(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit) { ClipperLib::ClipperOffset co; ClipperLib::Paths out; out.reserve(paths.size()); ClipperLib::Paths out_this; + if (joinType == jtRound) + co.ArcTolerance = miterLimit; + else + co.MiterLimit = miterLimit; + co.ShortestEdgeLength = double(std::abs(offset * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR)); for (const ClipperLib::Path &path : paths) { co.Clear(); - co.MiterLimit = 2.; // Execute reorients the contours so that the outer most contour has a positive area. Thus the output // contours will be CCW oriented even though the input paths are CW oriented. // Offset is applied after contour reorientation, thus the signum of the offset value is reversed. - co.AddPath(path, ClipperLib::jtMiter, ClipperLib::etClosedPolygon); - bool ccw = ClipperLib::Orientation(path); - co.Execute(out_this, ccw ? ClipperSafetyOffset : - ClipperSafetyOffset); + co.AddPath(path, joinType, endType); + bool ccw = endType == ClipperLib::etClosedPolygon ? ClipperLib::Orientation(path) : true; + co.Execute(out_this, ccw ? offset : - offset); if (! ccw) { // Reverse the resulting contours. for (ClipperLib::Path &path : out_this) @@ -192,38 +238,71 @@ static ClipperLib::Paths safety_offset(PathsProvider &&paths) return out; } -// Only safe for a single path. -template -ClipperLib::Paths _offset(PathsProvider &&input, ClipperLib::EndType endType, const float delta, ClipperLib::JoinType joinType, double miterLimit) +template +static ClipperLib::Paths raw_offset_polyline(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit) { - // perform offset - ClipperLib::ClipperOffset co; - if (joinType == jtRound) - co.ArcTolerance = miterLimit; - else - co.MiterLimit = miterLimit; - float delta_scaled = delta; - co.ShortestEdgeLength = double(std::abs(delta_scaled * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR)); - co.AddPaths(std::forward(input), joinType, endType); - ClipperLib::Paths retval; - co.Execute(retval, delta_scaled); - return retval; + assert(offset > 0); + return raw_offset(std::forward(paths), offset, joinType, miterLimit); } -Slic3r::Polygons offset(const Slic3r::Polygon& polygon, const float delta, ClipperLib::JoinType joinType, double miterLimit) - { return to_polygons(_offset(ClipperUtils::SinglePathProvider(polygon.points), ClipperLib::etClosedPolygon, delta, joinType, miterLimit)); } +// Offset outside by 10um, one by one. +template +static ClipperLib::Paths safety_offset(PathsProvider &&paths) +{ + return raw_offset(std::forward(paths), ClipperSafetyOffset, DefaultJoinType, DefaultMiterLimit); +} + +template +static TResult expand_paths(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit) +{ + assert(offset > 0); + return clipper_union(raw_offset(std::forward(paths), offset, joinType, miterLimit)); +} + +template static void remove_outermost_polygon(Container & solution); +template<> static void remove_outermost_polygon(ClipperLib::Paths &solution) + { if (! solution.empty()) solution.erase(solution.begin()); } +template<> static void remove_outermost_polygon(ClipperLib::PolyTree &solution) + { solution.RemoveOutermostPolygon(); } + +template +static TResult shrink_paths(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit) +{ + assert(offset > 0); + TResult out; + if (auto raw = raw_offset(std::forward(paths), - offset, joinType, miterLimit); ! raw.empty()) { + ClipperLib::Clipper clipper; + clipper.AddPaths(raw, ClipperLib::ptSubject, true); + ClipperLib::IntRect r = clipper.GetBounds(); + clipper.AddPath({ { r.left - 10, r.bottom + 10 }, { r.right + 10, r.bottom + 10 }, { r.right + 10, r.top - 10 }, { r.left - 10, r.top - 10 } }, ClipperLib::ptSubject, true); + clipper.ReverseSolution(true); + clipper.Execute(ClipperLib::ctUnion, out, ClipperLib::pftNegative, ClipperLib::pftNegative); + remove_outermost_polygon(out); + } + return out; +} + +template +static TResult offset_paths(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit) +{ + assert(offset != 0); + return offset > 0 ? + expand_paths(std::forward(paths), offset, joinType, miterLimit) : + shrink_paths(std::forward(paths), - offset, joinType, miterLimit); +} + +Slic3r::Polygons offset(const Slic3r::Polygon &polygon, const float delta, ClipperLib::JoinType joinType, double miterLimit) + { return to_polygons(raw_offset(ClipperUtils::SinglePathProvider(polygon.points), delta, joinType, miterLimit)); } -#ifdef CLIPPERUTILS_UNSAFE_OFFSET Slic3r::Polygons offset(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType, double miterLimit) - { return to_polygons(_offset(ClipperUtils::PolygonsProvider(polygons), ClipperLib::etClosedPolygon, delta, joinType, miterLimit)); } + { return to_polygons(offset_paths(ClipperUtils::PolygonsProvider(polygons), delta, joinType, miterLimit)); } Slic3r::ExPolygons offset_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType, double miterLimit) - { return ClipperPaths_to_Slic3rExPolygons(_offset(ClipperUtils::PolygonsProvider(polygons), ClipperLib::etClosedPolygon, delta, joinType, miterLimit)); } -#endif // CLIPPERUTILS_UNSAFE_OFFSET + { return PolyTreeToExPolygons(offset_paths(ClipperUtils::PolygonsProvider(polygons), delta, joinType, miterLimit)); } Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType, double miterLimit) - { return to_polygons(_offset(ClipperUtils::SinglePathProvider(polyline.points), ClipperLib::etOpenButt, delta, joinType, miterLimit)); } + { assert(delta > 0); return to_polygons(clipper_union(raw_offset_polyline(ClipperUtils::SinglePathProvider(polyline.points), delta, joinType, miterLimit))); } Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, ClipperLib::JoinType joinType, double miterLimit) - { return to_polygons(_offset(ClipperUtils::PolylinesProvider(polylines), ClipperLib::etOpenButt, delta, joinType, miterLimit)); } + { assert(delta > 0); return to_polygons(clipper_union(raw_offset_polyline(ClipperUtils::PolylinesProvider(polylines), delta, joinType, miterLimit))); } // returns number of expolygons collected (0 or 1). static int offset_expolygon_inner(const Slic3r::ExPolygon &expoly, const float delta, ClipperLib::JoinType joinType, double miterLimit, ClipperLib::Paths &out) @@ -274,14 +353,8 @@ static int offset_expolygon_inner(const Slic3r::ExPolygon &expoly, const float d append(out, std::move(contours)); } 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()) { + // Subtract the offsetted holes from the offsetted contours. + if (auto output = clipper_do(ClipperLib::ctDifference, contours, holes, ClipperLib::pftNonZero); ! output.empty()) { append(out, std::move(output)); } else { // The offsetted holes have eaten up the offsetted outer contour. @@ -308,7 +381,7 @@ static int offset_expolygon_inner(const Slic3r::Surface &surface, const float de static int offset_expolygon_inner(const Slic3r::Surface *surface, const float delta, ClipperLib::JoinType joinType, double miterLimit, ClipperLib::Paths &out) { return offset_expolygon_inner(surface->expolygon, delta, joinType, miterLimit, out); } -ClipperLib::Paths _offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType, double miterLimit) +ClipperLib::Paths expolygon_offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType, double miterLimit) { ClipperLib::Paths out; offset_expolygon_inner(expolygon, delta, joinType, miterLimit, out); @@ -317,9 +390,9 @@ ClipperLib::Paths _offset(const Slic3r::ExPolygon &expolygon, const float delta, // 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. +// Each ExPolygon is offsetted separately. For outer offset, the the offsetted ExPolygons shall be united outside of this function. template -ClipperLib::Paths _offset(const ExPolygonVector &expolygons, const float delta, ClipperLib::JoinType joinType, double miterLimit) +static std::pair expolygons_offset_raw(const ExPolygonVector &expolygons, const float delta, ClipperLib::JoinType joinType, double miterLimit) { // Offsetted ExPolygons before they are united. ClipperLib::Paths output; @@ -329,124 +402,76 @@ ClipperLib::Paths _offset(const ExPolygonVector &expolygons, const float delta, size_t expolygons_collected = 0; for (const auto &expoly : expolygons) expolygons_collected += offset_expolygon_inner(expoly, delta, joinType, miterLimit, output); + return std::make_pair(std::move(output), expolygons_collected); +} - // 4) Unite the offsetted expolygons. - if (expolygons_collected > 1 && delta > 0) { +// See comment on expolygon_offsets_raw. In addition, for positive offset the contours are united. +template +static ClipperLib::Paths expolygons_offset(const ExPolygonVector &expolygons, const float delta, ClipperLib::JoinType joinType, double miterLimit) +{ + auto [output, expolygons_collected] = expolygons_offset_raw(expolygons, delta, joinType, miterLimit); + // Unite the offsetted expolygons. + return 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(output, ClipperLib::ptSubject, true); - clipper.Execute(ClipperLib::ctUnion, output, ClipperLib::pftNonZero, ClipperLib::pftNonZero); - } else { + clipper_union(output) : // Negative offset. The shrunk expolygons shall not mutually intersect. Just copy the output. - } - - return output; + output; +} + +// See comment on expolygons_offset_raw. In addition, the polygons are always united to conver to polytree. +template +static ClipperLib::PolyTree expolygons_offset_pt(const ExPolygonVector &expolygons, const float delta, ClipperLib::JoinType joinType, double miterLimit) +{ + auto [output, expolygons_collected] = expolygons_offset_raw(expolygons, delta, joinType, miterLimit); + // Unite the offsetted expolygons for both the + return clipper_union(output); } Slic3r::Polygons offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType, double miterLimit) - { return to_polygons(_offset(expolygon, delta, joinType, miterLimit)); } + { return to_polygons(expolygon_offset(expolygon, delta, joinType, miterLimit)); } Slic3r::Polygons offset(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType, double miterLimit) - { return to_polygons(_offset(expolygons, delta, joinType, miterLimit)); } + { return to_polygons(expolygons_offset(expolygons, delta, joinType, miterLimit)); } Slic3r::Polygons offset(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType, double miterLimit) - { return to_polygons(_offset(surfaces, delta, joinType, miterLimit)); } + { return to_polygons(expolygons_offset(surfaces, delta, joinType, miterLimit)); } Slic3r::Polygons offset(const Slic3r::SurfacesPtr &surfaces, const float delta, ClipperLib::JoinType joinType, double miterLimit) - { return to_polygons(_offset(surfaces, delta, joinType, miterLimit)); } + { return to_polygons(expolygons_offset(surfaces, delta, joinType, miterLimit)); } Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType, double miterLimit) - { return ClipperPaths_to_Slic3rExPolygons(_offset(expolygon, delta, joinType, miterLimit)); } + //FIXME one may spare one Clipper Union call. + { return ClipperPaths_to_Slic3rExPolygons(expolygon_offset(expolygon, delta, joinType, miterLimit)); } Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType, double miterLimit) - { return ClipperPaths_to_Slic3rExPolygons(_offset(expolygons, delta, joinType, miterLimit)); } + { return PolyTreeToExPolygons(expolygons_offset_pt(expolygons, delta, joinType, miterLimit)); } Slic3r::ExPolygons offset_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType, double miterLimit) - { return ClipperPaths_to_Slic3rExPolygons(_offset(surfaces, delta, joinType, miterLimit)); } + { return PolyTreeToExPolygons(expolygons_offset_pt(surfaces, delta, joinType, miterLimit)); } -#ifdef CLIPPERUTILS_UNSAFE_OFFSET -Slic3r::Polygons union_safety_offset(const Slic3r::Polygons &polygons) - { return offset(polygons, ClipperSafetyOffset); } -Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::Polygons &polygons) - { return offset_ex(polygons, ClipperSafetyOffset); } -#endif // CLIPPERUTILS_UNSAFE_OFFSET - -Slic3r::Polygons union_safety_offset(const Slic3r::ExPolygons &expolygons) - { return offset(expolygons, ClipperSafetyOffset); } -Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::ExPolygons &expolygons) - { return offset_ex(expolygons, ClipperSafetyOffset); } - -ClipperLib::Paths _offset2(const Polygons &polygons, const float delta1, const float delta2, const ClipperLib::JoinType joinType, const double miterLimit) -{ - // prepare ClipperOffset object - ClipperLib::ClipperOffset co; - if (joinType == jtRound) { - co.ArcTolerance = miterLimit; - } else { - co.MiterLimit = miterLimit; - } - float delta_scaled1 = delta1; - float delta_scaled2 = delta2; - co.ShortestEdgeLength = double(std::max(std::abs(delta_scaled1), std::abs(delta_scaled2)) * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR); - - // perform first offset - ClipperLib::Paths output1; - co.AddPaths(ClipperUtils::PolygonsProvider(polygons), joinType, ClipperLib::etClosedPolygon); - co.Execute(output1, delta_scaled1); - - // perform second offset - co.Clear(); - co.AddPaths(output1, joinType, ClipperLib::etClosedPolygon); - ClipperLib::Paths retval; - co.Execute(retval, delta_scaled2); - - return retval; -} - -Polygons offset2(const Polygons &polygons, const float delta1, const float delta2, const ClipperLib::JoinType joinType, const double miterLimit) -{ - return to_polygons(_offset2(polygons, delta1, delta2, joinType, miterLimit)); -} - -ExPolygons offset2_ex(const Polygons &polygons, const float delta1, const float delta2, const ClipperLib::JoinType joinType, const double miterLimit) -{ - return ClipperPaths_to_Slic3rExPolygons(_offset2(polygons, delta1, delta2, joinType, miterLimit)); -} - -//FIXME Vojtech: This functon may likely be optimized to avoid some of the Slic3r to Clipper -// conversions and unnecessary Clipper calls. It is not that bad now as Clipper uses Slic3r's own Point / Polygon types directly. Polygons offset2(const ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit) { - return offset(offset_ex(expolygons, delta1, joinType, miterLimit), delta2, joinType, miterLimit); + return to_polygons(offset_paths(expolygons_offset(expolygons, delta1, joinType, miterLimit), delta2, joinType, miterLimit)); } ExPolygons offset2_ex(const ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit) { - return offset_ex(offset_ex(expolygons, delta1, joinType, miterLimit), delta2, joinType, miterLimit); + return PolyTreeToExPolygons(offset_paths(expolygons_offset(expolygons, delta1, joinType, miterLimit), delta2, joinType, miterLimit)); } -template -TResult _clipper_do( - const ClipperLib::ClipType clipType, - TSubj && subject, - TClip && clip, - const ClipperLib::PolyFillType fillType) +// Offset outside, then inside produces morphological closing. All deltas should be positive. +Slic3r::Polygons closing(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit) { - ClipperLib::Clipper clipper; - clipper.AddPaths(std::forward(subject), ClipperLib::ptSubject, true); - clipper.AddPaths(std::forward(clip), ClipperLib::ptClip, true); - TResult retval; - clipper.Execute(clipType, retval, fillType, fillType); - return retval; + assert(delta1 > 0); + assert(delta2 > 0); + return to_polygons(shrink_paths(expand_paths(ClipperUtils::PolygonsProvider(polygons), delta1, joinType, miterLimit), delta2, joinType, miterLimit)); +} +Slic3r::ExPolygons closing_ex(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit) +{ + assert(delta1 > 0); + assert(delta2 > 0); + return PolyTreeToExPolygons(shrink_paths(expand_paths(ClipperUtils::PolygonsProvider(polygons), delta1, joinType, miterLimit), delta2, joinType, miterLimit)); } -template -TResult _clipper_do( - const ClipperLib::ClipType clipType, - TSubj && subject, - TClip && clip, - const ClipperLib::PolyFillType fillType, - const ApplySafetyOffset do_safety_offset) +// Offset inside, then outside produces morphological opening. All deltas should be positive. +Slic3r::Polygons opening(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit) { - // Safety offset only allowed on intersection and difference. - assert(do_safety_offset == ApplySafetyOffset::No || clipType != ClipperLib::ctUnion); - return do_safety_offset == ApplySafetyOffset::Yes ? - _clipper_do(clipType, std::forward(subject), safety_offset(std::forward(clip)), fillType) : - _clipper_do(clipType, std::forward(subject), std::forward(clip), fillType); + assert(delta1 > 0); + assert(delta2 > 0); + return to_polygons(expand_paths(shrink_paths(ClipperUtils::PolygonsProvider(polygons), delta1, joinType, miterLimit), delta2, joinType, miterLimit)); } // Fix of #117: A large fractal pyramid takes ages to slice @@ -457,29 +482,22 @@ TResult _clipper_do( // 1) Peform the Clipper operation with the output to Paths. This method handles overlaps in a reasonable time. // 2) Run Clipper Union once again to extract the PolyTree from the result of 1). template -inline ClipperLib::PolyTree _clipper_do_polytree2( +inline ClipperLib::PolyTree clipper_do_polytree( const ClipperLib::ClipType clipType, PathProvider1 &&subject, PathProvider2 &&clip, const ClipperLib::PolyFillType fillType) { - ClipperLib::Clipper clipper; - clipper.AddPaths(std::forward(subject), ClipperLib::ptSubject, true); - clipper.AddPaths(std::forward(clip), ClipperLib::ptClip, true); // Perform the operation with the output to input_subject. // This pass does not generate a PolyTree, which is a very expensive operation with the current Clipper library // if there are overapping edges. - ClipperLib::Paths input_subject; - clipper.Execute(clipType, input_subject, fillType, fillType); - // Perform an additional Union operation to generate the PolyTree ordering. - clipper.Clear(); - clipper.AddPaths(input_subject, ClipperLib::ptSubject, true); - ClipperLib::PolyTree retval; - clipper.Execute(ClipperLib::ctUnion, retval, fillType, fillType); - return retval; + if (auto output = clipper_do(clipType, subject, clip, fillType); ! output.empty()) + // Perform an additional Union operation to generate the PolyTree ordering. + return clipper_union(output, fillType); + return ClipperLib::PolyTree(); } template -inline ClipperLib::PolyTree _clipper_do_polytree2( +inline ClipperLib::PolyTree clipper_do_polytree( const ClipperLib::ClipType clipType, PathProvider1 &&subject, PathProvider2 &&clip, @@ -488,14 +506,14 @@ inline ClipperLib::PolyTree _clipper_do_polytree2( { assert(do_safety_offset == ApplySafetyOffset::No || clipType != ClipperLib::ctUnion); return do_safety_offset == ApplySafetyOffset::Yes ? - _clipper_do_polytree2(clipType, std::forward(subject), safety_offset(std::forward(clip)), fillType) : - _clipper_do_polytree2(clipType, std::forward(subject), std::forward(clip), fillType); + clipper_do_polytree(clipType, std::forward(subject), safety_offset(std::forward(clip)), fillType) : + clipper_do_polytree(clipType, std::forward(subject), std::forward(clip), fillType); } template static inline Polygons _clipper(ClipperLib::ClipType clipType, TSubj &&subject, TClip &&clip, ApplySafetyOffset do_safety_offset) { - return to_polygons(_clipper_do(clipType, std::forward(subject), std::forward(clip), ClipperLib::pftNonZero, do_safety_offset)); + return to_polygons(clipper_do(clipType, std::forward(subject), std::forward(clip), ClipperLib::pftNonZero, do_safety_offset)); } Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) @@ -529,7 +547,7 @@ Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::Polygons template static ExPolygons _clipper_ex(ClipperLib::ClipType clipType, TSubject &&subject, TClip &&clip, ApplySafetyOffset do_safety_offset, ClipperLib::PolyFillType fill_type = ClipperLib::pftNonZero) - { return PolyTreeToExPolygons(_clipper_do_polytree2(clipType, std::forward(subject), std::forward(clip), fill_type, do_safety_offset)); } + { return PolyTreeToExPolygons(clipper_do_polytree(clipType, std::forward(subject), std::forward(clip), fill_type, do_safety_offset)); } Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) { return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } @@ -578,9 +596,9 @@ Slic3r::ExPolygons intersection_ex(const Slic3r::SurfacesPtr &subject, const Sli Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, ClipperLib::PolyFillType fill_type) { return _clipper_ex(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ApplySafetyOffset::No, fill_type); } Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons &subject) - { return PolyTreeToExPolygons(_clipper_do_polytree2(ClipperLib::ctUnion, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftNonZero)); } + { return PolyTreeToExPolygons(clipper_do_polytree(ClipperLib::ctUnion, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftNonZero)); } Slic3r::ExPolygons union_ex(const Slic3r::Surfaces &subject) - { return PolyTreeToExPolygons(_clipper_do_polytree2(ClipperLib::ctUnion, ClipperUtils::SurfacesProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftNonZero)); } + { return PolyTreeToExPolygons(clipper_do_polytree(ClipperLib::ctUnion, ClipperUtils::SurfacesProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftNonZero)); } template Polylines _clipper_pl_open(ClipperLib::ClipType clipType, PathsProvider1 &&subject, PathsProvider2 &&clip) @@ -692,14 +710,15 @@ Lines _clipper_ln(ClipperLib::ClipType clipType, const Lines &subject, const Pol return retval; } +// Convert polygons / expolygons into ClipperLib::PolyTree using ClipperLib::pftEvenOdd, thus union will NOT be performed. +// If the contours are not intersecting, their orientation shall not be modified by union_pt(). ClipperLib::PolyTree union_pt(const Polygons &subject) { - return _clipper_do(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftEvenOdd); + return clipper_do(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftEvenOdd); } - ClipperLib::PolyTree union_pt(const ExPolygons &subject) { - return _clipper_do(ClipperLib::ctUnion, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftEvenOdd); + return clipper_do(ClipperLib::ctUnion, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftEvenOdd); } // Simple spatial ordering of Polynodes @@ -730,7 +749,7 @@ static void traverse_pt_noholes(const ClipperLib::PolyNodes &nodes, Polygons *ou }); } -static void traverse_pt_outside_in(const ClipperLib::PolyNodes &nodes, Polygons *retval) +static void traverse_pt_outside_in(ClipperLib::PolyNodes &&nodes, Polygons *retval) { // collect ordering points Points ordering_points; @@ -740,22 +759,20 @@ static void traverse_pt_outside_in(const ClipperLib::PolyNodes &nodes, Polygons // Perform the ordering, push results recursively. //FIXME pass the last point to chain_clipper_polynodes? - for (const ClipperLib::PolyNode *node : chain_clipper_polynodes(ordering_points, nodes)) { - retval->emplace_back(node->Contour); + for (ClipperLib::PolyNode *node : chain_clipper_polynodes(ordering_points, nodes)) { + retval->emplace_back(std::move(node->Contour)); if (node->IsHole()) // Orient a hole, which is clockwise oriented, to CCW. retval->back().reverse(); // traverse the next depth - traverse_pt_outside_in(node->Childs, retval); + traverse_pt_outside_in(std::move(node->Childs), retval); } } Polygons union_pt_chained_outside_in(const Polygons &subject) { - ClipperLib::PolyTree polytree = union_pt(subject); - Polygons retval; - traverse_pt_outside_in(polytree.Childs, &retval); + traverse_pt_outside_in(union_pt(subject).Childs, &retval); return retval; } diff --git a/src/libslic3r/ClipperUtils.hpp b/src/libslic3r/ClipperUtils.hpp index f49d922c1..829611176 100644 --- a/src/libslic3r/ClipperUtils.hpp +++ b/src/libslic3r/ClipperUtils.hpp @@ -12,16 +12,26 @@ using Slic3r::ClipperLib::jtMiter; using Slic3r::ClipperLib::jtRound; using Slic3r::ClipperLib::jtSquare; -static constexpr const float ClipperSafetyOffset = 10.f; +namespace Slic3r { + +static constexpr const float ClipperSafetyOffset = 10.f; + +static constexpr const Slic3r::ClipperLib::JoinType DefaultJoinType = Slic3r::ClipperLib::jtMiter; +//FIXME evaluate the default miter limit. 3 seems to be extreme, Cura uses 1.2. +// Mitter Limit 3 is useful for perimeter generator, where sharp corners are extruded without needing a gap fill. +// However such a high limit causes issues with large positive or negative offsets, where a sharp corner +// is extended excessively. +static constexpr const double DefaultMiterLimit = 3.; + +static constexpr const Slic3r::ClipperLib::JoinType DefaultLineJoinType = Slic3r::ClipperLib::jtSquare; +// Miter limit is ignored for jtSquare. +static constexpr const double DefaultLineMiterLimit = 0.; + enum class ApplySafetyOffset { No, Yes }; -#define CLIPPERUTILS_UNSAFE_OFFSET - -namespace Slic3r { - namespace ClipperUtils { class PathsProviderIteratorBase { public: @@ -81,6 +91,33 @@ namespace ClipperUtils { static Points s_end; }; + template + class PathsProvider { + public: + PathsProvider(const std::vector &paths) : m_paths(paths) {} + + struct iterator : public PathsProviderIteratorBase { + public: + explicit iterator(typename std::vector::const_iterator it) : m_it(it) {} + const Points& operator*() const { return *m_it; } + bool operator==(const iterator &rhs) const { return m_it == rhs.m_it; } + bool operator!=(const iterator &rhs) const { return !(*this == rhs); } + const Points& operator++(int) { return *(m_it ++); } + iterator& operator++() { ++ m_it; return *this; } + private: + typename std::vector::const_iterator m_it; + }; + + iterator cbegin() const { return iterator(m_paths.begin()); } + iterator begin() const { return this->cbegin(); } + iterator cend() const { return iterator(m_paths.end()); } + iterator end() const { return this->cend(); } + size_t size() const { return m_paths.size(); } + + private: + const std::vector &m_paths; + }; + template class MultiPointsProvider { public: @@ -261,36 +298,66 @@ namespace ClipperUtils { }; } -ExPolygons ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input); +// Perform union of input polygons using the non-zero rule, convert to ExPolygons. +ExPolygons ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input, bool do_union = false); // offset Polygons -Slic3r::Polygons offset(const Slic3r::Polygon &polygon, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); +// Wherever applicable, please use the expand() / shrink() variants instead, they convey their purpose better. +Slic3r::Polygons offset(const Slic3r::Polygon &polygon, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); // offset Polylines -Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtSquare, double miterLimit = 3); -Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtSquare, double miterLimit = 3); -Slic3r::Polygons offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); -Slic3r::Polygons offset(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); -Slic3r::Polygons offset(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); -Slic3r::Polygons offset(const Slic3r::SurfacesPtr &surfaces, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); -Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); -Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); -Slic3r::ExPolygons offset_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); +// Wherever applicable, please use the expand() / shrink() variants instead, they convey their purpose better. +// Input polygons for negative offset shall be "normalized": There must be no overlap / intersections between the input polygons. +Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType = DefaultLineJoinType, double miterLimit = DefaultLineMiterLimit); +Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, ClipperLib::JoinType joinType = DefaultLineJoinType, double miterLimit = DefaultLineMiterLimit); +Slic3r::Polygons offset(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); +Slic3r::Polygons offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); +Slic3r::Polygons offset(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); +Slic3r::Polygons offset(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); +Slic3r::Polygons offset(const Slic3r::SurfacesPtr &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); +Slic3r::ExPolygons offset_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); +Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); +Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); +Slic3r::ExPolygons offset_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); + +inline Slic3r::Polygons union_safety_offset (const Slic3r::Polygons &polygons) { return offset (polygons, ClipperSafetyOffset); } +inline Slic3r::Polygons union_safety_offset (const Slic3r::ExPolygons &expolygons) { return offset (expolygons, ClipperSafetyOffset); } +inline Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::Polygons &polygons) { return offset_ex(polygons, ClipperSafetyOffset); } +inline Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::ExPolygons &expolygons) { return offset_ex(expolygons, ClipperSafetyOffset); } + +Slic3r::Polygons union_safety_offset(const Slic3r::Polygons &expolygons); Slic3r::Polygons union_safety_offset(const Slic3r::ExPolygons &expolygons); +Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::Polygons &polygons); Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::ExPolygons &expolygons); -Slic3r::Polygons offset2(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); -Slic3r::ExPolygons offset2_ex(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); +// Aliases for the various offset(...) functions, conveying the purpose of the offset. +inline Slic3r::Polygons expand(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) + { assert(delta > 0); return offset(polygons, delta, joinType, miterLimit); } +inline Slic3r::ExPolygons expand_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) + { assert(delta > 0); return offset_ex(polygons, delta, joinType, miterLimit); } +// Input polygons for shrinking shall be "normalized": There must be no overlap / intersections between the input polygons. +inline Slic3r::Polygons shrink(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) + { assert(delta > 0); return offset(polygons, -delta, joinType, miterLimit); } +inline Slic3r::ExPolygons shrink_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) + { assert(delta > 0); return offset_ex(polygons, -delta, joinType, miterLimit); } -#ifdef CLIPPERUTILS_UNSAFE_OFFSET -Slic3r::Polygons offset(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); -Slic3r::ExPolygons offset_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); -ClipperLib::Paths _offset2(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); -Slic3r::Polygons offset2(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); -Slic3r::ExPolygons offset2_ex(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); -Slic3r::Polygons union_safety_offset(const Slic3r::Polygons &expolygons); -Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::Polygons &polygons); -#endif // CLIPPERUTILS_UNSAFE_OFFSET +// Wherever applicable, please use the opening() / closing() variants instead, they convey their purpose better. +// Input polygons for negative offset shall be "normalized": There must be no overlap / intersections between the input polygons. +Slic3r::Polygons offset2(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); +Slic3r::ExPolygons offset2_ex(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); + +// Offset outside, then inside produces morphological closing. All deltas should be positive. +Slic3r::Polygons closing(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); +inline Slic3r::Polygons closing(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) { return closing(polygons, delta, delta, joinType, miterLimit); } +Slic3r::ExPolygons closing_ex(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); +inline Slic3r::ExPolygons closing_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) { return closing_ex(polygons, delta, delta, joinType, miterLimit); } +inline Slic3r::ExPolygons closing_ex(const Slic3r::ExPolygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) { return offset2_ex(polygons, delta, - delta, joinType, miterLimit); } + +// Offset inside, then outside produces morphological opening. All deltas should be positive. +// Input polygons for opening shall be "normalized": There must be no overlap / intersections between the input polygons. +Slic3r::Polygons opening(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); +inline Slic3r::Polygons opening(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) { return opening(polygons, delta, delta, joinType, miterLimit); } +inline Slic3r::ExPolygons opening_ex(const Slic3r::ExPolygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) { return offset2_ex(polygons, - delta, delta, joinType, miterLimit); } Slic3r::Lines _clipper_ln(ClipperLib::ClipType clipType, const Slic3r::Lines &subject, const Slic3r::Polygons &clip); @@ -366,6 +433,8 @@ Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, ClipperLib::PolyFil Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons &subject); Slic3r::ExPolygons union_ex(const Slic3r::Surfaces &subject); +// Convert polygons / expolygons into ClipperLib::PolyTree using ClipperLib::pftEvenOdd, thus union will NOT be performed. +// If the contours are not intersecting, their orientation shall not be modified by union_pt(). ClipperLib::PolyTree union_pt(const Slic3r::Polygons &subject); ClipperLib::PolyTree union_pt(const Slic3r::ExPolygons &subject); diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 7ba6de7d4..117066690 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -252,11 +252,11 @@ std::vector group_fills(const Layer &layer) // Corners of infill regions, which would not be filled with an extrusion path with a radius of distance_between_surfaces/2 Polygons collapsed = diff( surfaces_polygons, - offset2(surfaces_polygons, (float)-distance_between_surfaces/2, (float)+distance_between_surfaces/2 + ClipperSafetyOffset)); + opening(surfaces_polygons, float(distance_between_surfaces /2), float(distance_between_surfaces / 2 + ClipperSafetyOffset))); //FIXME why the voids are added to collapsed here? First it is expensive, second the result may lead to some unwanted regions being // added if two offsetted void regions merge. // polygons_append(voids, collapsed); - ExPolygons extensions = intersection_ex(offset(collapsed, (float)distance_between_surfaces), voids, ApplySafetyOffset::Yes); + ExPolygons extensions = intersection_ex(expand(collapsed, float(distance_between_surfaces)), voids, ApplySafetyOffset::Yes); // Now find an internal infill SurfaceFill to add these extrusions to. SurfaceFill *internal_solid_fill = nullptr; unsigned int region_id = 0; diff --git a/src/libslic3r/Fill/FillRectilinear.cpp b/src/libslic3r/Fill/FillRectilinear.cpp index baf57f426..fc0c1b30c 100644 --- a/src/libslic3r/Fill/FillRectilinear.cpp +++ b/src/libslic3r/Fill/FillRectilinear.cpp @@ -402,19 +402,19 @@ public: hole.rotate(angle); } - double mitterLimit = 3.; + double miterLimit = DefaultMiterLimit; // for the infill pattern, don't cut the corners. // default miterLimt = 3 - //double mitterLimit = 10.; + //double miterLimit = 10.; assert(aoffset1 < 0); assert(aoffset2 <= 0); assert(aoffset2 == 0 || aoffset2 < aoffset1); // bool sticks_removed = remove_sticks(polygons_src); // if (sticks_removed) BOOST_LOG_TRIVIAL(error) << "Sticks removed!"; - polygons_outer = offset(polygons_src, float(aoffset1), ClipperLib::jtMiter, mitterLimit); + polygons_outer = offset(polygons_src, float(aoffset1), ClipperLib::jtMiter, miterLimit); if (aoffset2 < 0) - polygons_inner = offset(polygons_outer, float(aoffset2 - aoffset1), ClipperLib::jtMiter, mitterLimit); + polygons_inner = shrink(polygons_outer, float(aoffset1 - aoffset2), ClipperLib::jtMiter, miterLimit); // Filter out contours with zero area or small area, contours with 2 points only. const double min_area_threshold = 0.01 * aoffset2 * aoffset2; remove_small(polygons_outer, min_area_threshold); diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index 49de854f2..ef3e18825 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -868,7 +868,7 @@ static Polygons get_boundary_external(const Layer &layer) } // Used offset_ex for cases when another object will be in the hole of another polygon - boundary = to_polygons(offset_ex(boundary, perimeter_offset)); + boundary = expand(boundary, perimeter_offset); // Reverse all polygons for making normals point from the polygon out. for (Polygon &poly : boundary) poly.reverse(); diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index 8cc22647f..878a9ef58 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -212,7 +212,7 @@ void SeamPlacer::init(const Print& print) std::vector deltas(input.points.size(), offset); input.make_counter_clockwise(); out.front() = mittered_offset_path_scaled(input.points, deltas, 3.); - return ClipperPaths_to_Slic3rExPolygons(out); + return ClipperPaths_to_Slic3rExPolygons(out, true); // perform union }; diff --git a/src/libslic3r/MultiMaterialSegmentation.cpp b/src/libslic3r/MultiMaterialSegmentation.cpp index a91d43e78..b17ee000a 100644 --- a/src/libslic3r/MultiMaterialSegmentation.cpp +++ b/src/libslic3r/MultiMaterialSegmentation.cpp @@ -1400,7 +1400,7 @@ static inline std::vector> mmu_segmentation_top_and_bott if (std::vector &top = top_raw[color_idx]; ! top.empty() && ! top[layer_idx].empty()) if (ExPolygons top_ex = union_ex(top[layer_idx]); ! top_ex.empty()) { // Clean up thin projections. They are not printable anyways. - top_ex = offset2_ex(top_ex, - stat.small_region_threshold, + stat.small_region_threshold); + top_ex = opening_ex(top_ex, stat.small_region_threshold); if (! top_ex.empty()) { append(triangles_by_color_top[color_idx][layer_idx + layer_idx_offset], top_ex); float offset = 0.f; @@ -1408,8 +1408,7 @@ static inline std::vector> mmu_segmentation_top_and_bott for (int last_idx = int(layer_idx) - 1; last_idx >= std::max(int(layer_idx - stat.top_solid_layers), int(0)); --last_idx) { offset -= stat.extrusion_width; layer_slices_trimmed = intersection_ex(layer_slices_trimmed, input_expolygons[last_idx]); - ExPolygons last = offset2_ex(intersection_ex(top_ex, offset_ex(layer_slices_trimmed, offset)), - - stat.small_region_threshold, + stat.small_region_threshold); + ExPolygons last = opening_ex(intersection_ex(top_ex, offset_ex(layer_slices_trimmed, offset)), stat.small_region_threshold); if (last.empty()) break; append(triangles_by_color_top[color_idx][last_idx + layer_idx_offset], std::move(last)); @@ -1419,7 +1418,7 @@ static inline std::vector> mmu_segmentation_top_and_bott if (std::vector &bottom = bottom_raw[color_idx]; ! bottom.empty() && ! bottom[layer_idx].empty()) if (ExPolygons bottom_ex = union_ex(bottom[layer_idx]); ! bottom_ex.empty()) { // Clean up thin projections. They are not printable anyways. - bottom_ex = offset2_ex(bottom_ex, - stat.small_region_threshold, + stat.small_region_threshold); + bottom_ex = opening_ex(bottom_ex, stat.small_region_threshold); if (! bottom_ex.empty()) { append(triangles_by_color_bottom[color_idx][layer_idx + layer_idx_offset], bottom_ex); float offset = 0.f; @@ -1427,8 +1426,7 @@ static inline std::vector> mmu_segmentation_top_and_bott for (size_t last_idx = layer_idx + 1; last_idx < std::min(layer_idx + stat.bottom_solid_layers, num_layers); ++last_idx) { offset -= stat.extrusion_width; layer_slices_trimmed = intersection_ex(layer_slices_trimmed, input_expolygons[last_idx]); - ExPolygons last = offset2_ex(intersection_ex(bottom_ex, offset_ex(layer_slices_trimmed, offset)), - - stat.small_region_threshold, + stat.small_region_threshold); + ExPolygons last = opening_ex(intersection_ex(bottom_ex, offset_ex(layer_slices_trimmed, offset)), stat.small_region_threshold); if (last.empty()) break; append(triangles_by_color_bottom[color_idx][last_idx + layer_idx_offset], std::move(last)); diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index 3190845bd..55d3c6aa5 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -347,10 +347,10 @@ void PerimeterGenerator::process() // the following offset2 ensures almost nothing in @thin_walls is narrower than $min_width // (actually, something larger than that still may exist due to mitering or other causes) coord_t min_width = coord_t(scale_(this->ext_perimeter_flow.nozzle_diameter() / 3)); - ExPolygons expp = offset2_ex( + ExPolygons expp = opening_ex( // medial axis requires non-overlapping geometry diff_ex(last, offset(offsets, float(ext_perimeter_width / 2.) + ClipperSafetyOffset)), - - float(min_width / 2.), float(min_width / 2.)); + float(min_width / 2.)); // the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop for (ExPolygon &ex : expp) ex.medial_axis(ext_perimeter_width + ext_perimeter_spacing2, min_width, &thin_walls); @@ -495,7 +495,7 @@ void PerimeterGenerator::process() double max = 2. * perimeter_spacing; ExPolygons gaps_ex = diff_ex( //FIXME offset2 would be enough and cheaper. - offset2_ex(gaps, - float(min / 2.), float(min / 2.)), + opening_ex(gaps, float(min / 2.)), offset2_ex(gaps, - float(max / 2.), float(max / 2. + ClipperSafetyOffset))); ThickPolylines polylines; for (const ExPolygon &ex : gaps_ex) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index f49eddf3e..235013143 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -774,7 +774,7 @@ void PrintObject::detect_surfaces_type() ExPolygons upper_slices = interface_shells ? diff_ex(layerm->slices.surfaces, upper_layer->m_regions[region_id]->slices.surfaces, ApplySafetyOffset::Yes) : diff_ex(layerm->slices.surfaces, upper_layer->lslices, ApplySafetyOffset::Yes); - surfaces_append(top, offset2_ex(upper_slices, -offset, offset), stTop); + surfaces_append(top, opening_ex(upper_slices, offset), stTop); } else { // if no upper layer, all surfaces of this one are solid // we clone surfaces because we're going to clear the slices collection @@ -792,15 +792,15 @@ void PrintObject::detect_surfaces_type() to_polygons(lower_layer->get_region(region_id)->slices.surfaces) : to_polygons(lower_layer->slices); surfaces_append(bottom, - offset2_ex(diff(layerm->slices.surfaces, lower_slices, true), -offset, offset), + opening_ex(diff(layerm->slices.surfaces, lower_slices, true), offset), surface_type_bottom_other); #else // Any surface lying on the void is a true bottom bridge (an overhang) surfaces_append( bottom, - offset2_ex( + opening_ex( diff_ex(layerm->slices.surfaces, lower_layer->lslices, ApplySafetyOffset::Yes), - -offset, offset), + offset), surface_type_bottom_other); // if user requested internal shells, we need to identify surfaces // lying on other slices not belonging to this region @@ -809,12 +809,12 @@ void PrintObject::detect_surfaces_type() // on something else, excluding those lying on our own region surfaces_append( bottom, - offset2_ex( + opening_ex( diff_ex( intersection(layerm->slices.surfaces, lower_layer->lslices), // supported lower_layer->m_regions[region_id]->slices.surfaces, ApplySafetyOffset::Yes), - -offset, offset), + offset), stBottom); } #endif @@ -1337,7 +1337,7 @@ void PrintObject::discover_vertical_shells() // get a triangle in $too_narrow; if we grow it below then the shell // would have a different shape from the external surface and we'd still // have the same angle, so the next shell would be grown even more and so on. - Polygons too_narrow = diff(shell, offset2(shell, -margin, margin, ClipperLib::jtMiter, 5.), true); + Polygons too_narrow = diff(shell, opening(shell, margin, ClipperLib::jtMiter, 5.), true); if (! too_narrow.empty()) { // grow the collapsing parts and add the extra area to the neighbor layer // as well as to our original surfaces so that we support this @@ -1453,7 +1453,7 @@ void PrintObject::bridge_over_infill() // The gaps will be filled by a separate region, which makes the infill less stable and it takes longer. { float min_width = float(bridge_flow.scaled_width()) * 3.f; - to_bridge_pp = offset2(to_bridge_pp, -min_width, +min_width); + to_bridge_pp = opening(to_bridge_pp, min_width); } if (to_bridge_pp.empty()) continue; @@ -1744,7 +1744,7 @@ void PrintObject::clip_fill_surfaces() for (const LayerRegion *layerm : layer->m_regions) pw = std::min(pw, (float)layerm->flow(frPerimeter).scaled_width()); // Append such thick perimeters to the areas that need support - polygons_append(overhangs, offset2(perimeters, -pw, +pw)); + polygons_append(overhangs, opening(perimeters, pw)); } // Find new internal infill. polygons_append(overhangs, std::move(upper_internal)); @@ -1884,7 +1884,7 @@ void PrintObject::discover_horizontal_shells() float margin = float(neighbor_layerm->flow(frExternalPerimeter).scaled_width()); Polygons too_narrow = diff( new_internal_solid, - offset2(new_internal_solid, -margin, +margin + ClipperSafetyOffset, jtMiter, 5)); + opening(new_internal_solid, margin, margin + ClipperSafetyOffset, jtMiter, 5)); // Trim the regularized region by the original region. if (! too_narrow.empty()) new_internal_solid = solid = diff(new_internal_solid, too_narrow); @@ -1903,7 +1903,7 @@ void PrintObject::discover_horizontal_shells() // have the same angle, so the next shell would be grown even more and so on. Polygons too_narrow = diff( new_internal_solid, - offset2(new_internal_solid, -margin, +margin + ClipperSafetyOffset, ClipperLib::jtMiter, 5)); + opening(new_internal_solid, margin, margin + ClipperSafetyOffset, ClipperLib::jtMiter, 5)); if (! too_narrow.empty()) { // grow the collapsing parts and add the extra area to the neighbor layer // as well as to our original surfaces so that we support this @@ -1915,7 +1915,7 @@ void PrintObject::discover_horizontal_shells() polygons_append(internal, to_polygons(surface.expolygon)); polygons_append(new_internal_solid, intersection( - offset(too_narrow, +margin), + expand(too_narrow, +margin), // Discard bridges as they are grown for anchoring and we can't // remove such anchors. (This may happen when a bridge is being // anchored onto a wall where little space remains after the bridge diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp index 7db0de626..e2844a624 100644 --- a/src/libslic3r/PrintObjectSlice.cpp +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -393,7 +393,7 @@ static std::vector> slices_to_regions( } } if (merged) - expolygons = offset2_ex(expolygons, float(scale_(EPSILON)), -float(scale_(EPSILON))); + expolygons = closing_ex(expolygons, float(scale_(EPSILON))); slices_by_region[temp_slices[i].region_id][z_idx] = std::move(expolygons); i = j; } @@ -648,7 +648,7 @@ static inline void apply_mm_segmentation(PrintObject &print_object, ThrowOnCance ByRegion &src = by_region[region_id]; if (src.needs_merge) // Multiple regions were merged into one. - src.expolygons = offset2_ex(src.expolygons, float(scale_(10 * EPSILON)), - float(scale_(10 * EPSILON))); + src.expolygons = closing_ex(src.expolygons, float(scale_(10 * EPSILON))); layer->get_region(region_id)->slices.set(std::move(src.expolygons), stInternal); } } diff --git a/src/libslic3r/SLA/SupportPointGenerator.cpp b/src/libslic3r/SLA/SupportPointGenerator.cpp index bbc6b03fa..c32da0431 100644 --- a/src/libslic3r/SLA/SupportPointGenerator.cpp +++ b/src/libslic3r/SLA/SupportPointGenerator.cpp @@ -186,8 +186,8 @@ static std::vector make_layers( // Produce 2 bands around the island, a safe band for dangling overhangs // and an unsafe band for sloped overhangs. // These masks include the original island - auto dangl_mask = offset(bottom_polygons, between_layers_offset, ClipperLib::jtSquare); - auto overh_mask = offset(bottom_polygons, slope_offset, ClipperLib::jtSquare); + auto dangl_mask = expand(bottom_polygons, between_layers_offset, ClipperLib::jtSquare); + auto overh_mask = expand(bottom_polygons, slope_offset, ClipperLib::jtSquare); // Absolutely hopeless overhangs are those outside the unsafe band top.overhangs = diff_ex(*top.polygon, overh_mask); diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index 9a5638c01..167aedcc2 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -367,6 +367,29 @@ PrintObjectSupportMaterial::PrintObjectSupportMaterial(const PrintObject *object // Object is printed with the same extruder as the support. m_support_params.can_merge_support_regions = true; } + + + m_support_params.base_angle = Geometry::deg2rad(float(m_object_config->support_material_angle.value)); + m_support_params.interface_angle = Geometry::deg2rad(float(m_object_config->support_material_angle.value + 90.)); + m_support_params.interface_spacing = m_object_config->support_material_interface_spacing.value + m_support_params.support_material_interface_flow.spacing(); + m_support_params.interface_density = std::min(1., m_support_params.support_material_interface_flow.spacing() / m_support_params.interface_spacing); + m_support_params.support_spacing = m_object_config->support_material_spacing.value + m_support_params.support_material_flow.spacing(); + m_support_params.support_density = std::min(1., m_support_params.support_material_flow.spacing() / m_support_params.support_spacing); + if (m_object_config->support_material_interface_layers.value == 0) { + // No interface layers allowed, print everything with the base support pattern. + m_support_params.interface_spacing = m_support_params.support_spacing; + m_support_params.interface_density = m_support_params.support_density; + } + + SupportMaterialPattern support_pattern = m_object_config->support_material_pattern; + m_support_params.with_sheath = m_object_config->support_material_with_sheath; + m_support_params.base_fill_pattern = support_pattern == smpHoneycomb ? ipHoneycomb : (m_support_params.support_density > 0.95 ? ipRectilinear : ipSupportBase); + m_support_params.interface_fill_pattern = (m_support_params.interface_density > 0.95 ? ipRectilinear : ipSupportBase); + m_support_params.contact_fill_pattern = + (m_object_config->support_material_interface_pattern == smipAuto && m_slicing_params.soluble_interface) || + m_object_config->support_material_interface_pattern == smipConcentric ? + ipConcentric : + (m_support_params.interface_density > 0.95 ? ipRectilinear : ipSupportBase); } // Using the std::deque as an allocator. @@ -883,7 +906,14 @@ public: // Merge the support polygons by applying morphological closing and inwards smoothing. auto closing_distance = scaled(m_support_material_closing_radius); auto smoothing_distance = scaled(m_extrusion_width); - return smooth_outward(offset(offset_ex(*m_support_polygons, closing_distance), - closing_distance), smoothing_distance); +#ifdef SLIC3R_DEBUG + SVG::export_expolygons(debug_out_path("extract_support_from_grid_trimmed-%s-%d-%d-%lf.svg", step_name, iRun, layer_id, print_z), + { { { diff_ex(expand(*m_support_polygons, closing_distance), closing(*m_support_polygons, closing_distance, SUPPORT_SURFACES_OFFSET_PARAMETERS)) }, { "closed", "blue", 0.5f } }, + { { union_ex(smooth_outward(closing(*m_support_polygons, closing_distance, SUPPORT_SURFACES_OFFSET_PARAMETERS), smoothing_distance)) }, { "regularized", "red", "black", "", scaled(0.1f), 0.5f } }, + { { union_ex(*m_support_polygons) }, { "src", "green", 0.5f } }, + }); +#endif /* SLIC3R_DEBUG */ + return smooth_outward(closing(*m_support_polygons, closing_distance, SUPPORT_SURFACES_OFFSET_PARAMETERS), smoothing_distance); } assert(false); return Polygons(); @@ -1250,7 +1280,7 @@ namespace SupportMaterialInternal { Polygons bridges; { // Surface supporting this layer, expanded by 0.5 * nozzle_diameter, as we consider this kind of overhang to be sufficiently supported. - Polygons lower_grown_slices = offset(lower_layer_polygons, + Polygons lower_grown_slices = expand(lower_layer_polygons, //FIXME to mimic the decision in the perimeter generator, we should use half the external perimeter width. 0.5f * float(scale_(print_config.nozzle_diameter.get_at(layerm.region().config().perimeter_extruder-1))), SUPPORT_SURFACES_OFFSET_PARAMETERS); @@ -1414,7 +1444,7 @@ static inline std::tuple detect_overhangs( overhang_polygons = to_polygons(layer.lslices); #endif // Expand for better stability. - contact_polygons = offset(overhang_polygons, scaled(object_config.raft_expansion.value)); + contact_polygons = expand(overhang_polygons, scaled(object_config.raft_expansion.value)); } else if (! layer.regions().empty()) { @@ -1475,20 +1505,20 @@ static inline std::tuple detect_overhangs( //FIXME cache the lower layer offset if this layer has multiple regions. #if 0 //FIXME this solution will trigger stupid supports for sharp corners, see GH #4874 - diff_polygons = offset2( + diff_polygons = opening( diff(layerm_polygons, // Likely filtering out thin regions from the lower layer, that will not be covered by perimeters, thus they // are not supporting this layer. // However this may lead to a situation where regions at the current layer that are narrow thus not extrudable will generate unnecessary supports. // For example, see GH issue #3094 - offset2(lower_layer_polygons, - 0.5f * fw, lower_layer_offset + 0.5f * fw, SUPPORT_SURFACES_OFFSET_PARAMETERS)), - //FIXME This offset2 is targeted to reduce very thin regions to support, but it may lead to + opening(lower_layer_polygons, 0.5f * fw, lower_layer_offset + 0.5f * fw, SUPPORT_SURFACES_OFFSET_PARAMETERS)), + //FIXME This opening is targeted to reduce very thin regions to support, but it may lead to // no support at all for not so steep overhangs. - - 0.1f * fw, 0.1f * fw); + 0.1f * fw); #else diff_polygons = diff(layerm_polygons, - offset(lower_layer_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS)); + expand(lower_layer_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS)); #endif if (buildplate_only && ! annotations.buildplate_covered[layer_id].empty()) { // Don't support overhangs above the top surfaces. @@ -1500,7 +1530,7 @@ static inline std::tuple detect_overhangs( // This is done to increase size of the supporting columns below, as they are calculated by // propagating these contact surfaces downwards. diff_polygons = diff( - intersection(offset(diff_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS), layerm_polygons), + intersection(expand(diff_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS), layerm_polygons), lower_layer_polygons); } //FIXME add user defined filtering here based on minimal area or minimum radius or whatever. @@ -1516,7 +1546,7 @@ static inline std::tuple detect_overhangs( // Subtracting them as they are may leave unwanted narrow // residues of diff_polygons that would then be supported. diff_polygons = diff(diff_polygons, - offset(union_(annotations.blockers_layers[layer_id]), float(1000.*SCALED_EPSILON))); + expand(union_(annotations.blockers_layers[layer_id]), float(1000.*SCALED_EPSILON))); } #ifdef SLIC3R_DEBUG @@ -1588,7 +1618,7 @@ static inline std::tuple detect_overhangs( #endif // SLIC3R_DEBUG enforcer_polygons = diff(intersection(layer.lslices, annotations.enforcers_layers[layer_id]), // Inflate just a tiny bit to avoid intersection of the overhang areas with the object. - offset(lower_layer_polygons, 0.05f * no_interface_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS)); + expand(lower_layer_polygons, 0.05f * no_interface_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS)); #ifdef SLIC3R_DEBUG SVG::export_expolygons(debug_out_path("support-top-contacts-enforcers-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z), { { layer.lslices, { "layer.lslices", "gray", 0.2f } }, @@ -1720,7 +1750,7 @@ static inline void fill_contact_layer( if (lower_layer_polygons_for_dense_interface_cache.empty()) lower_layer_polygons_for_dense_interface_cache = //FIXME no_interface_offset * 0.6f offset is not quite correct, one shall derive it based on an angle thus depending on layer height. - offset2(lower_layer_polygons, - no_interface_offset * 0.5f, no_interface_offset * (0.6f + 0.5f), SUPPORT_SURFACES_OFFSET_PARAMETERS); + opening(lower_layer_polygons, no_interface_offset * 0.5f, no_interface_offset * (0.6f + 0.5f), SUPPORT_SURFACES_OFFSET_PARAMETERS); return lower_layer_polygons_for_dense_interface_cache; }; @@ -1733,7 +1763,7 @@ static inline void fill_contact_layer( #endif // SLIC3R_DEBUG )); // 2) infill polygons, expand them by half the extrusion width + a tiny bit of extra. - bool reduce_interfaces = layer_id > 0 && ! slicing_params.soluble_interface; + bool reduce_interfaces = object_config.support_material_style.value != smsSnug && layer_id > 0 && !slicing_params.soluble_interface; if (reduce_interfaces) { // Reduce the amount of dense interfaces: Do not generate dense interfaces below overhangs with 60% overhang of the extrusions. Polygons dense_interface_polygons = diff(overhang_polygons, lower_layer_polygons_for_dense_interface()); @@ -1741,7 +1771,7 @@ static inline void fill_contact_layer( dense_interface_polygons = diff( // Regularize the contour. - offset(dense_interface_polygons, no_interface_offset * 0.1f), + expand(dense_interface_polygons, no_interface_offset * 0.1f), slices_margin.polygons); // Support islands, to be stretched into a grid. //FIXME The regularization of dense_interface_polygons above may stretch dense_interface_polygons outside of the contact polygons, @@ -1799,7 +1829,7 @@ static inline void fill_contact_layer( dense_interface_polygons = diff( // Regularize the contour. - offset(dense_interface_polygons, no_interface_offset * 0.1f), + expand(dense_interface_polygons, no_interface_offset * 0.1f), slices_margin.all_polygons); // Support islands, to be stretched into a grid. //FIXME The regularization of dense_interface_polygons above may stretch dense_interface_polygons outside of the contact polygons, @@ -1937,7 +1967,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ ); // Now apply the contact areas to the layer where they need to be made. - if (! contact_polygons.empty()) { + if (! contact_polygons.empty() || ! overhang_polygons.empty()) { // Allocate the two empty layers. auto [new_layer, bridging_layer] = new_contact_layer(*m_print_config, *m_object_config, m_slicing_params, m_support_params.support_layer_height_min, layer, layer_storage, layer_storage_mutex); if (new_layer) { @@ -2041,8 +2071,7 @@ static inline PrintObjectSupportMaterial::MyLayer* detect_bottom_contacts( layer_new.idx_object_layer_below = layer_id; layer_new.bridging = !slicing_params.soluble_interface && object.config().thick_bridges; //FIXME how much to inflate the bottom surface, as it is being extruded with a bridging flow? The following line uses a normal flow. - //FIXME why is the offset positive? It will be trimmed by the object later on anyway, but then it just wastes CPU clocks. - layer_new.polygons = offset(touching, float(support_params.support_material_flow.scaled_width()), SUPPORT_SURFACES_OFFSET_PARAMETERS); + layer_new.polygons = expand(touching, float(support_params.support_material_flow.scaled_width()), SUPPORT_SURFACES_OFFSET_PARAMETERS); if (! slicing_params.soluble_interface) { // Walk the top surfaces, snap the top of the new bottom surface to the closest top of the top surface, @@ -2081,7 +2110,7 @@ static inline PrintObjectSupportMaterial::MyLayer* detect_bottom_contacts( // Trim the already created base layers above the current layer intersecting with the new bottom contacts layer. //FIXME Maybe this is no more needed, as the overlapping base layers are trimmed by the bottom layers at the final stage? - touching = offset(touching, float(SCALED_EPSILON)); + touching = expand(touching, float(SCALED_EPSILON)); for (int layer_id_above = layer_id + 1; layer_id_above < int(object.total_layer_count()); ++ layer_id_above) { const Layer &layer_above = *object.layers()[layer_id_above]; if (layer_above.print_z > layer_new.print_z - EPSILON) @@ -2249,7 +2278,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta #endif // These are the overhang surfaces. They are touching the object and they are not expanded away from the object. // Use a slight positive offset to overlap the touching regions. - polygons_append(polygons_new, offset(*top_contact.overhang_polygons, float(SCALED_EPSILON))); + polygons_append(polygons_new, expand(*top_contact.overhang_polygons, float(SCALED_EPSILON))); polygons_append(overhangs_projection, union_(polygons_new)); polygons_append(enforcers_projection, enforcers_new); } @@ -2736,7 +2765,6 @@ void PrintObjectSupportMaterial::generate_base_layers( ApplySafetyOffset::Yes); // safety offset to merge the touching source polygons layer_intermediate.layer_type = sltBase; - // For snug supports, expand the interfaces into the intermediate layer to make it printable. #if 0 // coordf_t fillet_radius_scaled = scale_(m_object_config->support_material_spacing); // Fillet the base polygons and trim them again with the top, interface and contact layers. @@ -2868,7 +2896,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raf if (brim_inner) { Polygons holes = ex.holes; polygons_reverse(holes); - holes = offset(holes, - brim_separation, ClipperLib::jtRound, float(scale_(0.1))); + holes = shrink(holes, brim_separation, ClipperLib::jtRound, float(scale_(0.1))); polygons_reverse(holes); polygons_append(brim, std::move(holes)); } else @@ -2900,11 +2928,11 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raf Polygons interface_polygons; if (contacts != nullptr && ! contacts->polygons.empty()) - polygons_append(interface_polygons, offset(contacts->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS)); + polygons_append(interface_polygons, expand(contacts->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS)); if (interfaces != nullptr && ! interfaces->polygons.empty()) - polygons_append(interface_polygons, offset(interfaces->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS)); + polygons_append(interface_polygons, expand(interfaces->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS)); if (base_interfaces != nullptr && ! base_interfaces->polygons.empty()) - polygons_append(interface_polygons, offset(base_interfaces->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS)); + polygons_append(interface_polygons, expand(base_interfaces->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS)); // Output vector. MyLayersPtr raft_layers; @@ -2931,7 +2959,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raf new_layer.print_z = m_slicing_params.first_print_layer_height; new_layer.height = m_slicing_params.first_print_layer_height; new_layer.bottom_z = 0.; - new_layer.polygons = inflate_factor_1st_layer > 0 ? offset(base, inflate_factor_1st_layer) : base; + new_layer.polygons = inflate_factor_1st_layer > 0 ? expand(base, inflate_factor_1st_layer) : base; } // Insert the base layers. for (size_t i = 1; i < m_slicing_params.base_raft_layers; ++ i) { @@ -2965,7 +2993,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raf auto nsteps = std::max(5, int(ceil(inflate_factor_1st_layer / m_support_params.first_layer_flow.scaled_width()))); float step = inflate_factor_1st_layer / nsteps; for (int i = 0; i < nsteps; ++ i) - raft = diff(offset(raft, step), trimming); + raft = diff(expand(raft, step), trimming); } else raft = diff(raft, trimming); if (contacts != nullptr) @@ -3028,26 +3056,44 @@ std::pairsupport_material_style.value == smsSnug; + auto smoothing_distance = m_support_params.support_material_interface_flow.scaled_spacing() * 1.5; + auto minimum_island_radius = m_support_params.support_material_interface_flow.scaled_spacing() / m_support_params.interface_density; + auto closing_distance = smoothing_distance; // scaled(m_object_config->support_material_closing_radius.value); tbb::spin_mutex layer_storage_mutex; // Insert a new layer into base_interface_layers, if intersection with base exists. - auto insert_layer = [&layer_storage, &layer_storage_mutex](MyLayer &intermediate_layer, Polygons &bottom, Polygons &&top, const Polygons *subtract, SupporLayerType type) { + auto insert_layer = [&layer_storage, &layer_storage_mutex, snug_supports, closing_distance, smoothing_distance, minimum_island_radius]( + MyLayer &intermediate_layer, Polygons &bottom, Polygons &&top, const Polygons *subtract, SupporLayerType type) -> MyLayer* { assert(! bottom.empty() || ! top.empty()); - MyLayer &layer_new = layer_allocate(layer_storage, layer_storage_mutex, type); - layer_new.print_z = intermediate_layer.print_z; - layer_new.bottom_z = intermediate_layer.bottom_z; - layer_new.height = intermediate_layer.height; - layer_new.bridging = intermediate_layer.bridging; // Merge top into bottom, unite them with a safety offset. append(bottom, std::move(top)); - layer_new.polygons = intersection(union_safety_offset(std::move(bottom)), intermediate_layer.polygons); - // Subtract the interface from the base regions. - intermediate_layer.polygons = diff(intermediate_layer.polygons, layer_new.polygons); - if (subtract) - // Trim the base interface layer with the interface layer. - layer_new.polygons = diff(std::move(layer_new.polygons), *subtract); - //FIXME filter layer_new.polygons islands by a minimum area? -// $interface_area = [ grep abs($_->area) >= $area_threshold, @$interface_area ]; - return &layer_new; + // Merge top / bottom interfaces. For snug supports, merge using closing distance and regularize (close concave corners). + bottom = intersection( + snug_supports ? + smooth_outward(closing(std::move(bottom), closing_distance + minimum_island_radius, closing_distance, SUPPORT_SURFACES_OFFSET_PARAMETERS), smoothing_distance) : + union_safety_offset(std::move(bottom)), + intermediate_layer.polygons); + if (! bottom.empty()) { + //FIXME Remove non-printable tiny islands, let them be printed using the base support. + //bottom = offset(offset_ex(std::move(bottom), - minimum_island_radius), minimum_island_radius); + if (! bottom.empty()) { + MyLayer &layer_new = layer_allocate(layer_storage, layer_storage_mutex, type); + layer_new.polygons = std::move(bottom); + layer_new.print_z = intermediate_layer.print_z; + layer_new.bottom_z = intermediate_layer.bottom_z; + layer_new.height = intermediate_layer.height; + layer_new.bridging = intermediate_layer.bridging; + // Subtract the interface from the base regions. + intermediate_layer.polygons = diff(intermediate_layer.polygons, layer_new.polygons); + if (subtract) + // Trim the base interface layer with the interface layer. + layer_new.polygons = diff(std::move(layer_new.polygons), *subtract); + //FIXME filter layer_new.polygons islands by a minimum area? + // $interface_area = [ grep abs($_->area) >= $area_threshold, @$interface_area ]; + return &layer_new; + } + } + return nullptr; }; tbb::parallel_for(tbb::blocked_range(0, int(intermediate_layers.size())), [&bottom_contacts, &top_contacts, &intermediate_layers, &insert_layer, @@ -3196,7 +3242,7 @@ static inline void fill_expolygons_with_sheath_generate_paths( return; if (! with_sheath) { - fill_expolygons_generate_paths(dst, offset2_ex(polygons, float(SCALED_EPSILON), float(- SCALED_EPSILON)), filler, density, role, flow); + fill_expolygons_generate_paths(dst, closing_ex(polygons, float(SCALED_EPSILON)), filler, density, role, flow); return; } @@ -3208,7 +3254,7 @@ static inline void fill_expolygons_with_sheath_generate_paths( // Clip the sheath path to avoid the extruder to get exactly on the first point of the loop. double clip_length = spacing * 0.15; - for (ExPolygon &expoly : offset2_ex(polygons, float(SCALED_EPSILON), float(- SCALED_EPSILON - 0.5*flow.scaled_width()))) { + for (ExPolygon &expoly : closing_ex(polygons, float(SCALED_EPSILON), float(SCALED_EPSILON + 0.5*flow.scaled_width()))) { // Don't reorder the skirt and its infills. std::unique_ptr eec; if (no_sort) { @@ -3461,10 +3507,10 @@ void LoopInterfaceProcessor::generate(MyLayerExtruded &top_contact_layer, const // make more loops Polygons loop_polygons = loops0; for (int i = 1; i < n_contact_loops; ++ i) - polygons_append(loop_polygons, - offset2( + polygons_append(loop_polygons, + opening( loops0, - - i * flow.scaled_spacing() - 0.5f * flow.scaled_spacing(), + i * flow.scaled_spacing() + 0.5f * flow.scaled_spacing(), 0.5f * flow.scaled_spacing())); // Clip such loops to the side oriented towards the object. // Collect split points, so they will be recognized after the clipping. @@ -3476,7 +3522,7 @@ void LoopInterfaceProcessor::generate(MyLayerExtruded &top_contact_layer, const map_split_points[it->first_point()] = -1; loop_lines.push_back(it->split_at_first_point()); } - loop_lines = intersection_pl(loop_lines, offset(overhang_polygons, scale_(SUPPORT_MATERIAL_MARGIN))); + loop_lines = intersection_pl(loop_lines, expand(overhang_polygons, scale_(SUPPORT_MATERIAL_MARGIN))); // Because a closed loop has been split to a line, loop_lines may contain continuous segments split to 2 pieces. // Try to connect them. for (int i_line = 0; i_line < int(loop_lines.size()); ++ i_line) { @@ -3816,27 +3862,9 @@ void PrintObjectSupportMaterial::generate_toolpaths( LoopInterfaceProcessor loop_interface_processor(1.5 * m_support_params.support_material_interface_flow.scaled_width()); loop_interface_processor.n_contact_loops = this->has_contact_loops() ? 1 : 0; - float base_angle = Geometry::deg2rad(float(m_object_config->support_material_angle.value)); - float interface_angle = Geometry::deg2rad(float(m_object_config->support_material_angle.value + 90.)); - coordf_t interface_spacing = m_object_config->support_material_interface_spacing.value + m_support_params.support_material_interface_flow.spacing(); - coordf_t interface_density = std::min(1., m_support_params.support_material_interface_flow.spacing() / interface_spacing); - coordf_t support_spacing = m_object_config->support_material_spacing.value + m_support_params.support_material_flow.spacing(); - coordf_t support_density = std::min(1., m_support_params.support_material_flow.spacing() / support_spacing); - if (m_object_config->support_material_interface_layers.value == 0) { - // No interface layers allowed, print everything with the base support pattern. - interface_spacing = support_spacing; - interface_density = support_density; - } - - // Prepare fillers. - SupportMaterialPattern support_pattern = m_object_config->support_material_pattern; - bool with_sheath = m_object_config->support_material_with_sheath; - InfillPattern infill_pattern = support_pattern == smpHoneycomb ? ipHoneycomb : (support_density > 0.95 ? ipRectilinear : ipSupportBase); - std::vector angles; - angles.push_back(base_angle); - - if (support_pattern == smpRectilinearGrid) - angles.push_back(interface_angle); + std::vector angles { m_support_params.base_angle }; + if (m_object_config->support_material_pattern == smpRectilinearGrid) + angles.push_back(m_support_params.interface_angle); BoundingBox bbox_object(Point(-scale_(1.), -scale_(1.0)), Point(scale_(1.), scale_(1.))); @@ -3848,16 +3876,16 @@ void PrintObjectSupportMaterial::generate_toolpaths( float raft_angle_interface = 0.f; if (m_slicing_params.base_raft_layers > 1) { // There are all raft layer types (1st layer, base, interface & contact layers) available. - raft_angle_1st_layer = interface_angle; - raft_angle_base = base_angle; - raft_angle_interface = interface_angle; + raft_angle_1st_layer = m_support_params.interface_angle; + raft_angle_base = m_support_params.base_angle; + raft_angle_interface = m_support_params.interface_angle; } else if (m_slicing_params.base_raft_layers == 1 || m_slicing_params.interface_raft_layers > 1) { // 1st layer, interface & contact layers available. - raft_angle_1st_layer = base_angle; + raft_angle_1st_layer = m_support_params.base_angle; if (this->has_support()) // Print 1st layer at 45 degrees from both the interface and base angles as both can land on the 1st layer. raft_angle_1st_layer += 0.7854f; - raft_angle_interface = interface_angle; + raft_angle_interface = m_support_params.interface_angle; } else if (m_slicing_params.interface_raft_layers == 1) { // Only the contact raft layer is non-empty, which will be printed as the 1st layer. assert(m_slicing_params.base_raft_layers == 0); @@ -3874,7 +3902,7 @@ void PrintObjectSupportMaterial::generate_toolpaths( size_t n_raft_layers = size_t(std::max(0, int(m_slicing_params.raft_layers()) - 1)); tbb::parallel_for(tbb::blocked_range(0, n_raft_layers), [this, &support_layers, &raft_layers, - infill_pattern, &bbox_object, support_density, interface_density, raft_angle_1st_layer, raft_angle_base, raft_angle_interface, link_max_length_factor, with_sheath] + &bbox_object, raft_angle_1st_layer, raft_angle_base, raft_angle_interface, link_max_length_factor] (const tbb::blocked_range& range) { for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id) { @@ -3883,8 +3911,8 @@ void PrintObjectSupportMaterial::generate_toolpaths( assert(support_layer.support_fills.entities.empty()); MyLayer &raft_layer = *raft_layers[support_layer_id]; - std::unique_ptr filler_interface = std::unique_ptr(Fill::new_from_type(ipRectilinear)); - std::unique_ptr filler_support = std::unique_ptr(Fill::new_from_type(infill_pattern)); + std::unique_ptr filler_interface = std::unique_ptr(Fill::new_from_type(m_support_params.interface_fill_pattern)); + std::unique_ptr filler_support = std::unique_ptr(Fill::new_from_type(m_support_params.base_fill_pattern)); filler_interface->set_bounding_box(bbox_object); filler_support->set_bounding_box(bbox_object); @@ -3900,17 +3928,17 @@ void PrintObjectSupportMaterial::generate_toolpaths( Fill * filler = filler_support.get(); filler->angle = raft_angle_base; filler->spacing = m_support_params.support_material_flow.spacing(); - filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_density)); + filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / m_support_params.support_density)); fill_expolygons_with_sheath_generate_paths( // Destination support_layer.support_fills.entities, // Regions to fill to_infill_polygons, // Filler and its parameters - filler, float(support_density), + filler, float(m_support_params.support_density), // Extrusion parameters erSupportMaterial, flow, - with_sheath, false); + m_support_params.with_sheath, false); } } @@ -3929,7 +3957,7 @@ void PrintObjectSupportMaterial::generate_toolpaths( filler->spacing = m_support_params.support_material_flow.spacing(); assert(! raft_layer.bridging); flow = Flow(float(m_support_params.support_material_interface_flow.width()), float(raft_layer.height), m_support_params.support_material_flow.nozzle_diameter()); - density = float(interface_density); + density = float(m_support_params.interface_density); } else continue; filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density)); @@ -3970,15 +3998,9 @@ void PrintObjectSupportMaterial::generate_toolpaths( }; std::vector layer_caches(support_layers.size()); - - const auto fill_type_interface = - (m_object_config->support_material_interface_pattern == smipAuto && m_slicing_params.soluble_interface) || - m_object_config->support_material_interface_pattern == smipConcentric ? - ipConcentric : ipRectilinear; - tbb::parallel_for(tbb::blocked_range(n_raft_layers, support_layers.size()), [this, &support_layers, &bottom_contacts, &top_contacts, &intermediate_layers, &interface_layers, &base_interface_layers, &layer_caches, &loop_interface_processor, - infill_pattern, &bbox_object, support_density, fill_type_interface, interface_density, interface_angle, &angles, link_max_length_factor, with_sheath] + &bbox_object, &angles, link_max_length_factor] (const tbb::blocked_range& range) { // Indices of the 1st layer in their respective container at the support layer height. size_t idx_layer_bottom_contact = size_t(-1); @@ -3987,14 +4009,14 @@ void PrintObjectSupportMaterial::generate_toolpaths( size_t idx_layer_interface = size_t(-1); size_t idx_layer_base_interface = size_t(-1); const auto fill_type_first_layer = ipRectilinear; - auto filler_interface = std::unique_ptr(Fill::new_from_type(fill_type_interface)); + auto filler_interface = std::unique_ptr(Fill::new_from_type(m_support_params.contact_fill_pattern)); // Filler for the 1st layer interface, if different from filler_interface. - auto filler_first_layer_ptr = std::unique_ptr(range.begin() == 0 && fill_type_interface != fill_type_first_layer ? Fill::new_from_type(fill_type_first_layer) : nullptr); + auto filler_first_layer_ptr = std::unique_ptr(range.begin() == 0 && m_support_params.contact_fill_pattern != fill_type_first_layer ? Fill::new_from_type(fill_type_first_layer) : nullptr); // Pointer to the 1st layer interface filler. auto filler_first_layer = filler_first_layer_ptr ? filler_first_layer_ptr.get() : filler_interface.get(); // Filler for the base interface (to be used for soluble interface / non soluble base, to produce non soluble interface layer below soluble interface layer). - auto filler_base_interface = std::unique_ptr(base_interface_layers.empty() ? nullptr : Fill::new_from_type(ipRectilinear)); - auto filler_support = std::unique_ptr(Fill::new_from_type(infill_pattern)); + auto filler_base_interface = std::unique_ptr(base_interface_layers.empty() ? nullptr : Fill::new_from_type(m_support_params.interface_density > 0.95 ? ipRectilinear : ipSupportBase)); + auto filler_support = std::unique_ptr(Fill::new_from_type(m_support_params.base_fill_pattern)); filler_interface->set_bounding_box(bbox_object); if (filler_first_layer_ptr) filler_first_layer_ptr->set_bounding_box(bbox_object); @@ -4084,8 +4106,8 @@ void PrintObjectSupportMaterial::generate_toolpaths( // If zero interface layers are configured, use the same angle as for the base layers. angles[support_layer_id % angles.size()] : // Use interface angle for the interface layers. - interface_angle; - double density = interface_as_base ? support_density : interface_density; + m_support_params.interface_angle; + double density = interface_as_base ? m_support_params.support_density : m_support_params.interface_density; filler_interface->spacing = interface_as_base ? m_support_params.support_material_flow.spacing() : m_support_params.support_material_interface_flow.spacing(); filler_interface->link_max_length = coord_t(scale_(filler_interface->spacing * link_max_length_factor / density)); fill_expolygons_generate_paths( @@ -4106,9 +4128,9 @@ void PrintObjectSupportMaterial::generate_toolpaths( // the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b) assert(! base_interface_layer.layer->bridging); Flow interface_flow = m_support_params.support_material_flow.with_height(float(base_interface_layer.layer->height)); - filler->angle = interface_angle; + filler->angle = m_support_params.interface_angle; filler->spacing = m_support_params.support_material_interface_flow.spacing(); - filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / interface_density)); + filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / m_support_params.interface_density)); fill_expolygons_generate_paths( // Destination base_interface_layer.extrusions, @@ -4116,7 +4138,7 @@ void PrintObjectSupportMaterial::generate_toolpaths( // Regions to fill union_safety_offset_ex(base_interface_layer.polygons_to_extrude()), // Filler and its parameters - filler, float(interface_density), + filler, float(m_support_params.interface_density), // Extrusion parameters erSupportMaterial, interface_flow); } @@ -4130,9 +4152,9 @@ void PrintObjectSupportMaterial::generate_toolpaths( assert(! base_layer.layer->bridging); auto flow = m_support_params.support_material_flow.with_height(float(base_layer.layer->height)); filler->spacing = m_support_params.support_material_flow.spacing(); - filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_density)); - float density = float(support_density); - bool sheath = with_sheath; + filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / m_support_params.support_density)); + float density = float(m_support_params.support_density); + bool sheath = m_support_params.with_sheath; bool no_sort = false; if (base_layer.layer->bottom_z < EPSILON) { // Base flange (the 1st layer). diff --git a/src/libslic3r/SupportMaterial.hpp b/src/libslic3r/SupportMaterial.hpp index a1bd81297..65604ef72 100644 --- a/src/libslic3r/SupportMaterial.hpp +++ b/src/libslic3r/SupportMaterial.hpp @@ -132,6 +132,18 @@ public: // coordf_t support_layer_height_max; coordf_t gap_xy; + + float base_angle; + float interface_angle; + coordf_t interface_spacing; + coordf_t interface_density; + coordf_t support_spacing; + coordf_t support_density; + + InfillPattern base_fill_pattern; + InfillPattern interface_fill_pattern; + InfillPattern contact_fill_pattern; + bool with_sheath; }; // Layers are allocated and owned by a deque. Once a layer is allocated, it is maintained diff --git a/src/libslic3r/TriangleMeshSlicer.cpp b/src/libslic3r/TriangleMeshSlicer.cpp index d3c9a49b5..0f3d0f5b6 100644 --- a/src/libslic3r/TriangleMeshSlicer.cpp +++ b/src/libslic3r/TriangleMeshSlicer.cpp @@ -1617,7 +1617,7 @@ static void make_expolygons(const Polygons &loops, const float closing_radius, c /* The following line is commented out because it can generate wrong polygons, see for example issue #661 */ - //ExPolygons ex_slices = offset2_ex(p_slices, +safety_offset, -safety_offset); + //ExPolygons ex_slices = closing(p_slices, safety_offset); #ifdef SLIC3R_TRIANGLEMESH_DEBUG size_t holes_count = 0; diff --git a/tests/libslic3r/test_clipper_utils.cpp b/tests/libslic3r/test_clipper_utils.cpp index 3cbd21a40..1b2b45eca 100644 --- a/tests/libslic3r/test_clipper_utils.cpp +++ b/tests/libslic3r/test_clipper_utils.cpp @@ -34,7 +34,7 @@ SCENARIO("Various Clipper operations - xs/t/11_clipper.t", "[ClipperUtils]") { } } WHEN("offset2_ex") { - ExPolygons result = Slic3r::offset2_ex(square_with_hole, 5.f, -2.f); + ExPolygons result = Slic3r::offset2_ex({ square_with_hole }, 5.f, -2.f); THEN("offset matches") { REQUIRE(result == ExPolygons { { { { 203, 203 }, { 97, 203 }, { 97, 97 }, { 203, 97 } }, diff --git a/xs/xsp/Clipper.xsp b/xs/xsp/Clipper.xsp index bae6a103e..eae3afeff 100644 --- a/xs/xsp/Clipper.xsp +++ b/xs/xsp/Clipper.xsp @@ -49,7 +49,7 @@ offset2_ex(polygons, delta1, delta2, joinType = Slic3r::ClipperLib::jtMiter, mit Slic3r::ClipperLib::JoinType joinType double miterLimit CODE: - RETVAL = offset2_ex(polygons, delta1, delta2, joinType, miterLimit); + RETVAL = offset2_ex(union_ex(polygons), delta1, delta2, joinType, miterLimit); OUTPUT: RETVAL