diff --git a/src/clipper/clipper.cpp b/src/clipper/clipper.cpp index e46a0797ce..84d68b1e66 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 74e6601f96..b1dae3c248 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/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index bf5ecc7f5d..d5444edc63 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -121,10 +121,10 @@ void AppConfig::set_defaults() if (get("auto_toolbar_size").empty()) set("auto_toolbar_size", "100"); + + if (get("notify_release").empty()) + set("notify_release", "all"); // or "none" or "release" - - if (get("notify_testing_release").empty()) - set("notify_testing_release", "1"); #if ENABLE_ENVIRONMENT_MAP if (get("use_environment_map").empty()) set("use_environment_map", "0"); diff --git a/src/libslic3r/Brim.cpp b/src/libslic3r/Brim.cpp index db31975e35..69a9e87f94 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 e8f87a6e3d..9b95bfed66 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,28 @@ bool has_duplicate_points(const ClipperLib::PolyTree &polytree) } #endif -// Offset outside by 10um, one by one. -template -static ClipperLib::Paths safety_offset(PathsProvider &&paths) +// 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 +188,122 @@ static ClipperLib::Paths safety_offset(PathsProvider &&paths) return out; } -// Only safe for a single path. +// Offset outside by 10um, one by one. template -ClipperLib::Paths _offset(PathsProvider &&input, ClipperLib::EndType endType, const float delta, ClipperLib::JoinType joinType, double miterLimit) +static ClipperLib::Paths safety_offset(PathsProvider &&paths) { - // 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 raw_offset(std::forward(paths), ClipperSafetyOffset, DefaultJoinType, DefaultMiterLimit); +} + +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; } -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)); } +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)); +} + +template +static ClipperLib::Paths raw_offset_polyline(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit) +{ + assert(offset > 0); + return raw_offset(std::forward(paths), offset, joinType, miterLimit); +} + +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)); +} + +// used by shrink_paths() +template static void remove_outermost_polygon(Container & solution); +template<> void remove_outermost_polygon(ClipperLib::Paths &solution) + { if (! solution.empty()) solution.erase(solution.begin()); } +template<> 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 +354,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 +382,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 +391,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 +403,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 +483,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 +507,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 +548,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 +597,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 +711,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 +750,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 +760,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 f49d922c14..829611176d 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 7ba6de7d47..117066690e 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 baf57f4269..fc0c1b30c7 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 49de854f2a..ef3e188255 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 8cc22647fd..878a9ef587 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/Layer.hpp b/src/libslic3r/Layer.hpp index e6bf4b4fc0..60440de97f 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -188,17 +188,23 @@ public: // Extrusion paths for the support base and for the support interface and contacts. ExtrusionEntityCollection support_fills; + // Is there any valid extrusion assigned to this LayerRegion? virtual bool has_extrusions() const { return ! support_fills.empty(); } + // Zero based index of an interface layer, used for alternating direction of interface / contact layers. + size_t interface_id() const { return m_interface_id; } + protected: friend class PrintObject; // The constructor has been made public to be able to insert additional support layers for the skirt or a wipe tower // between the raft and the object first layer. - SupportLayer(size_t id, PrintObject *object, coordf_t height, coordf_t print_z, coordf_t slice_z) : - Layer(id, object, height, print_z, slice_z) {} + SupportLayer(size_t id, size_t interface_id, PrintObject *object, coordf_t height, coordf_t print_z, coordf_t slice_z) : + Layer(id, object, height, print_z, slice_z), m_interface_id(interface_id) {} virtual ~SupportLayer() = default; + + size_t m_interface_id; }; template diff --git a/src/libslic3r/MultiMaterialSegmentation.cpp b/src/libslic3r/MultiMaterialSegmentation.cpp index b48c718286..b17ee000ad 100644 --- a/src/libslic3r/MultiMaterialSegmentation.cpp +++ b/src/libslic3r/MultiMaterialSegmentation.cpp @@ -190,8 +190,7 @@ static inline std::vector to_lines(const std::vector; ulp_cmp_type ulp_cmp; static constexpr int ULPS = boost::polygon::voronoi_diagram_traits::vertex_equality_predicate_type::ULPS; - return ulp_cmp(vertex.x(), double(ipt.x()), ULPS) == ulp_cmp_type::EQUAL && - ulp_cmp(vertex.y(), double(ipt.y()), ULPS) == ulp_cmp_type::EQUAL; + return ulp_cmp(vertex.x(), ipt.x(), ULPS) == ulp_cmp_type::EQUAL && + ulp_cmp(vertex.y(), ipt.y(), ULPS) == ulp_cmp_type::EQUAL; } -static inline bool vertex_equal_to_point(const Voronoi::VD::vertex_type *vertex, const Point &ipt) +static inline bool vertex_equal_to_point(const Voronoi::VD::vertex_type *vertex, const Vec2d &ipt) { return vertex_equal_to_point(*vertex, ipt); } @@ -509,6 +508,8 @@ static inline Point mk_point(const Voronoi::Internal::point_type &point) { retur static inline Point mk_point(const voronoi_diagram::vertex_type &point) { return {coord_t(point.x()), coord_t(point.y())}; } +static inline Point mk_point(const Vec2d &point) { return {coord_t(std::round(point.x())), coord_t(std::round(point.y()))}; } + static inline Vec2d mk_vec2(const voronoi_diagram::vertex_type *point) { return {point->x(), point->y()}; } struct MMU_Graph @@ -528,7 +529,7 @@ struct MMU_Graph struct Node { - Point point; + Vec2d point; std::list arc_idxs; void remove_edge(const size_t to_idx, MMU_Graph &graph) @@ -665,48 +666,67 @@ struct MMU_Graph struct CPoint { CPoint() = delete; - CPoint(const Point &point, size_t contour_idx, size_t point_idx) : m_point(point), m_point_idx(point_idx), m_contour_idx(contour_idx) {} - CPoint(const Point &point, size_t point_idx) : m_point(point), m_point_idx(point_idx), m_contour_idx(0) {} + CPoint(const Vec2d &point, size_t contour_idx, size_t point_idx) : m_point_double(point), m_point(mk_point(point)), m_point_idx(point_idx), m_contour_idx(contour_idx) {} + CPoint(const Vec2d &point, size_t point_idx) : m_point_double(point), m_point(mk_point(point)), m_point_idx(point_idx), m_contour_idx(0) {} + const Vec2d m_point_double; const Point m_point; size_t m_point_idx; size_t m_contour_idx; + [[nodiscard]] const Vec2d &point_double() const { return m_point_double; } [[nodiscard]] const Point &point() const { return m_point; } - bool operator==(const CPoint &rhs) const { return this->m_point == rhs.m_point && this->m_contour_idx == rhs.m_contour_idx && this->m_point_idx == rhs.m_point_idx; } + bool operator==(const CPoint &rhs) const { return this->m_point_double == rhs.m_point_double && this->m_contour_idx == rhs.m_contour_idx && this->m_point_idx == rhs.m_point_idx; } }; struct CPointAccessor { const Point* operator()(const CPoint &pt) const { return &pt.point(); }}; typedef ClosestPointInRadiusLookup CPointLookupType; - CPointLookupType closest_voronoi_point(3 * coord_t(SCALED_EPSILON)); + CPointLookupType closest_voronoi_point(coord_t(SCALED_EPSILON)); CPointLookupType closest_contour_point(3 * coord_t(SCALED_EPSILON)); for (const Polygon &polygon : color_poly_tmp) for (const Point &pt : polygon.points) - closest_contour_point.insert(CPoint(pt, &polygon - &color_poly_tmp.front(), &pt - &polygon.points.front())); + closest_contour_point.insert(CPoint(Vec2d(pt.x(), pt.y()), &polygon - &color_poly_tmp.front(), &pt - &polygon.points.front())); for (const voronoi_diagram::vertex_type &vertex : vd.vertices()) { vertex.color(-1); - Point vertex_point = mk_point(vertex); + Vec2d vertex_point_double = Vec2d(vertex.x(), vertex.y()); + Point vertex_point = mk_point(vertex); - const Point &first_point = this->nodes[this->get_border_arc(vertex.incident_edge()->cell()->source_index()).from_idx].point; - const Point &second_point = this->nodes[this->get_border_arc(vertex.incident_edge()->twin()->cell()->source_index()).from_idx].point; + const Vec2d &first_point_double = this->nodes[this->get_border_arc(vertex.incident_edge()->cell()->source_index()).from_idx].point; + const Vec2d &second_point_double = this->nodes[this->get_border_arc(vertex.incident_edge()->twin()->cell()->source_index()).from_idx].point; - if (vertex_equal_to_point(&vertex, first_point)) { + if (vertex_equal_to_point(&vertex, first_point_double)) { assert(vertex.color() != vertex.incident_edge()->cell()->source_index()); assert(vertex.color() != vertex.incident_edge()->twin()->cell()->source_index()); vertex.color(this->get_border_arc(vertex.incident_edge()->cell()->source_index()).from_idx); - } else if (vertex_equal_to_point(&vertex, second_point)) { + } else if (vertex_equal_to_point(&vertex, second_point_double)) { assert(vertex.color() != vertex.incident_edge()->cell()->source_index()); assert(vertex.color() != vertex.incident_edge()->twin()->cell()->source_index()); vertex.color(this->get_border_arc(vertex.incident_edge()->twin()->cell()->source_index()).from_idx); } else if (bbox.contains(vertex_point)) { if (auto [contour_pt, c_dist_sqr] = closest_contour_point.find(vertex_point); contour_pt != nullptr && c_dist_sqr < Slic3r::sqr(3 * SCALED_EPSILON)) { vertex.color(this->get_global_index(contour_pt->m_contour_idx, contour_pt->m_point_idx)); - } else if (auto [voronoi_pt, v_dist_sqr] = closest_voronoi_point.find(vertex_point); voronoi_pt == nullptr || v_dist_sqr >= Slic3r::sqr(3 * SCALED_EPSILON)) { - closest_voronoi_point.insert(CPoint(vertex_point, this->nodes_count())); + } else if (auto [voronoi_pt, v_dist_sqr] = closest_voronoi_point.find(vertex_point); voronoi_pt == nullptr || v_dist_sqr >= Slic3r::sqr(SCALED_EPSILON / 10.0)) { + closest_voronoi_point.insert(CPoint(vertex_point_double, this->nodes_count())); vertex.color(this->nodes_count()); - this->nodes.push_back({vertex_point}); + this->nodes.push_back({vertex_point_double}); } else { - vertex.color(voronoi_pt->m_point_idx); + // Boost Voronoi diagram generator sometimes creates two very closed points instead of one point. + // For the example points (146872.99999999997, -146872.99999999997) and (146873, -146873), this example also included in Voronoi generator test cases. + std::vector> all_closes_c_points = closest_voronoi_point.find_all(vertex_point); + int merge_to_point = -1; + for (const std::pair &c_point : all_closes_c_points) + if ((vertex_point_double - c_point.first->point_double()).squaredNorm() <= Slic3r::sqr(EPSILON)) { + merge_to_point = int(c_point.first->m_point_idx); + break; + } + + if (merge_to_point != -1) { + vertex.color(merge_to_point); + } else { + closest_voronoi_point.insert(CPoint(vertex_point_double, this->nodes_count())); + vertex.color(this->nodes_count()); + this->nodes.push_back({vertex_point_double}); + } } } } @@ -850,7 +870,7 @@ static MMU_Graph build_graph(size_t layer_idx, const std::vectorvertex0()->color()].point; - Point real_v1 = graph.nodes[edge_it->vertex1()->color()].point; + Vec2d real_v0_double = graph.nodes[edge_it->vertex0()->color()].point; + Vec2d real_v1_double = graph.nodes[edge_it->vertex1()->color()].point; + Point real_v0 = Point(coord_t(real_v0_double.x()), coord_t(real_v0_double.y())); + Point real_v1 = Point(coord_t(real_v1_double.x()), coord_t(real_v1_double.y())); if (is_point_closer_to_beginning_of_line(contour_line, intersection)) { Line first_part(intersection, real_v0); @@ -999,8 +1021,9 @@ static MMU_Graph build_graph(size_t layer_idx, const std::vectorvertex1()->color(), graph.get_border_arc(edge_it->cell()->source_index()).from_idx); } } else { - const size_t int_point_idx = graph.get_border_arc(edge_it->cell()->source_index()).to_idx; - const Point int_point = graph.nodes[int_point_idx].point; + const size_t int_point_idx = graph.get_border_arc(edge_it->cell()->source_index()).to_idx; + const Vec2d int_point_double = graph.nodes[int_point_idx].point; + const Point int_point = Point(coord_t(int_point_double.x()), coord_t(int_point_double.y())); const Line first_part(int_point, real_v0); const Line second_part(int_point, real_v1); @@ -1039,12 +1062,12 @@ static MMU_Graph build_graph(size_t layer_idx, const std::vector &lines) { Polygon poly_out; poly_out.points.reserve(lines.size()); - for (const Line &line : lines) - poly_out.points.emplace_back(line.a); + for (const Linef &line : lines) + poly_out.points.emplace_back(mk_point(line.a)); return poly_out; } @@ -1056,7 +1079,7 @@ static std::vector> extract_colored_segments(const MM { std::vector used_arcs(graph.arcs.size(), false); // When there is no next arc, then is returned original_arc or edge with is marked as used - auto get_next = [&graph, &used_arcs](const Line &process_line, const MMU_Graph::Arc &original_arc) -> const MMU_Graph::Arc & { + auto get_next = [&graph, &used_arcs](const Linef &process_line, const MMU_Graph::Arc &original_arc) -> const MMU_Graph::Arc & { std::vector> sorted_arcs; for (const size_t &arc_idx : graph.nodes[original_arc.to_idx].arc_idxs) { const MMU_Graph::Arc &arc = graph.arcs[arc_idx]; @@ -1064,8 +1087,8 @@ static std::vector> extract_colored_segments(const MM continue; assert(original_arc.to_idx == arc.from_idx); - Vec2d process_line_vec_n = (process_line.a - process_line.b).cast().normalized(); - Vec2d neighbour_line_vec_n = (graph.nodes[arc.to_idx].point - graph.nodes[arc.from_idx].point).cast().normalized(); + Vec2d process_line_vec_n = (process_line.a - process_line.b).normalized(); + Vec2d neighbour_line_vec_n = (graph.nodes[arc.to_idx].point - graph.nodes[arc.from_idx].point).normalized(); double angle = ::acos(std::clamp(neighbour_line_vec_n.dot(process_line_vec_n), -1.0, 1.0)); if (Slic3r::cross2(neighbour_line_vec_n, process_line_vec_n) < 0.0) @@ -1098,17 +1121,17 @@ static std::vector> extract_colored_segments(const MM for (const size_t &arc_idx : node.arc_idxs) { const MMU_Graph::Arc &arc = graph.arcs[arc_idx]; - if (arc.type == MMU_Graph::ARC_TYPE::NON_BORDER || used_arcs[arc_idx])continue; + if (arc.type == MMU_Graph::ARC_TYPE::NON_BORDER || used_arcs[arc_idx]) + continue; - - Line process_line(node.point, graph.nodes[arc.to_idx].point); + Linef process_line(node.point, graph.nodes[arc.to_idx].point); used_arcs[arc_idx] = true; - Lines face_lines; + std::vector face_lines; face_lines.emplace_back(process_line); - Point start_p = process_line.a; + Vec2d start_p = process_line.a; - Line p_vec = process_line; + Linef p_vec = process_line; const MMU_Graph::Arc *p_arc = &arc; do { const MMU_Graph::Arc &next = get_next(p_vec, *p_arc); @@ -1118,7 +1141,7 @@ static std::vector> extract_colored_segments(const MM break; used_arcs[next_arc_idx] = true; - p_vec = Line(graph.nodes[next.from_idx].point, graph.nodes[next.to_idx].point); + p_vec = Linef(graph.nodes[next.from_idx].point, graph.nodes[next.to_idx].point); p_arc = &next; } while (graph.nodes[p_arc->to_idx].point != start_p || !all_arc_used(graph.nodes[p_arc->to_idx])); @@ -1141,16 +1164,16 @@ static inline double compute_edge_length(const MMU_Graph &graph, const size_t st used_arcs[start_arc_idx] = true; const MMU_Graph::Arc *arc = &graph.arcs[start_arc_idx]; size_t idx = start_idx; - double line_total_length = (graph.nodes[arc->to_idx].point - graph.nodes[idx].point).cast().norm();; + double line_total_length = (graph.nodes[arc->to_idx].point - graph.nodes[idx].point).norm();; while (graph.nodes[arc->to_idx].arc_idxs.size() == 2) { bool found = false; for (const size_t &arc_idx : graph.nodes[arc->to_idx].arc_idxs) { if (const MMU_Graph::Arc &arc_n = graph.arcs[arc_idx]; arc_n.type == MMU_Graph::ARC_TYPE::NON_BORDER && !used_arcs[arc_idx] && arc_n.to_idx != idx) { - Line first_line(graph.nodes[idx].point, graph.nodes[arc->to_idx].point); - Line second_line(graph.nodes[arc->to_idx].point, graph.nodes[arc_n.to_idx].point); + Linef first_line(graph.nodes[idx].point, graph.nodes[arc->to_idx].point); + Linef second_line(graph.nodes[arc->to_idx].point, graph.nodes[arc_n.to_idx].point); - Vec2d first_line_vec = (first_line.a - first_line.b).cast(); - Vec2d second_line_vec = (second_line.b - second_line.a).cast(); + Vec2d first_line_vec = (first_line.a - first_line.b); + Vec2d second_line_vec = (second_line.b - second_line.a); Vec2d first_line_vec_n = first_line_vec.normalized(); Vec2d second_line_vec_n = second_line_vec.normalized(); double angle = ::acos(std::clamp(first_line_vec_n.dot(second_line_vec_n), -1.0, 1.0)); @@ -1163,7 +1186,7 @@ static inline double compute_edge_length(const MMU_Graph &graph, const size_t st idx = arc->to_idx; arc = &arc_n; - line_total_length += (graph.nodes[arc->to_idx].point - graph.nodes[idx].point).cast().norm(); + line_total_length += (graph.nodes[arc->to_idx].point - graph.nodes[idx].point).norm(); used_arcs[arc_idx] = true; found = true; break; @@ -1185,7 +1208,7 @@ static void remove_multiple_edges_in_vertices(MMU_Graph &graph, const std::vecto for (const std::pair &colored_segment : colored_segment_p) { size_t first_idx = graph.get_global_index(poly_idx, colored_segment.first); size_t second_idx = graph.get_global_index(poly_idx, (colored_segment.second + 1) % graph.polygon_sizes[poly_idx]); - Line seg_line(graph.nodes[first_idx].point, graph.nodes[second_idx].point); + Linef seg_line(graph.nodes[first_idx].point, graph.nodes[second_idx].point); if (graph.nodes[first_idx].arc_idxs.size() >= 3) { std::vector> arc_to_check; @@ -1377,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; @@ -1385,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)); @@ -1396,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; @@ -1404,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)); @@ -1502,7 +1523,7 @@ static void export_graph_to_svg(const std::string &path, const MMU_Graph &graph, for (const MMU_Graph::Node &node : graph.nodes) for (const size_t &arc_idx : node.arc_idxs) { const MMU_Graph::Arc &arc = graph.arcs[arc_idx]; - Line arc_line(node.point, graph.nodes[arc.to_idx].point); + Line arc_line(mk_point(node.point), mk_point(graph.nodes[arc.to_idx].point)); if (arc.type == MMU_Graph::ARC_TYPE::BORDER && arc.color >= 0 && arc.color < int(colors.size())) svg.draw(arc_line, colors[arc.color], stroke_width); else diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index 3190845bd2..55d3c6aa59 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/Print.cpp b/src/libslic3r/Print.cpp index b33e8bda23..03a3d1e575 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1139,7 +1139,7 @@ void Print::_make_wipe_tower() // Insert the new support layer. double height = lt.print_z - (i == 0 ? 0. : m_wipe_tower_data.tool_ordering.layer_tools()[i-1].print_z); //FIXME the support layer ID is set to -1, as Vojtech hopes it is not being used anyway. - it_layer = m_objects.front()->insert_support_layer(it_layer, -1, height, lt.print_z, lt.print_z - 0.5 * height); + it_layer = m_objects.front()->insert_support_layer(it_layer, -1, 0, height, lt.print_z, lt.print_z - 0.5 * height); ++ it_layer; } } diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 0056aee333..ac986216e5 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -300,8 +300,8 @@ public: size_t support_layer_count() const { return m_support_layers.size(); } void clear_support_layers(); SupportLayer* get_support_layer(int idx) { return m_support_layers[idx]; } - SupportLayer* add_support_layer(int id, coordf_t height, coordf_t print_z); - SupportLayerPtrs::iterator insert_support_layer(SupportLayerPtrs::iterator pos, size_t id, coordf_t height, coordf_t print_z, coordf_t slice_z); + SupportLayer* add_support_layer(int id, int interface_id, coordf_t height, coordf_t print_z); + SupportLayerPtrs::iterator insert_support_layer(SupportLayerPtrs::iterator pos, size_t id, size_t interface_id, coordf_t height, coordf_t print_z, coordf_t slice_z); void delete_support_layer(int idx); // Initialize the layer_height_profile from the model_object's layer_height_profile, from model_object's layer height table, or from slicing parameters. diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index f49eddf3e4..dd5a2b5731 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -461,15 +461,15 @@ void PrintObject::clear_support_layers() m_support_layers.clear(); } -SupportLayer* PrintObject::add_support_layer(int id, coordf_t height, coordf_t print_z) +SupportLayer* PrintObject::add_support_layer(int id, int interface_id, coordf_t height, coordf_t print_z) { - m_support_layers.emplace_back(new SupportLayer(id, this, height, print_z, -1)); + m_support_layers.emplace_back(new SupportLayer(id, interface_id, this, height, print_z, -1)); return m_support_layers.back(); } -SupportLayerPtrs::iterator PrintObject::insert_support_layer(SupportLayerPtrs::iterator pos, size_t id, coordf_t height, coordf_t print_z, coordf_t slice_z) +SupportLayerPtrs::iterator PrintObject::insert_support_layer(SupportLayerPtrs::iterator pos, size_t id, size_t interface_id, coordf_t height, coordf_t print_z, coordf_t slice_z) { - return m_support_layers.insert(pos, new SupportLayer(id, this, height, print_z, slice_z)); + return m_support_layers.insert(pos, new SupportLayer(id, interface_id, this, height, print_z, slice_z)); } // Called by Print::apply(). @@ -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 7db0de626a..e2844a624c 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 bbc6b03fa5..c32da04319 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 9a5638c013..1322b40711 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. @@ -397,6 +420,11 @@ inline void layers_append(PrintObjectSupportMaterial::MyLayersPtr &dst, const Pr dst.insert(dst.end(), src.begin(), src.end()); } +// Support layer that is covered by some form of dense interface. +static constexpr const std::initializer_list support_types_interface { + PrintObjectSupportMaterial::sltRaftInterface, PrintObjectSupportMaterial::sltBottomContact, PrintObjectSupportMaterial::sltBottomInterface, PrintObjectSupportMaterial::sltTopContact, PrintObjectSupportMaterial::sltTopInterface +}; + void PrintObjectSupportMaterial::generate(PrintObject &object) { BOOST_LOG_TRIVIAL(info) << "Support generator - Start"; @@ -546,6 +574,7 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) // Sort the layers lexicographically by a raising print_z and a decreasing height. std::sort(layers_sorted.begin(), layers_sorted.end(), [](auto *l1, auto *l2) { return *l1 < *l2; }); int layer_id = 0; + int layer_id_interface = 0; assert(object.support_layers().empty()); for (size_t i = 0; i < layers_sorted.size();) { // Find the last layer with roughly the same print_z, find the minimum layer height of all. @@ -557,17 +586,43 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) coordf_t zavg = 0.5 * (layers_sorted[i]->print_z + layers_sorted[j - 1]->print_z); coordf_t height_min = layers_sorted[i]->height; bool empty = true; + // For snug supports, layers where the direction of the support interface shall change are accounted for. + size_t num_interfaces = 0; + size_t num_top_contacts = 0; + double top_contact_bottom_z = 0; for (size_t u = i; u < j; ++u) { MyLayer &layer = *layers_sorted[u]; - if (! layer.polygons.empty()) - empty = false; + if (! layer.polygons.empty()) { + empty = false; + num_interfaces += one_of(layer.layer_type, support_types_interface); + if (layer.layer_type == sltTopContact) { + ++ num_top_contacts; + assert(num_top_contacts <= 1); + // All top contact layers sharing this print_z shall also share bottom_z. + //assert(num_top_contacts == 1 || (top_contact_bottom_z - layer.bottom_z) < EPSILON); + top_contact_bottom_z = layer.bottom_z; + } + } layer.print_z = zavg; height_min = std::min(height_min, layer.height); } if (! empty) { // Here the upper_layer and lower_layer pointers are left to null at the support layers, // as they are never used. These pointers are candidates for removal. - object.add_support_layer(layer_id ++, height_min, zavg); + bool this_layer_contacts_only = num_top_contacts > 0 && num_top_contacts == num_interfaces; + size_t this_layer_id_interface = layer_id_interface; + if (this_layer_contacts_only) { + // Find a supporting layer for its interface ID. + for (auto it = object.support_layers().rbegin(); it != object.support_layers().rend(); ++ it) + if (const SupportLayer &other_layer = **it; std::abs(other_layer.print_z - top_contact_bottom_z) < EPSILON) { + // other_layer supports this top contact layer. Assign a different support interface direction to this layer + // from the layer that supports it. + this_layer_id_interface = other_layer.interface_id() + 1; + } + } + object.add_support_layer(layer_id ++, this_layer_id_interface, height_min, zavg); + if (num_interfaces && ! this_layer_contacts_only) + ++ layer_id_interface; } i = j; } @@ -883,7 +938,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 +1312,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 +1476,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 +1537,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 +1562,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 +1578,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 +1650,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 +1782,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 +1795,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 +1803,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 +1861,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 +1999,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 +2103,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 +2142,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 +2310,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); } @@ -2478,14 +2539,16 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::raft_and_int // or the bottom of the first intermediate layer is aligned with the bottom of the raft contact layer. // Intermediate layers are always printed with a normal etrusion flow (non-bridging). size_t idx_layer_object = 0; - for (size_t idx_extreme = 0; idx_extreme < extremes.size(); ++ idx_extreme) { + size_t idx_extreme_first = 0; + if (! extremes.empty() && std::abs(extremes.front()->extreme_z() - m_slicing_params.raft_interface_top_z) < EPSILON) { + // This is a raft contact layer, its height has been decided in this->top_contact_layers(). + // Ignore this layer when calculating the intermediate support layers. + assert(extremes.front()->layer_type == sltTopContact); + ++ idx_extreme_first; + } + for (size_t idx_extreme = idx_extreme_first; idx_extreme < extremes.size(); ++ idx_extreme) { MyLayer *extr2 = extremes[idx_extreme]; coordf_t extr2z = extr2->extreme_z(); - if (std::abs(extr2z - m_slicing_params.raft_interface_top_z) < EPSILON) { - // This is a raft contact layer, its height has been decided in this->top_contact_layers(). - assert(extr2->layer_type == sltTopContact); - continue; - } if (std::abs(extr2z - m_slicing_params.first_print_layer_height) < EPSILON) { // This is a bottom of a synchronized (or soluble) top contact layer, its height has been decided in this->top_contact_layers(). assert(extr2->layer_type == sltTopContact); @@ -2502,7 +2565,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::raft_and_int } assert(extr2z >= m_slicing_params.raft_interface_top_z + EPSILON); assert(extr2z >= m_slicing_params.first_print_layer_height + EPSILON); - MyLayer *extr1 = (idx_extreme == 0) ? nullptr : extremes[idx_extreme - 1]; + MyLayer *extr1 = (idx_extreme == idx_extreme_first) ? nullptr : extremes[idx_extreme - 1]; // Fuse a support layer firmly to the raft top interface (not to the raft contacts). coordf_t extr1z = (extr1 == nullptr) ? m_slicing_params.raft_interface_top_z : extr1->extreme_z(); assert(extr2z >= extr1z); @@ -2736,7 +2799,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 +2930,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 +2962,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 +2993,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 +3027,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 +3090,43 @@ std::pair(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 +3275,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 +3287,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 +3540,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 +3555,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 +3895,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 +3909,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 +3935,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 +3944,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 +3961,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 +3990,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 +4031,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 +4042,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); @@ -4005,6 +4060,9 @@ void PrintObjectSupportMaterial::generate_toolpaths( { SupportLayer &support_layer = *support_layers[support_layer_id]; LayerCache &layer_cache = layer_caches[support_layer_id]; + float interface_angle_delta = m_object_config->support_material_style.value == smsSnug ? + (support_layer.interface_id() & 1) ? float(- M_PI / 4.) : float(+ M_PI / 4.) : + 0; // Find polygons with the same print_z. MyLayerExtruded &bottom_contact_layer = layer_cache.bottom_contact_layer; @@ -4084,8 +4142,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 + interface_angle_delta; + 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 +4164,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 + interface_angle_delta; 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 +4174,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 +4188,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 a1bd81297e..65604ef721 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 d3c9a49b5c..0f3d0f5b64 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/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index a210542019..a98998a848 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -919,7 +919,6 @@ bool GUI_App::on_init_inner() } }); Bind(EVT_SLIC3R_ALPHA_VERSION_ONLINE, [this](const wxCommandEvent& evt) { - //app_config->set("version_alpha_online", into_u8(evt.GetString())); app_config->save(); if (this->plater_ != nullptr && app_config->get("notify_testing_release") == "1") { if (*Semver::parse(SLIC3R_VERSION) < *Semver::parse(into_u8(evt.GetString()))) { @@ -928,7 +927,6 @@ bool GUI_App::on_init_inner() } }); Bind(EVT_SLIC3R_BETA_VERSION_ONLINE, [this](const wxCommandEvent& evt) { - //app_config->set("version_beta_online", into_u8(evt.GetString())); app_config->save(); if (this->plater_ != nullptr && app_config->get("notify_testing_release") == "1") { if (*Semver::parse(SLIC3R_VERSION) < *Semver::parse(into_u8(evt.GetString()))) { @@ -2022,14 +2020,14 @@ void GUI_App::add_config_menu(wxMenuBar *menu) menu->Append(local_menu, _L("&Configuration")); } -void GUI_App::open_preferences(size_t open_on_tab) +void GUI_App::open_preferences(size_t open_on_tab, const std::string& highlight_option) { bool app_layout_changed = false; { // the dialog needs to be destroyed before the call to recreate_GUI() // or sometimes the application crashes into wxDialogBase() destructor // so we put it into an inner scope - PreferencesDialog dlg(mainframe, open_on_tab); + PreferencesDialog dlg(mainframe, open_on_tab, highlight_option); dlg.ShowModal(); app_layout_changed = dlg.settings_layout_changed(); #if ENABLE_GCODE_LINES_ID_IN_H_SLIDER diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 3061bbe132..d350da9693 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -259,7 +259,7 @@ public: wxString current_language_code_safe() const; bool is_localized() const { return m_wxLocale->GetLocale() != "English"; } - void open_preferences(size_t open_on_tab = 0); + void open_preferences(size_t open_on_tab = 0, const std::string& highlight_option = std::string()); virtual bool OnExceptionInMainLoop() override; // Calls wxLaunchDefaultBrowser if user confirms in dialog. diff --git a/src/slic3r/GUI/GalleryDialog.cpp b/src/slic3r/GUI/GalleryDialog.cpp index fef131b887..f5e3083a78 100644 --- a/src/slic3r/GUI/GalleryDialog.cpp +++ b/src/slic3r/GUI/GalleryDialog.cpp @@ -77,8 +77,10 @@ GalleryDialog::GalleryDialog(wxWindow* parent, bool modify_gallery/* = false*/) m_list_ctrl = new wxListCtrl(this, wxID_ANY, wxDefaultPosition, wxSize(50 * wxGetApp().em_unit(), 35 * wxGetApp().em_unit()), wxLC_ICON | wxSIMPLE_BORDER); - m_list_ctrl->Bind(wxEVT_LIST_ITEM_SELECTED, &GalleryDialog::select, this); - m_list_ctrl->Bind(wxEVT_LIST_ITEM_DESELECTED, &GalleryDialog::deselect, this); + m_list_ctrl->Bind(wxEVT_LIST_ITEM_SELECTED, &GalleryDialog::select, this); + m_list_ctrl->Bind(wxEVT_LIST_ITEM_DESELECTED, &GalleryDialog::deselect, this); + m_list_ctrl->Bind(wxEVT_LIST_KEY_DOWN, &GalleryDialog::key_down, this); + m_list_ctrl->Bind(wxEVT_LIST_ITEM_RIGHT_CLICK, &GalleryDialog::show_context_menu, this); m_list_ctrl->Bind(wxEVT_LIST_ITEM_ACTIVATED, [this](wxListEvent& event) { m_selected_items.clear(); select(event); @@ -111,19 +113,11 @@ GalleryDialog::GalleryDialog(wxWindow* parent, bool modify_gallery/* = false*/) this->Bind(wxEVT_BUTTON, method, this, ID); }; - auto enable_del_fn = [this]() { - if (m_selected_items.empty()) - return false; - for (const Item& item : m_selected_items) - if (item.is_system) - return false; - return true; - }; - - add_btn(0, ID_BTN_ADD_CUSTOM_SHAPE, _L("Add"), _L("Add one or more custom shapes"), &GalleryDialog::add_custom_shapes); - add_btn(1, ID_BTN_DEL_CUSTOM_SHAPE, _L("Delete"), _L("Delete one or more custom shape. You can't delete system shapes"), &GalleryDialog::del_custom_shapes, enable_del_fn); - add_btn(2, ID_BTN_REPLACE_CUSTOM_PNG, _L("Replace PNG"), _L("Replace PNG for custom shape. You can't raplace PNG for system shape"),&GalleryDialog::replace_custom_png, [this]() { return (m_selected_items.size() == 1 && !m_selected_items[0].is_system); }); - buttons->InsertStretchSpacer(3, 2* BORDER_W); + size_t btn_pos = 0; + add_btn(btn_pos++, ID_BTN_ADD_CUSTOM_SHAPE, _L("Add"), _L("Add one or more custom shapes"), &GalleryDialog::add_custom_shapes); + add_btn(btn_pos++, ID_BTN_DEL_CUSTOM_SHAPE, _L("Delete"), _L("Delete one or more custom shape. You can't delete system shapes"), &GalleryDialog::del_custom_shapes, [this](){ return can_delete(); }); + //add_btn(btn_pos++, ID_BTN_REPLACE_CUSTOM_PNG, _L("Change thumbnail"), _L("Replace PNG for custom shape. You can't raplace thimbnail for system shape"), &GalleryDialog::change_thumbnail, [this](){ return can_change_thumbnail(); }); + buttons->InsertStretchSpacer(btn_pos, 2* BORDER_W); load_label_icon_list(); @@ -146,6 +140,21 @@ GalleryDialog::~GalleryDialog() { } +bool GalleryDialog::can_delete() +{ + if (m_selected_items.empty()) + return false; + for (const Item& item : m_selected_items) + if (item.is_system) + return false; + return true; +} + +bool GalleryDialog::can_change_thumbnail() +{ + return (m_selected_items.size() == 1 && !m_selected_items[0].is_system); +} + void GalleryDialog::on_dpi_changed(const wxRect& suggested_rect) { const int& em = em_unit(); @@ -405,7 +414,7 @@ void GalleryDialog::add_custom_shapes(wxEvent& event) load_files(input_files); } -void GalleryDialog::del_custom_shapes(wxEvent& event) +void GalleryDialog::del_custom_shapes() { auto custom_dir = get_dir(false); @@ -438,7 +447,7 @@ static void show_warning(const wxString& title, const std::string& error_file_ty dialog.ShowModal(); } -void GalleryDialog::replace_custom_png(wxEvent& event) +void GalleryDialog::change_thumbnail() { if (m_selected_items.size() != 1 || m_selected_items[0].is_system) return; @@ -494,6 +503,23 @@ void GalleryDialog::deselect(wxListEvent& event) m_selected_items.erase(std::remove_if(m_selected_items.begin(), m_selected_items.end(), [name](Item item) { return item.name == name; })); } +void GalleryDialog::show_context_menu(wxListEvent& event) +{ + wxMenu* menu = new wxMenu(); + if (can_delete()) + append_menu_item(menu, wxID_ANY, _L("Delete"), "", [this](wxCommandEvent&) { del_custom_shapes(); }); + if (can_change_thumbnail()) + append_menu_item(menu, wxID_ANY, _L("Change thumbnail"), "", [this](wxCommandEvent&) { change_thumbnail(); }); + + this->PopupMenu(menu); +} + +void GalleryDialog::key_down(wxListEvent& event) +{ + if (can_delete() && (event.GetKeyCode() == WXK_DELETE || event.GetKeyCode() == WXK_BACK)) + del_custom_shapes(); +} + void GalleryDialog::update() { m_selected_items.clear(); diff --git a/src/slic3r/GUI/GalleryDialog.hpp b/src/slic3r/GUI/GalleryDialog.hpp index 8cf3f00960..2aa04ffa2e 100644 --- a/src/slic3r/GUI/GalleryDialog.hpp +++ b/src/slic3r/GUI/GalleryDialog.hpp @@ -33,10 +33,17 @@ class GalleryDialog : public DPIDialog void load_label_icon_list(); void add_custom_shapes(wxEvent& event); - void del_custom_shapes(wxEvent& event); - void replace_custom_png(wxEvent& event); + void del_custom_shapes(); + void del_custom_shapes(wxEvent& event) { del_custom_shapes(); } + void change_thumbnail(); + void change_thumbnail(wxEvent& event) { change_thumbnail(); } void select(wxListEvent& event); void deselect(wxListEvent& event); + void show_context_menu(wxListEvent& event); + void key_down(wxListEvent& event); + + bool can_delete(); + bool can_change_thumbnail(); void update(); diff --git a/src/slic3r/GUI/HintNotification.cpp b/src/slic3r/GUI/HintNotification.cpp index fc410fce24..21aecd15b1 100644 --- a/src/slic3r/GUI/HintNotification.cpp +++ b/src/slic3r/GUI/HintNotification.cpp @@ -919,7 +919,7 @@ void NotificationManager::HintNotification::render_preferences_button(ImGuiWrapp } if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) { - wxGetApp().open_preferences(2); + wxGetApp().open_preferences(2, "show_hints"); } ImGui::PopStyleColor(5); diff --git a/src/slic3r/GUI/OG_CustomCtrl.cpp b/src/slic3r/GUI/OG_CustomCtrl.cpp index f01a1e9620..18553b9cf3 100644 --- a/src/slic3r/GUI/OG_CustomCtrl.cpp +++ b/src/slic3r/GUI/OG_CustomCtrl.cpp @@ -72,6 +72,11 @@ void OG_CustomCtrl::init_ctrl_lines() const std::vector& og_lines = opt_group->get_lines(); for (const Line& line : og_lines) { + if (line.is_separator()) { + ctrl_lines.emplace_back(CtrlLine(0, this, line)); + continue; + } + if (line.full_width && ( // description line line.widget != nullptr || @@ -124,6 +129,15 @@ wxPoint OG_CustomCtrl::get_pos(const Line& line, Field* field_in/* = nullptr*/) line_height = win_height; }; + auto correct_horiz_pos = [this](int& h_pos, Field* field) { + if (m_max_win_width > 0 && field->getWindow()) { + int win_width = field->getWindow()->GetSize().GetWidth(); + if (dynamic_cast(field)) + win_width *= 0.5; + h_pos += m_max_win_width - win_width; + } + }; + for (CtrlLine& ctrl_line : ctrl_lines) { if (&ctrl_line.og_line == &line) { @@ -160,6 +174,7 @@ wxPoint OG_CustomCtrl::get_pos(const Line& line, Field* field_in/* = nullptr*/) h_pos += 3 * blinking_button_width; Field* field = opt_group->get_field(option_set.front().opt_id); correct_line_height(ctrl_line.height, field->getWindow()); + correct_horiz_pos(h_pos, field); break; } @@ -189,8 +204,10 @@ wxPoint OG_CustomCtrl::get_pos(const Line& line, Field* field_in/* = nullptr*/) } h_pos += (opt.opt.gui_type == ConfigOptionDef::GUIType::legend ? 1 : 3) * blinking_button_width; - if (field == field_in) + if (field == field_in) { + correct_horiz_pos(h_pos, field); break; + } if (opt.opt.gui_type == ConfigOptionDef::GUIType::legend) h_pos += 2 * blinking_button_width; @@ -361,6 +378,28 @@ void OG_CustomCtrl::correct_widgets_position(wxSizer* widget, const Line& line, } }; +void OG_CustomCtrl::init_max_win_width() +{ + if (opt_group->ctrl_horiz_alignment == wxALIGN_RIGHT && m_max_win_width == 0) + for (CtrlLine& line : ctrl_lines) { + if (int max_win_width = line.get_max_win_width(); + m_max_win_width < max_win_width) + m_max_win_width = max_win_width; + } +} + +void OG_CustomCtrl::set_max_win_width(int max_win_width) +{ + if (m_max_win_width == max_win_width) + return; + m_max_win_width = max_win_width; + for (CtrlLine& line : ctrl_lines) + line.correct_items_positions(); + + GetParent()->Layout(); +} + + void OG_CustomCtrl::msw_rescale() { #ifdef __WXOSX__ @@ -374,6 +413,8 @@ void OG_CustomCtrl::msw_rescale() m_bmp_mode_sz = create_scaled_bitmap("mode_simple", this, wxOSX ? 10 : 12).GetSize(); m_bmp_blinking_sz = create_scaled_bitmap("search_blink", this).GetSize(); + m_max_win_width = 0; + wxCoord v_pos = 0; for (CtrlLine& line : ctrl_lines) { line.msw_rescale(); @@ -407,6 +448,21 @@ OG_CustomCtrl::CtrlLine::CtrlLine( wxCoord height, } } +int OG_CustomCtrl::CtrlLine::get_max_win_width() +{ + int max_win_width = 0; + if (!draw_just_act_buttons) { + const std::vector