New ClipperUtils functions: opening(), closing() as an alternative

for offset2() with clear meaning.
New ClipperUtils functions: expand(), shrink() as an alternative
for offset() with clear meaning.
All offset values for the new functions are positive.

Various offsetting ClipperUtils (offset, offset2, offset2_ex) working
over Polygons were marked as unsafe, sometimes producing invalid output
if called for more than one polygon. These functions were reworked
to offset polygons one by one. The new functions working over Polygons
shall work the same way as the old safe ones working over ExPolygons,
but working with Polygons shall be computationally more efficient.

Improvements in FDM support generator:
1) For both grid and snug supports: Don't filter out supports for which
   the contacts are completely reduced by support / object XY separation.
2) Rounding / merging of supports using the closing radius parameter is
   now smoother, it does not produce sharp corners.
3) Snug supports: When calculating support interfaces, expand the projected
   support contact areas to produce wider, printable and more stable interfaces.
4) Don't reduce support interfaces for snug supports for steep overhangs,
   that would normally not need them. Snug supports often produce very
   narrow support interface regions and turning them off makes the support
   interfaces disappear.
This commit is contained in:
Vojtech Bubnik 2021-10-14 09:11:19 +02:00
parent 2f9ce6bedb
commit 7ff76d0768
19 changed files with 480 additions and 354 deletions

View File

@ -157,6 +157,19 @@ bool PolyNode::IsHole() const
return result; 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 // Miscellaneous global functions
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -3444,7 +3457,8 @@ void ClipperOffset::Execute(Paths& solution, double delta)
clpr.AddPath(outer, ptSubject, true); clpr.AddPath(outer, ptSubject, true);
clpr.ReverseSolution(true); clpr.ReverseSolution(true);
clpr.Execute(ctUnion, solution, pftNegative, pftNegative); 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.ReverseSolution(true);
clpr.Execute(ctUnion, solution, pftNegative, pftNegative); clpr.Execute(ctUnion, solution, pftNegative, pftNegative);
//remove the outer PolyNode rectangle ... //remove the outer PolyNode rectangle ...
if (solution.ChildCount() == 1 && solution.Childs[0]->ChildCount() > 0) solution.RemoveOutermostPolygon();
{
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();
} }
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------

View File

@ -180,6 +180,7 @@ public:
PolyNode* GetFirst() const { return Childs.empty() ? nullptr : Childs.front(); } PolyNode* GetFirst() const { return Childs.empty() ? nullptr : Childs.front(); }
void Clear() { AllNodes.clear(); Childs.clear(); } void Clear() { AllNodes.clear(); Childs.clear(); }
int Total() const; int Total() const;
void RemoveOutermostPolygon();
private: private:
PolyTree(const PolyTree &src) = delete; PolyTree(const PolyTree &src) = delete;
PolyTree& operator=(const PolyTree &src) = delete; PolyTree& operator=(const PolyTree &src) = delete;
@ -521,6 +522,7 @@ public:
double MiterLimit; double MiterLimit;
double ArcTolerance; double ArcTolerance;
double ShortestEdgeLength; double ShortestEdgeLength;
private: private:
Paths m_destPolys; Paths m_destPolys;
Path m_srcPoly; Path m_srcPoly;
@ -528,6 +530,8 @@ private:
std::vector<DoublePoint> m_normals; std::vector<DoublePoint> m_normals;
double m_delta, m_sinA, m_sin, m_cos; double m_delta, m_sinA, m_sin, m_cos;
double m_miterLim, m_StepsPerRad; 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; IntPoint m_lowest;
PolyNode m_polyNodes; PolyNode m_polyNodes;

View File

@ -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))); 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) 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) 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)); 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) 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) 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)); 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) 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); 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())); size_t num_loops = size_t(floor(max_brim_width(print.objects()) / flow.spacing()));
for (size_t i = 0; i < num_loops; ++i) { for (size_t i = 0; i < num_loops; ++i) {
try_cancel(); try_cancel();
islands = offset(islands, float(flow.scaled_spacing()), ClipperLib::jtSquare); islands = expand(islands, float(flow.scaled_spacing()), ClipperLib::jtSquare);
for (Polygon &poly : islands) for (Polygon &poly : islands)
poly.douglas_peucker(SCALED_RESOLUTION); 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); loops = union_pt_chained_outside_in(loops);

View File

@ -117,15 +117,6 @@ Polylines PolyTreeToPolylines(ClipperLib::PolyTree &&polytree)
return out; 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 #if 0
// Global test. // Global test.
bool has_duplicate_points(const ClipperLib::PolyTree &polytree) bool has_duplicate_points(const ClipperLib::PolyTree &polytree)
@ -165,23 +156,78 @@ bool has_duplicate_points(const ClipperLib::PolyTree &polytree)
} }
#endif #endif
// Offset outside by 10um, one by one. template<class TResult, class TSubj, class TClip>
template<typename PathsProvider> TResult clipper_do(
static ClipperLib::Paths safety_offset(PathsProvider &&paths) const ClipperLib::ClipType clipType,
TSubj && subject,
TClip && clip,
const ClipperLib::PolyFillType fillType)
{
ClipperLib::Clipper clipper;
clipper.AddPaths(std::forward<TSubj>(subject), ClipperLib::ptSubject, true);
clipper.AddPaths(std::forward<TClip>(clip), ClipperLib::ptClip, true);
TResult retval;
clipper.Execute(clipType, retval, fillType, fillType);
return retval;
}
template<class TResult, class TSubj, class TClip>
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<TResult>(clipType, std::forward<TSubj>(subject), safety_offset(std::forward<TClip>(clip)), fillType) :
clipper_do<TResult>(clipType, std::forward<TSubj>(subject), std::forward<TClip>(clip), fillType);
}
template<class TResult, class TSubj>
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<TSubj>(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<ClipperLib::PolyTree>(input, do_union ? ClipperLib::pftNonZero : ClipperLib::pftEvenOdd));
}
// Offset CCW contours outside, CW contours (holes) inside.
// Don't calculate union of the output paths.
template<typename PathsProvider, ClipperLib::EndType endType = ClipperLib::etClosedPolygon>
static ClipperLib::Paths raw_offset(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit)
{ {
ClipperLib::ClipperOffset co; ClipperLib::ClipperOffset co;
ClipperLib::Paths out; ClipperLib::Paths out;
out.reserve(paths.size()); out.reserve(paths.size());
ClipperLib::Paths out_this; 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) { for (const ClipperLib::Path &path : paths) {
co.Clear(); co.Clear();
co.MiterLimit = 2.;
// Execute reorients the contours so that the outer most contour has a positive area. Thus the output // 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. // 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. // Offset is applied after contour reorientation, thus the signum of the offset value is reversed.
co.AddPath(path, ClipperLib::jtMiter, ClipperLib::etClosedPolygon); co.AddPath(path, joinType, endType);
bool ccw = ClipperLib::Orientation(path); bool ccw = endType == ClipperLib::etClosedPolygon ? ClipperLib::Orientation(path) : true;
co.Execute(out_this, ccw ? ClipperSafetyOffset : - ClipperSafetyOffset); co.Execute(out_this, ccw ? offset : - offset);
if (! ccw) { if (! ccw) {
// Reverse the resulting contours. // Reverse the resulting contours.
for (ClipperLib::Path &path : out_this) for (ClipperLib::Path &path : out_this)
@ -192,38 +238,71 @@ static ClipperLib::Paths safety_offset(PathsProvider &&paths)
return out; return out;
} }
// Only safe for a single path. template<typename PathsProvider, ClipperLib::EndType endType = ClipperLib::etClosedPolygon>
template<typename PathsProvider> static ClipperLib::Paths raw_offset_polyline(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit)
ClipperLib::Paths _offset(PathsProvider &&input, ClipperLib::EndType endType, const float delta, ClipperLib::JoinType joinType, double miterLimit)
{ {
// perform offset assert(offset > 0);
ClipperLib::ClipperOffset co; return raw_offset<PathsProvider, ClipperLib::etOpenButt>(std::forward<PathsProvider>(paths), offset, joinType, miterLimit);
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<PathsProvider>(input), joinType, endType);
ClipperLib::Paths retval;
co.Execute(retval, delta_scaled);
return retval;
} }
Slic3r::Polygons offset(const Slic3r::Polygon& polygon, const float delta, ClipperLib::JoinType joinType, double miterLimit) // Offset outside by 10um, one by one.
{ return to_polygons(_offset(ClipperUtils::SinglePathProvider(polygon.points), ClipperLib::etClosedPolygon, delta, joinType, miterLimit)); } template<typename PathsProvider>
static ClipperLib::Paths safety_offset(PathsProvider &&paths)
{
return raw_offset(std::forward<PathsProvider>(paths), ClipperSafetyOffset, DefaultJoinType, DefaultMiterLimit);
}
template<class TResult, typename PathsProvider>
static TResult expand_paths(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit)
{
assert(offset > 0);
return clipper_union<TResult>(raw_offset(std::forward<PathsProvider>(paths), offset, joinType, miterLimit));
}
template<class Container> static void remove_outermost_polygon(Container & solution);
template<> static void remove_outermost_polygon<ClipperLib::Paths>(ClipperLib::Paths &solution)
{ if (! solution.empty()) solution.erase(solution.begin()); }
template<> static void remove_outermost_polygon<ClipperLib::PolyTree>(ClipperLib::PolyTree &solution)
{ solution.RemoveOutermostPolygon(); }
template<class TResult, typename PathsProvider>
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<PathsProvider>(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<class TResult, typename PathsProvider>
static TResult offset_paths(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit)
{
assert(offset != 0);
return offset > 0 ?
expand_paths<TResult>(std::forward<PathsProvider>(paths), offset, joinType, miterLimit) :
shrink_paths<TResult>(std::forward<PathsProvider>(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) 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<ClipperLib::Paths>(ClipperUtils::PolygonsProvider(polygons), delta, joinType, miterLimit)); }
Slic3r::ExPolygons offset_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType, double 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)); } { return PolyTreeToExPolygons(offset_paths<ClipperLib::PolyTree>(ClipperUtils::PolygonsProvider(polygons), delta, joinType, miterLimit)); }
#endif // CLIPPERUTILS_UNSAFE_OFFSET
Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType, double 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<ClipperLib::Paths>(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) 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<ClipperLib::Paths>(raw_offset_polyline(ClipperUtils::PolylinesProvider(polylines), delta, joinType, miterLimit))); }
// returns number of expolygons collected (0 or 1). // 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) static int offset_expolygon_inner(const Slic3r::ExPolygon &expoly, const float delta, ClipperLib::JoinType joinType, double miterLimit, ClipperLib::Paths &out)
@ -275,13 +354,7 @@ static int offset_expolygon_inner(const Slic3r::ExPolygon &expoly, const float d
} else if (delta < 0) { } else if (delta < 0) {
// Negative offset. There is a chance, that the offsetted hole intersects the outer contour. // Negative offset. There is a chance, that the offsetted hole intersects the outer contour.
// Subtract the offsetted holes from the offsetted contours. // Subtract the offsetted holes from the offsetted contours.
ClipperLib::Clipper clipper; if (auto output = clipper_do<ClipperLib::Paths>(ClipperLib::ctDifference, contours, holes, ClipperLib::pftNonZero); ! output.empty()) {
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()) {
append(out, std::move(output)); append(out, std::move(output));
} else { } else {
// The offsetted holes have eaten up the offsetted outer contour. // The offsetted holes have eaten up the offsetted outer contour.
@ -308,7 +381,7 @@ static int offset_expolygon_inner(const Slic3r::Surface &surface, const float de
static int offset_expolygon_inner(const Slic3r::Surface *surface, const float delta, ClipperLib::JoinType joinType, double miterLimit, ClipperLib::Paths &out) 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); } { 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; ClipperLib::Paths out;
offset_expolygon_inner(expolygon, delta, joinType, miterLimit, out); offset_expolygon_inner(expolygon, delta, joinType, miterLimit, out);
@ -317,9 +390,9 @@ ClipperLib::Paths _offset(const Slic3r::ExPolygon &expolygon, const float delta,
// This is a safe variant of the polygons offset, tailored for multiple ExPolygons. // 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. // 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<typename ExPolygonVector> template<typename ExPolygonVector>
ClipperLib::Paths _offset(const ExPolygonVector &expolygons, const float delta, ClipperLib::JoinType joinType, double miterLimit) static std::pair<ClipperLib::Paths, size_t> expolygons_offset_raw(const ExPolygonVector &expolygons, const float delta, ClipperLib::JoinType joinType, double miterLimit)
{ {
// Offsetted ExPolygons before they are united. // Offsetted ExPolygons before they are united.
ClipperLib::Paths output; ClipperLib::Paths output;
@ -329,124 +402,76 @@ ClipperLib::Paths _offset(const ExPolygonVector &expolygons, const float delta,
size_t expolygons_collected = 0; size_t expolygons_collected = 0;
for (const auto &expoly : expolygons) for (const auto &expoly : expolygons)
expolygons_collected += offset_expolygon_inner(expoly, delta, joinType, miterLimit, output); expolygons_collected += offset_expolygon_inner(expoly, delta, joinType, miterLimit, output);
return std::make_pair(std::move(output), expolygons_collected);
}
// 4) Unite the offsetted expolygons. // See comment on expolygon_offsets_raw. In addition, for positive offset the contours are united.
if (expolygons_collected > 1 && delta > 0) { template<typename ExPolygonVector>
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. // There is a chance that the outwards offsetted expolygons may intersect. Perform a union.
ClipperLib::Clipper clipper; clipper_union<ClipperLib::Paths>(output) :
clipper.Clear();
clipper.AddPaths(output, ClipperLib::ptSubject, true);
clipper.Execute(ClipperLib::ctUnion, output, ClipperLib::pftNonZero, ClipperLib::pftNonZero);
} else {
// Negative offset. The shrunk expolygons shall not mutually intersect. Just copy the output. // Negative offset. The shrunk expolygons shall not mutually intersect. Just copy the output.
} output;
}
return output; // See comment on expolygons_offset_raw. In addition, the polygons are always united to conver to polytree.
template<typename ExPolygonVector>
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<ClipperLib::PolyTree>(output);
} }
Slic3r::Polygons offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType, double miterLimit) 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) 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) 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) 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) 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) 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) 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) 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<ClipperLib::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) 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<ClipperLib::PolyTree>(expolygons_offset(expolygons, delta1, joinType, miterLimit), delta2, joinType, miterLimit));
} }
template<class TResult, class TSubj, class TClip> // Offset outside, then inside produces morphological closing. All deltas should be positive.
TResult _clipper_do( Slic3r::Polygons closing(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit)
const ClipperLib::ClipType clipType,
TSubj && subject,
TClip && clip,
const ClipperLib::PolyFillType fillType)
{ {
ClipperLib::Clipper clipper; assert(delta1 > 0);
clipper.AddPaths(std::forward<TSubj>(subject), ClipperLib::ptSubject, true); assert(delta2 > 0);
clipper.AddPaths(std::forward<TClip>(clip), ClipperLib::ptClip, true); return to_polygons(shrink_paths<ClipperLib::Paths>(expand_paths<ClipperLib::Paths>(ClipperUtils::PolygonsProvider(polygons), delta1, joinType, miterLimit), delta2, joinType, miterLimit));
TResult retval; }
clipper.Execute(clipType, retval, fillType, fillType); Slic3r::ExPolygons closing_ex(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit)
return retval; {
assert(delta1 > 0);
assert(delta2 > 0);
return PolyTreeToExPolygons(shrink_paths<ClipperLib::PolyTree>(expand_paths<ClipperLib::Paths>(ClipperUtils::PolygonsProvider(polygons), delta1, joinType, miterLimit), delta2, joinType, miterLimit));
} }
template<class TResult, class TSubj, class TClip> // Offset inside, then outside produces morphological opening. All deltas should be positive.
TResult _clipper_do( Slic3r::Polygons opening(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit)
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(delta1 > 0);
assert(do_safety_offset == ApplySafetyOffset::No || clipType != ClipperLib::ctUnion); assert(delta2 > 0);
return do_safety_offset == ApplySafetyOffset::Yes ? return to_polygons(expand_paths<ClipperLib::Paths>(shrink_paths<ClipperLib::Paths>(ClipperUtils::PolygonsProvider(polygons), delta1, joinType, miterLimit), delta2, joinType, miterLimit));
_clipper_do<TResult>(clipType, std::forward<TSubj>(subject), safety_offset(std::forward<TClip>(clip)), fillType) :
_clipper_do<TResult>(clipType, std::forward<TSubj>(subject), std::forward<TClip>(clip), fillType);
} }
// Fix of #117: A large fractal pyramid takes ages to slice // Fix of #117: A large fractal pyramid takes ages to slice
@ -457,29 +482,22 @@ TResult _clipper_do(
// 1) Peform the Clipper operation with the output to Paths. This method handles overlaps in a reasonable time. // 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). // 2) Run Clipper Union once again to extract the PolyTree from the result of 1).
template<typename PathProvider1, typename PathProvider2> template<typename PathProvider1, typename PathProvider2>
inline ClipperLib::PolyTree _clipper_do_polytree2( inline ClipperLib::PolyTree clipper_do_polytree(
const ClipperLib::ClipType clipType, const ClipperLib::ClipType clipType,
PathProvider1 &&subject, PathProvider1 &&subject,
PathProvider2 &&clip, PathProvider2 &&clip,
const ClipperLib::PolyFillType fillType) const ClipperLib::PolyFillType fillType)
{ {
ClipperLib::Clipper clipper;
clipper.AddPaths(std::forward<PathProvider1>(subject), ClipperLib::ptSubject, true);
clipper.AddPaths(std::forward<PathProvider2>(clip), ClipperLib::ptClip, true);
// Perform the operation with the output to input_subject. // 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 // This pass does not generate a PolyTree, which is a very expensive operation with the current Clipper library
// if there are overapping edges. // if there are overapping edges.
ClipperLib::Paths input_subject; if (auto output = clipper_do<ClipperLib::Paths>(clipType, subject, clip, fillType); ! output.empty())
clipper.Execute(clipType, input_subject, fillType, fillType); // Perform an additional Union operation to generate the PolyTree ordering.
// Perform an additional Union operation to generate the PolyTree ordering. return clipper_union<ClipperLib::PolyTree>(output, fillType);
clipper.Clear(); return ClipperLib::PolyTree();
clipper.AddPaths(input_subject, ClipperLib::ptSubject, true);
ClipperLib::PolyTree retval;
clipper.Execute(ClipperLib::ctUnion, retval, fillType, fillType);
return retval;
} }
template<typename PathProvider1, typename PathProvider2> template<typename PathProvider1, typename PathProvider2>
inline ClipperLib::PolyTree _clipper_do_polytree2( inline ClipperLib::PolyTree clipper_do_polytree(
const ClipperLib::ClipType clipType, const ClipperLib::ClipType clipType,
PathProvider1 &&subject, PathProvider1 &&subject,
PathProvider2 &&clip, PathProvider2 &&clip,
@ -488,14 +506,14 @@ inline ClipperLib::PolyTree _clipper_do_polytree2(
{ {
assert(do_safety_offset == ApplySafetyOffset::No || clipType != ClipperLib::ctUnion); assert(do_safety_offset == ApplySafetyOffset::No || clipType != ClipperLib::ctUnion);
return do_safety_offset == ApplySafetyOffset::Yes ? return do_safety_offset == ApplySafetyOffset::Yes ?
_clipper_do_polytree2(clipType, std::forward<PathProvider1>(subject), safety_offset(std::forward<PathProvider2>(clip)), fillType) : clipper_do_polytree(clipType, std::forward<PathProvider1>(subject), safety_offset(std::forward<PathProvider2>(clip)), fillType) :
_clipper_do_polytree2(clipType, std::forward<PathProvider1>(subject), std::forward<PathProvider2>(clip), fillType); clipper_do_polytree(clipType, std::forward<PathProvider1>(subject), std::forward<PathProvider2>(clip), fillType);
} }
template<class TSubj, class TClip> template<class TSubj, class TClip>
static inline Polygons _clipper(ClipperLib::ClipType clipType, TSubj &&subject, TClip &&clip, ApplySafetyOffset do_safety_offset) static inline Polygons _clipper(ClipperLib::ClipType clipType, TSubj &&subject, TClip &&clip, ApplySafetyOffset do_safety_offset)
{ {
return to_polygons(_clipper_do<ClipperLib::Paths>(clipType, std::forward<TSubj>(subject), std::forward<TClip>(clip), ClipperLib::pftNonZero, do_safety_offset)); return to_polygons(clipper_do<ClipperLib::Paths>(clipType, std::forward<TSubj>(subject), std::forward<TClip>(clip), ClipperLib::pftNonZero, do_safety_offset));
} }
Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)
@ -529,7 +547,7 @@ Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::Polygons
template <typename TSubject, typename TClip> template <typename TSubject, typename TClip>
static ExPolygons _clipper_ex(ClipperLib::ClipType clipType, TSubject &&subject, TClip &&clip, ApplySafetyOffset do_safety_offset, ClipperLib::PolyFillType fill_type = ClipperLib::pftNonZero) 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<TSubject>(subject), std::forward<TClip>(clip), fill_type, do_safety_offset)); } { return PolyTreeToExPolygons(clipper_do_polytree(clipType, std::forward<TSubject>(subject), std::forward<TClip>(clip), fill_type, do_safety_offset)); }
Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset 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); } { return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); }
@ -578,9 +596,9 @@ Slic3r::ExPolygons intersection_ex(const Slic3r::SurfacesPtr &subject, const Sli
Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, ClipperLib::PolyFillType fill_type) 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); } { return _clipper_ex(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ApplySafetyOffset::No, fill_type); }
Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons &subject) 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) 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<typename PathsProvider1, typename PathsProvider2> template<typename PathsProvider1, typename PathsProvider2>
Polylines _clipper_pl_open(ClipperLib::ClipType clipType, PathsProvider1 &&subject, PathsProvider2 &&clip) Polylines _clipper_pl_open(ClipperLib::ClipType clipType, PathsProvider1 &&subject, PathsProvider2 &&clip)
@ -692,14 +710,15 @@ Lines _clipper_ln(ClipperLib::ClipType clipType, const Lines &subject, const Pol
return retval; 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) ClipperLib::PolyTree union_pt(const Polygons &subject)
{ {
return _clipper_do<ClipperLib::PolyTree>(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftEvenOdd); return clipper_do<ClipperLib::PolyTree>(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftEvenOdd);
} }
ClipperLib::PolyTree union_pt(const ExPolygons &subject) ClipperLib::PolyTree union_pt(const ExPolygons &subject)
{ {
return _clipper_do<ClipperLib::PolyTree>(ClipperLib::ctUnion, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftEvenOdd); return clipper_do<ClipperLib::PolyTree>(ClipperLib::ctUnion, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftEvenOdd);
} }
// Simple spatial ordering of Polynodes // Simple spatial ordering of Polynodes
@ -730,7 +749,7 @@ static void traverse_pt_noholes(const ClipperLib::PolyNodes &nodes, Polygons *ou
}); });
} }
static void traverse_pt_outside_in(const ClipperLib::PolyNodes &nodes, Polygons *retval) static void traverse_pt_outside_in(ClipperLib::PolyNodes &&nodes, Polygons *retval)
{ {
// collect ordering points // collect ordering points
Points ordering_points; Points ordering_points;
@ -740,22 +759,20 @@ static void traverse_pt_outside_in(const ClipperLib::PolyNodes &nodes, Polygons
// Perform the ordering, push results recursively. // Perform the ordering, push results recursively.
//FIXME pass the last point to chain_clipper_polynodes? //FIXME pass the last point to chain_clipper_polynodes?
for (const ClipperLib::PolyNode *node : chain_clipper_polynodes(ordering_points, nodes)) { for (ClipperLib::PolyNode *node : chain_clipper_polynodes(ordering_points, nodes)) {
retval->emplace_back(node->Contour); retval->emplace_back(std::move(node->Contour));
if (node->IsHole()) if (node->IsHole())
// Orient a hole, which is clockwise oriented, to CCW. // Orient a hole, which is clockwise oriented, to CCW.
retval->back().reverse(); retval->back().reverse();
// traverse the next depth // 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) Polygons union_pt_chained_outside_in(const Polygons &subject)
{ {
ClipperLib::PolyTree polytree = union_pt(subject);
Polygons retval; Polygons retval;
traverse_pt_outside_in(polytree.Childs, &retval); traverse_pt_outside_in(union_pt(subject).Childs, &retval);
return retval; return retval;
} }

View File

@ -12,16 +12,26 @@ using Slic3r::ClipperLib::jtMiter;
using Slic3r::ClipperLib::jtRound; using Slic3r::ClipperLib::jtRound;
using Slic3r::ClipperLib::jtSquare; 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 { enum class ApplySafetyOffset {
No, No,
Yes Yes
}; };
#define CLIPPERUTILS_UNSAFE_OFFSET
namespace Slic3r {
namespace ClipperUtils { namespace ClipperUtils {
class PathsProviderIteratorBase { class PathsProviderIteratorBase {
public: public:
@ -81,6 +91,33 @@ namespace ClipperUtils {
static Points s_end; static Points s_end;
}; };
template<typename PathType>
class PathsProvider {
public:
PathsProvider(const std::vector<PathType> &paths) : m_paths(paths) {}
struct iterator : public PathsProviderIteratorBase {
public:
explicit iterator(typename std::vector<PathType>::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<PathType>::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<PathType> &m_paths;
};
template<typename MultiPointType> template<typename MultiPointType>
class MultiPointsProvider { class MultiPointsProvider {
public: 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 // 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 // offset Polylines
Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtSquare, double miterLimit = 3); // Wherever applicable, please use the expand() / shrink() variants instead, they convey their purpose better.
Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtSquare, double miterLimit = 3); // Input polygons for negative offset shall be "normalized": There must be no overlap / intersections between the input polygons.
Slic3r::Polygons offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType = DefaultLineJoinType, double miterLimit = DefaultLineMiterLimit);
Slic3r::Polygons offset(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, ClipperLib::JoinType joinType = DefaultLineJoinType, double miterLimit = DefaultLineMiterLimit);
Slic3r::Polygons offset(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); Slic3r::Polygons offset(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
Slic3r::Polygons offset(const Slic3r::SurfacesPtr &surfaces, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); Slic3r::Polygons offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
Slic3r::ExPolygons offset_ex(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 = DefaultJoinType, double miterLimit = DefaultMiterLimit);
Slic3r::ExPolygons offset_ex(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 = DefaultJoinType, double miterLimit = DefaultMiterLimit);
Slic3r::ExPolygons offset_ex(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 = 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::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::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); // Aliases for the various offset(...) functions, conveying the purpose of the offset.
Slic3r::ExPolygons offset2_ex(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); 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 // Wherever applicable, please use the opening() / closing() variants instead, they convey their purpose better.
Slic3r::Polygons offset(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); // Input polygons for negative offset shall be "normalized": There must be no overlap / intersections between the input polygons.
Slic3r::ExPolygons offset_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); Slic3r::Polygons offset2(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
ClipperLib::Paths _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::ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
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); // Offset outside, then inside produces morphological closing. All deltas should be positive.
Slic3r::Polygons union_safety_offset(const Slic3r::Polygons &expolygons); Slic3r::Polygons closing(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::Polygons &polygons); 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); }
#endif // CLIPPERUTILS_UNSAFE_OFFSET 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); 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::ExPolygons &subject);
Slic3r::ExPolygons union_ex(const Slic3r::Surfaces &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::Polygons &subject);
ClipperLib::PolyTree union_pt(const Slic3r::ExPolygons &subject); ClipperLib::PolyTree union_pt(const Slic3r::ExPolygons &subject);

View File

@ -252,11 +252,11 @@ std::vector<SurfaceFill> 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 // Corners of infill regions, which would not be filled with an extrusion path with a radius of distance_between_surfaces/2
Polygons collapsed = diff( Polygons collapsed = diff(
surfaces_polygons, 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 //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. // added if two offsetted void regions merge.
// polygons_append(voids, collapsed); // 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. // Now find an internal infill SurfaceFill to add these extrusions to.
SurfaceFill *internal_solid_fill = nullptr; SurfaceFill *internal_solid_fill = nullptr;
unsigned int region_id = 0; unsigned int region_id = 0;

View File

@ -402,19 +402,19 @@ public:
hole.rotate(angle); hole.rotate(angle);
} }
double mitterLimit = 3.; double miterLimit = DefaultMiterLimit;
// for the infill pattern, don't cut the corners. // for the infill pattern, don't cut the corners.
// default miterLimt = 3 // default miterLimt = 3
//double mitterLimit = 10.; //double miterLimit = 10.;
assert(aoffset1 < 0); assert(aoffset1 < 0);
assert(aoffset2 <= 0); assert(aoffset2 <= 0);
assert(aoffset2 == 0 || aoffset2 < aoffset1); assert(aoffset2 == 0 || aoffset2 < aoffset1);
// bool sticks_removed = // bool sticks_removed =
remove_sticks(polygons_src); remove_sticks(polygons_src);
// if (sticks_removed) BOOST_LOG_TRIVIAL(error) << "Sticks removed!"; // 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) 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. // Filter out contours with zero area or small area, contours with 2 points only.
const double min_area_threshold = 0.01 * aoffset2 * aoffset2; const double min_area_threshold = 0.01 * aoffset2 * aoffset2;
remove_small(polygons_outer, min_area_threshold); remove_small(polygons_outer, min_area_threshold);

View File

@ -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 // 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. // Reverse all polygons for making normals point from the polygon out.
for (Polygon &poly : boundary) for (Polygon &poly : boundary)
poly.reverse(); poly.reverse();

View File

@ -212,7 +212,7 @@ void SeamPlacer::init(const Print& print)
std::vector<float> deltas(input.points.size(), offset); std::vector<float> deltas(input.points.size(), offset);
input.make_counter_clockwise(); input.make_counter_clockwise();
out.front() = mittered_offset_path_scaled(input.points, deltas, 3.); out.front() = mittered_offset_path_scaled(input.points, deltas, 3.);
return ClipperPaths_to_Slic3rExPolygons(out); return ClipperPaths_to_Slic3rExPolygons(out, true); // perform union
}; };

View File

@ -1400,7 +1400,7 @@ static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
if (std::vector<Polygons> &top = top_raw[color_idx]; ! top.empty() && ! top[layer_idx].empty()) if (std::vector<Polygons> &top = top_raw[color_idx]; ! top.empty() && ! top[layer_idx].empty())
if (ExPolygons top_ex = union_ex(top[layer_idx]); ! top_ex.empty()) { if (ExPolygons top_ex = union_ex(top[layer_idx]); ! top_ex.empty()) {
// Clean up thin projections. They are not printable anyways. // 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()) { if (! top_ex.empty()) {
append(triangles_by_color_top[color_idx][layer_idx + layer_idx_offset], top_ex); append(triangles_by_color_top[color_idx][layer_idx + layer_idx_offset], top_ex);
float offset = 0.f; float offset = 0.f;
@ -1408,8 +1408,7 @@ static inline std::vector<std::vector<ExPolygons>> 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) { 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; offset -= stat.extrusion_width;
layer_slices_trimmed = intersection_ex(layer_slices_trimmed, input_expolygons[last_idx]); 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)), ExPolygons last = opening_ex(intersection_ex(top_ex, offset_ex(layer_slices_trimmed, offset)), stat.small_region_threshold);
- stat.small_region_threshold, + stat.small_region_threshold);
if (last.empty()) if (last.empty())
break; break;
append(triangles_by_color_top[color_idx][last_idx + layer_idx_offset], std::move(last)); append(triangles_by_color_top[color_idx][last_idx + layer_idx_offset], std::move(last));
@ -1419,7 +1418,7 @@ static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
if (std::vector<Polygons> &bottom = bottom_raw[color_idx]; ! bottom.empty() && ! bottom[layer_idx].empty()) if (std::vector<Polygons> &bottom = bottom_raw[color_idx]; ! bottom.empty() && ! bottom[layer_idx].empty())
if (ExPolygons bottom_ex = union_ex(bottom[layer_idx]); ! bottom_ex.empty()) { if (ExPolygons bottom_ex = union_ex(bottom[layer_idx]); ! bottom_ex.empty()) {
// Clean up thin projections. They are not printable anyways. // 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()) { if (! bottom_ex.empty()) {
append(triangles_by_color_bottom[color_idx][layer_idx + layer_idx_offset], bottom_ex); append(triangles_by_color_bottom[color_idx][layer_idx + layer_idx_offset], bottom_ex);
float offset = 0.f; float offset = 0.f;
@ -1427,8 +1426,7 @@ static inline std::vector<std::vector<ExPolygons>> 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) { 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; offset -= stat.extrusion_width;
layer_slices_trimmed = intersection_ex(layer_slices_trimmed, input_expolygons[last_idx]); 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)), ExPolygons last = opening_ex(intersection_ex(bottom_ex, offset_ex(layer_slices_trimmed, offset)), stat.small_region_threshold);
- stat.small_region_threshold, + stat.small_region_threshold);
if (last.empty()) if (last.empty())
break; break;
append(triangles_by_color_bottom[color_idx][last_idx + layer_idx_offset], std::move(last)); append(triangles_by_color_bottom[color_idx][last_idx + layer_idx_offset], std::move(last));

View File

@ -347,10 +347,10 @@ void PerimeterGenerator::process()
// the following offset2 ensures almost nothing in @thin_walls is narrower than $min_width // 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) // (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)); 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 // medial axis requires non-overlapping geometry
diff_ex(last, offset(offsets, float(ext_perimeter_width / 2.) + ClipperSafetyOffset)), 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 // the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop
for (ExPolygon &ex : expp) for (ExPolygon &ex : expp)
ex.medial_axis(ext_perimeter_width + ext_perimeter_spacing2, min_width, &thin_walls); 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; double max = 2. * perimeter_spacing;
ExPolygons gaps_ex = diff_ex( ExPolygons gaps_ex = diff_ex(
//FIXME offset2 would be enough and cheaper. //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))); offset2_ex(gaps, - float(max / 2.), float(max / 2. + ClipperSafetyOffset)));
ThickPolylines polylines; ThickPolylines polylines;
for (const ExPolygon &ex : gaps_ex) for (const ExPolygon &ex : gaps_ex)

View File

@ -774,7 +774,7 @@ void PrintObject::detect_surfaces_type()
ExPolygons upper_slices = interface_shells ? 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->m_regions[region_id]->slices.surfaces, ApplySafetyOffset::Yes) :
diff_ex(layerm->slices.surfaces, upper_layer->lslices, 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 { } else {
// if no upper layer, all surfaces of this one are solid // if no upper layer, all surfaces of this one are solid
// we clone surfaces because we're going to clear the slices collection // 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->get_region(region_id)->slices.surfaces) :
to_polygons(lower_layer->slices); to_polygons(lower_layer->slices);
surfaces_append(bottom, 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); surface_type_bottom_other);
#else #else
// Any surface lying on the void is a true bottom bridge (an overhang) // Any surface lying on the void is a true bottom bridge (an overhang)
surfaces_append( surfaces_append(
bottom, bottom,
offset2_ex( opening_ex(
diff_ex(layerm->slices.surfaces, lower_layer->lslices, ApplySafetyOffset::Yes), diff_ex(layerm->slices.surfaces, lower_layer->lslices, ApplySafetyOffset::Yes),
-offset, offset), offset),
surface_type_bottom_other); surface_type_bottom_other);
// if user requested internal shells, we need to identify surfaces // if user requested internal shells, we need to identify surfaces
// lying on other slices not belonging to this region // 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 // on something else, excluding those lying on our own region
surfaces_append( surfaces_append(
bottom, bottom,
offset2_ex( opening_ex(
diff_ex( diff_ex(
intersection(layerm->slices.surfaces, lower_layer->lslices), // supported intersection(layerm->slices.surfaces, lower_layer->lslices), // supported
lower_layer->m_regions[region_id]->slices.surfaces, lower_layer->m_regions[region_id]->slices.surfaces,
ApplySafetyOffset::Yes), ApplySafetyOffset::Yes),
-offset, offset), offset),
stBottom); stBottom);
} }
#endif #endif
@ -1337,7 +1337,7 @@ void PrintObject::discover_vertical_shells()
// get a triangle in $too_narrow; if we grow it below then the shell // 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 // 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. // 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()) { if (! too_narrow.empty()) {
// grow the collapsing parts and add the extra area to the neighbor layer // 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 // 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. // 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; 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; if (to_bridge_pp.empty()) continue;
@ -1744,7 +1744,7 @@ void PrintObject::clip_fill_surfaces()
for (const LayerRegion *layerm : layer->m_regions) for (const LayerRegion *layerm : layer->m_regions)
pw = std::min(pw, (float)layerm->flow(frPerimeter).scaled_width()); pw = std::min(pw, (float)layerm->flow(frPerimeter).scaled_width());
// Append such thick perimeters to the areas that need support // 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. // Find new internal infill.
polygons_append(overhangs, std::move(upper_internal)); 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()); float margin = float(neighbor_layerm->flow(frExternalPerimeter).scaled_width());
Polygons too_narrow = diff( Polygons too_narrow = diff(
new_internal_solid, 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. // Trim the regularized region by the original region.
if (! too_narrow.empty()) if (! too_narrow.empty())
new_internal_solid = solid = diff(new_internal_solid, too_narrow); 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. // have the same angle, so the next shell would be grown even more and so on.
Polygons too_narrow = diff( Polygons too_narrow = diff(
new_internal_solid, 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()) { if (! too_narrow.empty()) {
// grow the collapsing parts and add the extra area to the neighbor layer // 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 // 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(internal, to_polygons(surface.expolygon));
polygons_append(new_internal_solid, polygons_append(new_internal_solid,
intersection( intersection(
offset(too_narrow, +margin), expand(too_narrow, +margin),
// Discard bridges as they are grown for anchoring and we can't // Discard bridges as they are grown for anchoring and we can't
// remove such anchors. (This may happen when a bridge is being // remove such anchors. (This may happen when a bridge is being
// anchored onto a wall where little space remains after the bridge // anchored onto a wall where little space remains after the bridge

View File

@ -393,7 +393,7 @@ static std::vector<std::vector<ExPolygons>> slices_to_regions(
} }
} }
if (merged) 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); slices_by_region[temp_slices[i].region_id][z_idx] = std::move(expolygons);
i = j; i = j;
} }
@ -648,7 +648,7 @@ static inline void apply_mm_segmentation(PrintObject &print_object, ThrowOnCance
ByRegion &src = by_region[region_id]; ByRegion &src = by_region[region_id];
if (src.needs_merge) if (src.needs_merge)
// Multiple regions were merged into one. // 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); layer->get_region(region_id)->slices.set(std::move(src.expolygons), stInternal);
} }
} }

View File

@ -186,8 +186,8 @@ static std::vector<SupportPointGenerator::MyLayer> make_layers(
// Produce 2 bands around the island, a safe band for dangling overhangs // Produce 2 bands around the island, a safe band for dangling overhangs
// and an unsafe band for sloped overhangs. // and an unsafe band for sloped overhangs.
// These masks include the original island // These masks include the original island
auto dangl_mask = offset(bottom_polygons, between_layers_offset, ClipperLib::jtSquare); auto dangl_mask = expand(bottom_polygons, between_layers_offset, ClipperLib::jtSquare);
auto overh_mask = offset(bottom_polygons, slope_offset, ClipperLib::jtSquare); auto overh_mask = expand(bottom_polygons, slope_offset, ClipperLib::jtSquare);
// Absolutely hopeless overhangs are those outside the unsafe band // Absolutely hopeless overhangs are those outside the unsafe band
top.overhangs = diff_ex(*top.polygon, overh_mask); top.overhangs = diff_ex(*top.polygon, overh_mask);

View File

@ -367,6 +367,29 @@ PrintObjectSupportMaterial::PrintObjectSupportMaterial(const PrintObject *object
// Object is printed with the same extruder as the support. // Object is printed with the same extruder as the support.
m_support_params.can_merge_support_regions = true; 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. // Using the std::deque as an allocator.
@ -883,7 +906,14 @@ public:
// Merge the support polygons by applying morphological closing and inwards smoothing. // Merge the support polygons by applying morphological closing and inwards smoothing.
auto closing_distance = scaled<float>(m_support_material_closing_radius); auto closing_distance = scaled<float>(m_support_material_closing_radius);
auto smoothing_distance = scaled<float>(m_extrusion_width); auto smoothing_distance = scaled<float>(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<coord_t>(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); assert(false);
return Polygons(); return Polygons();
@ -1250,7 +1280,7 @@ namespace SupportMaterialInternal {
Polygons bridges; Polygons bridges;
{ {
// Surface supporting this layer, expanded by 0.5 * nozzle_diameter, as we consider this kind of overhang to be sufficiently supported. // 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. //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))), 0.5f * float(scale_(print_config.nozzle_diameter.get_at(layerm.region().config().perimeter_extruder-1))),
SUPPORT_SURFACES_OFFSET_PARAMETERS); SUPPORT_SURFACES_OFFSET_PARAMETERS);
@ -1414,7 +1444,7 @@ static inline std::tuple<Polygons, Polygons, Polygons, float> detect_overhangs(
overhang_polygons = to_polygons(layer.lslices); overhang_polygons = to_polygons(layer.lslices);
#endif #endif
// Expand for better stability. // Expand for better stability.
contact_polygons = offset(overhang_polygons, scaled<float>(object_config.raft_expansion.value)); contact_polygons = expand(overhang_polygons, scaled<float>(object_config.raft_expansion.value));
} }
else if (! layer.regions().empty()) else if (! layer.regions().empty())
{ {
@ -1475,20 +1505,20 @@ static inline std::tuple<Polygons, Polygons, Polygons, float> detect_overhangs(
//FIXME cache the lower layer offset if this layer has multiple regions. //FIXME cache the lower layer offset if this layer has multiple regions.
#if 0 #if 0
//FIXME this solution will trigger stupid supports for sharp corners, see GH #4874 //FIXME this solution will trigger stupid supports for sharp corners, see GH #4874
diff_polygons = offset2( diff_polygons = opening(
diff(layerm_polygons, diff(layerm_polygons,
// Likely filtering out thin regions from the lower layer, that will not be covered by perimeters, thus they // Likely filtering out thin regions from the lower layer, that will not be covered by perimeters, thus they
// are not supporting this layer. // 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. // 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 // For example, see GH issue #3094
offset2(lower_layer_polygons, - 0.5f * fw, lower_layer_offset + 0.5f * fw, SUPPORT_SURFACES_OFFSET_PARAMETERS)), opening(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 //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. // no support at all for not so steep overhangs.
- 0.1f * fw, 0.1f * fw); 0.1f * fw);
#else #else
diff_polygons = diff_polygons =
diff(layerm_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 #endif
if (buildplate_only && ! annotations.buildplate_covered[layer_id].empty()) { if (buildplate_only && ! annotations.buildplate_covered[layer_id].empty()) {
// Don't support overhangs above the top surfaces. // Don't support overhangs above the top surfaces.
@ -1500,7 +1530,7 @@ static inline std::tuple<Polygons, Polygons, Polygons, float> detect_overhangs(
// This is done to increase size of the supporting columns below, as they are calculated by // This is done to increase size of the supporting columns below, as they are calculated by
// propagating these contact surfaces downwards. // propagating these contact surfaces downwards.
diff_polygons = diff( 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); lower_layer_polygons);
} }
//FIXME add user defined filtering here based on minimal area or minimum radius or whatever. //FIXME add user defined filtering here based on minimal area or minimum radius or whatever.
@ -1516,7 +1546,7 @@ static inline std::tuple<Polygons, Polygons, Polygons, float> detect_overhangs(
// Subtracting them as they are may leave unwanted narrow // Subtracting them as they are may leave unwanted narrow
// residues of diff_polygons that would then be supported. // residues of diff_polygons that would then be supported.
diff_polygons = diff(diff_polygons, 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 #ifdef SLIC3R_DEBUG
@ -1588,7 +1618,7 @@ static inline std::tuple<Polygons, Polygons, Polygons, float> detect_overhangs(
#endif // SLIC3R_DEBUG #endif // SLIC3R_DEBUG
enforcer_polygons = diff(intersection(layer.lslices, annotations.enforcers_layers[layer_id]), 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. // 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 #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), 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 } }, { { layer.lslices, { "layer.lslices", "gray", 0.2f } },
@ -1720,7 +1750,7 @@ static inline void fill_contact_layer(
if (lower_layer_polygons_for_dense_interface_cache.empty()) if (lower_layer_polygons_for_dense_interface_cache.empty())
lower_layer_polygons_for_dense_interface_cache = 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. //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; return lower_layer_polygons_for_dense_interface_cache;
}; };
@ -1733,7 +1763,7 @@ static inline void fill_contact_layer(
#endif // SLIC3R_DEBUG #endif // SLIC3R_DEBUG
)); ));
// 2) infill polygons, expand them by half the extrusion width + a tiny bit of extra. // 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) { if (reduce_interfaces) {
// Reduce the amount of dense interfaces: Do not generate dense interfaces below overhangs with 60% overhang of the extrusions. // 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()); Polygons dense_interface_polygons = diff(overhang_polygons, lower_layer_polygons_for_dense_interface());
@ -1741,7 +1771,7 @@ static inline void fill_contact_layer(
dense_interface_polygons = dense_interface_polygons =
diff( diff(
// Regularize the contour. // Regularize the contour.
offset(dense_interface_polygons, no_interface_offset * 0.1f), expand(dense_interface_polygons, no_interface_offset * 0.1f),
slices_margin.polygons); slices_margin.polygons);
// Support islands, to be stretched into a grid. // 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, //FIXME The regularization of dense_interface_polygons above may stretch dense_interface_polygons outside of the contact polygons,
@ -1799,7 +1829,7 @@ static inline void fill_contact_layer(
dense_interface_polygons = dense_interface_polygons =
diff( diff(
// Regularize the contour. // 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); slices_margin.all_polygons);
// Support islands, to be stretched into a grid. // 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, //FIXME The regularization of dense_interface_polygons above may stretch dense_interface_polygons outside of the contact polygons,
@ -1937,7 +1967,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
); );
// Now apply the contact areas to the layer where they need to be made. // 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. // 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); 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) { if (new_layer) {
@ -2041,8 +2071,7 @@ static inline PrintObjectSupportMaterial::MyLayer* detect_bottom_contacts(
layer_new.idx_object_layer_below = layer_id; layer_new.idx_object_layer_below = layer_id;
layer_new.bridging = !slicing_params.soluble_interface && object.config().thick_bridges; 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 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 = expand(touching, float(support_params.support_material_flow.scaled_width()), SUPPORT_SURFACES_OFFSET_PARAMETERS);
layer_new.polygons = offset(touching, float(support_params.support_material_flow.scaled_width()), SUPPORT_SURFACES_OFFSET_PARAMETERS);
if (! slicing_params.soluble_interface) { 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, // Walk the top surfaces, snap the top of the new bottom surface to the closest top of the top surface,
@ -2081,7 +2110,7 @@ static inline PrintObjectSupportMaterial::MyLayer* detect_bottom_contacts(
// Trim the already created base layers above the current layer intersecting with the new bottom contacts layer. // 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? //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) { 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]; const Layer &layer_above = *object.layers()[layer_id_above];
if (layer_above.print_z > layer_new.print_z - EPSILON) if (layer_above.print_z > layer_new.print_z - EPSILON)
@ -2249,7 +2278,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta
#endif #endif
// These are the overhang surfaces. They are touching the object and they are not expanded away from the object. // 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. // 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(overhangs_projection, union_(polygons_new));
polygons_append(enforcers_projection, enforcers_new); polygons_append(enforcers_projection, enforcers_new);
} }
@ -2736,7 +2765,6 @@ void PrintObjectSupportMaterial::generate_base_layers(
ApplySafetyOffset::Yes); // safety offset to merge the touching source polygons ApplySafetyOffset::Yes); // safety offset to merge the touching source polygons
layer_intermediate.layer_type = sltBase; layer_intermediate.layer_type = sltBase;
// For snug supports, expand the interfaces into the intermediate layer to make it printable.
#if 0 #if 0
// coordf_t fillet_radius_scaled = scale_(m_object_config->support_material_spacing); // 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. // Fillet the base polygons and trim them again with the top, interface and contact layers.
@ -2868,7 +2896,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raf
if (brim_inner) { if (brim_inner) {
Polygons holes = ex.holes; Polygons holes = ex.holes;
polygons_reverse(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_reverse(holes);
polygons_append(brim, std::move(holes)); polygons_append(brim, std::move(holes));
} else } else
@ -2900,11 +2928,11 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raf
Polygons interface_polygons; Polygons interface_polygons;
if (contacts != nullptr && ! contacts->polygons.empty()) 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()) 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()) 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. // Output vector.
MyLayersPtr raft_layers; MyLayersPtr raft_layers;
@ -2931,7 +2959,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raf
new_layer.print_z = m_slicing_params.first_print_layer_height; new_layer.print_z = m_slicing_params.first_print_layer_height;
new_layer.height = m_slicing_params.first_print_layer_height; new_layer.height = m_slicing_params.first_print_layer_height;
new_layer.bottom_z = 0.; 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. // Insert the base layers.
for (size_t i = 1; i < m_slicing_params.base_raft_layers; ++ i) { for (size_t i = 1; i < m_slicing_params.base_raft_layers; ++ i) {
@ -2965,7 +2993,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raf
auto nsteps = std::max(5, int(ceil(inflate_factor_1st_layer / m_support_params.first_layer_flow.scaled_width()))); 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; float step = inflate_factor_1st_layer / nsteps;
for (int i = 0; i < nsteps; ++ i) for (int i = 0; i < nsteps; ++ i)
raft = diff(offset(raft, step), trimming); raft = diff(expand(raft, step), trimming);
} else } else
raft = diff(raft, trimming); raft = diff(raft, trimming);
if (contacts != nullptr) if (contacts != nullptr)
@ -3028,26 +3056,44 @@ std::pair<PrintObjectSupportMaterial::MyLayersPtr, PrintObjectSupportMaterial::M
interface_layers.assign(intermediate_layers.size(), nullptr); interface_layers.assign(intermediate_layers.size(), nullptr);
if (num_base_interface_layers_top || num_base_interface_layers_bottom) if (num_base_interface_layers_top || num_base_interface_layers_bottom)
base_interface_layers.assign(intermediate_layers.size(), nullptr); base_interface_layers.assign(intermediate_layers.size(), nullptr);
bool snug_supports = m_object_config->support_material_style.value == smsSnug;
auto smoothing_distance = m_support_params.support_material_interface_flow.scaled_spacing() * 1.5;
auto minimum_island_radius = m_support_params.support_material_interface_flow.scaled_spacing() / m_support_params.interface_density;
auto closing_distance = smoothing_distance; // scaled<float>(m_object_config->support_material_closing_radius.value);
tbb::spin_mutex layer_storage_mutex; tbb::spin_mutex layer_storage_mutex;
// Insert a new layer into base_interface_layers, if intersection with base exists. // 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()); 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. // Merge top into bottom, unite them with a safety offset.
append(bottom, std::move(top)); append(bottom, std::move(top));
layer_new.polygons = intersection(union_safety_offset(std::move(bottom)), intermediate_layer.polygons); // Merge top / bottom interfaces. For snug supports, merge using closing distance and regularize (close concave corners).
// Subtract the interface from the base regions. bottom = intersection(
intermediate_layer.polygons = diff(intermediate_layer.polygons, layer_new.polygons); snug_supports ?
if (subtract) smooth_outward(closing(std::move(bottom), closing_distance + minimum_island_radius, closing_distance, SUPPORT_SURFACES_OFFSET_PARAMETERS), smoothing_distance) :
// Trim the base interface layer with the interface layer. union_safety_offset(std::move(bottom)),
layer_new.polygons = diff(std::move(layer_new.polygons), *subtract); intermediate_layer.polygons);
//FIXME filter layer_new.polygons islands by a minimum area? if (! bottom.empty()) {
// $interface_area = [ grep abs($_->area) >= $area_threshold, @$interface_area ]; //FIXME Remove non-printable tiny islands, let them be printed using the base support.
return &layer_new; //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<int>(0, int(intermediate_layers.size())), tbb::parallel_for(tbb::blocked_range<int>(0, int(intermediate_layers.size())),
[&bottom_contacts, &top_contacts, &intermediate_layers, &insert_layer, [&bottom_contacts, &top_contacts, &intermediate_layers, &insert_layer,
@ -3196,7 +3242,7 @@ static inline void fill_expolygons_with_sheath_generate_paths(
return; return;
if (! with_sheath) { 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; return;
} }
@ -3208,7 +3254,7 @@ static inline void fill_expolygons_with_sheath_generate_paths(
// Clip the sheath path to avoid the extruder to get exactly on the first point of the loop. // Clip the sheath path to avoid the extruder to get exactly on the first point of the loop.
double clip_length = spacing * 0.15; 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. // Don't reorder the skirt and its infills.
std::unique_ptr<ExtrusionEntityCollection> eec; std::unique_ptr<ExtrusionEntityCollection> eec;
if (no_sort) { if (no_sort) {
@ -3462,9 +3508,9 @@ void LoopInterfaceProcessor::generate(MyLayerExtruded &top_contact_layer, const
Polygons loop_polygons = loops0; Polygons loop_polygons = loops0;
for (int i = 1; i < n_contact_loops; ++ i) for (int i = 1; i < n_contact_loops; ++ i)
polygons_append(loop_polygons, polygons_append(loop_polygons,
offset2( opening(
loops0, loops0,
- i * flow.scaled_spacing() - 0.5f * flow.scaled_spacing(), i * flow.scaled_spacing() + 0.5f * flow.scaled_spacing(),
0.5f * flow.scaled_spacing())); 0.5f * flow.scaled_spacing()));
// Clip such loops to the side oriented towards the object. // Clip such loops to the side oriented towards the object.
// Collect split points, so they will be recognized after the clipping. // Collect split points, so they will be recognized after the clipping.
@ -3476,7 +3522,7 @@ void LoopInterfaceProcessor::generate(MyLayerExtruded &top_contact_layer, const
map_split_points[it->first_point()] = -1; map_split_points[it->first_point()] = -1;
loop_lines.push_back(it->split_at_first_point()); 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. // Because a closed loop has been split to a line, loop_lines may contain continuous segments split to 2 pieces.
// Try to connect them. // Try to connect them.
for (int i_line = 0; i_line < int(loop_lines.size()); ++ i_line) { for (int i_line = 0; i_line < int(loop_lines.size()); ++ i_line) {
@ -3816,27 +3862,9 @@ void PrintObjectSupportMaterial::generate_toolpaths(
LoopInterfaceProcessor loop_interface_processor(1.5 * m_support_params.support_material_interface_flow.scaled_width()); 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; 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)); std::vector<float> angles { m_support_params.base_angle };
float interface_angle = Geometry::deg2rad(float(m_object_config->support_material_angle.value + 90.)); if (m_object_config->support_material_pattern == smpRectilinearGrid)
coordf_t interface_spacing = m_object_config->support_material_interface_spacing.value + m_support_params.support_material_interface_flow.spacing(); angles.push_back(m_support_params.interface_angle);
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<float> angles;
angles.push_back(base_angle);
if (support_pattern == smpRectilinearGrid)
angles.push_back(interface_angle);
BoundingBox bbox_object(Point(-scale_(1.), -scale_(1.0)), Point(scale_(1.), scale_(1.))); BoundingBox bbox_object(Point(-scale_(1.), -scale_(1.0)), Point(scale_(1.), scale_(1.)));
@ -3848,16 +3876,16 @@ void PrintObjectSupportMaterial::generate_toolpaths(
float raft_angle_interface = 0.f; float raft_angle_interface = 0.f;
if (m_slicing_params.base_raft_layers > 1) { if (m_slicing_params.base_raft_layers > 1) {
// There are all raft layer types (1st layer, base, interface & contact layers) available. // There are all raft layer types (1st layer, base, interface & contact layers) available.
raft_angle_1st_layer = interface_angle; raft_angle_1st_layer = m_support_params.interface_angle;
raft_angle_base = base_angle; raft_angle_base = m_support_params.base_angle;
raft_angle_interface = interface_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) { } else if (m_slicing_params.base_raft_layers == 1 || m_slicing_params.interface_raft_layers > 1) {
// 1st layer, interface & contact layers available. // 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()) 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. // 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_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) { } 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. // Only the contact raft layer is non-empty, which will be printed as the 1st layer.
assert(m_slicing_params.base_raft_layers == 0); assert(m_slicing_params.base_raft_layers == 0);
@ -3874,7 +3902,7 @@ void PrintObjectSupportMaterial::generate_toolpaths(
size_t n_raft_layers = size_t(std::max(0, int(m_slicing_params.raft_layers()) - 1)); size_t n_raft_layers = size_t(std::max(0, int(m_slicing_params.raft_layers()) - 1));
tbb::parallel_for(tbb::blocked_range<size_t>(0, n_raft_layers), tbb::parallel_for(tbb::blocked_range<size_t>(0, n_raft_layers),
[this, &support_layers, &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<size_t>& range) { (const tbb::blocked_range<size_t>& range) {
for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id) for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id)
{ {
@ -3883,8 +3911,8 @@ void PrintObjectSupportMaterial::generate_toolpaths(
assert(support_layer.support_fills.entities.empty()); assert(support_layer.support_fills.entities.empty());
MyLayer &raft_layer = *raft_layers[support_layer_id]; MyLayer &raft_layer = *raft_layers[support_layer_id];
std::unique_ptr<Fill> filler_interface = std::unique_ptr<Fill>(Fill::new_from_type(ipRectilinear)); std::unique_ptr<Fill> filler_interface = std::unique_ptr<Fill>(Fill::new_from_type(m_support_params.interface_fill_pattern));
std::unique_ptr<Fill> filler_support = std::unique_ptr<Fill>(Fill::new_from_type(infill_pattern)); std::unique_ptr<Fill> filler_support = std::unique_ptr<Fill>(Fill::new_from_type(m_support_params.base_fill_pattern));
filler_interface->set_bounding_box(bbox_object); filler_interface->set_bounding_box(bbox_object);
filler_support->set_bounding_box(bbox_object); filler_support->set_bounding_box(bbox_object);
@ -3900,17 +3928,17 @@ void PrintObjectSupportMaterial::generate_toolpaths(
Fill * filler = filler_support.get(); Fill * filler = filler_support.get();
filler->angle = raft_angle_base; filler->angle = raft_angle_base;
filler->spacing = m_support_params.support_material_flow.spacing(); 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( fill_expolygons_with_sheath_generate_paths(
// Destination // Destination
support_layer.support_fills.entities, support_layer.support_fills.entities,
// Regions to fill // Regions to fill
to_infill_polygons, to_infill_polygons,
// Filler and its parameters // Filler and its parameters
filler, float(support_density), filler, float(m_support_params.support_density),
// Extrusion parameters // Extrusion parameters
erSupportMaterial, flow, erSupportMaterial, flow,
with_sheath, false); m_support_params.with_sheath, false);
} }
} }
@ -3929,7 +3957,7 @@ void PrintObjectSupportMaterial::generate_toolpaths(
filler->spacing = m_support_params.support_material_flow.spacing(); filler->spacing = m_support_params.support_material_flow.spacing();
assert(! raft_layer.bridging); 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()); 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 } else
continue; continue;
filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density)); filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density));
@ -3970,15 +3998,9 @@ void PrintObjectSupportMaterial::generate_toolpaths(
}; };
std::vector<LayerCache> layer_caches(support_layers.size()); std::vector<LayerCache> 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<size_t>(n_raft_layers, support_layers.size()), tbb::parallel_for(tbb::blocked_range<size_t>(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, [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<size_t>& range) { (const tbb::blocked_range<size_t>& range) {
// Indices of the 1st layer in their respective container at the support layer height. // Indices of the 1st layer in their respective container at the support layer height.
size_t idx_layer_bottom_contact = size_t(-1); size_t idx_layer_bottom_contact = size_t(-1);
@ -3987,14 +4009,14 @@ void PrintObjectSupportMaterial::generate_toolpaths(
size_t idx_layer_interface = size_t(-1); size_t idx_layer_interface = size_t(-1);
size_t idx_layer_base_interface = size_t(-1); size_t idx_layer_base_interface = size_t(-1);
const auto fill_type_first_layer = ipRectilinear; const auto fill_type_first_layer = ipRectilinear;
auto filler_interface = std::unique_ptr<Fill>(Fill::new_from_type(fill_type_interface)); auto filler_interface = std::unique_ptr<Fill>(Fill::new_from_type(m_support_params.contact_fill_pattern));
// Filler for the 1st layer interface, if different from filler_interface. // Filler for the 1st layer interface, if different from filler_interface.
auto filler_first_layer_ptr = std::unique_ptr<Fill>(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<Fill>(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. // Pointer to the 1st layer interface filler.
auto filler_first_layer = filler_first_layer_ptr ? filler_first_layer_ptr.get() : filler_interface.get(); 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). // 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<Fill>(base_interface_layers.empty() ? nullptr : Fill::new_from_type(ipRectilinear)); auto filler_base_interface = std::unique_ptr<Fill>(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>(Fill::new_from_type(infill_pattern)); auto filler_support = std::unique_ptr<Fill>(Fill::new_from_type(m_support_params.base_fill_pattern));
filler_interface->set_bounding_box(bbox_object); filler_interface->set_bounding_box(bbox_object);
if (filler_first_layer_ptr) if (filler_first_layer_ptr)
filler_first_layer_ptr->set_bounding_box(bbox_object); filler_first_layer_ptr->set_bounding_box(bbox_object);
@ -4084,8 +4106,8 @@ void PrintObjectSupportMaterial::generate_toolpaths(
// If zero interface layers are configured, use the same angle as for the base layers. // If zero interface layers are configured, use the same angle as for the base layers.
angles[support_layer_id % angles.size()] : angles[support_layer_id % angles.size()] :
// Use interface angle for the interface layers. // Use interface angle for the interface layers.
interface_angle; m_support_params.interface_angle;
double density = interface_as_base ? support_density : interface_density; 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->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)); filler_interface->link_max_length = coord_t(scale_(filler_interface->spacing * link_max_length_factor / density));
fill_expolygons_generate_paths( fill_expolygons_generate_paths(
@ -4106,9 +4128,9 @@ void PrintObjectSupportMaterial::generate_toolpaths(
// the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b) // 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); assert(! base_interface_layer.layer->bridging);
Flow interface_flow = m_support_params.support_material_flow.with_height(float(base_interface_layer.layer->height)); Flow interface_flow = m_support_params.support_material_flow.with_height(float(base_interface_layer.layer->height));
filler->angle = interface_angle; filler->angle = m_support_params.interface_angle;
filler->spacing = m_support_params.support_material_interface_flow.spacing(); filler->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( fill_expolygons_generate_paths(
// Destination // Destination
base_interface_layer.extrusions, base_interface_layer.extrusions,
@ -4116,7 +4138,7 @@ void PrintObjectSupportMaterial::generate_toolpaths(
// Regions to fill // Regions to fill
union_safety_offset_ex(base_interface_layer.polygons_to_extrude()), union_safety_offset_ex(base_interface_layer.polygons_to_extrude()),
// Filler and its parameters // Filler and its parameters
filler, float(interface_density), filler, float(m_support_params.interface_density),
// Extrusion parameters // Extrusion parameters
erSupportMaterial, interface_flow); erSupportMaterial, interface_flow);
} }
@ -4130,9 +4152,9 @@ void PrintObjectSupportMaterial::generate_toolpaths(
assert(! base_layer.layer->bridging); assert(! base_layer.layer->bridging);
auto flow = m_support_params.support_material_flow.with_height(float(base_layer.layer->height)); 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->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));
float density = float(support_density); float density = float(m_support_params.support_density);
bool sheath = with_sheath; bool sheath = m_support_params.with_sheath;
bool no_sort = false; bool no_sort = false;
if (base_layer.layer->bottom_z < EPSILON) { if (base_layer.layer->bottom_z < EPSILON) {
// Base flange (the 1st layer). // Base flange (the 1st layer).

View File

@ -132,6 +132,18 @@ public:
// coordf_t support_layer_height_max; // coordf_t support_layer_height_max;
coordf_t gap_xy; 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 // Layers are allocated and owned by a deque. Once a layer is allocated, it is maintained

View File

@ -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, /* The following line is commented out because it can generate wrong polygons,
see for example issue #661 */ 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 #ifdef SLIC3R_TRIANGLEMESH_DEBUG
size_t holes_count = 0; size_t holes_count = 0;

View File

@ -34,7 +34,7 @@ SCENARIO("Various Clipper operations - xs/t/11_clipper.t", "[ClipperUtils]") {
} }
} }
WHEN("offset2_ex") { WHEN("offset2_ex") {
ExPolygons result = Slic3r::offset2_ex(square_with_hole, 5.f, -2.f); ExPolygons result = Slic3r::offset2_ex({ square_with_hole }, 5.f, -2.f);
THEN("offset matches") { THEN("offset matches") {
REQUIRE(result == ExPolygons { { REQUIRE(result == ExPolygons { {
{ { 203, 203 }, { 97, 203 }, { 97, 97 }, { 203, 97 } }, { { 203, 203 }, { 97, 203 }, { 97, 97 }, { 203, 97 } },

View File

@ -49,7 +49,7 @@ offset2_ex(polygons, delta1, delta2, joinType = Slic3r::ClipperLib::jtMiter, mit
Slic3r::ClipperLib::JoinType joinType Slic3r::ClipperLib::JoinType joinType
double miterLimit double miterLimit
CODE: CODE:
RETVAL = offset2_ex(polygons, delta1, delta2, joinType, miterLimit); RETVAL = offset2_ex(union_ex(polygons), delta1, delta2, joinType, miterLimit);
OUTPUT: OUTPUT:
RETVAL RETVAL