From a7e17df25f66d3fb4fce213574ba25ebea1d238d Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 2 May 2023 12:59:20 +0200 Subject: [PATCH 01/34] ClipperLib: Further optimization of memory allocation using scalable_allocator. ClipperLib: SimplifyPolygon() - changed default winding number to positive, added strictly_simple parameter. ClipperUtlis simplify_polygons() - removed "remove_collinear" parameter --- src/clipper/clipper.cpp | 206 +++++++++++------------- src/clipper/clipper.hpp | 43 +++-- src/libslic3r/Arachne/WallToolPaths.cpp | 4 +- src/libslic3r/ClipperUtils.cpp | 25 ++- src/libslic3r/ClipperUtils.hpp | 4 +- 5 files changed, 137 insertions(+), 145 deletions(-) diff --git a/src/clipper/clipper.cpp b/src/clipper/clipper.cpp index c775a3226..5da79d3a1 100644 --- a/src/clipper/clipper.cpp +++ b/src/clipper/clipper.cpp @@ -73,25 +73,6 @@ static int const Skip = -2; //edge that would otherwise close a path #define TOLERANCE (1.0e-20) #define NEAR_ZERO(val) (((val) > -TOLERANCE) && ((val) < TOLERANCE)) -// Output polygon. -struct OutRec { - int Idx; - bool IsHole; - bool IsOpen; - //The 'FirstLeft' field points to another OutRec that contains or is the - //'parent' of OutRec. It is 'first left' because the ActiveEdgeList (AEL) is - //parsed left from the current edge (owning OutRec) until the owner OutRec - //is found. This field simplifies sorting the polygons into a tree structure - //which reflects the parent/child relationships of all polygons. - //This field should be renamed Parent, and will be later. - OutRec *FirstLeft; - // Used only by void Clipper::BuildResult2(PolyTree& polytree) - PolyNode *PolyNd; - // Linked list of output points, dynamically allocated. - OutPt *Pts; - OutPt *BottomPt; -}; - //------------------------------------------------------------------------------ inline IntPoint IntPoint2d(cInt x, cInt y) @@ -1061,8 +1042,7 @@ IntRect ClipperBase::GetBounds() Clipper::Clipper(int initOptions) : ClipperBase(), m_OutPtsFree(nullptr), - m_OutPtsChunkSize(32), - m_OutPtsChunkLast(32), + m_OutPtsChunkLast(m_OutPtsChunkSize), m_ActiveEdges(nullptr), m_SortedEdges(nullptr) { @@ -1153,23 +1133,23 @@ bool Clipper::ExecuteInternal() //FIXME Vojtech: Does it not invalidate the loop hierarchy maintained as OutRec::FirstLeft pointers? //FIXME Vojtech: The area is calculated with floats, it may not be numerically stable! { - for (OutRec *outRec : m_PolyOuts) - if (outRec->Pts && !outRec->IsOpen && (outRec->IsHole ^ m_ReverseOutput) == (Area(*outRec) > 0)) - ReversePolyPtLinks(outRec->Pts); + for (OutRec &outRec : m_PolyOuts) + if (outRec.Pts && !outRec.IsOpen && (outRec.IsHole ^ m_ReverseOutput) == (Area(outRec) > 0)) + ReversePolyPtLinks(outRec.Pts); } JoinCommonEdges(); //unfortunately FixupOutPolygon() must be done after JoinCommonEdges() { - for (OutRec *outRec : m_PolyOuts) - if (outRec->Pts) { - if (outRec->IsOpen) + for (OutRec &outRec : m_PolyOuts) + if (outRec.Pts) { + if (outRec.IsOpen) // Removes duplicate points. - FixupOutPolyline(*outRec); + FixupOutPolyline(outRec); else // Removes duplicate points and simplifies consecutive parallel edges by removing the middle vertex. - FixupOutPolygon(*outRec); + FixupOutPolygon(outRec); } } // For each polygon, search for exactly duplicate non-successive points. @@ -1194,22 +1174,18 @@ OutPt* Clipper::AllocateOutPt() m_OutPtsFree = pt->Next; } else if (m_OutPtsChunkLast < m_OutPtsChunkSize) { // Get a point from the last chunk. - pt = m_OutPts.back() + (m_OutPtsChunkLast ++); + pt = &m_OutPts.back()[m_OutPtsChunkLast ++]; } else { // The last chunk is full. Allocate a new one. - m_OutPts.push_back(new OutPt[m_OutPtsChunkSize]); + m_OutPts.push_back({}); m_OutPtsChunkLast = 1; - pt = m_OutPts.back(); + pt = &m_OutPts.back().front(); } return pt; } void Clipper::DisposeAllOutRecs() { - for (OutPt *pts : m_OutPts) - delete[] pts; - for (OutRec *rec : m_PolyOuts) - delete rec; m_OutPts.clear(); m_OutPtsFree = nullptr; m_OutPtsChunkLast = m_OutPtsChunkSize; @@ -1832,7 +1808,7 @@ void Clipper::IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &Pt) } //------------------------------------------------------------------------------ -void Clipper::SetHoleState(TEdge *e, OutRec *outrec) const +void Clipper::SetHoleState(TEdge *e, OutRec *outrec) { bool IsHole = false; TEdge *e2 = e->PrevInAEL; @@ -1842,7 +1818,7 @@ void Clipper::SetHoleState(TEdge *e, OutRec *outrec) const { IsHole = !IsHole; if (! outrec->FirstLeft) - outrec->FirstLeft = m_PolyOuts[e2->OutIdx]; + outrec->FirstLeft = &m_PolyOuts[e2->OutIdx]; } e2 = e2->PrevInAEL; } @@ -1883,18 +1859,18 @@ bool Param1RightOfParam2(OutRec* outRec1, OutRec* outRec2) OutRec* Clipper::GetOutRec(int Idx) { - OutRec* outrec = m_PolyOuts[Idx]; - while (outrec != m_PolyOuts[outrec->Idx]) - outrec = m_PolyOuts[outrec->Idx]; + OutRec* outrec = &m_PolyOuts[Idx]; + while (outrec != &m_PolyOuts[outrec->Idx]) + outrec = &m_PolyOuts[outrec->Idx]; return outrec; } //------------------------------------------------------------------------------ -void Clipper::AppendPolygon(TEdge *e1, TEdge *e2) const +void Clipper::AppendPolygon(TEdge *e1, TEdge *e2) { //get the start and ends of both output polygons ... - OutRec *outRec1 = m_PolyOuts[e1->OutIdx]; - OutRec *outRec2 = m_PolyOuts[e2->OutIdx]; + OutRec *outRec1 = &m_PolyOuts[e1->OutIdx]; + OutRec *outRec2 = &m_PolyOuts[e2->OutIdx]; OutRec *holeStateRec; if (Param1RightOfParam2(outRec1, outRec2)) @@ -1991,16 +1967,16 @@ void Clipper::AppendPolygon(TEdge *e1, TEdge *e2) const OutRec* Clipper::CreateOutRec() { - OutRec* result = new OutRec; - result->IsHole = false; - result->IsOpen = false; - result->FirstLeft = 0; - result->Pts = 0; - result->BottomPt = 0; - result->PolyNd = 0; - m_PolyOuts.push_back(result); - result->Idx = (int)m_PolyOuts.size()-1; - return result; + m_PolyOuts.push_back({}); + OutRec &result = m_PolyOuts.back(); + result.IsHole = false; + result.IsOpen = false; + result.FirstLeft = 0; + result.Pts = 0; + result.BottomPt = 0; + result.PolyNd = 0; + result.Idx = (int)m_PolyOuts.size()-1; + return &result; } //------------------------------------------------------------------------------ @@ -2022,7 +1998,7 @@ OutPt* Clipper::AddOutPt(TEdge *e, const IntPoint &pt) return newOp; } else { - OutRec *outRec = m_PolyOuts[e->OutIdx]; + OutRec *outRec = &m_PolyOuts[e->OutIdx]; //OutRec.Pts is the 'Left-most' point & OutRec.Pts.Prev is the 'Right-most' OutPt* op = outRec->Pts; @@ -2045,7 +2021,7 @@ OutPt* Clipper::AddOutPt(TEdge *e, const IntPoint &pt) OutPt* Clipper::GetLastOutPt(TEdge *e) { - OutRec *outRec = m_PolyOuts[e->OutIdx]; + OutRec *outRec = &m_PolyOuts[e->OutIdx]; if (e->Side == esLeft) return outRec->Pts; else @@ -2216,7 +2192,7 @@ void Clipper::ProcessHorizontal(TEdge *horzEdge) { Direction dir; cInt horzLeft, horzRight; - bool IsOpen = (horzEdge->OutIdx >= 0 && m_PolyOuts[horzEdge->OutIdx]->IsOpen); + bool IsOpen = (horzEdge->OutIdx >= 0 && m_PolyOuts[horzEdge->OutIdx].IsOpen); GetHorzDirection(*horzEdge, dir, horzLeft, horzRight); @@ -2778,12 +2754,12 @@ int PointCount(OutPt *Pts) void Clipper::BuildResult(Paths &polys) { polys.reserve(m_PolyOuts.size()); - for (OutRec* outRec : m_PolyOuts) + for (OutRec &outRec : m_PolyOuts) { - assert(! outRec->IsOpen); - if (!outRec->Pts) continue; + assert(! outRec.IsOpen); + if (!outRec.Pts) continue; Path pg; - OutPt* p = outRec->Pts->Prev; + OutPt* p = outRec.Pts->Prev; int cnt = PointCount(p); if (cnt < 2) continue; pg.reserve(cnt); @@ -2802,31 +2778,31 @@ void Clipper::BuildResult2(PolyTree& polytree) polytree.Clear(); polytree.AllNodes.reserve(m_PolyOuts.size()); //add each output polygon/contour to polytree ... - for (OutRec* outRec : m_PolyOuts) + for (OutRec &outRec : m_PolyOuts) { - int cnt = PointCount(outRec->Pts); - if ((outRec->IsOpen && cnt < 2) || (!outRec->IsOpen && cnt < 3)) + int cnt = PointCount(outRec.Pts); + if ((outRec.IsOpen && cnt < 2) || (!outRec.IsOpen && cnt < 3)) // Ignore an invalid output loop or a polyline. continue; //skip OutRecs that (a) contain outermost polygons or //(b) already have the correct owner/child linkage ... - if (outRec->FirstLeft && - (outRec->IsHole == outRec->FirstLeft->IsHole || ! outRec->FirstLeft->Pts)) { - OutRec* orfl = outRec->FirstLeft; - while (orfl && ((orfl->IsHole == outRec->IsHole) || !orfl->Pts)) + if (outRec.FirstLeft && + (outRec.IsHole == outRec.FirstLeft->IsHole || ! outRec.FirstLeft->Pts)) { + OutRec* orfl = outRec.FirstLeft; + while (orfl && ((orfl->IsHole == outRec.IsHole) || !orfl->Pts)) orfl = orfl->FirstLeft; - outRec->FirstLeft = orfl; + outRec.FirstLeft = orfl; } //nb: polytree takes ownership of all the PolyNodes polytree.AllNodes.emplace_back(PolyNode()); PolyNode* pn = &polytree.AllNodes.back(); - outRec->PolyNd = pn; + outRec.PolyNd = pn; pn->Parent = 0; pn->Index = 0; pn->Contour.reserve(cnt); - OutPt *op = outRec->Pts->Prev; + OutPt *op = outRec.Pts->Prev; for (int j = 0; j < cnt; j++) { pn->Contour.emplace_back(op->Pt); @@ -2836,18 +2812,18 @@ void Clipper::BuildResult2(PolyTree& polytree) //fixup PolyNode links etc ... polytree.Childs.reserve(m_PolyOuts.size()); - for (OutRec* outRec : m_PolyOuts) + for (OutRec &outRec : m_PolyOuts) { - if (!outRec->PolyNd) continue; - if (outRec->IsOpen) + if (!outRec.PolyNd) continue; + if (outRec.IsOpen) { - outRec->PolyNd->m_IsOpen = true; - polytree.AddChild(*outRec->PolyNd); + outRec.PolyNd->m_IsOpen = true; + polytree.AddChild(*outRec.PolyNd); } - else if (outRec->FirstLeft && outRec->FirstLeft->PolyNd) - outRec->FirstLeft->PolyNd->AddChild(*outRec->PolyNd); + else if (outRec.FirstLeft && outRec.FirstLeft->PolyNd) + outRec.FirstLeft->PolyNd->AddChild(*outRec.PolyNd); else - polytree.AddChild(*outRec->PolyNd); + polytree.AddChild(*outRec.PolyNd); } } //------------------------------------------------------------------------------ @@ -3193,26 +3169,26 @@ bool Clipper::JoinPoints(Join *j, OutRec* outRec1, OutRec* outRec2) //---------------------------------------------------------------------- // This is potentially very expensive! O(n^3)! -void Clipper::FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec) const +void Clipper::FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec) { //tests if NewOutRec contains the polygon before reassigning FirstLeft - for (OutRec *outRec : m_PolyOuts) + for (OutRec &outRec : m_PolyOuts) { - if (!outRec->Pts || !outRec->FirstLeft) continue; - OutRec* firstLeft = outRec->FirstLeft; + if (!outRec.Pts || !outRec.FirstLeft) continue; + OutRec* firstLeft = outRec.FirstLeft; // Skip empty polygons. while (firstLeft && !firstLeft->Pts) firstLeft = firstLeft->FirstLeft; - if (firstLeft == OldOutRec && Poly2ContainsPoly1(outRec->Pts, NewOutRec->Pts)) - outRec->FirstLeft = NewOutRec; + if (firstLeft == OldOutRec && Poly2ContainsPoly1(outRec.Pts, NewOutRec->Pts)) + outRec.FirstLeft = NewOutRec; } } //---------------------------------------------------------------------- -void Clipper::FixupFirstLefts2(OutRec* OldOutRec, OutRec* NewOutRec) const +void Clipper::FixupFirstLefts2(OutRec* OldOutRec, OutRec* NewOutRec) { //reassigns FirstLeft WITHOUT testing if NewOutRec contains the polygon - for (OutRec *outRec : m_PolyOuts) - if (outRec->FirstLeft == OldOutRec) outRec->FirstLeft = NewOutRec; + for (OutRec &outRec : m_PolyOuts) + if (outRec.FirstLeft == OldOutRec) outRec.FirstLeft = NewOutRec; } //---------------------------------------------------------------------- @@ -3253,13 +3229,13 @@ void Clipper::JoinCommonEdges() if (m_UsingPolyTree) for (size_t j = 0; j < m_PolyOuts.size() - 1; j++) { - OutRec* oRec = m_PolyOuts[j]; - OutRec* firstLeft = oRec->FirstLeft; + OutRec &oRec = m_PolyOuts[j]; + OutRec* firstLeft = oRec.FirstLeft; while (firstLeft && !firstLeft->Pts) firstLeft = firstLeft->FirstLeft; - if (!oRec->Pts || firstLeft != outRec1 || - oRec->IsHole == outRec1->IsHole) continue; - if (Poly2ContainsPoly1(oRec->Pts, join.OutPt2)) - oRec->FirstLeft = outRec2; + if (!oRec.Pts || firstLeft != outRec1 || + oRec.IsHole == outRec1->IsHole) continue; + if (Poly2ContainsPoly1(oRec.Pts, join.OutPt2)) + oRec.FirstLeft = outRec2; } if (Poly2ContainsPoly1(outRec2->Pts, outRec1->Pts)) @@ -3771,13 +3747,13 @@ void Clipper::DoSimplePolygons() size_t i = 0; while (i < m_PolyOuts.size()) { - OutRec* outrec = m_PolyOuts[i++]; - OutPt* op = outrec->Pts; - if (!op || outrec->IsOpen) continue; + OutRec &outrec = m_PolyOuts[i++]; + OutPt* op = outrec.Pts; + if (!op || outrec.IsOpen) continue; do //for each Pt in Polygon until duplicate found do ... { OutPt* op2 = op->Next; - while (op2 != outrec->Pts) + while (op2 != outrec.Pts) { if ((op->Pt == op2->Pt) && op2->Next != op && op2->Prev != op) { @@ -3789,37 +3765,37 @@ void Clipper::DoSimplePolygons() op2->Prev = op3; op3->Next = op2; - outrec->Pts = op; + outrec.Pts = op; OutRec* outrec2 = CreateOutRec(); outrec2->Pts = op2; UpdateOutPtIdxs(*outrec2); - if (Poly2ContainsPoly1(outrec2->Pts, outrec->Pts)) + if (Poly2ContainsPoly1(outrec2->Pts, outrec.Pts)) { //OutRec2 is contained by OutRec1 ... - outrec2->IsHole = !outrec->IsHole; - outrec2->FirstLeft = outrec; + outrec2->IsHole = !outrec.IsHole; + outrec2->FirstLeft = &outrec; // For each m_PolyOuts, replace FirstLeft from outRec2 to outrec. - if (m_UsingPolyTree) FixupFirstLefts2(outrec2, outrec); + if (m_UsingPolyTree) FixupFirstLefts2(outrec2, &outrec); } else - if (Poly2ContainsPoly1(outrec->Pts, outrec2->Pts)) + if (Poly2ContainsPoly1(outrec.Pts, outrec2->Pts)) { //OutRec1 is contained by OutRec2 ... - outrec2->IsHole = outrec->IsHole; - outrec->IsHole = !outrec2->IsHole; - outrec2->FirstLeft = outrec->FirstLeft; - outrec->FirstLeft = outrec2; + outrec2->IsHole = outrec.IsHole; + outrec.IsHole = !outrec2->IsHole; + outrec2->FirstLeft = outrec.FirstLeft; + outrec.FirstLeft = outrec2; // For each m_PolyOuts, replace FirstLeft from outrec to outrec2. - if (m_UsingPolyTree) FixupFirstLefts2(outrec, outrec2); + if (m_UsingPolyTree) FixupFirstLefts2(&outrec, outrec2); } else { //the 2 polygons are separate ... - outrec2->IsHole = outrec->IsHole; - outrec2->FirstLeft = outrec->FirstLeft; + outrec2->IsHole = outrec.IsHole; + outrec2->FirstLeft = outrec.FirstLeft; // For each polygon of m_PolyOuts, replace FirstLeft from outrec to outrec2 if the polygon is inside outRec2. //FIXME This is potentially very expensive! O(n^3)! - if (m_UsingPolyTree) FixupFirstLefts1(outrec, outrec2); + if (m_UsingPolyTree) FixupFirstLefts1(&outrec, outrec2); } op2 = op; //ie get ready for the Next iteration } @@ -3827,7 +3803,7 @@ void Clipper::DoSimplePolygons() } op = op->Next; } - while (op != outrec->Pts); + while (op != outrec.Pts); } } //------------------------------------------------------------------------------ @@ -3845,10 +3821,10 @@ void ReversePaths(Paths& p) } //------------------------------------------------------------------------------ -Paths SimplifyPolygon(const Path &in_poly, PolyFillType fillType) +Paths SimplifyPolygon(const Path &in_poly, PolyFillType fillType, bool strictly_simple /* = true */) { Clipper c; - c.StrictlySimple(true); + c.StrictlySimple(strictly_simple); c.AddPath(in_poly, ptSubject, true); Paths out; c.Execute(ctUnion, out, fillType, fillType); diff --git a/src/clipper/clipper.hpp b/src/clipper/clipper.hpp index d190d09b5..c88545454 100644 --- a/src/clipper/clipper.hpp +++ b/src/clipper/clipper.hpp @@ -52,6 +52,7 @@ //use_deprecated: Enables temporary support for the obsolete functions //#define use_deprecated +#include #include #include #include @@ -199,7 +200,8 @@ double Area(const Path &poly); inline bool Orientation(const Path &poly) { return Area(poly) >= 0; } int PointInPolygon(const IntPoint &pt, const Path &path); -Paths SimplifyPolygon(const Path &in_poly, PolyFillType fillType = pftEvenOdd); +// Union with "strictly simple" fix enabled. +Paths SimplifyPolygon(const Path &in_poly, PolyFillType fillType = pftNonZero, bool strictly_simple = true); void CleanPolygon(const Path& in_poly, Path& out_poly, double distance = 1.415); void CleanPolygon(Path& poly, double distance = 1.415); @@ -284,7 +286,25 @@ enum EdgeSide { esLeft = 1, esRight = 2}; using OutPts = std::vector>; - struct OutRec; + // Output polygon. + struct OutRec { + int Idx; + bool IsHole; + bool IsOpen; + //The 'FirstLeft' field points to another OutRec that contains or is the + //'parent' of OutRec. It is 'first left' because the ActiveEdgeList (AEL) is + //parsed left from the current edge (owning OutRec) until the owner OutRec + //is found. This field simplifies sorting the polygons into a tree structure + //which reflects the parent/child relationships of all polygons. + //This field should be renamed Parent, and will be later. + OutRec* FirstLeft; + // Used only by void Clipper::BuildResult2(PolyTree& polytree) + PolyNode* PolyNd; + // Linked list of output points, dynamically allocated. + OutPt* Pts; + OutPt* BottomPt; + }; + struct Join { Join(OutPt *OutPt1, OutPt *OutPt2, IntPoint OffPt) : OutPt1(OutPt1), OutPt2(OutPt2), OffPt(OffPt) {} @@ -432,12 +452,12 @@ protected: private: // Output polygons. - std::vector> m_PolyOuts; + std::deque> m_PolyOuts; // Output points, allocated by a continuous sets of m_OutPtsChunkSize. - std::vector> m_OutPts; + static constexpr const size_t m_OutPtsChunkSize = 32; + std::deque, Allocator>> m_OutPts; // List of free output points, to be used before taking a point from m_OutPts or allocating a new chunk. OutPt *m_OutPtsFree; - size_t m_OutPtsChunkSize; size_t m_OutPtsChunkLast; std::vector> m_Joins; @@ -482,7 +502,7 @@ private: void AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &pt); OutPt* AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &pt); OutRec* GetOutRec(int idx); - void AppendPolygon(TEdge *e1, TEdge *e2) const; + void AppendPolygon(TEdge *e1, TEdge *e2); void IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &pt); OutRec* CreateOutRec(); OutPt* AddOutPt(TEdge *e, const IntPoint &pt); @@ -498,7 +518,7 @@ private: void ProcessEdgesAtTopOfScanbeam(const cInt topY); void BuildResult(Paths& polys); void BuildResult2(PolyTree& polytree); - void SetHoleState(TEdge *e, OutRec *outrec) const; + void SetHoleState(TEdge *e, OutRec *outrec); bool FixupIntersectionOrder(); void FixupOutPolygon(OutRec &outrec); void FixupOutPolyline(OutRec &outrec); @@ -508,8 +528,8 @@ private: bool JoinHorz(OutPt* op1, OutPt* op1b, OutPt* op2, OutPt* op2b, const IntPoint &Pt, bool DiscardLeft); void JoinCommonEdges(); void DoSimplePolygons(); - void FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec) const; - void FixupFirstLefts2(OutRec* OldOutRec, OutRec* NewOutRec) const; + void FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec); + void FixupFirstLefts2(OutRec* OldOutRec, OutRec* NewOutRec); #ifdef CLIPPERLIB_USE_XYZ void SetZ(IntPoint& pt, TEdge& e1, TEdge& e2); #endif @@ -567,10 +587,11 @@ class clipperException : public std::exception }; //------------------------------------------------------------------------------ +// Union with "strictly simple" fix enabled. template -inline Paths SimplifyPolygons(PathsProvider &&in_polys, PolyFillType fillType = pftEvenOdd) { +inline Paths SimplifyPolygons(PathsProvider &&in_polys, PolyFillType fillType = pftNonZero, bool strictly_simple = true) { Clipper c; - c.StrictlySimple(true); + c.StrictlySimple(strictly_simple); c.AddPaths(std::forward(in_polys), ptSubject, true); Paths out; c.Execute(ctUnion, out, fillType, fillType); diff --git a/src/libslic3r/Arachne/WallToolPaths.cpp b/src/libslic3r/Arachne/WallToolPaths.cpp index fce69d5e4..6c5dafdac 100644 --- a/src/libslic3r/Arachne/WallToolPaths.cpp +++ b/src/libslic3r/Arachne/WallToolPaths.cpp @@ -232,7 +232,7 @@ std::unique_ptr cre void fixSelfIntersections(const coord_t epsilon, Polygons &thiss) { if (epsilon < 1) { - ClipperLib::SimplifyPolygons(ClipperUtils::PolygonsProvider(thiss)); + ClipperLib::SimplifyPolygons(ClipperUtils::PolygonsProvider(thiss), ClipperLib::pftEvenOdd); return; } @@ -273,7 +273,7 @@ void fixSelfIntersections(const coord_t epsilon, Polygons &thiss) } } - ClipperLib::SimplifyPolygons(ClipperUtils::PolygonsProvider(thiss)); + ClipperLib::SimplifyPolygons(ClipperUtils::PolygonsProvider(thiss), ClipperLib::pftEvenOdd); } /*! diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index 95a533718..d10e14cc5 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -964,21 +964,18 @@ Polygons union_pt_chained_outside_in(const Polygons &subject) return retval; } -Polygons simplify_polygons(const Polygons &subject, bool preserve_collinear) +Polygons simplify_polygons(const Polygons &subject) { CLIPPER_UTILS_TIME_LIMIT_MILLIS(CLIPPER_UTILS_TIME_LIMIT_DEFAULT); ClipperLib::Paths output; - if (preserve_collinear) { - ClipperLib::Clipper c; - c.PreserveCollinear(true); - c.StrictlySimple(true); - c.AddPaths(ClipperUtils::PolygonsProvider(subject), ClipperLib::ptSubject, true); - c.Execute(ClipperLib::ctUnion, output, ClipperLib::pftNonZero, ClipperLib::pftNonZero); - } else { - output = ClipperLib::SimplifyPolygons(ClipperUtils::PolygonsProvider(subject), ClipperLib::pftNonZero); - } - + ClipperLib::Clipper c; +// c.PreserveCollinear(true); + //FIXME StrictlySimple is very expensive! Is it needed? + c.StrictlySimple(true); + c.AddPaths(ClipperUtils::PolygonsProvider(subject), ClipperLib::ptSubject, true); + c.Execute(ClipperLib::ctUnion, output, ClipperLib::pftNonZero, ClipperLib::pftNonZero); + // convert into Slic3r polygons return to_polygons(std::move(output)); } @@ -987,12 +984,10 @@ ExPolygons simplify_polygons_ex(const Polygons &subject, bool preserve_collinear { CLIPPER_UTILS_TIME_LIMIT_MILLIS(CLIPPER_UTILS_TIME_LIMIT_DEFAULT); - if (! preserve_collinear) - return union_ex(simplify_polygons(subject, false)); - ClipperLib::PolyTree polytree; ClipperLib::Clipper c; - c.PreserveCollinear(true); +// c.PreserveCollinear(true); + //FIXME StrictlySimple is very expensive! Is it needed? c.StrictlySimple(true); c.AddPaths(ClipperUtils::PolygonsProvider(subject), ClipperLib::ptSubject, true); c.Execute(ClipperLib::ctUnion, polytree, ClipperLib::pftNonZero, ClipperLib::pftNonZero); diff --git a/src/libslic3r/ClipperUtils.hpp b/src/libslic3r/ClipperUtils.hpp index aaa06107d..ab967fd8f 100644 --- a/src/libslic3r/ClipperUtils.hpp +++ b/src/libslic3r/ClipperUtils.hpp @@ -596,8 +596,8 @@ void traverse_pt(const ClipperLib::PolyNodes &nodes, ExOrJustPolygons *retval) /* OTHER */ -Slic3r::Polygons simplify_polygons(const Slic3r::Polygons &subject, bool preserve_collinear = false); -Slic3r::ExPolygons simplify_polygons_ex(const Slic3r::Polygons &subject, bool preserve_collinear = false); +Slic3r::Polygons simplify_polygons(const Slic3r::Polygons &subject); +Slic3r::ExPolygons simplify_polygons_ex(const Slic3r::Polygons &subject); Polygons top_level_islands(const Slic3r::Polygons &polygons); From 0836b06c7360462e3b8719864de4a446887c0d9e Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 2 May 2023 15:48:24 +0200 Subject: [PATCH 02/34] Little refactoring of douglas_peucker() --- src/libslic3r/ExPolygon.cpp | 4 ++-- src/libslic3r/Geometry.cpp | 16 ++++++------- src/libslic3r/MultiPoint.cpp | 2 +- src/libslic3r/MultiPoint.hpp | 2 +- src/libslic3r/Polygon.cpp | 45 +++++++++++++++++++++++++----------- src/libslic3r/Polygon.hpp | 3 ++- src/libslic3r/Polyline.cpp | 2 +- 7 files changed, 46 insertions(+), 28 deletions(-) diff --git a/src/libslic3r/ExPolygon.cpp b/src/libslic3r/ExPolygon.cpp index 42f026e0b..19489bddb 100644 --- a/src/libslic3r/ExPolygon.cpp +++ b/src/libslic3r/ExPolygon.cpp @@ -184,14 +184,14 @@ Polygons ExPolygon::simplify_p(double tolerance) const { Polygon p = this->contour; p.points.push_back(p.points.front()); - p.points = MultiPoint::_douglas_peucker(p.points, tolerance); + p.points = MultiPoint::douglas_peucker(p.points, tolerance); p.points.pop_back(); pp.emplace_back(std::move(p)); } // holes for (Polygon p : this->holes) { p.points.push_back(p.points.front()); - p.points = MultiPoint::_douglas_peucker(p.points, tolerance); + p.points = MultiPoint::douglas_peucker(p.points, tolerance); p.points.pop_back(); pp.emplace_back(std::move(p)); } diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index 5542d73ee..f2860ea8e 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -52,15 +52,15 @@ template bool contains(const ExPolygons &vector, const Point &point); void simplify_polygons(const Polygons &polygons, double tolerance, Polygons* retval) { - Polygons pp; - for (Polygons::const_iterator it = polygons.begin(); it != polygons.end(); ++it) { - Polygon p = *it; - p.points.push_back(p.points.front()); - p.points = MultiPoint::_douglas_peucker(p.points, tolerance); - p.points.pop_back(); - pp.push_back(p); + Polygons simplified_raw; + for (const Polygon &source_polygon : polygons) { + Points simplified = MultiPoint::douglas_peucker(to_polyline(source_polygon).points, tolerance); + if (simplified.size() > 3) { + simplified.pop_back(); + simplified_raw.push_back(Polygon{ std::move(simplified) }); + } } - *retval = Slic3r::simplify_polygons(pp); + *retval = Slic3r::simplify_polygons(simplified_raw); } double linint(double value, double oldmin, double oldmax, double newmin, double newmax) diff --git a/src/libslic3r/MultiPoint.cpp b/src/libslic3r/MultiPoint.cpp index bb4d62cc0..ce54a54c0 100644 --- a/src/libslic3r/MultiPoint.cpp +++ b/src/libslic3r/MultiPoint.cpp @@ -103,7 +103,7 @@ bool MultiPoint::remove_duplicate_points() return false; } -Points MultiPoint::_douglas_peucker(const Points &pts, const double tolerance) +Points MultiPoint::douglas_peucker(const Points &pts, const double tolerance) { Points result_pts; double tolerance_sq = tolerance * tolerance; diff --git a/src/libslic3r/MultiPoint.hpp b/src/libslic3r/MultiPoint.hpp index 4cf4b5e14..62b53255b 100644 --- a/src/libslic3r/MultiPoint.hpp +++ b/src/libslic3r/MultiPoint.hpp @@ -81,7 +81,7 @@ public: } } - static Points _douglas_peucker(const Points &points, const double tolerance); + static Points douglas_peucker(const Points &points, const double tolerance); static Points visivalingam(const Points& pts, const double& tolerance); inline auto begin() { return points.begin(); } diff --git a/src/libslic3r/Polygon.cpp b/src/libslic3r/Polygon.cpp index 299e22adc..88ac1b03f 100644 --- a/src/libslic3r/Polygon.cpp +++ b/src/libslic3r/Polygon.cpp @@ -96,7 +96,7 @@ bool Polygon::make_clockwise() void Polygon::douglas_peucker(double tolerance) { this->points.push_back(this->points.front()); - Points p = MultiPoint::_douglas_peucker(this->points, tolerance); + Points p = MultiPoint::douglas_peucker(this->points, tolerance); p.pop_back(); this->points = std::move(p); } @@ -110,7 +110,7 @@ Polygons Polygon::simplify(double tolerance) const // on the whole polygon Points points = this->points; points.push_back(points.front()); - Polygon p(MultiPoint::_douglas_peucker(points, tolerance)); + Polygon p(MultiPoint::douglas_peucker(points, tolerance)); p.points.pop_back(); Polygons pp; @@ -577,23 +577,40 @@ void remove_collinear(Polygons &polys) remove_collinear(poly); } -Polygons polygons_simplify(const Polygons &source_polygons, double tolerance) +static inline void simplify_polygon_impl(const Points &points, double tolerance, bool strictly_simple, Polygons &out) +{ + Points simplified = MultiPoint::douglas_peucker(points, tolerance); + // then remove the last (repeated) point. + simplified.pop_back(); + // Simplify the decimated contour by ClipperLib. + bool ccw = ClipperLib::Area(simplified) > 0.; + for (Points& path : ClipperLib::SimplifyPolygons(ClipperUtils::SinglePathProvider(simplified), ClipperLib::pftNonZero, strictly_simple)) { + if (!ccw) + // ClipperLib likely reoriented negative area contours to become positive. Reverse holes back to CW. + std::reverse(path.begin(), path.end()); + out.emplace_back(std::move(path)); + } +} + +Polygons polygons_simplify(Polygons &&source_polygons, double tolerance, bool strictly_simple /* = true */) +{ + Polygons out; + out.reserve(source_polygons.size()); + for (Polygon &source_polygon : source_polygons) { + // Run Douglas / Peucker simplification algorithm on an open polyline (by repeating the first point at the end of the polyline), + source_polygon.points.emplace_back(source_polygon.points.front()); + simplify_polygon_impl(source_polygon.points, tolerance, strictly_simple, out); + } + return out; +} + +Polygons polygons_simplify(const Polygons &source_polygons, double tolerance, bool strictly_simple /* = true */) { Polygons out; out.reserve(source_polygons.size()); for (const Polygon &source_polygon : source_polygons) { // Run Douglas / Peucker simplification algorithm on an open polyline (by repeating the first point at the end of the polyline), - Points simplified = MultiPoint::_douglas_peucker(to_polyline(source_polygon).points, tolerance); - // then remove the last (repeated) point. - simplified.pop_back(); - // Simplify the decimated contour by ClipperLib. - bool ccw = ClipperLib::Area(simplified) > 0.; - for (Points &path : ClipperLib::SimplifyPolygons(ClipperUtils::SinglePathProvider(simplified), ClipperLib::pftNonZero)) { - if (! ccw) - // ClipperLib likely reoriented negative area contours to become positive. Reverse holes back to CW. - std::reverse(path.begin(), path.end()); - out.emplace_back(std::move(path)); - } + simplify_polygon_impl(to_polyline(source_polygon).points, tolerance, strictly_simple, out); } return out; } diff --git a/src/libslic3r/Polygon.hpp b/src/libslic3r/Polygon.hpp index bf4a087b0..e0c3958fd 100644 --- a/src/libslic3r/Polygon.hpp +++ b/src/libslic3r/Polygon.hpp @@ -149,7 +149,8 @@ inline void polygons_append(Polygons &dst, Polygons &&src) } } -Polygons polygons_simplify(const Polygons &polys, double tolerance); +Polygons polygons_simplify(Polygons &&polys, double tolerance, bool strictly_simple = true); +Polygons polygons_simplify(const Polygons &polys, double tolerance, bool strictly_simple = true); inline void polygons_rotate(Polygons &polys, double angle) { diff --git a/src/libslic3r/Polyline.cpp b/src/libslic3r/Polyline.cpp index 5743e38bd..524736575 100644 --- a/src/libslic3r/Polyline.cpp +++ b/src/libslic3r/Polyline.cpp @@ -110,7 +110,7 @@ Points Polyline::equally_spaced_points(double distance) const void Polyline::simplify(double tolerance) { - this->points = MultiPoint::_douglas_peucker(this->points, tolerance); + this->points = MultiPoint::douglas_peucker(this->points, tolerance); } #if 0 From 31a5daa5e4ab315d668b1b63f1691ae4961235a6 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 2 May 2023 15:53:08 +0200 Subject: [PATCH 03/34] Optimization of triangle mesh slicing: scalable_allocator and hashing of shared mutexes. --- src/libslic3r/TriangleMeshSlicer.cpp | 42 ++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/src/libslic3r/TriangleMeshSlicer.cpp b/src/libslic3r/TriangleMeshSlicer.cpp index 460cd901e..0261b6121 100644 --- a/src/libslic3r/TriangleMeshSlicer.cpp +++ b/src/libslic3r/TriangleMeshSlicer.cpp @@ -15,6 +15,9 @@ #include #include +#include + +#include #ifndef NDEBUG // #define EXPENSIVE_DEBUG_CHECKS @@ -139,7 +142,7 @@ public: #endif }; -using IntersectionLines = std::vector; +using IntersectionLines = std::vector>; enum class FacetSliceType { NoSlice = 0, @@ -351,6 +354,21 @@ inline FacetSliceType slice_facet( return FacetSliceType::NoSlice; } +class LinesMutexes { +public: + std::mutex& operator()(size_t slice_id) { + ankerl::unordered_dense::hash hash; + return m_mutexes[hash(slice_id) % m_mutexes.size()].mutex; + } + +private: + struct CacheLineAlignedMutex + { + alignas(std::hardware_destructive_interference_size) std::mutex mutex; + }; + std::array m_mutexes; +}; + template void slice_facet_at_zs( // Scaled or unscaled vertices. transform_vertex_fn may scale zs. @@ -361,7 +379,7 @@ void slice_facet_at_zs( // Scaled or unscaled zs. If vertices have their zs scaled or transform_vertex_fn scales them, then zs have to be scaled as well. const std::vector &zs, std::vector &lines, - std::array &lines_mutex) + LinesMutexes &lines_mutex) { stl_vertex vertices[3] { transform_vertex_fn(mesh_vertices[indices(0)]), transform_vertex_fn(mesh_vertices[indices(1)]), transform_vertex_fn(mesh_vertices[indices(2)]) }; @@ -380,7 +398,7 @@ void slice_facet_at_zs( if (min_z != max_z && slice_facet(*it, vertices, indices, edge_ids, idx_vertex_lowest, false, il) == FacetSliceType::Slicing) { assert(il.edge_type != IntersectionLine::FacetEdgeType::Horizontal); size_t slice_id = it - zs.begin(); - boost::lock_guard l(lines_mutex[slice_id % lines_mutex.size()]); + boost::lock_guard l(lines_mutex(slice_id)); lines[slice_id].emplace_back(il); } } @@ -395,8 +413,8 @@ static inline std::vector slice_make_lines( const std::vector &zs, const ThrowOnCancel throw_on_cancel_fn) { - std::vector lines(zs.size(), IntersectionLines()); - std::array lines_mutex; + std::vector lines(zs.size(), IntersectionLines{}); + LinesMutexes lines_mutex; tbb::parallel_for( tbb::blocked_range(0, int(indices.size())), [&vertices, &transform_vertex_fn, &indices, &face_edge_ids, &zs, &lines, &lines_mutex, throw_on_cancel_fn](const tbb::blocked_range &range) { @@ -475,7 +493,7 @@ void slice_facet_with_slabs( const int num_edges, const std::vector &zs, SlabLines &lines, - std::array &lines_mutex) + LinesMutexes &lines_mutex) { const stl_triangle_vertex_indices &indices = mesh_triangles[facet_idx]; stl_vertex vertices[3] { mesh_vertices[indices(0)], mesh_vertices[indices(1)], mesh_vertices[indices(2)] }; @@ -494,7 +512,7 @@ void slice_facet_with_slabs( auto emit_slab_edge = [&lines, &lines_mutex](IntersectionLine il, size_t slab_id, bool reverse) { if (reverse) il.reverse(); - boost::lock_guard l(lines_mutex[(slab_id + lines_mutex.size() / 2) % lines_mutex.size()]); + boost::lock_guard l(lines_mutex(slab_id)); lines.between_slices[slab_id].emplace_back(il); }; @@ -530,7 +548,7 @@ void slice_facet_with_slabs( }; // Don't flip the FacetEdgeType::Top edge, it will be flipped when chaining. // if (! ProjectionFromTop) il.reverse(); - boost::lock_guard l(lines_mutex[line_id % lines_mutex.size()]); + boost::lock_guard l(lines_mutex(line_id)); lines.at_slice[line_id].emplace_back(il); } } else { @@ -649,7 +667,7 @@ void slice_facet_with_slabs( if (! ProjectionFromTop) il.reverse(); size_t line_id = it - zs.begin(); - boost::lock_guard l(lines_mutex[line_id % lines_mutex.size()]); + boost::lock_guard l(lines_mutex(line_id)); lines.at_slice[line_id].emplace_back(il); } } @@ -804,8 +822,8 @@ inline std::pair slice_slabs_make_lines( std::pair out; SlabLines &lines_top = out.first; SlabLines &lines_bottom = out.second; - std::array lines_mutex_top; - std::array lines_mutex_bottom; + LinesMutexes lines_mutex_top; + LinesMutexes lines_mutex_bottom; if (top) { lines_top.at_slice.assign(zs.size(), IntersectionLines()); @@ -1540,7 +1558,7 @@ static std::vector make_slab_loops( } // Used to cut the mesh into two halves. -static ExPolygons make_expolygons_simple(std::vector &lines) +static ExPolygons make_expolygons_simple(IntersectionLines &lines) { ExPolygons slices; Polygons holes; From 04f557693b8369d7019e861a4bf26bab44ee4464 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 2 May 2023 16:26:16 +0200 Subject: [PATCH 04/34] Follow-up to 31a5daa5e4ab315d668b1b63f1691ae4961235a6: Fixed missing include --- src/libslic3r/TriangleMeshSlicer.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libslic3r/TriangleMeshSlicer.cpp b/src/libslic3r/TriangleMeshSlicer.cpp index 0261b6121..2c6570219 100644 --- a/src/libslic3r/TriangleMeshSlicer.cpp +++ b/src/libslic3r/TriangleMeshSlicer.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include From a077f669ee5409c7550df538dd00c6b294d7019f Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 2 May 2023 16:32:03 +0200 Subject: [PATCH 05/34] Slight optimization of distance_to_squared() --- src/libslic3r/Line.hpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/libslic3r/Line.hpp b/src/libslic3r/Line.hpp index 1edd1f5e9..d90757bed 100644 --- a/src/libslic3r/Line.hpp +++ b/src/libslic3r/Line.hpp @@ -40,11 +40,12 @@ template auto get_b(L &&l) { return Traits>::get_b(l) // Distance to the closest point of line. template -double distance_to_squared(const L &line, const Vec, Scalar> &point, Vec, Scalar> *nearest_point) +inline double distance_to_squared(const L &line, const Vec, Scalar> &point, Vec, Scalar> *nearest_point) { - const Vec, double> v = (get_b(line) - get_a(line)).template cast(); - const Vec, double> va = (point - get_a(line)).template cast(); - const double l2 = v.squaredNorm(); // avoid a sqrt + using VecType = Vec, double>; + const VecType v = (get_b(line) - get_a(line)).template cast(); + const VecType va = (point - get_a(line)).template cast(); + const double l2 = v.squaredNorm(); if (l2 == 0.0) { // a == b case *nearest_point = get_a(line); @@ -53,19 +54,20 @@ double distance_to_squared(const L &line, const Vec, Scalar> &point, V // Consider the line extending the segment, parameterized as a + t (b - a). // We find projection of this point onto the line. // It falls where t = [(this-a) . (b-a)] / |b-a|^2 - const double t = va.dot(v) / l2; + const double t = va.dot(v); if (t <= 0.0) { // beyond the 'a' end of the segment *nearest_point = get_a(line); return va.squaredNorm(); - } else if (t >= 1.0) { + } else if (t >= l2) { // beyond the 'b' end of the segment *nearest_point = get_b(line); return (point - get_b(line)).template cast().squaredNorm(); } - *nearest_point = (get_a(line).template cast() + t * v).template cast>(); - return (t * v - va).squaredNorm(); + const VecType w = ((t / l2) * v).eval(); + *nearest_point = (get_a(line).template cast() + w).template cast>(); + return (w - va).squaredNorm(); } // Distance to the closest point of line. From e7f4704ddc1c64a55b1a9bc57ecb3ffc3e6631b6 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 2 May 2023 16:35:39 +0200 Subject: [PATCH 06/34] Organic supports, TreeModelVolumes: Cheaper simplification. --- src/libslic3r/TreeModelVolumes.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/libslic3r/TreeModelVolumes.cpp b/src/libslic3r/TreeModelVolumes.cpp index 5a1fcec56..3c82b5c6c 100644 --- a/src/libslic3r/TreeModelVolumes.cpp +++ b/src/libslic3r/TreeModelVolumes.cpp @@ -34,6 +34,8 @@ using namespace std::literals; // had to use a define beacuse the macro processing inside macro BOOST_LOG_TRIVIAL() #define error_level_not_in_cache error +static constexpr const bool polygons_strictly_simple = false; + TreeSupportMeshGroupSettings::TreeSupportMeshGroupSettings(const PrintObject &print_object) { const PrintConfig &print_config = print_object.print()->config(); @@ -175,7 +177,7 @@ TreeModelVolumes::TreeModelVolumes( tbb::parallel_for(tbb::blocked_range(num_raft_layers, num_layers, std::min(1, std::max(16, num_layers / (8 * tbb::this_task_arena::max_concurrency())))), [&](const tbb::blocked_range &range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) - outlines[layer_idx] = to_polygons(expolygons_simplify(print_object.get_layer(layer_idx - num_raft_layers)->lslices, mesh_settings.resolution)); + outlines[layer_idx] = polygons_simplify(to_polygons(print_object.get_layer(layer_idx - num_raft_layers)->lslices), mesh_settings.resolution, polygons_strictly_simple); }); } #endif @@ -585,7 +587,7 @@ void TreeModelVolumes::calculateCollision(const coord_t radius, const LayerIndex if (processing_last_mesh) { if (! dst.empty()) collisions = union_(collisions, dst); - dst = polygons_simplify(collisions, min_resolution); + dst = polygons_simplify(collisions, min_resolution, polygons_strictly_simple); } else append(dst, std::move(collisions)); throw_on_cancel(); @@ -609,7 +611,7 @@ void TreeModelVolumes::calculateCollision(const coord_t radius, const LayerIndex if (processing_last_mesh) { if (! dst.empty()) placable = union_(placable, dst); - dst = polygons_simplify(placable, min_resolution); + dst = polygons_simplify(placable, min_resolution, polygons_strictly_simple); } else append(dst, placable); throw_on_cancel(); @@ -657,7 +659,7 @@ void TreeModelVolumes::calculateCollisionHolefree(const std::vectorgetCollision(m_increase_until_radius, layer_idx, false)), 5 - increase_radius_ceil, ClipperLib::jtRound, m_min_resolution), - m_min_resolution)); + m_min_resolution, polygons_strictly_simple)); throw_on_cancel(); } } @@ -744,7 +746,7 @@ void TreeModelVolumes::calculateAvoidance(const std::vector &ke ClipperLib::jtRound, m_min_resolution)); if (task.to_model) latest_avoidance = diff(latest_avoidance, getPlaceableAreas(task.radius, layer_idx, throw_on_cancel)); - latest_avoidance = polygons_simplify(latest_avoidance, m_min_resolution); + latest_avoidance = polygons_simplify(latest_avoidance, m_min_resolution, polygons_strictly_simple); data.emplace_back(RadiusLayerPair{task.radius, layer_idx}, latest_avoidance); throw_on_cancel(); } @@ -865,12 +867,12 @@ void TreeModelVolumes::calculateWallRestrictions(const std::vector Date: Tue, 2 May 2023 18:17:08 +0200 Subject: [PATCH 07/34] ClipperLib: emplace_back() instead of push_back(). --- src/clipper/clipper.cpp | 90 ++++++++++++++++++++--------------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/src/clipper/clipper.cpp b/src/clipper/clipper.cpp index 5da79d3a1..3691877ee 100644 --- a/src/clipper/clipper.cpp +++ b/src/clipper/clipper.cpp @@ -112,7 +112,7 @@ int PolyTree::Total() const void PolyNode::AddChild(PolyNode& child) { unsigned cnt = (unsigned)Childs.size(); - Childs.push_back(&child); + Childs.emplace_back(&child); child.Parent = this; child.Index = cnt; } @@ -674,7 +674,7 @@ TEdge* ClipperBase::ProcessBound(TEdge* E, bool NextIsForward) locMin.RightBound = E; E->WindDelta = 0; Result = ProcessBound(E, NextIsForward); - m_MinimaList.push_back(locMin); + m_MinimaList.emplace_back(locMin); } return Result; } @@ -896,7 +896,7 @@ bool ClipperBase::AddPathInternal(const Path &pg, int highI, PolyType PolyTyp, b E->NextInLML = E->Next; E = E->Next; } - m_MinimaList.push_back(locMin); + m_MinimaList.emplace_back(locMin); return true; } @@ -949,7 +949,7 @@ bool ClipperBase::AddPathInternal(const Path &pg, int highI, PolyType PolyTyp, b locMin.LeftBound = 0; else if (locMin.RightBound->OutIdx == Skip) locMin.RightBound = 0; - m_MinimaList.push_back(locMin); + m_MinimaList.emplace_back(locMin); if (!leftBoundIsForward) E = E2; } return true; @@ -1177,7 +1177,7 @@ OutPt* Clipper::AllocateOutPt() pt = &m_OutPts.back()[m_OutPtsChunkLast ++]; } else { // The last chunk is full. Allocate a new one. - m_OutPts.push_back({}); + m_OutPts.emplace_back(); m_OutPtsChunkLast = 1; pt = &m_OutPts.back().front(); } @@ -1967,7 +1967,7 @@ void Clipper::AppendPolygon(TEdge *e1, TEdge *e2) OutRec* Clipper::CreateOutRec() { - m_PolyOuts.push_back({}); + m_PolyOuts.emplace_back(); OutRec &result = m_PolyOuts.back(); result.IsHole = false; result.IsOpen = false; @@ -2576,7 +2576,7 @@ void Clipper::ProcessEdgesAtTopOfScanbeam(const cInt topY) if(IsMaximaEdge) { - if (m_StrictSimple) m_Maxima.push_back(e->Top.x()); + if (m_StrictSimple) m_Maxima.emplace_back(e->Top.x()); TEdge* ePrev = e->PrevInAEL; DoMaxima(e); if( !ePrev ) e = m_ActiveEdges; @@ -3349,7 +3349,7 @@ void ClipperOffset::AddPath(const Path& path, JoinType joinType, EndType endType break; } newNode->Contour.reserve(highI + 1); - newNode->Contour.push_back(path[0]); + newNode->Contour.emplace_back(path[0]); int j = 0, k = 0; for (int i = 1; i <= highI; i++) { bool same = false; @@ -3362,7 +3362,7 @@ void ClipperOffset::AddPath(const Path& path, JoinType joinType, EndType endType if (same) continue; j++; - newNode->Contour.push_back(path[i]); + newNode->Contour.emplace_back(path[i]); if (path[i].y() > newNode->Contour[k].y() || (path[i].y() == newNode->Contour[k].y() && path[i].x() < newNode->Contour[k].x())) k = j; @@ -3490,7 +3490,7 @@ void ClipperOffset::DoOffset(double delta) { PolyNode& node = *m_polyNodes.Childs[i]; if (node.m_endtype == etClosedPolygon) - m_destPolys.push_back(node.Contour); + m_destPolys.emplace_back(node.Contour); } return; } @@ -3532,7 +3532,7 @@ void ClipperOffset::DoOffset(double delta) double X = 1.0, Y = 0.0; for (cInt j = 1; j <= steps; j++) { - m_destPoly.push_back(IntPoint2d( + m_destPoly.emplace_back(IntPoint2d( Round(m_srcPoly[0].x() + X * delta), Round(m_srcPoly[0].y() + Y * delta))); double X2 = X; @@ -3545,7 +3545,7 @@ void ClipperOffset::DoOffset(double delta) double X = -1.0, Y = -1.0; for (int j = 0; j < 4; ++j) { - m_destPoly.push_back(IntPoint2d( + m_destPoly.emplace_back(IntPoint2d( Round(m_srcPoly[0].x() + X * delta), Round(m_srcPoly[0].y() + Y * delta))); if (X < 0) X = 1; @@ -3553,32 +3553,32 @@ void ClipperOffset::DoOffset(double delta) else X = -1; } } - m_destPolys.push_back(m_destPoly); + m_destPolys.emplace_back(m_destPoly); continue; } //build m_normals ... m_normals.clear(); m_normals.reserve(len); for (int j = 0; j < len - 1; ++j) - m_normals.push_back(GetUnitNormal(m_srcPoly[j], m_srcPoly[j + 1])); + m_normals.emplace_back(GetUnitNormal(m_srcPoly[j], m_srcPoly[j + 1])); if (node.m_endtype == etClosedLine || node.m_endtype == etClosedPolygon) - m_normals.push_back(GetUnitNormal(m_srcPoly[len - 1], m_srcPoly[0])); + m_normals.emplace_back(GetUnitNormal(m_srcPoly[len - 1], m_srcPoly[0])); else - m_normals.push_back(DoublePoint(m_normals[len - 2])); + m_normals.emplace_back(DoublePoint(m_normals[len - 2])); if (node.m_endtype == etClosedPolygon) { int k = len - 1; for (int j = 0; j < len; ++j) OffsetPoint(j, k, node.m_jointype); - m_destPolys.push_back(m_destPoly); + m_destPolys.emplace_back(m_destPoly); } else if (node.m_endtype == etClosedLine) { int k = len - 1; for (int j = 0; j < len; ++j) OffsetPoint(j, k, node.m_jointype); - m_destPolys.push_back(m_destPoly); + m_destPolys.emplace_back(m_destPoly); m_destPoly.clear(); //re-build m_normals ... DoublePoint n = m_normals[len -1]; @@ -3588,7 +3588,7 @@ void ClipperOffset::DoOffset(double delta) k = 0; for (int j = len - 1; j >= 0; j--) OffsetPoint(j, k, node.m_jointype); - m_destPolys.push_back(m_destPoly); + m_destPolys.emplace_back(m_destPoly); } else { @@ -3601,9 +3601,9 @@ void ClipperOffset::DoOffset(double delta) { int j = len - 1; pt1 = IntPoint2d(Round(m_srcPoly[j].x() + m_normals[j].x() * delta), Round(m_srcPoly[j].y() + m_normals[j].y() * delta)); - m_destPoly.push_back(pt1); + m_destPoly.emplace_back(pt1); pt1 = IntPoint2d(Round(m_srcPoly[j].x() - m_normals[j].x() * delta), Round(m_srcPoly[j].y() - m_normals[j].y() * delta)); - m_destPoly.push_back(pt1); + m_destPoly.emplace_back(pt1); } else { @@ -3628,9 +3628,9 @@ void ClipperOffset::DoOffset(double delta) if (node.m_endtype == etOpenButt) { pt1 = IntPoint2d(Round(m_srcPoly[0].x() - m_normals[0].x() * delta), Round(m_srcPoly[0].y() - m_normals[0].y() * delta)); - m_destPoly.push_back(pt1); + m_destPoly.emplace_back(pt1); pt1 = IntPoint2d(Round(m_srcPoly[0].x() + m_normals[0].x() * delta), Round(m_srcPoly[0].y() + m_normals[0].y() * delta)); - m_destPoly.push_back(pt1); + m_destPoly.emplace_back(pt1); } else { @@ -3641,7 +3641,7 @@ void ClipperOffset::DoOffset(double delta) else DoRound(0, 1); } - m_destPolys.push_back(m_destPoly); + m_destPolys.emplace_back(m_destPoly); } } } @@ -3657,7 +3657,7 @@ void ClipperOffset::OffsetPoint(int j, int& k, JoinType jointype) double cosA = (m_normals[k].x() * m_normals[j].x() + m_normals[j].y() * m_normals[k].y() ); if (cosA > 0) // angle => 0 degrees { - m_destPoly.push_back(IntPoint2d(Round(m_srcPoly[j].x() + m_normals[k].x() * m_delta), + m_destPoly.emplace_back(IntPoint2d(Round(m_srcPoly[j].x() + m_normals[k].x() * m_delta), Round(m_srcPoly[j].y() + m_normals[k].y() * m_delta))); return; } @@ -3668,10 +3668,10 @@ void ClipperOffset::OffsetPoint(int j, int& k, JoinType jointype) if (m_sinA * m_delta < 0) { - m_destPoly.push_back(IntPoint2d(Round(m_srcPoly[j].x() + m_normals[k].x() * m_delta), + m_destPoly.emplace_back(IntPoint2d(Round(m_srcPoly[j].x() + m_normals[k].x() * m_delta), Round(m_srcPoly[j].y() + m_normals[k].y() * m_delta))); - m_destPoly.push_back(m_srcPoly[j]); - m_destPoly.push_back(IntPoint2d(Round(m_srcPoly[j].x() + m_normals[j].x() * m_delta), + m_destPoly.emplace_back(m_srcPoly[j]); + m_destPoly.emplace_back(IntPoint2d(Round(m_srcPoly[j].x() + m_normals[j].x() * m_delta), Round(m_srcPoly[j].y() + m_normals[j].y() * m_delta))); } else @@ -3695,10 +3695,10 @@ void ClipperOffset::DoSquare(int j, int k) { double dx = std::tan(std::atan2(m_sinA, m_normals[k].x() * m_normals[j].x() + m_normals[k].y() * m_normals[j].y()) / 4); - m_destPoly.push_back(IntPoint2d( + m_destPoly.emplace_back(IntPoint2d( Round(m_srcPoly[j].x() + m_delta * (m_normals[k].x() - m_normals[k].y() * dx)), Round(m_srcPoly[j].y() + m_delta * (m_normals[k].y() + m_normals[k].x() * dx)))); - m_destPoly.push_back(IntPoint2d( + m_destPoly.emplace_back(IntPoint2d( Round(m_srcPoly[j].x() + m_delta * (m_normals[j].x() + m_normals[j].y() * dx)), Round(m_srcPoly[j].y() + m_delta * (m_normals[j].y() - m_normals[j].x() * dx)))); } @@ -3707,7 +3707,7 @@ void ClipperOffset::DoSquare(int j, int k) void ClipperOffset::DoMiter(int j, int k, double r) { double q = m_delta / r; - m_destPoly.push_back(IntPoint2d(Round(m_srcPoly[j].x() + (m_normals[k].x() + m_normals[j].x()) * q), + m_destPoly.emplace_back(IntPoint2d(Round(m_srcPoly[j].x() + (m_normals[k].x() + m_normals[j].x()) * q), Round(m_srcPoly[j].y() + (m_normals[k].y() + m_normals[j].y()) * q))); } //------------------------------------------------------------------------------ @@ -3721,14 +3721,14 @@ void ClipperOffset::DoRound(int j, int k) double X = m_normals[k].x(), Y = m_normals[k].y(), X2; for (int i = 0; i < steps; ++i) { - m_destPoly.push_back(IntPoint2d( + m_destPoly.emplace_back(IntPoint2d( Round(m_srcPoly[j].x() + X * m_delta), Round(m_srcPoly[j].y() + Y * m_delta))); X2 = X; X = X * m_cos - m_sin * Y; Y = X2 * m_sin + Y * m_cos; } - m_destPoly.push_back(IntPoint2d( + m_destPoly.emplace_back(IntPoint2d( Round(m_srcPoly[j].x() + m_normals[j].x() * m_delta), Round(m_srcPoly[j].y() + m_normals[j].y() * m_delta))); } @@ -3996,8 +3996,8 @@ void Minkowski(const Path& poly, const Path& path, Path p; p.reserve(polyCnt); for (size_t j = 0; j < poly.size(); ++j) - p.push_back(IntPoint2d(path[i].x() + poly[j].x(), path[i].y() + poly[j].y())); - pp.push_back(p); + p.emplace_back(IntPoint2d(path[i].x() + poly[j].x(), path[i].y() + poly[j].y())); + pp.emplace_back(p); } else for (size_t i = 0; i < pathCnt; ++i) @@ -4005,8 +4005,8 @@ void Minkowski(const Path& poly, const Path& path, Path p; p.reserve(polyCnt); for (size_t j = 0; j < poly.size(); ++j) - p.push_back(IntPoint2d(path[i].x() - poly[j].x(), path[i].y() - poly[j].y())); - pp.push_back(p); + p.emplace_back(IntPoint2d(path[i].x() - poly[j].x(), path[i].y() - poly[j].y())); + pp.emplace_back(p); } solution.clear(); @@ -4016,12 +4016,12 @@ void Minkowski(const Path& poly, const Path& path, { Path quad; quad.reserve(4); - quad.push_back(pp[i % pathCnt][j % polyCnt]); - quad.push_back(pp[(i + 1) % pathCnt][j % polyCnt]); - quad.push_back(pp[(i + 1) % pathCnt][(j + 1) % polyCnt]); - quad.push_back(pp[i % pathCnt][(j + 1) % polyCnt]); + quad.emplace_back(pp[i % pathCnt][j % polyCnt]); + quad.emplace_back(pp[(i + 1) % pathCnt][j % polyCnt]); + quad.emplace_back(pp[(i + 1) % pathCnt][(j + 1) % polyCnt]); + quad.emplace_back(pp[i % pathCnt][(j + 1) % polyCnt]); if (!Orientation(quad)) ReversePath(quad); - solution.push_back(quad); + solution.emplace_back(quad); } } //------------------------------------------------------------------------------ @@ -4081,7 +4081,7 @@ void AddPolyNodeToPaths(const PolyNode& polynode, NodeType nodetype, Paths& path else if (nodetype == ntOpen) return; if (!polynode.Contour.empty() && match) - paths.push_back(polynode.Contour); + paths.emplace_back(polynode.Contour); for (int i = 0; i < polynode.ChildCount(); ++i) AddPolyNodeToPaths(*polynode.Childs[i], nodetype, paths); } @@ -4093,7 +4093,7 @@ void AddPolyNodeToPaths(PolyNode&& polynode, NodeType nodetype, Paths& paths) else if (nodetype == ntOpen) return; if (!polynode.Contour.empty() && match) - paths.push_back(std::move(polynode.Contour)); + paths.emplace_back(std::move(polynode.Contour)); for (int i = 0; i < polynode.ChildCount(); ++i) AddPolyNodeToPaths(std::move(*polynode.Childs[i]), nodetype, paths); } @@ -4131,7 +4131,7 @@ void OpenPathsFromPolyTree(PolyTree& polytree, Paths& paths) //Open paths are top level only, so ... for (int i = 0; i < polytree.ChildCount(); ++i) if (polytree.Childs[i]->IsOpen()) - paths.push_back(polytree.Childs[i]->Contour); + paths.emplace_back(polytree.Childs[i]->Contour); } //------------------------------------------------------------------------------ From 63ca221394acca695508dd333633b43b1a5e9744 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 2 May 2023 18:20:38 +0200 Subject: [PATCH 08/34] douglas_peucker(): Optimized for 32bit Point types. --- src/libslic3r/MultiPoint.cpp | 44 +++++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/src/libslic3r/MultiPoint.cpp b/src/libslic3r/MultiPoint.cpp index ce54a54c0..90928d3c1 100644 --- a/src/libslic3r/MultiPoint.cpp +++ b/src/libslic3r/MultiPoint.cpp @@ -106,7 +106,7 @@ bool MultiPoint::remove_duplicate_points() Points MultiPoint::douglas_peucker(const Points &pts, const double tolerance) { Points result_pts; - double tolerance_sq = tolerance * tolerance; + auto tolerance_sq = int64_t(sqr(tolerance)); if (! pts.empty()) { const Point *anchor = &pts.front(); size_t anchor_idx = 0; @@ -120,14 +120,42 @@ Points MultiPoint::douglas_peucker(const Points &pts, const double tolerance) dpStack.reserve(pts.size()); dpStack.emplace_back(floater_idx); for (;;) { - double max_dist_sq = 0.0; - size_t furthest_idx = anchor_idx; + int64_t max_dist_sq = 0; + size_t furthest_idx = anchor_idx; // find point furthest from line seg created by (anchor, floater) and note it - for (size_t i = anchor_idx + 1; i < floater_idx; ++ i) { - double dist_sq = Line::distance_to_squared(pts[i], *anchor, *floater); - if (dist_sq > max_dist_sq) { - max_dist_sq = dist_sq; - furthest_idx = i; + { + const Point a = *anchor; + const Point f = *floater; + const Vec2i64 v = (f - a).cast(); + const int64_t l2 = v.squaredNorm(); + // Make up for rounding when converting from int64_t to double. Double mantissa is just 52 bits. + if (l2 < (1 << 14)) { + for (size_t i = anchor_idx + 1; i < floater_idx; ++ i) + if (int64_t dist_sq = (pts[i] - a).cast().squaredNorm(); dist_sq > max_dist_sq) { + max_dist_sq = dist_sq; + furthest_idx = i; + } + } else { + const double dl2 = double(l2); + const Vec2d dv = v.cast(); + for (size_t i = anchor_idx + 1; i < floater_idx; ++ i) { + const Point p = pts[i]; + const Vec2i64 va = (p - a).template cast(); + const int64_t t = va.dot(v); + int64_t dist_sq; + if (t <= 0) { + dist_sq = va.squaredNorm(); + } else if (t >= l2) { + dist_sq = (p - f).cast().squaredNorm(); + } else { + const Vec2i64 w = ((double(t) / dl2) * dv).cast(); + dist_sq = (w - va).squaredNorm(); + } + if (dist_sq > max_dist_sq) { + max_dist_sq = dist_sq; + furthest_idx = i; + } + } } } // remove point if less than tolerance From 2600ba71ad52d3f43d3efd244493458567a7b457 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 2 May 2023 18:36:59 +0200 Subject: [PATCH 09/34] Fix of undefined hardware_destructive_interference_size --- src/libslic3r/TriangleMeshSlicer.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/TriangleMeshSlicer.cpp b/src/libslic3r/TriangleMeshSlicer.cpp index 2c6570219..747f36215 100644 --- a/src/libslic3r/TriangleMeshSlicer.cpp +++ b/src/libslic3r/TriangleMeshSlicer.cpp @@ -36,6 +36,13 @@ #include #include +#ifdef __cpp_lib_hardware_interference_size + using std::hardware_destructive_interference_size; +#else + // 64 bytes on x86-64 │ L1_CACHE_BYTES │ L1_CACHE_SHIFT │ __cacheline_aligned │ ... + constexpr std::size_t hardware_destructive_interference_size = 64; +#endif + // #define SLIC3R_DEBUG_SLICE_PROCESSING #ifdef SLIC3R_DEBUG_SLICE_PROCESSING @@ -365,7 +372,7 @@ public: private: struct CacheLineAlignedMutex { - alignas(std::hardware_destructive_interference_size) std::mutex mutex; + alignas(hardware_destructive_interference_size) std::mutex mutex; }; std::array m_mutexes; }; From 15ccecf8858f2f5e11da62b5ccb1b25300b27538 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 2 May 2023 18:37:24 +0200 Subject: [PATCH 10/34] TreeSupport optimization: better parallel scaling, simpler simplification --- src/libslic3r/TreeSupport.cpp | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/libslic3r/TreeSupport.cpp b/src/libslic3r/TreeSupport.cpp index 174e91a9c..3452bb71c 100644 --- a/src/libslic3r/TreeSupport.cpp +++ b/src/libslic3r/TreeSupport.cpp @@ -59,6 +59,8 @@ namespace Slic3r namespace FFFTreeSupport { +static constexpr const bool polygons_strictly_simple = false; + TreeSupportSettings::TreeSupportSettings(const TreeSupportMeshGroupSettings& mesh_group_settings, const SlicingParameters &slicing_params) : angle(mesh_group_settings.support_tree_angle), angle_slow(mesh_group_settings.support_tree_angle_slow), @@ -925,13 +927,13 @@ static std::optional> polyline_sample_next_point_at_dis ret = diff(offset(ret, step_size, ClipperLib::jtRound, scaled(0.01)), collision_trimmed()); // ensure that if many offsets are done the performance does not suffer extremely by the new vertices of jtRound. if (i % 10 == 7) - ret = polygons_simplify(ret, scaled(0.015)); + ret = polygons_simplify(ret, scaled(0.015), polygons_strictly_simple); } // offset the remainder float last_offset = distance - steps * step_size; if (last_offset > SCALED_EPSILON) ret = offset(ret, distance - steps * step_size, ClipperLib::jtRound, scaled(0.01)); - ret = polygons_simplify(ret, scaled(0.015)); + ret = polygons_simplify(ret, scaled(0.015), polygons_strictly_simple); if (do_final_difference) ret = diff(ret, collision_trimmed()); @@ -1797,7 +1799,7 @@ static Point move_inside_if_outside(const Polygons &polygons, Point from, int di } if (settings.no_error && settings.move) // as ClipperLib::jtRound has to be used for offsets this simplify is VERY important for performance. - polygons_simplify(increased, scaled(0.025)); + polygons_simplify(increased, scaled(0.025), polygons_strictly_simple); } else // if no movement is done the areas keep parent area as no move == offset(0) increased = parent.influence_area; @@ -1821,6 +1823,7 @@ static Point move_inside_if_outside(const Polygons &polygons, Point from, int di BOOST_LOG_TRIVIAL(debug) << "Corrected taint leading to a wrong non gracious value on layer " << layer_idx - 1 << " targeting " << current_elem.target_height << " with radius " << radius; } else + // Cannot route to gracious areas. Push the tree away from object and route it down anyways. to_model_data = safe_union(diff_clipped(increased, volumes.getCollision(radius, layer_idx - 1, settings.use_min_distance))); } } @@ -1966,7 +1969,7 @@ static void increase_areas_one_layer( { using AvoidanceType = TreeModelVolumes::AvoidanceType; - tbb::parallel_for(tbb::blocked_range(0, merging_areas.size()), + tbb::parallel_for(tbb::blocked_range(0, merging_areas.size(), 1), [&](const tbb::blocked_range &range) { for (size_t merging_area_idx = range.begin(); merging_area_idx < range.end(); ++ merging_area_idx) { SupportElementMerging &merging_area = merging_areas[merging_area_idx]; @@ -2209,9 +2212,10 @@ static void increase_areas_one_layer( // A point can be set on the top most tip layer (maybe more if it should not move for a few layers). parent.state.result_on_layer_reset(); } + throw_on_cancel(); } - }); + }, tbb::simple_partitioner()); } [[nodiscard]] static SupportElementState merge_support_element_states( @@ -3325,7 +3329,7 @@ static void finalize_interface_and_support_areas( base_layer_polygons = smooth_outward(union_(base_layer_polygons), config.support_line_width); //FIXME was .smooth(50); //smooth_outward(closing(std::move(bottom), closing_distance + minimum_island_radius, closing_distance, SUPPORT_SURFACES_OFFSET_PARAMETERS), smoothing_distance) : // simplify a bit, to ensure the output does not contain outrageous amounts of vertices. Should not be necessary, just a precaution. - base_layer_polygons = polygons_simplify(base_layer_polygons, std::min(scaled(0.03), double(config.resolution))); + base_layer_polygons = polygons_simplify(base_layer_polygons, std::min(scaled(0.03), double(config.resolution)), polygons_strictly_simple); } if (! support_roof_polygons.empty() && ! base_layer_polygons.empty()) { @@ -4270,7 +4274,7 @@ static std::vector draw_branches( const SlicingParameters &slicing_params = print_object.slicing_parameters(); MeshSlicingParams mesh_slicing_params; mesh_slicing_params.mode = MeshSlicingParams::SlicingMode::Positive; - tbb::parallel_for(tbb::blocked_range(0, trees.size()), + tbb::parallel_for(tbb::blocked_range(0, trees.size(), 1), [&trees, &config, &slicing_params, &move_bounds, &mesh_slicing_params, &throw_on_cancel](const tbb::blocked_range &range) { indexed_triangle_set partial_mesh; std::vector slice_z; @@ -4322,9 +4326,9 @@ static std::vector draw_branches( tree.first_layer_id = new_begin; } } - }); + }, tbb::simple_partitioner()); - tbb::parallel_for(tbb::blocked_range(0, trees.size()), + tbb::parallel_for(tbb::blocked_range(0, trees.size(), 1), [&trees, &throw_on_cancel](const tbb::blocked_range &range) { for (size_t tree_id = range.begin(); tree_id < range.end(); ++ tree_id) { Tree &tree = trees[tree_id]; @@ -4335,7 +4339,7 @@ static std::vector draw_branches( } throw_on_cancel(); } - }); + }, tbb::simple_partitioner()); size_t num_layers = 0; for (Tree &tree : trees) @@ -4356,14 +4360,14 @@ static std::vector draw_branches( } std::vector support_layer_storage(move_bounds.size()); - tbb::parallel_for(tbb::blocked_range(0, std::min(move_bounds.size(), slices.size())), + tbb::parallel_for(tbb::blocked_range(0, std::min(move_bounds.size(), slices.size()), 1), [&slices, &support_layer_storage, &throw_on_cancel](const tbb::blocked_range &range) { for (size_t slice_id = range.begin(); slice_id < range.end(); ++ slice_id) { Slice &slice = slices[slice_id]; support_layer_storage[slice_id] = slice.num_branches > 1 ? union_(slice.polygons) : std::move(slice.polygons); throw_on_cancel(); } - }); + }, tbb::simple_partitioner()); //FIXME simplify! return support_layer_storage; From 0c6f2261a3518ef2ae5337e34179632c503d4f48 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 2 May 2023 18:45:07 +0200 Subject: [PATCH 11/34] Tree Supports: Extend bottoms of trees downwards to the object surface if those supports are not sitting on a flat surface. --- src/libslic3r/TreeSupport.cpp | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/TreeSupport.cpp b/src/libslic3r/TreeSupport.cpp index 3452bb71c..820b60d49 100644 --- a/src/libslic3r/TreeSupport.cpp +++ b/src/libslic3r/TreeSupport.cpp @@ -4275,7 +4275,7 @@ static std::vector draw_branches( MeshSlicingParams mesh_slicing_params; mesh_slicing_params.mode = MeshSlicingParams::SlicingMode::Positive; tbb::parallel_for(tbb::blocked_range(0, trees.size(), 1), - [&trees, &config, &slicing_params, &move_bounds, &mesh_slicing_params, &throw_on_cancel](const tbb::blocked_range &range) { + [&trees, &volumes, &config, &slicing_params, &move_bounds, &mesh_slicing_params, &throw_on_cancel](const tbb::blocked_range &range) { indexed_triangle_set partial_mesh; std::vector slice_z; for (size_t tree_id = range.begin(); tree_id < range.end(); ++ tree_id) { @@ -4297,7 +4297,23 @@ static std::vector draw_branches( slice_z.emplace_back(float(0.5 * (bottom_z + print_z))); } std::vector slices = slice_mesh(partial_mesh, slice_z, mesh_slicing_params, throw_on_cancel); - size_t num_empty = std::find_if(slices.begin(), slices.end(), [](auto &s) { return !s.empty(); }) - slices.begin(); + size_t num_empty = 0; + + if (layer_begin > 0 && branch.has_root && ! branch.path.front()->state.to_model_gracious && ! slices.front().empty()) { + // Drop down areas that do rest non - gracefully on the model to ensure the branch actually rests on something. + std::vector bottom_extra_slices; + Polygons rest_support; + for (LayerIndex layer_idx = layer_begin - 1; layer_idx >= 0; -- layer_idx) { + rest_support = diff_clipped(rest_support.empty() ? slices.front() : rest_support, volumes.getCollision(0, layer_idx, false)); + if (area(rest_support) < tiny_area_threshold) + break; + bottom_extra_slices.emplace_back(rest_support); + } + layer_begin -= LayerIndex(bottom_extra_slices.size()); + slices.insert(slices.begin(), std::make_move_iterator(bottom_extra_slices.rbegin()), std::make_move_iterator(bottom_extra_slices.rend())); + } else + num_empty = std::find_if(slices.begin(), slices.end(), [](auto &s) { return !s.empty(); }) - slices.begin(); + layer_begin += LayerIndex(num_empty); for (; slices.back().empty(); -- layer_end); LayerIndex new_begin = tree.first_layer_id == -1 ? layer_begin : std::min(tree.first_layer_id, layer_begin); From d0f38cd0b4c65e0b2b2e91d9df3669fc3f65bdd8 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 2 May 2023 19:26:33 +0200 Subject: [PATCH 12/34] macOS clang is buggy, it does not implement __cpp_lib_hardware_interference_size correctly. --- src/libslic3r/TriangleMeshSlicer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/TriangleMeshSlicer.cpp b/src/libslic3r/TriangleMeshSlicer.cpp index 747f36215..da696e1ec 100644 --- a/src/libslic3r/TriangleMeshSlicer.cpp +++ b/src/libslic3r/TriangleMeshSlicer.cpp @@ -36,7 +36,7 @@ #include #include -#ifdef __cpp_lib_hardware_interference_size +#if defined(__cpp_lib_hardware_interference_size) && ! defined(__APPLE__) using std::hardware_destructive_interference_size; #else // 64 bytes on x86-64 │ L1_CACHE_BYTES │ L1_CACHE_SHIFT │ __cacheline_aligned │ ... From af6c27b8613959319bbb66d1f066e55c2e231b1e Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 2 May 2023 19:50:12 +0200 Subject: [PATCH 13/34] douglas_peucker(): fix after 63ca221394acca695508dd333633b43b1a5e9744 --- src/libslic3r/MultiPoint.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/MultiPoint.cpp b/src/libslic3r/MultiPoint.cpp index 90928d3c1..fb4727abe 100644 --- a/src/libslic3r/MultiPoint.cpp +++ b/src/libslic3r/MultiPoint.cpp @@ -124,12 +124,10 @@ Points MultiPoint::douglas_peucker(const Points &pts, const double tolerance) size_t furthest_idx = anchor_idx; // find point furthest from line seg created by (anchor, floater) and note it { - const Point a = *anchor; - const Point f = *floater; + const Point a = *anchor; + const Point f = *floater; const Vec2i64 v = (f - a).cast(); - const int64_t l2 = v.squaredNorm(); - // Make up for rounding when converting from int64_t to double. Double mantissa is just 52 bits. - if (l2 < (1 << 14)) { + if (const int64_t l2 = v.squaredNorm(); l2 == 0) { for (size_t i = anchor_idx + 1; i < floater_idx; ++ i) if (int64_t dist_sq = (pts[i] - a).cast().squaredNorm(); dist_sq > max_dist_sq) { max_dist_sq = dist_sq; From 234956dfda61d9669d7a16b3957cd3de277c525c Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 3 May 2023 14:26:06 +0200 Subject: [PATCH 14/34] Fix for SPE-1686 : Bug with instance rotation around X or Y Steps to repro: 1. Add object 2. Increase instances 3. Select some instance 4. Rotate around X or Y 5. After MouseUp: OSX -> hard crash MSW and Linux -> object and instance are selected at a same time --- src/slic3r/GUI/GLCanvas3D.cpp | 30 +++++++++++++++++++++++++----- src/slic3r/GUI/GUI_ObjectList.cpp | 24 +++++++++++------------- 2 files changed, 36 insertions(+), 18 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index dbb210680..8116e8843 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3641,6 +3641,7 @@ void GLCanvas3D::do_move(const std::string& snapshot_type) } // Fixes flying instances + std::set obj_idx_for_update_info_items; for (const std::pair& i : done) { ModelObject* m = m_model->objects[i.first]; const double shift_z = m->get_instance_min_z(i.second); @@ -3649,8 +3650,11 @@ void GLCanvas3D::do_move(const std::string& snapshot_type) m_selection.translate(i.first, i.second, shift); m->translate_instance(i.second, shift); } - wxGetApp().obj_list()->update_info_items(static_cast(i.first)); + obj_idx_for_update_info_items.emplace(i.first); } + //update sinking information in ObjectList + for (int id : obj_idx_for_update_info_items) + wxGetApp().obj_list()->update_info_items(static_cast(id)); // if the selection is not valid to allow for layer editing after the move, we need to turn off the tool if it is running // similar to void Plater::priv::selection_changed() @@ -3728,6 +3732,7 @@ void GLCanvas3D::do_rotate(const std::string& snapshot_type) } // Fixes sinking/flying instances + std::set obj_idx_for_update_info_items; for (const std::pair& i : done) { ModelObject* m = m_model->objects[i.first]; const double shift_z = m->get_instance_min_z(i.second); @@ -3738,8 +3743,11 @@ void GLCanvas3D::do_rotate(const std::string& snapshot_type) m->translate_instance(i.second, shift); } - wxGetApp().obj_list()->update_info_items(static_cast(i.first)); + obj_idx_for_update_info_items.emplace(i.first); } + //update sinking information in ObjectList + for (int id : obj_idx_for_update_info_items) + wxGetApp().obj_list()->update_info_items(static_cast(id)); if (!done.empty()) post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_ROTATED)); @@ -3797,6 +3805,7 @@ void GLCanvas3D::do_scale(const std::string& snapshot_type) } // Fixes sinking/flying instances + std::set obj_idx_for_update_info_items; for (const std::pair& i : done) { ModelObject* m = m_model->objects[i.first]; const double shift_z = m->get_instance_min_z(i.second); @@ -3806,8 +3815,11 @@ void GLCanvas3D::do_scale(const std::string& snapshot_type) m_selection.translate(i.first, i.second, shift); m->translate_instance(i.second, shift); } - wxGetApp().obj_list()->update_info_items(static_cast(i.first)); + obj_idx_for_update_info_items.emplace(i.first); } + //update sinking information in ObjectList + for (int id : obj_idx_for_update_info_items) + wxGetApp().obj_list()->update_info_items(static_cast(id)); if (!done.empty()) post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_SCALED)); @@ -3860,6 +3872,7 @@ void GLCanvas3D::do_mirror(const std::string& snapshot_type) } // Fixes sinking/flying instances + std::set obj_idx_for_update_info_items; for (const std::pair& i : done) { ModelObject* m = m_model->objects[i.first]; double shift_z = m->get_instance_min_z(i.second); @@ -3869,8 +3882,11 @@ void GLCanvas3D::do_mirror(const std::string& snapshot_type) m_selection.translate(i.first, i.second, shift); m->translate_instance(i.second, shift); } - wxGetApp().obj_list()->update_info_items(static_cast(i.first)); + obj_idx_for_update_info_items.emplace(i.first); } + //update sinking information in ObjectList + for (int id : obj_idx_for_update_info_items) + wxGetApp().obj_list()->update_info_items(static_cast(id)); post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); @@ -3922,6 +3938,7 @@ void GLCanvas3D::do_reset_skew(const std::string& snapshot_type) } // Fixes sinking/flying instances + std::set obj_idx_for_update_info_items; for (const std::pair& i : done) { ModelObject* m = m_model->objects[i.first]; double shift_z = m->get_instance_min_z(i.second); @@ -3931,8 +3948,11 @@ void GLCanvas3D::do_reset_skew(const std::string& snapshot_type) m_selection.translate(i.first, i.second, shift); m->translate_instance(i.second, shift); } - wxGetApp().obj_list()->update_info_items(static_cast(i.first)); + obj_idx_for_update_info_items.emplace(i.first); } + //update sinking information in ObjectList + for (int id : obj_idx_for_update_info_items) + wxGetApp().obj_list()->update_info_items(static_cast(id)); post_event(SimpleEvent(EVT_GLCANVAS_RESET_SKEW)); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 85d12bec9..ffed1eef6 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2994,21 +2994,19 @@ void ObjectList::update_info_items(size_t obj_idx, wxDataViewItemArray* selectio wxGetApp().notification_manager()->push_updated_item_info_notification(type); } else if (shows && ! should_show) { - if (!selections) + if (!selections && IsSelected(item)) { Unselect(item); - m_objects_model->Delete(item); - if (selections) { - if (selections->Index(item) != wxNOT_FOUND) { - // If info item was deleted from the list, - // it's need to be deleted from selection array, if it was there - selections->Remove(item); - // Select item_obj, if info_item doesn't exist for item anymore, but was selected - if (selections->Index(item_obj) == wxNOT_FOUND) - selections->Add(item_obj); - } - } - else Select(item_obj); + } + m_objects_model->Delete(item); + if (selections && selections->Index(item) != wxNOT_FOUND) { + // If info item was deleted from the list, + // it's need to be deleted from selection array, if it was there + selections->Remove(item); + // Select item_obj, if info_item doesn't exist for item anymore, but was selected + if (selections->Index(item_obj) == wxNOT_FOUND) + selections->Add(item_obj); + } } } } From a039391131fc6cecbfbca406bd3a2627b93814cc Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 4 May 2023 13:56:05 +0200 Subject: [PATCH 15/34] Fixed rendering of horizontal ellipsis in ImGui controls --- src/slic3r/GUI/ImGuiWrapper.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 0754c35cc..f2e6b0287 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -1620,6 +1620,8 @@ void ImGuiWrapper::init_font(bool compress) ImFontGlyphRangesBuilder builder; builder.AddRanges(m_glyph_ranges); + builder.AddChar(ImWchar(0x2026)); // … + if (m_font_cjk) { // This is a temporary fix of https://github.com/prusa3d/PrusaSlicer/issues/8171. The translation // contains characters not in the ImGui ranges for simplified Chinese. For now, just add them manually. From 34015349c177feef289dc3f480f5d52cc69ddecf Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Thu, 4 May 2023 14:59:50 +0200 Subject: [PATCH 16/34] Fix a rounded extrusion model when the new width is smaller than layer height - the code used radius in place of diameter --- src/libslic3r/Flow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/Flow.cpp b/src/libslic3r/Flow.cpp index 1084e6f10..bc3a2d777 100644 --- a/src/libslic3r/Flow.cpp +++ b/src/libslic3r/Flow.cpp @@ -176,7 +176,7 @@ Flow Flow::with_cross_section(float area_new) const return this->with_width(width_new); } else { // Create a rounded extrusion. - auto dmr = float(sqrt(area_new / M_PI)); + auto dmr = 2.0 * float(sqrt(area_new / M_PI)); return Flow(dmr, dmr, m_spacing, m_nozzle_diameter, false); } } else From 8593ad1f80011994b33a130c2d634cb83acda5f6 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 4 May 2023 15:26:41 +0200 Subject: [PATCH 17/34] Organic supports improvements: Removing collisions with trees, limiting how far tree bottoms at slanted surfaces could be extended down below their last full circle position. Placable areas are now calculated sitting on slightly inflated top surface to indicate support of tree bottoms at slanted surfaces. --- src/libslic3r/ClipperUtils.cpp | 2 + src/libslic3r/ClipperUtils.hpp | 3 + src/libslic3r/TreeModelVolumes.cpp | 17 ++-- src/libslic3r/TreeModelVolumes.hpp | 3 +- src/libslic3r/TreeSupport.cpp | 156 ++++++++++++++++++++++------- src/libslic3r/TreeSupport.hpp | 12 +++ 6 files changed, 147 insertions(+), 46 deletions(-) diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index d10e14cc5..85ef53c88 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -685,6 +685,8 @@ Slic3r::Polygons diff(const Slic3r::Surfaces &subject, const Slic3r::Polygons &c { return _clipper(ClipperLib::ctDifference, ClipperUtils::SurfacesProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } Slic3r::Polygons intersection(const Slic3r::Polygon &subject, const Slic3r::Polygon &clip, ApplySafetyOffset do_safety_offset) { return _clipper(ClipperLib::ctIntersection, ClipperUtils::SinglePathProvider(subject.points), ClipperUtils::SinglePathProvider(clip.points), do_safety_offset); } +Slic3r::Polygons intersection_clipped(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) + { return intersection(subject, ClipperUtils::clip_clipper_polygons_with_subject_bbox(clip, get_extents(subject).inflated(SCALED_EPSILON)), do_safety_offset); } Slic3r::Polygons intersection(const Slic3r::Polygons &subject, const Slic3r::ExPolygon &clip, ApplySafetyOffset do_safety_offset) { return _clipper(ClipperLib::ctIntersection, ClipperUtils::PolygonsProvider(subject), ClipperUtils::ExPolygonProvider(clip), do_safety_offset); } Slic3r::Polygons intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) diff --git a/src/libslic3r/ClipperUtils.hpp b/src/libslic3r/ClipperUtils.hpp index ab967fd8f..774e9cb42 100644 --- a/src/libslic3r/ClipperUtils.hpp +++ b/src/libslic3r/ClipperUtils.hpp @@ -453,6 +453,9 @@ inline Slic3r::Lines diff_ln(const Slic3r::Lines &subject, const Slic3r::Polygon Slic3r::Polygons intersection(const Slic3r::Polygon &subject, const Slic3r::Polygon &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::Polygons intersection(const Slic3r::Polygons &subject, const Slic3r::ExPolygon &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::Polygons intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); +// Optimized version clipping the "clipping" polygon using clip_clipper_polygon_with_subject_bbox(). +// To be used with complex clipping polygons, where majority of the clipping polygons are outside of the source polygon. +Slic3r::Polygons intersection_clipped(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::Polygons intersection(const Slic3r::ExPolygon &subject, const Slic3r::ExPolygon &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::Polygons intersection(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::Polygons intersection(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); diff --git a/src/libslic3r/TreeModelVolumes.cpp b/src/libslic3r/TreeModelVolumes.cpp index 3c82b5c6c..10840d1b5 100644 --- a/src/libslic3r/TreeModelVolumes.cpp +++ b/src/libslic3r/TreeModelVolumes.cpp @@ -426,7 +426,7 @@ const Polygons& TreeModelVolumes::getPlaceableAreas(const coord_t orig_radius, L return (*result).get(); if (m_precalculated) { BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate Placeable Areas at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!"; - tree_supports_show_error("Not precalculated Placeable areas requested."sv, false); + tree_supports_show_error(format("Not precalculated Placeable areas requested, radius %1%, layer %2%", radius, layer_idx), false); } if (orig_radius == 0) // Placable areas for radius 0 are calculated in the general collision code. @@ -597,16 +597,19 @@ void TreeModelVolumes::calculateCollision(const coord_t radius, const LayerIndex // 3) Optionally calculate placables. if (calculate_placable) { // Now calculate the placable areas. - tbb::parallel_for(tbb::blocked_range(std::max(data.idx_begin, 1), data.idx_end), - [&collision_areas_offsetted, &anti_overhang = m_anti_overhang, processing_last_mesh, - min_resolution = m_min_resolution, &data_placeable, &throw_on_cancel] + tbb::parallel_for(tbb::blocked_range(std::max(z_distance_bottom_layers + 1, data.idx_begin), data.idx_end), + [&collision_areas_offsetted, &outlines, &anti_overhang = m_anti_overhang, processing_last_mesh, + min_resolution = m_min_resolution, z_distance_bottom_layers, xy_distance, &data_placeable, &throw_on_cancel] (const tbb::blocked_range& range) { for (LayerIndex layer_idx = range.begin(); layer_idx != range.end(); ++ layer_idx) { - LayerIndex layer_idx_below = layer_idx - 1; + LayerIndex layer_idx_below = layer_idx - z_distance_bottom_layers - 1; assert(layer_idx_below >= 0); const Polygons ¤t = collision_areas_offsetted[layer_idx]; - const Polygons &below = collision_areas_offsetted[layer_idx_below]; - Polygons placable = diff(below, layer_idx_below < int(anti_overhang.size()) ? union_(current, anti_overhang[layer_idx_below]) : current); + const Polygons &below = outlines[layer_idx_below]; + Polygons placable = diff( + // Inflate the surface to sit on by the separation distance to increase chance of a support being placed on a sloped surface. + offset(below, xy_distance), + layer_idx_below < int(anti_overhang.size()) ? union_(current, anti_overhang[layer_idx_below]) : current); auto &dst = data_placeable[layer_idx]; if (processing_last_mesh) { if (! dst.empty()) diff --git a/src/libslic3r/TreeModelVolumes.hpp b/src/libslic3r/TreeModelVolumes.hpp index 139b12328..659baf1ff 100644 --- a/src/libslic3r/TreeModelVolumes.hpp +++ b/src/libslic3r/TreeModelVolumes.hpp @@ -215,6 +215,7 @@ public: void clear() { this->clear_all_but_object_collision(); m_collision_cache.clear(); + m_placeable_areas_cache.clear(); } void clear_all_but_object_collision() { //m_collision_cache.clear_all_but_radius0(); @@ -223,7 +224,7 @@ public: m_avoidance_cache_slow.clear(); m_avoidance_cache_to_model.clear(); m_avoidance_cache_to_model_slow.clear(); - m_placeable_areas_cache.clear(); + m_placeable_areas_cache.clear_all_but_radius0(); m_avoidance_cache_holefree.clear(); m_avoidance_cache_holefree_to_model.clear(); m_wall_restrictions_cache.clear(); diff --git a/src/libslic3r/TreeSupport.cpp b/src/libslic3r/TreeSupport.cpp index 820b60d49..9a7b203c6 100644 --- a/src/libslic3r/TreeSupport.cpp +++ b/src/libslic3r/TreeSupport.cpp @@ -299,7 +299,7 @@ static bool inline g_showed_critical_error = false; static bool inline g_showed_performance_warning = false; void tree_supports_show_error(std::string_view message, bool critical) { // todo Remove! ONLY FOR PUBLIC BETA!! - +// printf("Error: %s, critical: %d\n", message.data(), int(critical)); #ifdef TREE_SUPPORT_SHOW_ERRORS_WIN32 static bool showed_critical = false; static bool showed_performance = false; @@ -2158,6 +2158,10 @@ static void increase_areas_one_layer( " Distance to top: " << parent.state.distance_to_top << " Elephant foot increases " << parent.state.elephant_foot_increases << " use_min_xy_dist " << parent.state.use_min_xy_dist << " to buildplate " << parent.state.to_buildplate << " gracious " << parent.state.to_model_gracious << " safe " << parent.state.can_use_safe_radius << " until move " << parent.state.dont_move_until; tree_supports_show_error("Potentially lost branch!"sv, true); +#ifdef TREE_SUPPORTS_TRACK_LOST + if (result) + result->lost = true; +#endif // TREE_SUPPORTS_TRACK_LOST } else result = increase_single_area(volumes, config, settings, layer_idx, parent, settings.increase_speed == slow_speed ? offset_slow : offset_fast, to_bp_data, to_model_data, inc_wo_collision, 0, mergelayer); @@ -2211,6 +2215,9 @@ static void increase_areas_one_layer( // But as branches connecting with the model that are to small have to be culled, the bottom most point has to be not set. // A point can be set on the top most tip layer (maybe more if it should not move for a few layers). parent.state.result_on_layer_reset(); +#ifdef TREE_SUPPORTS_TRACK_LOST + parent.state.verylost = true; +#endif // TREE_SUPPORTS_TRACK_LOST } throw_on_cancel(); @@ -4234,26 +4241,35 @@ static std::vector draw_branches( branch.path.emplace_back(&start_element); // Traverse each branch until it branches again. SupportElement &first_parent = layer_above[start_element.parents[parent_idx]]; + assert(! first_parent.state.marked); assert(branch.path.back()->state.layer_idx + 1 == first_parent.state.layer_idx); branch.path.emplace_back(&first_parent); if (first_parent.parents.size() < 2) first_parent.state.marked = true; SupportElement *next_branch = nullptr; - if (first_parent.parents.size() == 1) + if (first_parent.parents.size() == 1) { for (SupportElement *parent = &first_parent;;) { + assert(parent->state.marked); SupportElement &next_parent = move_bounds[parent->state.layer_idx + 1][parent->parents.front()]; + assert(! next_parent.state.marked); assert(branch.path.back()->state.layer_idx + 1 == next_parent.state.layer_idx); branch.path.emplace_back(&next_parent); if (next_parent.parents.size() > 1) { + // Branching point was reached. next_branch = &next_parent; break; } next_parent.state.marked = true; if (next_parent.parents.size() == 0) + // Tip is reached. break; parent = &next_parent; } + } else if (first_parent.parents.size() > 1) + // Branching point was reached. + next_branch = &first_parent; assert(branch.path.size() >= 2); + assert(next_branch == nullptr || ! next_branch->state.marked); branch.has_root = root; branch.has_tip = ! next_branch; out.branches.emplace_back(std::move(branch)); @@ -4263,13 +4279,37 @@ static std::vector draw_branches( } }; - for (LayerIndex layer_idx = 0; layer_idx + 1 < LayerIndex(move_bounds.size()); ++ layer_idx) - for (SupportElement &start_element : move_bounds[layer_idx]) - if (! start_element.state.marked && ! start_element.parents.empty()) { + for (LayerIndex layer_idx = 0; layer_idx + 1 < LayerIndex(move_bounds.size()); ++ layer_idx) { +// int ielement; + for (SupportElement& start_element : move_bounds[layer_idx]) { + if (!start_element.state.marked && !start_element.parents.empty()) { +#if 0 + int found = 0; + if (layer_idx > 0) { + for (auto& el : move_bounds[layer_idx - 1]) { + for (auto iparent : el.parents) + if (iparent == ielement) + ++found; + } + if (found != 0) + printf("Found: %d\n", found); + } +#endif trees.push_back({}); TreeVisitor::visit_recursive(move_bounds, start_element, trees.back()); - assert(! trees.back().branches.empty()); + assert(!trees.back().branches.empty()); + //FIXME debugging +#if 0 + if (start_element.state.lost) { + } + else if (start_element.state.verylost) { + } else + trees.pop_back(); +#endif } +// ++ ielement; + } + } const SlicingParameters &slicing_params = print_object.slicing_parameters(); MeshSlicingParams mesh_slicing_params; @@ -4297,49 +4337,89 @@ static std::vector draw_branches( slice_z.emplace_back(float(0.5 * (bottom_z + print_z))); } std::vector slices = slice_mesh(partial_mesh, slice_z, mesh_slicing_params, throw_on_cancel); - size_t num_empty = 0; + //FIXME parallelize? + for (LayerIndex i = 0; i < LayerIndex(slices.size()); ++ i) + slices[i] = diff_clipped(slices[i], volumes.getCollision(0, layer_begin + i, true)); //FIXME parent_uses_min || draw_area.element->state.use_min_xy_dist); + size_t num_empty = 0; if (layer_begin > 0 && branch.has_root && ! branch.path.front()->state.to_model_gracious && ! slices.front().empty()) { // Drop down areas that do rest non - gracefully on the model to ensure the branch actually rests on something. - std::vector bottom_extra_slices; - Polygons rest_support; - for (LayerIndex layer_idx = layer_begin - 1; layer_idx >= 0; -- layer_idx) { + struct BottomExtraSlice { + Polygons polygons; + Polygons supported; + double area; + double supported_area; + }; + std::vector bottom_extra_slices; + Polygons rest_support; + coord_t bottom_radius = config.getRadius(branch.path.front()->state); + // Don't propagate further than 1.5 * bottom radius. + //LayerIndex layers_propagate_max = 2 * bottom_radius / config.layer_height; + LayerIndex layers_propagate_max = 5 * bottom_radius / config.layer_height; + LayerIndex layer_bottommost = std::max(0, layer_begin - layers_propagate_max); + // Only propagate until the rest area is smaller than this threshold. + double support_area_stop = 0.2 * M_PI * sqr(double(bottom_radius)); + // Only propagate until the rest area is smaller than this threshold. + double support_area_min = 0.1 * M_PI * sqr(double(config.min_radius)); + for (LayerIndex layer_idx = layer_begin - 1; layer_idx >= layer_bottommost; -- layer_idx) { rest_support = diff_clipped(rest_support.empty() ? slices.front() : rest_support, volumes.getCollision(0, layer_idx, false)); - if (area(rest_support) < tiny_area_threshold) + double rest_support_area = area(rest_support); + if (rest_support_area < support_area_stop) + // Don't propagate a fraction of the tree contact surface. break; - bottom_extra_slices.emplace_back(rest_support); + // Measure how much the rest_support is actually supported. + /* + Polygons supported = intersection_clipped(rest_support, volumes.getPlaceableAreas(0, layer_idx, []{})); + double supported_area = area(supported); + printf("Supported area: %d, %lf\n", layer_idx, supported_area); + */ + Polygons supported; + double supported_area; + bottom_extra_slices.push_back({ rest_support, std::move(supported), rest_support_area, supported_area }); } + // Now remove those bottom slices that are not supported at all. + while (! bottom_extra_slices.empty() && + area(intersection_clipped(bottom_extra_slices.back().polygons, volumes.getPlaceableAreas(0, layer_begin - LayerIndex(bottom_extra_slices.size()), [] {}))) < support_area_min) + bottom_extra_slices.pop_back(); layer_begin -= LayerIndex(bottom_extra_slices.size()); - slices.insert(slices.begin(), std::make_move_iterator(bottom_extra_slices.rbegin()), std::make_move_iterator(bottom_extra_slices.rend())); + slices.insert(slices.begin(), bottom_extra_slices.size(), {}); + size_t i = 0; + for (auto it = bottom_extra_slices.rbegin(); it != bottom_extra_slices.rend(); ++it, ++i) + slices[i] = std::move(it->polygons); } else num_empty = std::find_if(slices.begin(), slices.end(), [](auto &s) { return !s.empty(); }) - slices.begin(); layer_begin += LayerIndex(num_empty); - for (; slices.back().empty(); -- layer_end); - LayerIndex new_begin = tree.first_layer_id == -1 ? layer_begin : std::min(tree.first_layer_id, layer_begin); - LayerIndex new_end = tree.first_layer_id == -1 ? layer_end : std::max(tree.first_layer_id + LayerIndex(tree.slices.size()), layer_end); - size_t new_size = size_t(new_end - new_begin); - if (tree.first_layer_id == -1) { - } else if (tree.slices.capacity() < new_size) { - std::vector new_slices; - new_slices.reserve(new_size); - if (LayerIndex dif = tree.first_layer_id - new_begin; dif > 0) - new_slices.insert(new_slices.end(), dif, {}); - append(new_slices, std::move(tree.slices)); - tree.slices.swap(new_slices); - } else if (LayerIndex dif = tree.first_layer_id - new_begin; dif > 0) - tree.slices.insert(tree.slices.begin(), tree.first_layer_id - new_begin, {}); - tree.slices.insert(tree.slices.end(), new_size - tree.slices.size(), {}); - layer_begin -= LayerIndex(num_empty); - for (LayerIndex i = layer_begin; i != layer_end; ++ i) - if (Polygons &src = slices[i - layer_begin]; ! src.empty()) { - Slice &dst = tree.slices[i - new_begin]; - if (++ dst.num_branches > 1) - append(dst.polygons, std::move(src)); - else - dst.polygons = std::move(std::move(src)); - } - tree.first_layer_id = new_begin; + while (! slices.empty() && slices.back().empty()) { + slices.pop_back(); + -- layer_end; + } + if (layer_begin < layer_end) { + LayerIndex new_begin = tree.first_layer_id == -1 ? layer_begin : std::min(tree.first_layer_id, layer_begin); + LayerIndex new_end = tree.first_layer_id == -1 ? layer_end : std::max(tree.first_layer_id + LayerIndex(tree.slices.size()), layer_end); + size_t new_size = size_t(new_end - new_begin); + if (tree.first_layer_id == -1) { + } else if (tree.slices.capacity() < new_size) { + std::vector new_slices; + new_slices.reserve(new_size); + if (LayerIndex dif = tree.first_layer_id - new_begin; dif > 0) + new_slices.insert(new_slices.end(), dif, {}); + append(new_slices, std::move(tree.slices)); + tree.slices.swap(new_slices); + } else if (LayerIndex dif = tree.first_layer_id - new_begin; dif > 0) + tree.slices.insert(tree.slices.begin(), tree.first_layer_id - new_begin, {}); + tree.slices.insert(tree.slices.end(), new_size - tree.slices.size(), {}); + layer_begin -= LayerIndex(num_empty); + for (LayerIndex i = layer_begin; i != layer_end; ++ i) + if (Polygons &src = slices[i - layer_begin]; ! src.empty()) { + Slice &dst = tree.slices[i - new_begin]; + if (++ dst.num_branches > 1) + append(dst.polygons, std::move(src)); + else + dst.polygons = std::move(std::move(src)); + } + tree.first_layer_id = new_begin; + } } } }, tbb::simple_partitioner()); diff --git a/src/libslic3r/TreeSupport.hpp b/src/libslic3r/TreeSupport.hpp index 070903096..2c87a132d 100644 --- a/src/libslic3r/TreeSupport.hpp +++ b/src/libslic3r/TreeSupport.hpp @@ -93,6 +93,8 @@ struct AreaIncreaseSettings struct TreeSupportSettings; +// #define TREE_SUPPORTS_TRACK_LOST + // C++17 does not support in place initializers of bit values, thus a constructor zeroing the bits is provided. struct SupportElementStateBits { SupportElementStateBits() : @@ -102,6 +104,10 @@ struct SupportElementStateBits { supports_roof(false), can_use_safe_radius(false), skip_ovalisation(false), +#ifdef TREE_SUPPORTS_TRACK_LOST + lost(false), + verylost(false), +#endif // TREE_SUPPORTS_TRACK_LOST deleted(false), marked(false) {} @@ -136,6 +142,12 @@ struct SupportElementStateBits { */ bool skip_ovalisation : 1; +#ifdef TREE_SUPPORTS_TRACK_LOST + // Likely a lost branch, debugging information. + bool lost : 1; + bool verylost : 1; +#endif // TREE_SUPPORTS_TRACK_LOST + // Not valid anymore, to be deleted. bool deleted : 1; From 3b9037b44248ca19a49545ed31933c1b4c31bfe4 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 4 May 2023 15:27:09 +0200 Subject: [PATCH 18/34] nlopt in RelWithDebInfo now compiles as release. --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 66b9a777b..49fe3437f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -546,6 +546,7 @@ foreach(po_file ${L10N_PO_FILES}) endforeach() find_package(NLopt 1.4 REQUIRED) +slic3r_remap_configs(NLopt::nlopt RelWithDebInfo Release) if(SLIC3R_STATIC) set(OPENVDB_USE_STATIC_LIBS ON) From 6d0ceeb8861bfda4c3d84ffd1ea1c3fc700d406d Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 2 May 2023 12:59:20 +0200 Subject: [PATCH 19/34] ClipperLib: Further optimization of memory allocation using scalable_allocator. ClipperLib: SimplifyPolygon() - changed default winding number to positive, added strictly_simple parameter. ClipperUtlis simplify_polygons() - removed "remove_collinear" parameter --- src/clipper/clipper.cpp | 206 +++++++++++------------- src/clipper/clipper.hpp | 43 +++-- src/libslic3r/Arachne/WallToolPaths.cpp | 4 +- src/libslic3r/ClipperUtils.cpp | 25 ++- src/libslic3r/ClipperUtils.hpp | 4 +- 5 files changed, 137 insertions(+), 145 deletions(-) diff --git a/src/clipper/clipper.cpp b/src/clipper/clipper.cpp index c775a3226..5da79d3a1 100644 --- a/src/clipper/clipper.cpp +++ b/src/clipper/clipper.cpp @@ -73,25 +73,6 @@ static int const Skip = -2; //edge that would otherwise close a path #define TOLERANCE (1.0e-20) #define NEAR_ZERO(val) (((val) > -TOLERANCE) && ((val) < TOLERANCE)) -// Output polygon. -struct OutRec { - int Idx; - bool IsHole; - bool IsOpen; - //The 'FirstLeft' field points to another OutRec that contains or is the - //'parent' of OutRec. It is 'first left' because the ActiveEdgeList (AEL) is - //parsed left from the current edge (owning OutRec) until the owner OutRec - //is found. This field simplifies sorting the polygons into a tree structure - //which reflects the parent/child relationships of all polygons. - //This field should be renamed Parent, and will be later. - OutRec *FirstLeft; - // Used only by void Clipper::BuildResult2(PolyTree& polytree) - PolyNode *PolyNd; - // Linked list of output points, dynamically allocated. - OutPt *Pts; - OutPt *BottomPt; -}; - //------------------------------------------------------------------------------ inline IntPoint IntPoint2d(cInt x, cInt y) @@ -1061,8 +1042,7 @@ IntRect ClipperBase::GetBounds() Clipper::Clipper(int initOptions) : ClipperBase(), m_OutPtsFree(nullptr), - m_OutPtsChunkSize(32), - m_OutPtsChunkLast(32), + m_OutPtsChunkLast(m_OutPtsChunkSize), m_ActiveEdges(nullptr), m_SortedEdges(nullptr) { @@ -1153,23 +1133,23 @@ bool Clipper::ExecuteInternal() //FIXME Vojtech: Does it not invalidate the loop hierarchy maintained as OutRec::FirstLeft pointers? //FIXME Vojtech: The area is calculated with floats, it may not be numerically stable! { - for (OutRec *outRec : m_PolyOuts) - if (outRec->Pts && !outRec->IsOpen && (outRec->IsHole ^ m_ReverseOutput) == (Area(*outRec) > 0)) - ReversePolyPtLinks(outRec->Pts); + for (OutRec &outRec : m_PolyOuts) + if (outRec.Pts && !outRec.IsOpen && (outRec.IsHole ^ m_ReverseOutput) == (Area(outRec) > 0)) + ReversePolyPtLinks(outRec.Pts); } JoinCommonEdges(); //unfortunately FixupOutPolygon() must be done after JoinCommonEdges() { - for (OutRec *outRec : m_PolyOuts) - if (outRec->Pts) { - if (outRec->IsOpen) + for (OutRec &outRec : m_PolyOuts) + if (outRec.Pts) { + if (outRec.IsOpen) // Removes duplicate points. - FixupOutPolyline(*outRec); + FixupOutPolyline(outRec); else // Removes duplicate points and simplifies consecutive parallel edges by removing the middle vertex. - FixupOutPolygon(*outRec); + FixupOutPolygon(outRec); } } // For each polygon, search for exactly duplicate non-successive points. @@ -1194,22 +1174,18 @@ OutPt* Clipper::AllocateOutPt() m_OutPtsFree = pt->Next; } else if (m_OutPtsChunkLast < m_OutPtsChunkSize) { // Get a point from the last chunk. - pt = m_OutPts.back() + (m_OutPtsChunkLast ++); + pt = &m_OutPts.back()[m_OutPtsChunkLast ++]; } else { // The last chunk is full. Allocate a new one. - m_OutPts.push_back(new OutPt[m_OutPtsChunkSize]); + m_OutPts.push_back({}); m_OutPtsChunkLast = 1; - pt = m_OutPts.back(); + pt = &m_OutPts.back().front(); } return pt; } void Clipper::DisposeAllOutRecs() { - for (OutPt *pts : m_OutPts) - delete[] pts; - for (OutRec *rec : m_PolyOuts) - delete rec; m_OutPts.clear(); m_OutPtsFree = nullptr; m_OutPtsChunkLast = m_OutPtsChunkSize; @@ -1832,7 +1808,7 @@ void Clipper::IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &Pt) } //------------------------------------------------------------------------------ -void Clipper::SetHoleState(TEdge *e, OutRec *outrec) const +void Clipper::SetHoleState(TEdge *e, OutRec *outrec) { bool IsHole = false; TEdge *e2 = e->PrevInAEL; @@ -1842,7 +1818,7 @@ void Clipper::SetHoleState(TEdge *e, OutRec *outrec) const { IsHole = !IsHole; if (! outrec->FirstLeft) - outrec->FirstLeft = m_PolyOuts[e2->OutIdx]; + outrec->FirstLeft = &m_PolyOuts[e2->OutIdx]; } e2 = e2->PrevInAEL; } @@ -1883,18 +1859,18 @@ bool Param1RightOfParam2(OutRec* outRec1, OutRec* outRec2) OutRec* Clipper::GetOutRec(int Idx) { - OutRec* outrec = m_PolyOuts[Idx]; - while (outrec != m_PolyOuts[outrec->Idx]) - outrec = m_PolyOuts[outrec->Idx]; + OutRec* outrec = &m_PolyOuts[Idx]; + while (outrec != &m_PolyOuts[outrec->Idx]) + outrec = &m_PolyOuts[outrec->Idx]; return outrec; } //------------------------------------------------------------------------------ -void Clipper::AppendPolygon(TEdge *e1, TEdge *e2) const +void Clipper::AppendPolygon(TEdge *e1, TEdge *e2) { //get the start and ends of both output polygons ... - OutRec *outRec1 = m_PolyOuts[e1->OutIdx]; - OutRec *outRec2 = m_PolyOuts[e2->OutIdx]; + OutRec *outRec1 = &m_PolyOuts[e1->OutIdx]; + OutRec *outRec2 = &m_PolyOuts[e2->OutIdx]; OutRec *holeStateRec; if (Param1RightOfParam2(outRec1, outRec2)) @@ -1991,16 +1967,16 @@ void Clipper::AppendPolygon(TEdge *e1, TEdge *e2) const OutRec* Clipper::CreateOutRec() { - OutRec* result = new OutRec; - result->IsHole = false; - result->IsOpen = false; - result->FirstLeft = 0; - result->Pts = 0; - result->BottomPt = 0; - result->PolyNd = 0; - m_PolyOuts.push_back(result); - result->Idx = (int)m_PolyOuts.size()-1; - return result; + m_PolyOuts.push_back({}); + OutRec &result = m_PolyOuts.back(); + result.IsHole = false; + result.IsOpen = false; + result.FirstLeft = 0; + result.Pts = 0; + result.BottomPt = 0; + result.PolyNd = 0; + result.Idx = (int)m_PolyOuts.size()-1; + return &result; } //------------------------------------------------------------------------------ @@ -2022,7 +1998,7 @@ OutPt* Clipper::AddOutPt(TEdge *e, const IntPoint &pt) return newOp; } else { - OutRec *outRec = m_PolyOuts[e->OutIdx]; + OutRec *outRec = &m_PolyOuts[e->OutIdx]; //OutRec.Pts is the 'Left-most' point & OutRec.Pts.Prev is the 'Right-most' OutPt* op = outRec->Pts; @@ -2045,7 +2021,7 @@ OutPt* Clipper::AddOutPt(TEdge *e, const IntPoint &pt) OutPt* Clipper::GetLastOutPt(TEdge *e) { - OutRec *outRec = m_PolyOuts[e->OutIdx]; + OutRec *outRec = &m_PolyOuts[e->OutIdx]; if (e->Side == esLeft) return outRec->Pts; else @@ -2216,7 +2192,7 @@ void Clipper::ProcessHorizontal(TEdge *horzEdge) { Direction dir; cInt horzLeft, horzRight; - bool IsOpen = (horzEdge->OutIdx >= 0 && m_PolyOuts[horzEdge->OutIdx]->IsOpen); + bool IsOpen = (horzEdge->OutIdx >= 0 && m_PolyOuts[horzEdge->OutIdx].IsOpen); GetHorzDirection(*horzEdge, dir, horzLeft, horzRight); @@ -2778,12 +2754,12 @@ int PointCount(OutPt *Pts) void Clipper::BuildResult(Paths &polys) { polys.reserve(m_PolyOuts.size()); - for (OutRec* outRec : m_PolyOuts) + for (OutRec &outRec : m_PolyOuts) { - assert(! outRec->IsOpen); - if (!outRec->Pts) continue; + assert(! outRec.IsOpen); + if (!outRec.Pts) continue; Path pg; - OutPt* p = outRec->Pts->Prev; + OutPt* p = outRec.Pts->Prev; int cnt = PointCount(p); if (cnt < 2) continue; pg.reserve(cnt); @@ -2802,31 +2778,31 @@ void Clipper::BuildResult2(PolyTree& polytree) polytree.Clear(); polytree.AllNodes.reserve(m_PolyOuts.size()); //add each output polygon/contour to polytree ... - for (OutRec* outRec : m_PolyOuts) + for (OutRec &outRec : m_PolyOuts) { - int cnt = PointCount(outRec->Pts); - if ((outRec->IsOpen && cnt < 2) || (!outRec->IsOpen && cnt < 3)) + int cnt = PointCount(outRec.Pts); + if ((outRec.IsOpen && cnt < 2) || (!outRec.IsOpen && cnt < 3)) // Ignore an invalid output loop or a polyline. continue; //skip OutRecs that (a) contain outermost polygons or //(b) already have the correct owner/child linkage ... - if (outRec->FirstLeft && - (outRec->IsHole == outRec->FirstLeft->IsHole || ! outRec->FirstLeft->Pts)) { - OutRec* orfl = outRec->FirstLeft; - while (orfl && ((orfl->IsHole == outRec->IsHole) || !orfl->Pts)) + if (outRec.FirstLeft && + (outRec.IsHole == outRec.FirstLeft->IsHole || ! outRec.FirstLeft->Pts)) { + OutRec* orfl = outRec.FirstLeft; + while (orfl && ((orfl->IsHole == outRec.IsHole) || !orfl->Pts)) orfl = orfl->FirstLeft; - outRec->FirstLeft = orfl; + outRec.FirstLeft = orfl; } //nb: polytree takes ownership of all the PolyNodes polytree.AllNodes.emplace_back(PolyNode()); PolyNode* pn = &polytree.AllNodes.back(); - outRec->PolyNd = pn; + outRec.PolyNd = pn; pn->Parent = 0; pn->Index = 0; pn->Contour.reserve(cnt); - OutPt *op = outRec->Pts->Prev; + OutPt *op = outRec.Pts->Prev; for (int j = 0; j < cnt; j++) { pn->Contour.emplace_back(op->Pt); @@ -2836,18 +2812,18 @@ void Clipper::BuildResult2(PolyTree& polytree) //fixup PolyNode links etc ... polytree.Childs.reserve(m_PolyOuts.size()); - for (OutRec* outRec : m_PolyOuts) + for (OutRec &outRec : m_PolyOuts) { - if (!outRec->PolyNd) continue; - if (outRec->IsOpen) + if (!outRec.PolyNd) continue; + if (outRec.IsOpen) { - outRec->PolyNd->m_IsOpen = true; - polytree.AddChild(*outRec->PolyNd); + outRec.PolyNd->m_IsOpen = true; + polytree.AddChild(*outRec.PolyNd); } - else if (outRec->FirstLeft && outRec->FirstLeft->PolyNd) - outRec->FirstLeft->PolyNd->AddChild(*outRec->PolyNd); + else if (outRec.FirstLeft && outRec.FirstLeft->PolyNd) + outRec.FirstLeft->PolyNd->AddChild(*outRec.PolyNd); else - polytree.AddChild(*outRec->PolyNd); + polytree.AddChild(*outRec.PolyNd); } } //------------------------------------------------------------------------------ @@ -3193,26 +3169,26 @@ bool Clipper::JoinPoints(Join *j, OutRec* outRec1, OutRec* outRec2) //---------------------------------------------------------------------- // This is potentially very expensive! O(n^3)! -void Clipper::FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec) const +void Clipper::FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec) { //tests if NewOutRec contains the polygon before reassigning FirstLeft - for (OutRec *outRec : m_PolyOuts) + for (OutRec &outRec : m_PolyOuts) { - if (!outRec->Pts || !outRec->FirstLeft) continue; - OutRec* firstLeft = outRec->FirstLeft; + if (!outRec.Pts || !outRec.FirstLeft) continue; + OutRec* firstLeft = outRec.FirstLeft; // Skip empty polygons. while (firstLeft && !firstLeft->Pts) firstLeft = firstLeft->FirstLeft; - if (firstLeft == OldOutRec && Poly2ContainsPoly1(outRec->Pts, NewOutRec->Pts)) - outRec->FirstLeft = NewOutRec; + if (firstLeft == OldOutRec && Poly2ContainsPoly1(outRec.Pts, NewOutRec->Pts)) + outRec.FirstLeft = NewOutRec; } } //---------------------------------------------------------------------- -void Clipper::FixupFirstLefts2(OutRec* OldOutRec, OutRec* NewOutRec) const +void Clipper::FixupFirstLefts2(OutRec* OldOutRec, OutRec* NewOutRec) { //reassigns FirstLeft WITHOUT testing if NewOutRec contains the polygon - for (OutRec *outRec : m_PolyOuts) - if (outRec->FirstLeft == OldOutRec) outRec->FirstLeft = NewOutRec; + for (OutRec &outRec : m_PolyOuts) + if (outRec.FirstLeft == OldOutRec) outRec.FirstLeft = NewOutRec; } //---------------------------------------------------------------------- @@ -3253,13 +3229,13 @@ void Clipper::JoinCommonEdges() if (m_UsingPolyTree) for (size_t j = 0; j < m_PolyOuts.size() - 1; j++) { - OutRec* oRec = m_PolyOuts[j]; - OutRec* firstLeft = oRec->FirstLeft; + OutRec &oRec = m_PolyOuts[j]; + OutRec* firstLeft = oRec.FirstLeft; while (firstLeft && !firstLeft->Pts) firstLeft = firstLeft->FirstLeft; - if (!oRec->Pts || firstLeft != outRec1 || - oRec->IsHole == outRec1->IsHole) continue; - if (Poly2ContainsPoly1(oRec->Pts, join.OutPt2)) - oRec->FirstLeft = outRec2; + if (!oRec.Pts || firstLeft != outRec1 || + oRec.IsHole == outRec1->IsHole) continue; + if (Poly2ContainsPoly1(oRec.Pts, join.OutPt2)) + oRec.FirstLeft = outRec2; } if (Poly2ContainsPoly1(outRec2->Pts, outRec1->Pts)) @@ -3771,13 +3747,13 @@ void Clipper::DoSimplePolygons() size_t i = 0; while (i < m_PolyOuts.size()) { - OutRec* outrec = m_PolyOuts[i++]; - OutPt* op = outrec->Pts; - if (!op || outrec->IsOpen) continue; + OutRec &outrec = m_PolyOuts[i++]; + OutPt* op = outrec.Pts; + if (!op || outrec.IsOpen) continue; do //for each Pt in Polygon until duplicate found do ... { OutPt* op2 = op->Next; - while (op2 != outrec->Pts) + while (op2 != outrec.Pts) { if ((op->Pt == op2->Pt) && op2->Next != op && op2->Prev != op) { @@ -3789,37 +3765,37 @@ void Clipper::DoSimplePolygons() op2->Prev = op3; op3->Next = op2; - outrec->Pts = op; + outrec.Pts = op; OutRec* outrec2 = CreateOutRec(); outrec2->Pts = op2; UpdateOutPtIdxs(*outrec2); - if (Poly2ContainsPoly1(outrec2->Pts, outrec->Pts)) + if (Poly2ContainsPoly1(outrec2->Pts, outrec.Pts)) { //OutRec2 is contained by OutRec1 ... - outrec2->IsHole = !outrec->IsHole; - outrec2->FirstLeft = outrec; + outrec2->IsHole = !outrec.IsHole; + outrec2->FirstLeft = &outrec; // For each m_PolyOuts, replace FirstLeft from outRec2 to outrec. - if (m_UsingPolyTree) FixupFirstLefts2(outrec2, outrec); + if (m_UsingPolyTree) FixupFirstLefts2(outrec2, &outrec); } else - if (Poly2ContainsPoly1(outrec->Pts, outrec2->Pts)) + if (Poly2ContainsPoly1(outrec.Pts, outrec2->Pts)) { //OutRec1 is contained by OutRec2 ... - outrec2->IsHole = outrec->IsHole; - outrec->IsHole = !outrec2->IsHole; - outrec2->FirstLeft = outrec->FirstLeft; - outrec->FirstLeft = outrec2; + outrec2->IsHole = outrec.IsHole; + outrec.IsHole = !outrec2->IsHole; + outrec2->FirstLeft = outrec.FirstLeft; + outrec.FirstLeft = outrec2; // For each m_PolyOuts, replace FirstLeft from outrec to outrec2. - if (m_UsingPolyTree) FixupFirstLefts2(outrec, outrec2); + if (m_UsingPolyTree) FixupFirstLefts2(&outrec, outrec2); } else { //the 2 polygons are separate ... - outrec2->IsHole = outrec->IsHole; - outrec2->FirstLeft = outrec->FirstLeft; + outrec2->IsHole = outrec.IsHole; + outrec2->FirstLeft = outrec.FirstLeft; // For each polygon of m_PolyOuts, replace FirstLeft from outrec to outrec2 if the polygon is inside outRec2. //FIXME This is potentially very expensive! O(n^3)! - if (m_UsingPolyTree) FixupFirstLefts1(outrec, outrec2); + if (m_UsingPolyTree) FixupFirstLefts1(&outrec, outrec2); } op2 = op; //ie get ready for the Next iteration } @@ -3827,7 +3803,7 @@ void Clipper::DoSimplePolygons() } op = op->Next; } - while (op != outrec->Pts); + while (op != outrec.Pts); } } //------------------------------------------------------------------------------ @@ -3845,10 +3821,10 @@ void ReversePaths(Paths& p) } //------------------------------------------------------------------------------ -Paths SimplifyPolygon(const Path &in_poly, PolyFillType fillType) +Paths SimplifyPolygon(const Path &in_poly, PolyFillType fillType, bool strictly_simple /* = true */) { Clipper c; - c.StrictlySimple(true); + c.StrictlySimple(strictly_simple); c.AddPath(in_poly, ptSubject, true); Paths out; c.Execute(ctUnion, out, fillType, fillType); diff --git a/src/clipper/clipper.hpp b/src/clipper/clipper.hpp index d190d09b5..c88545454 100644 --- a/src/clipper/clipper.hpp +++ b/src/clipper/clipper.hpp @@ -52,6 +52,7 @@ //use_deprecated: Enables temporary support for the obsolete functions //#define use_deprecated +#include #include #include #include @@ -199,7 +200,8 @@ double Area(const Path &poly); inline bool Orientation(const Path &poly) { return Area(poly) >= 0; } int PointInPolygon(const IntPoint &pt, const Path &path); -Paths SimplifyPolygon(const Path &in_poly, PolyFillType fillType = pftEvenOdd); +// Union with "strictly simple" fix enabled. +Paths SimplifyPolygon(const Path &in_poly, PolyFillType fillType = pftNonZero, bool strictly_simple = true); void CleanPolygon(const Path& in_poly, Path& out_poly, double distance = 1.415); void CleanPolygon(Path& poly, double distance = 1.415); @@ -284,7 +286,25 @@ enum EdgeSide { esLeft = 1, esRight = 2}; using OutPts = std::vector>; - struct OutRec; + // Output polygon. + struct OutRec { + int Idx; + bool IsHole; + bool IsOpen; + //The 'FirstLeft' field points to another OutRec that contains or is the + //'parent' of OutRec. It is 'first left' because the ActiveEdgeList (AEL) is + //parsed left from the current edge (owning OutRec) until the owner OutRec + //is found. This field simplifies sorting the polygons into a tree structure + //which reflects the parent/child relationships of all polygons. + //This field should be renamed Parent, and will be later. + OutRec* FirstLeft; + // Used only by void Clipper::BuildResult2(PolyTree& polytree) + PolyNode* PolyNd; + // Linked list of output points, dynamically allocated. + OutPt* Pts; + OutPt* BottomPt; + }; + struct Join { Join(OutPt *OutPt1, OutPt *OutPt2, IntPoint OffPt) : OutPt1(OutPt1), OutPt2(OutPt2), OffPt(OffPt) {} @@ -432,12 +452,12 @@ protected: private: // Output polygons. - std::vector> m_PolyOuts; + std::deque> m_PolyOuts; // Output points, allocated by a continuous sets of m_OutPtsChunkSize. - std::vector> m_OutPts; + static constexpr const size_t m_OutPtsChunkSize = 32; + std::deque, Allocator>> m_OutPts; // List of free output points, to be used before taking a point from m_OutPts or allocating a new chunk. OutPt *m_OutPtsFree; - size_t m_OutPtsChunkSize; size_t m_OutPtsChunkLast; std::vector> m_Joins; @@ -482,7 +502,7 @@ private: void AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &pt); OutPt* AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &pt); OutRec* GetOutRec(int idx); - void AppendPolygon(TEdge *e1, TEdge *e2) const; + void AppendPolygon(TEdge *e1, TEdge *e2); void IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &pt); OutRec* CreateOutRec(); OutPt* AddOutPt(TEdge *e, const IntPoint &pt); @@ -498,7 +518,7 @@ private: void ProcessEdgesAtTopOfScanbeam(const cInt topY); void BuildResult(Paths& polys); void BuildResult2(PolyTree& polytree); - void SetHoleState(TEdge *e, OutRec *outrec) const; + void SetHoleState(TEdge *e, OutRec *outrec); bool FixupIntersectionOrder(); void FixupOutPolygon(OutRec &outrec); void FixupOutPolyline(OutRec &outrec); @@ -508,8 +528,8 @@ private: bool JoinHorz(OutPt* op1, OutPt* op1b, OutPt* op2, OutPt* op2b, const IntPoint &Pt, bool DiscardLeft); void JoinCommonEdges(); void DoSimplePolygons(); - void FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec) const; - void FixupFirstLefts2(OutRec* OldOutRec, OutRec* NewOutRec) const; + void FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec); + void FixupFirstLefts2(OutRec* OldOutRec, OutRec* NewOutRec); #ifdef CLIPPERLIB_USE_XYZ void SetZ(IntPoint& pt, TEdge& e1, TEdge& e2); #endif @@ -567,10 +587,11 @@ class clipperException : public std::exception }; //------------------------------------------------------------------------------ +// Union with "strictly simple" fix enabled. template -inline Paths SimplifyPolygons(PathsProvider &&in_polys, PolyFillType fillType = pftEvenOdd) { +inline Paths SimplifyPolygons(PathsProvider &&in_polys, PolyFillType fillType = pftNonZero, bool strictly_simple = true) { Clipper c; - c.StrictlySimple(true); + c.StrictlySimple(strictly_simple); c.AddPaths(std::forward(in_polys), ptSubject, true); Paths out; c.Execute(ctUnion, out, fillType, fillType); diff --git a/src/libslic3r/Arachne/WallToolPaths.cpp b/src/libslic3r/Arachne/WallToolPaths.cpp index fce69d5e4..6c5dafdac 100644 --- a/src/libslic3r/Arachne/WallToolPaths.cpp +++ b/src/libslic3r/Arachne/WallToolPaths.cpp @@ -232,7 +232,7 @@ std::unique_ptr cre void fixSelfIntersections(const coord_t epsilon, Polygons &thiss) { if (epsilon < 1) { - ClipperLib::SimplifyPolygons(ClipperUtils::PolygonsProvider(thiss)); + ClipperLib::SimplifyPolygons(ClipperUtils::PolygonsProvider(thiss), ClipperLib::pftEvenOdd); return; } @@ -273,7 +273,7 @@ void fixSelfIntersections(const coord_t epsilon, Polygons &thiss) } } - ClipperLib::SimplifyPolygons(ClipperUtils::PolygonsProvider(thiss)); + ClipperLib::SimplifyPolygons(ClipperUtils::PolygonsProvider(thiss), ClipperLib::pftEvenOdd); } /*! diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index 95a533718..d10e14cc5 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -964,21 +964,18 @@ Polygons union_pt_chained_outside_in(const Polygons &subject) return retval; } -Polygons simplify_polygons(const Polygons &subject, bool preserve_collinear) +Polygons simplify_polygons(const Polygons &subject) { CLIPPER_UTILS_TIME_LIMIT_MILLIS(CLIPPER_UTILS_TIME_LIMIT_DEFAULT); ClipperLib::Paths output; - if (preserve_collinear) { - ClipperLib::Clipper c; - c.PreserveCollinear(true); - c.StrictlySimple(true); - c.AddPaths(ClipperUtils::PolygonsProvider(subject), ClipperLib::ptSubject, true); - c.Execute(ClipperLib::ctUnion, output, ClipperLib::pftNonZero, ClipperLib::pftNonZero); - } else { - output = ClipperLib::SimplifyPolygons(ClipperUtils::PolygonsProvider(subject), ClipperLib::pftNonZero); - } - + ClipperLib::Clipper c; +// c.PreserveCollinear(true); + //FIXME StrictlySimple is very expensive! Is it needed? + c.StrictlySimple(true); + c.AddPaths(ClipperUtils::PolygonsProvider(subject), ClipperLib::ptSubject, true); + c.Execute(ClipperLib::ctUnion, output, ClipperLib::pftNonZero, ClipperLib::pftNonZero); + // convert into Slic3r polygons return to_polygons(std::move(output)); } @@ -987,12 +984,10 @@ ExPolygons simplify_polygons_ex(const Polygons &subject, bool preserve_collinear { CLIPPER_UTILS_TIME_LIMIT_MILLIS(CLIPPER_UTILS_TIME_LIMIT_DEFAULT); - if (! preserve_collinear) - return union_ex(simplify_polygons(subject, false)); - ClipperLib::PolyTree polytree; ClipperLib::Clipper c; - c.PreserveCollinear(true); +// c.PreserveCollinear(true); + //FIXME StrictlySimple is very expensive! Is it needed? c.StrictlySimple(true); c.AddPaths(ClipperUtils::PolygonsProvider(subject), ClipperLib::ptSubject, true); c.Execute(ClipperLib::ctUnion, polytree, ClipperLib::pftNonZero, ClipperLib::pftNonZero); diff --git a/src/libslic3r/ClipperUtils.hpp b/src/libslic3r/ClipperUtils.hpp index aaa06107d..ab967fd8f 100644 --- a/src/libslic3r/ClipperUtils.hpp +++ b/src/libslic3r/ClipperUtils.hpp @@ -596,8 +596,8 @@ void traverse_pt(const ClipperLib::PolyNodes &nodes, ExOrJustPolygons *retval) /* OTHER */ -Slic3r::Polygons simplify_polygons(const Slic3r::Polygons &subject, bool preserve_collinear = false); -Slic3r::ExPolygons simplify_polygons_ex(const Slic3r::Polygons &subject, bool preserve_collinear = false); +Slic3r::Polygons simplify_polygons(const Slic3r::Polygons &subject); +Slic3r::ExPolygons simplify_polygons_ex(const Slic3r::Polygons &subject); Polygons top_level_islands(const Slic3r::Polygons &polygons); From fd437dcaf5f392dffb15eadcd359a4de92d0000b Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 2 May 2023 15:48:24 +0200 Subject: [PATCH 20/34] Little refactoring of douglas_peucker() --- src/libslic3r/ExPolygon.cpp | 4 ++-- src/libslic3r/Geometry.cpp | 16 ++++++------- src/libslic3r/MultiPoint.cpp | 2 +- src/libslic3r/MultiPoint.hpp | 2 +- src/libslic3r/Polygon.cpp | 45 +++++++++++++++++++++++++----------- src/libslic3r/Polygon.hpp | 3 ++- src/libslic3r/Polyline.cpp | 2 +- 7 files changed, 46 insertions(+), 28 deletions(-) diff --git a/src/libslic3r/ExPolygon.cpp b/src/libslic3r/ExPolygon.cpp index 42f026e0b..19489bddb 100644 --- a/src/libslic3r/ExPolygon.cpp +++ b/src/libslic3r/ExPolygon.cpp @@ -184,14 +184,14 @@ Polygons ExPolygon::simplify_p(double tolerance) const { Polygon p = this->contour; p.points.push_back(p.points.front()); - p.points = MultiPoint::_douglas_peucker(p.points, tolerance); + p.points = MultiPoint::douglas_peucker(p.points, tolerance); p.points.pop_back(); pp.emplace_back(std::move(p)); } // holes for (Polygon p : this->holes) { p.points.push_back(p.points.front()); - p.points = MultiPoint::_douglas_peucker(p.points, tolerance); + p.points = MultiPoint::douglas_peucker(p.points, tolerance); p.points.pop_back(); pp.emplace_back(std::move(p)); } diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index 5542d73ee..f2860ea8e 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -52,15 +52,15 @@ template bool contains(const ExPolygons &vector, const Point &point); void simplify_polygons(const Polygons &polygons, double tolerance, Polygons* retval) { - Polygons pp; - for (Polygons::const_iterator it = polygons.begin(); it != polygons.end(); ++it) { - Polygon p = *it; - p.points.push_back(p.points.front()); - p.points = MultiPoint::_douglas_peucker(p.points, tolerance); - p.points.pop_back(); - pp.push_back(p); + Polygons simplified_raw; + for (const Polygon &source_polygon : polygons) { + Points simplified = MultiPoint::douglas_peucker(to_polyline(source_polygon).points, tolerance); + if (simplified.size() > 3) { + simplified.pop_back(); + simplified_raw.push_back(Polygon{ std::move(simplified) }); + } } - *retval = Slic3r::simplify_polygons(pp); + *retval = Slic3r::simplify_polygons(simplified_raw); } double linint(double value, double oldmin, double oldmax, double newmin, double newmax) diff --git a/src/libslic3r/MultiPoint.cpp b/src/libslic3r/MultiPoint.cpp index bb4d62cc0..ce54a54c0 100644 --- a/src/libslic3r/MultiPoint.cpp +++ b/src/libslic3r/MultiPoint.cpp @@ -103,7 +103,7 @@ bool MultiPoint::remove_duplicate_points() return false; } -Points MultiPoint::_douglas_peucker(const Points &pts, const double tolerance) +Points MultiPoint::douglas_peucker(const Points &pts, const double tolerance) { Points result_pts; double tolerance_sq = tolerance * tolerance; diff --git a/src/libslic3r/MultiPoint.hpp b/src/libslic3r/MultiPoint.hpp index 4cf4b5e14..62b53255b 100644 --- a/src/libslic3r/MultiPoint.hpp +++ b/src/libslic3r/MultiPoint.hpp @@ -81,7 +81,7 @@ public: } } - static Points _douglas_peucker(const Points &points, const double tolerance); + static Points douglas_peucker(const Points &points, const double tolerance); static Points visivalingam(const Points& pts, const double& tolerance); inline auto begin() { return points.begin(); } diff --git a/src/libslic3r/Polygon.cpp b/src/libslic3r/Polygon.cpp index 299e22adc..88ac1b03f 100644 --- a/src/libslic3r/Polygon.cpp +++ b/src/libslic3r/Polygon.cpp @@ -96,7 +96,7 @@ bool Polygon::make_clockwise() void Polygon::douglas_peucker(double tolerance) { this->points.push_back(this->points.front()); - Points p = MultiPoint::_douglas_peucker(this->points, tolerance); + Points p = MultiPoint::douglas_peucker(this->points, tolerance); p.pop_back(); this->points = std::move(p); } @@ -110,7 +110,7 @@ Polygons Polygon::simplify(double tolerance) const // on the whole polygon Points points = this->points; points.push_back(points.front()); - Polygon p(MultiPoint::_douglas_peucker(points, tolerance)); + Polygon p(MultiPoint::douglas_peucker(points, tolerance)); p.points.pop_back(); Polygons pp; @@ -577,23 +577,40 @@ void remove_collinear(Polygons &polys) remove_collinear(poly); } -Polygons polygons_simplify(const Polygons &source_polygons, double tolerance) +static inline void simplify_polygon_impl(const Points &points, double tolerance, bool strictly_simple, Polygons &out) +{ + Points simplified = MultiPoint::douglas_peucker(points, tolerance); + // then remove the last (repeated) point. + simplified.pop_back(); + // Simplify the decimated contour by ClipperLib. + bool ccw = ClipperLib::Area(simplified) > 0.; + for (Points& path : ClipperLib::SimplifyPolygons(ClipperUtils::SinglePathProvider(simplified), ClipperLib::pftNonZero, strictly_simple)) { + if (!ccw) + // ClipperLib likely reoriented negative area contours to become positive. Reverse holes back to CW. + std::reverse(path.begin(), path.end()); + out.emplace_back(std::move(path)); + } +} + +Polygons polygons_simplify(Polygons &&source_polygons, double tolerance, bool strictly_simple /* = true */) +{ + Polygons out; + out.reserve(source_polygons.size()); + for (Polygon &source_polygon : source_polygons) { + // Run Douglas / Peucker simplification algorithm on an open polyline (by repeating the first point at the end of the polyline), + source_polygon.points.emplace_back(source_polygon.points.front()); + simplify_polygon_impl(source_polygon.points, tolerance, strictly_simple, out); + } + return out; +} + +Polygons polygons_simplify(const Polygons &source_polygons, double tolerance, bool strictly_simple /* = true */) { Polygons out; out.reserve(source_polygons.size()); for (const Polygon &source_polygon : source_polygons) { // Run Douglas / Peucker simplification algorithm on an open polyline (by repeating the first point at the end of the polyline), - Points simplified = MultiPoint::_douglas_peucker(to_polyline(source_polygon).points, tolerance); - // then remove the last (repeated) point. - simplified.pop_back(); - // Simplify the decimated contour by ClipperLib. - bool ccw = ClipperLib::Area(simplified) > 0.; - for (Points &path : ClipperLib::SimplifyPolygons(ClipperUtils::SinglePathProvider(simplified), ClipperLib::pftNonZero)) { - if (! ccw) - // ClipperLib likely reoriented negative area contours to become positive. Reverse holes back to CW. - std::reverse(path.begin(), path.end()); - out.emplace_back(std::move(path)); - } + simplify_polygon_impl(to_polyline(source_polygon).points, tolerance, strictly_simple, out); } return out; } diff --git a/src/libslic3r/Polygon.hpp b/src/libslic3r/Polygon.hpp index bf4a087b0..e0c3958fd 100644 --- a/src/libslic3r/Polygon.hpp +++ b/src/libslic3r/Polygon.hpp @@ -149,7 +149,8 @@ inline void polygons_append(Polygons &dst, Polygons &&src) } } -Polygons polygons_simplify(const Polygons &polys, double tolerance); +Polygons polygons_simplify(Polygons &&polys, double tolerance, bool strictly_simple = true); +Polygons polygons_simplify(const Polygons &polys, double tolerance, bool strictly_simple = true); inline void polygons_rotate(Polygons &polys, double angle) { diff --git a/src/libslic3r/Polyline.cpp b/src/libslic3r/Polyline.cpp index 5743e38bd..524736575 100644 --- a/src/libslic3r/Polyline.cpp +++ b/src/libslic3r/Polyline.cpp @@ -110,7 +110,7 @@ Points Polyline::equally_spaced_points(double distance) const void Polyline::simplify(double tolerance) { - this->points = MultiPoint::_douglas_peucker(this->points, tolerance); + this->points = MultiPoint::douglas_peucker(this->points, tolerance); } #if 0 From c3178321b46008512a9f38259e49827113c4795e Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 2 May 2023 15:53:08 +0200 Subject: [PATCH 21/34] Optimization of triangle mesh slicing: scalable_allocator and hashing of shared mutexes. --- src/libslic3r/TriangleMeshSlicer.cpp | 42 ++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/src/libslic3r/TriangleMeshSlicer.cpp b/src/libslic3r/TriangleMeshSlicer.cpp index 460cd901e..0261b6121 100644 --- a/src/libslic3r/TriangleMeshSlicer.cpp +++ b/src/libslic3r/TriangleMeshSlicer.cpp @@ -15,6 +15,9 @@ #include #include +#include + +#include #ifndef NDEBUG // #define EXPENSIVE_DEBUG_CHECKS @@ -139,7 +142,7 @@ public: #endif }; -using IntersectionLines = std::vector; +using IntersectionLines = std::vector>; enum class FacetSliceType { NoSlice = 0, @@ -351,6 +354,21 @@ inline FacetSliceType slice_facet( return FacetSliceType::NoSlice; } +class LinesMutexes { +public: + std::mutex& operator()(size_t slice_id) { + ankerl::unordered_dense::hash hash; + return m_mutexes[hash(slice_id) % m_mutexes.size()].mutex; + } + +private: + struct CacheLineAlignedMutex + { + alignas(std::hardware_destructive_interference_size) std::mutex mutex; + }; + std::array m_mutexes; +}; + template void slice_facet_at_zs( // Scaled or unscaled vertices. transform_vertex_fn may scale zs. @@ -361,7 +379,7 @@ void slice_facet_at_zs( // Scaled or unscaled zs. If vertices have their zs scaled or transform_vertex_fn scales them, then zs have to be scaled as well. const std::vector &zs, std::vector &lines, - std::array &lines_mutex) + LinesMutexes &lines_mutex) { stl_vertex vertices[3] { transform_vertex_fn(mesh_vertices[indices(0)]), transform_vertex_fn(mesh_vertices[indices(1)]), transform_vertex_fn(mesh_vertices[indices(2)]) }; @@ -380,7 +398,7 @@ void slice_facet_at_zs( if (min_z != max_z && slice_facet(*it, vertices, indices, edge_ids, idx_vertex_lowest, false, il) == FacetSliceType::Slicing) { assert(il.edge_type != IntersectionLine::FacetEdgeType::Horizontal); size_t slice_id = it - zs.begin(); - boost::lock_guard l(lines_mutex[slice_id % lines_mutex.size()]); + boost::lock_guard l(lines_mutex(slice_id)); lines[slice_id].emplace_back(il); } } @@ -395,8 +413,8 @@ static inline std::vector slice_make_lines( const std::vector &zs, const ThrowOnCancel throw_on_cancel_fn) { - std::vector lines(zs.size(), IntersectionLines()); - std::array lines_mutex; + std::vector lines(zs.size(), IntersectionLines{}); + LinesMutexes lines_mutex; tbb::parallel_for( tbb::blocked_range(0, int(indices.size())), [&vertices, &transform_vertex_fn, &indices, &face_edge_ids, &zs, &lines, &lines_mutex, throw_on_cancel_fn](const tbb::blocked_range &range) { @@ -475,7 +493,7 @@ void slice_facet_with_slabs( const int num_edges, const std::vector &zs, SlabLines &lines, - std::array &lines_mutex) + LinesMutexes &lines_mutex) { const stl_triangle_vertex_indices &indices = mesh_triangles[facet_idx]; stl_vertex vertices[3] { mesh_vertices[indices(0)], mesh_vertices[indices(1)], mesh_vertices[indices(2)] }; @@ -494,7 +512,7 @@ void slice_facet_with_slabs( auto emit_slab_edge = [&lines, &lines_mutex](IntersectionLine il, size_t slab_id, bool reverse) { if (reverse) il.reverse(); - boost::lock_guard l(lines_mutex[(slab_id + lines_mutex.size() / 2) % lines_mutex.size()]); + boost::lock_guard l(lines_mutex(slab_id)); lines.between_slices[slab_id].emplace_back(il); }; @@ -530,7 +548,7 @@ void slice_facet_with_slabs( }; // Don't flip the FacetEdgeType::Top edge, it will be flipped when chaining. // if (! ProjectionFromTop) il.reverse(); - boost::lock_guard l(lines_mutex[line_id % lines_mutex.size()]); + boost::lock_guard l(lines_mutex(line_id)); lines.at_slice[line_id].emplace_back(il); } } else { @@ -649,7 +667,7 @@ void slice_facet_with_slabs( if (! ProjectionFromTop) il.reverse(); size_t line_id = it - zs.begin(); - boost::lock_guard l(lines_mutex[line_id % lines_mutex.size()]); + boost::lock_guard l(lines_mutex(line_id)); lines.at_slice[line_id].emplace_back(il); } } @@ -804,8 +822,8 @@ inline std::pair slice_slabs_make_lines( std::pair out; SlabLines &lines_top = out.first; SlabLines &lines_bottom = out.second; - std::array lines_mutex_top; - std::array lines_mutex_bottom; + LinesMutexes lines_mutex_top; + LinesMutexes lines_mutex_bottom; if (top) { lines_top.at_slice.assign(zs.size(), IntersectionLines()); @@ -1540,7 +1558,7 @@ static std::vector make_slab_loops( } // Used to cut the mesh into two halves. -static ExPolygons make_expolygons_simple(std::vector &lines) +static ExPolygons make_expolygons_simple(IntersectionLines &lines) { ExPolygons slices; Polygons holes; From 00ea0847b81d3229135444ba9f76fd2f5cbf5e7a Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 4 May 2023 17:25:26 +0200 Subject: [PATCH 22/34] Fix of merging bridging regions: Fixed building a DAG of overlapping regions in expand_bridges_detect_orientations() --- src/libslic3r/LayerRegion.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index d722f1e9c..ab52dd962 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -230,10 +230,13 @@ Surfaces expand_bridges_detect_orientations( bboxes[it - it_begin].overlap(bboxes[it2 - it_begin]) && // One may ignore holes, they are irrelevant for intersection test. ! intersection(it->expolygon.contour, it2->expolygon.contour).empty()) { - // The two bridge regions intersect. Give them the same group id. + // The two bridge regions intersect. Give them the same (lower) group id. uint32_t id = group_id(it->src_id); uint32_t id2 = group_id(it2->src_id); - bridges[it->src_id].group_id = bridges[it2->src_id].group_id = std::min(id, id2); + if (id < id2) + bridges[id2].group_id = id; + else + bridges[id].group_id = id2; } } } From 1bbe0c5be3bcea11b0f26a3fc2b11037e15a5fe0 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Thu, 4 May 2023 17:39:31 +0200 Subject: [PATCH 23/34] fix missing include for linux builds --- src/libslic3r/TreeModelVolumes.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libslic3r/TreeModelVolumes.cpp b/src/libslic3r/TreeModelVolumes.cpp index 10840d1b5..96f61a07d 100644 --- a/src/libslic3r/TreeModelVolumes.cpp +++ b/src/libslic3r/TreeModelVolumes.cpp @@ -17,6 +17,7 @@ #include "Print.hpp" #include "PrintConfig.hpp" #include "Utils.hpp" +#include "format.hpp" #include From cc938e754949bed278daac5e07c2639a9f54e86e Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Fri, 5 May 2023 10:21:15 +0200 Subject: [PATCH 24/34] WIP SupportGeneratorLayerStorage refactoring. --- src/libslic3r/CMakeLists.txt | 6 +- src/libslic3r/Support/SupportLayer.hpp | 130 +++++++++++++++ src/libslic3r/Support/SupportParameters.hpp | 66 ++++++++ src/libslic3r/SupportMaterial.hpp | 165 +------------------- src/libslic3r/TreeSupport.hpp | 4 +- src/libslic3r/pchheader.hpp | 1 + 6 files changed, 205 insertions(+), 167 deletions(-) create mode 100644 src/libslic3r/Support/SupportLayer.hpp create mode 100644 src/libslic3r/Support/SupportParameters.hpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 53074d3bd..32d44cb5c 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -276,10 +276,12 @@ set(SLIC3R_SOURCES SlicingAdaptive.hpp Subdivide.cpp Subdivide.hpp - SupportSpotsGenerator.cpp - SupportSpotsGenerator.hpp + Support/SupportLayer.hpp + Support/SupportParameters.hpp SupportMaterial.cpp SupportMaterial.hpp + SupportSpotsGenerator.cpp + SupportSpotsGenerator.hpp Surface.cpp Surface.hpp SurfaceCollection.cpp diff --git a/src/libslic3r/Support/SupportLayer.hpp b/src/libslic3r/Support/SupportLayer.hpp new file mode 100644 index 000000000..913e28136 --- /dev/null +++ b/src/libslic3r/Support/SupportLayer.hpp @@ -0,0 +1,130 @@ +#ifndef slic3r_SupportLayer_hpp_ +#define slic3r_SupportLayer_hpp_ + +#include +#include + +namespace Slic3r { + +// Support layer type to be used by SupportGeneratorLayer. This type carries a much more detailed information +// about the support layer type than the final support layers stored in a PrintObject. +enum class SupporLayerType { + Unknown = 0, + // Ratft base layer, to be printed with the support material. + RaftBase, + // Raft interface layer, to be printed with the support interface material. + RaftInterface, + // Bottom contact layer placed over a top surface of an object. To be printed with a support interface material. + BottomContact, + // Dense interface layer, to be printed with the support interface material. + // This layer is separated from an object by an BottomContact layer. + BottomInterface, + // Sparse base support layer, to be printed with a support material. + Base, + // Dense interface layer, to be printed with the support interface material. + // This layer is separated from an object with TopContact layer. + TopInterface, + // Top contact layer directly supporting an overhang. To be printed with a support interface material. + TopContact, + // Some undecided type yet. It will turn into Base first, then it may turn into BottomInterface or TopInterface. + Intermediate, +}; + +// A support layer type used internally by the SupportMaterial class. This class carries a much more detailed +// information about the support layer than the layers stored in the PrintObject, mainly +// the SupportGeneratorLayer is aware of the bridging flow and the interface gaps between the object and the support. +class SupportGeneratorLayer +{ +public: + void reset() { + *this = SupportGeneratorLayer(); + } + + bool operator==(const SupportGeneratorLayer &layer2) const { + return print_z == layer2.print_z && height == layer2.height && bridging == layer2.bridging; + } + + // Order the layers by lexicographically by an increasing print_z and a decreasing layer height. + bool operator<(const SupportGeneratorLayer &layer2) const { + if (print_z < layer2.print_z) { + return true; + } else if (print_z == layer2.print_z) { + if (height > layer2.height) + return true; + else if (height == layer2.height) { + // Bridging layers first. + return bridging && ! layer2.bridging; + } else + return false; + } else + return false; + } + + void merge(SupportGeneratorLayer &&rhs) { + // The union_() does not support move semantic yet, but maybe one day it will. + this->polygons = union_(this->polygons, std::move(rhs.polygons)); + auto merge = [](std::unique_ptr &dst, std::unique_ptr &src) { + if (! dst || dst->empty()) + dst = std::move(src); + else if (src && ! src->empty()) + *dst = union_(*dst, std::move(*src)); + }; + merge(this->contact_polygons, rhs.contact_polygons); + merge(this->overhang_polygons, rhs.overhang_polygons); + merge(this->enforcer_polygons, rhs.enforcer_polygons); + rhs.reset(); + } + + // For the bridging flow, bottom_print_z will be above bottom_z to account for the vertical separation. + // For the non-bridging flow, bottom_print_z will be equal to bottom_z. + coordf_t bottom_print_z() const { return print_z - height; } + + // To sort the extremes of top / bottom interface layers. + coordf_t extreme_z() const { return (this->layer_type == SupporLayerType::TopContact) ? this->bottom_z : this->print_z; } + + SupporLayerType layer_type { SupporLayerType::Unknown }; + // Z used for printing, in unscaled coordinates. + coordf_t print_z { 0 }; + // Bottom Z of this layer. For soluble layers, bottom_z + height = print_z, + // otherwise bottom_z + gap + height = print_z. + coordf_t bottom_z { 0 }; + // Layer height in unscaled coordinates. + coordf_t height { 0 }; + // Index of a PrintObject layer_id supported by this layer. This will be set for top contact layers. + // If this is not a contact layer, it will be set to size_t(-1). + size_t idx_object_layer_above { size_t(-1) }; + // Index of a PrintObject layer_id, which supports this layer. This will be set for bottom contact layers. + // If this is not a contact layer, it will be set to size_t(-1). + size_t idx_object_layer_below { size_t(-1) }; + // Use a bridging flow when printing this support layer. + bool bridging { false }; + + // Polygons to be filled by the support pattern. + Polygons polygons; + // Currently for the contact layers only. + std::unique_ptr contact_polygons; + std::unique_ptr overhang_polygons; + // Enforcers need to be propagated independently in case the "support on build plate only" option is enabled. + std::unique_ptr enforcer_polygons; +}; + +// Layers are allocated and owned by a deque. Once a layer is allocated, it is maintained +// up to the end of a generate() method. The layer storage may be replaced by an allocator class in the future, +// which would allocate layers by multiple chunks. +#if 0 +class SupportGeneratorLayerStorage { +public: +private: + template + using Allocator = tbb::scalable_allocator; + Slic3r::deque> m_data; + tbb::spin_mutex m_mutex; +}; +#else +#endif +using SupportGeneratorLayerStorage = std::deque; +using SupportGeneratorLayersPtr = std::vector; + +} // namespace Slic3r + +#endif /* slic3r_SupportLayer_hpp_ */ diff --git a/src/libslic3r/Support/SupportParameters.hpp b/src/libslic3r/Support/SupportParameters.hpp new file mode 100644 index 000000000..fd4f1f8b7 --- /dev/null +++ b/src/libslic3r/Support/SupportParameters.hpp @@ -0,0 +1,66 @@ +#ifndef slic3r_SupportParameters_hpp_ +#define slic3r_SupportParameters_hpp_ + +#include "../libslic3r.h" +#include "../Flow.hpp" + +namespace Slic3r { + +class PrintObject; +enum InfillPattern : int; + +struct SupportParameters { + SupportParameters(const PrintObject &object); + + // Flow at the 1st print layer. + Flow first_layer_flow; + // Flow at the support base (neither top, nor bottom interface). + // Also flow at the raft base with the exception of raft interface and contact layers. + Flow support_material_flow; + // Flow at the top interface and contact layers. + Flow support_material_interface_flow; + // Flow at the bottom interfaces and contacts. + Flow support_material_bottom_interface_flow; + // Flow at raft inteface & contact layers. + Flow raft_interface_flow; + // Is merging of regions allowed? Could the interface & base support regions be printed with the same extruder? + bool can_merge_support_regions; + + coordf_t support_layer_height_min; +// coordf_t support_layer_height_max; + + coordf_t gap_xy; + + float base_angle; + float interface_angle; + + // Density of the top / bottom interface and contact layers. + coordf_t interface_density; + // Density of the raft interface and contact layers. + coordf_t raft_interface_density; + // Density of the base support layers. + coordf_t support_density; + + // Pattern of the sparse infill including sparse raft layers. + InfillPattern base_fill_pattern; + // Pattern of the top / bottom interface and contact layers. + InfillPattern interface_fill_pattern; + // Pattern of the raft interface and contact layers. + InfillPattern raft_interface_fill_pattern; + // Pattern of the contact layers. + InfillPattern contact_fill_pattern; + // Shall the sparse (base) layers be printed with a single perimeter line (sheath) for robustness? + bool with_sheath; + + float raft_angle_1st_layer; + float raft_angle_base; + float raft_angle_interface; + + // Produce a raft interface angle for a given SupportLayer::interface_id() + float raft_interface_angle(size_t interface_id) const + { return this->raft_angle_interface + ((interface_id & 1) ? float(- M_PI / 4.) : float(+ M_PI / 4.)); } +}; + +} // namespace Slic3r + +#endif /* slic3r_SupportParameters_hpp_ */ diff --git a/src/libslic3r/SupportMaterial.hpp b/src/libslic3r/SupportMaterial.hpp index 2bd321144..24dc8507e 100644 --- a/src/libslic3r/SupportMaterial.hpp +++ b/src/libslic3r/SupportMaterial.hpp @@ -5,171 +5,12 @@ #include "PrintConfig.hpp" #include "Slicing.hpp" +#include "Support/SupportLayer.hpp" +#include "Support/SupportParameters.hpp" + namespace Slic3r { class PrintObject; -class PrintConfig; -class PrintObjectConfig; - -// Support layer type to be used by SupportGeneratorLayer. This type carries a much more detailed information -// about the support layer type than the final support layers stored in a PrintObject. -enum class SupporLayerType { - Unknown = 0, - // Ratft base layer, to be printed with the support material. - RaftBase, - // Raft interface layer, to be printed with the support interface material. - RaftInterface, - // Bottom contact layer placed over a top surface of an object. To be printed with a support interface material. - BottomContact, - // Dense interface layer, to be printed with the support interface material. - // This layer is separated from an object by an BottomContact layer. - BottomInterface, - // Sparse base support layer, to be printed with a support material. - Base, - // Dense interface layer, to be printed with the support interface material. - // This layer is separated from an object with TopContact layer. - TopInterface, - // Top contact layer directly supporting an overhang. To be printed with a support interface material. - TopContact, - // Some undecided type yet. It will turn into Base first, then it may turn into BottomInterface or TopInterface. - Intermediate, -}; - -// A support layer type used internally by the SupportMaterial class. This class carries a much more detailed -// information about the support layer than the layers stored in the PrintObject, mainly -// the SupportGeneratorLayer is aware of the bridging flow and the interface gaps between the object and the support. -class SupportGeneratorLayer -{ -public: - void reset() { - *this = SupportGeneratorLayer(); - } - - bool operator==(const SupportGeneratorLayer &layer2) const { - return print_z == layer2.print_z && height == layer2.height && bridging == layer2.bridging; - } - - // Order the layers by lexicographically by an increasing print_z and a decreasing layer height. - bool operator<(const SupportGeneratorLayer &layer2) const { - if (print_z < layer2.print_z) { - return true; - } else if (print_z == layer2.print_z) { - if (height > layer2.height) - return true; - else if (height == layer2.height) { - // Bridging layers first. - return bridging && ! layer2.bridging; - } else - return false; - } else - return false; - } - - void merge(SupportGeneratorLayer &&rhs) { - // The union_() does not support move semantic yet, but maybe one day it will. - this->polygons = union_(this->polygons, std::move(rhs.polygons)); - auto merge = [](std::unique_ptr &dst, std::unique_ptr &src) { - if (! dst || dst->empty()) - dst = std::move(src); - else if (src && ! src->empty()) - *dst = union_(*dst, std::move(*src)); - }; - merge(this->contact_polygons, rhs.contact_polygons); - merge(this->overhang_polygons, rhs.overhang_polygons); - merge(this->enforcer_polygons, rhs.enforcer_polygons); - rhs.reset(); - } - - // For the bridging flow, bottom_print_z will be above bottom_z to account for the vertical separation. - // For the non-bridging flow, bottom_print_z will be equal to bottom_z. - coordf_t bottom_print_z() const { return print_z - height; } - - // To sort the extremes of top / bottom interface layers. - coordf_t extreme_z() const { return (this->layer_type == SupporLayerType::TopContact) ? this->bottom_z : this->print_z; } - - SupporLayerType layer_type { SupporLayerType::Unknown }; - // Z used for printing, in unscaled coordinates. - coordf_t print_z { 0 }; - // Bottom Z of this layer. For soluble layers, bottom_z + height = print_z, - // otherwise bottom_z + gap + height = print_z. - coordf_t bottom_z { 0 }; - // Layer height in unscaled coordinates. - coordf_t height { 0 }; - // Index of a PrintObject layer_id supported by this layer. This will be set for top contact layers. - // If this is not a contact layer, it will be set to size_t(-1). - size_t idx_object_layer_above { size_t(-1) }; - // Index of a PrintObject layer_id, which supports this layer. This will be set for bottom contact layers. - // If this is not a contact layer, it will be set to size_t(-1). - size_t idx_object_layer_below { size_t(-1) }; - // Use a bridging flow when printing this support layer. - bool bridging { false }; - - // Polygons to be filled by the support pattern. - Polygons polygons; - // Currently for the contact layers only. - std::unique_ptr contact_polygons; - std::unique_ptr overhang_polygons; - // Enforcers need to be propagated independently in case the "support on build plate only" option is enabled. - std::unique_ptr enforcer_polygons; -}; - -// Layers are allocated and owned by a deque. Once a layer is allocated, it is maintained -// up to the end of a generate() method. The layer storage may be replaced by an allocator class in the future, -// which would allocate layers by multiple chunks. -using SupportGeneratorLayerStorage = std::deque; -using SupportGeneratorLayersPtr = std::vector; - -struct SupportParameters { - SupportParameters(const PrintObject &object); - - // Flow at the 1st print layer. - Flow first_layer_flow; - // Flow at the support base (neither top, nor bottom interface). - // Also flow at the raft base with the exception of raft interface and contact layers. - Flow support_material_flow; - // Flow at the top interface and contact layers. - Flow support_material_interface_flow; - // Flow at the bottom interfaces and contacts. - Flow support_material_bottom_interface_flow; - // Flow at raft inteface & contact layers. - Flow raft_interface_flow; - // Is merging of regions allowed? Could the interface & base support regions be printed with the same extruder? - bool can_merge_support_regions; - - coordf_t support_layer_height_min; -// coordf_t support_layer_height_max; - - coordf_t gap_xy; - - float base_angle; - float interface_angle; - - // Density of the top / bottom interface and contact layers. - coordf_t interface_density; - // Density of the raft interface and contact layers. - coordf_t raft_interface_density; - // Density of the base support layers. - coordf_t support_density; - - // Pattern of the sparse infill including sparse raft layers. - InfillPattern base_fill_pattern; - // Pattern of the top / bottom interface and contact layers. - InfillPattern interface_fill_pattern; - // Pattern of the raft interface and contact layers. - InfillPattern raft_interface_fill_pattern; - // Pattern of the contact layers. - InfillPattern contact_fill_pattern; - // Shall the sparse (base) layers be printed with a single perimeter line (sheath) for robustness? - bool with_sheath; - - float raft_angle_1st_layer; - float raft_angle_base; - float raft_angle_interface; - - // Produce a raft interface angle for a given SupportLayer::interface_id() - float raft_interface_angle(size_t interface_id) const - { return this->raft_angle_interface + ((interface_id & 1) ? float(- M_PI / 4.) : float(+ M_PI / 4.)); } -}; // Remove bridges from support contact areas. // To be called if PrintObjectConfig::dont_support_bridges. diff --git a/src/libslic3r/TreeSupport.hpp b/src/libslic3r/TreeSupport.hpp index 2c87a132d..2ed5d50ec 100644 --- a/src/libslic3r/TreeSupport.hpp +++ b/src/libslic3r/TreeSupport.hpp @@ -11,6 +11,7 @@ #include "TreeModelVolumes.hpp" #include "Point.hpp" +#include "Support/SupportLayer.hpp" #include @@ -39,10 +40,7 @@ namespace Slic3r // Forward declarations class Print; class PrintObject; -class SupportGeneratorLayer; struct SlicingParameters; -using SupportGeneratorLayerStorage = std::deque; -using SupportGeneratorLayersPtr = std::vector; namespace FFFTreeSupport { diff --git a/src/libslic3r/pchheader.hpp b/src/libslic3r/pchheader.hpp index 9017a5dea..e71b1461c 100644 --- a/src/libslic3r/pchheader.hpp +++ b/src/libslic3r/pchheader.hpp @@ -107,6 +107,7 @@ #include #include +#include #include #include From a236351fd3043e6a8a007830aaabc423387b1c6c Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Fri, 5 May 2023 12:59:01 +0200 Subject: [PATCH 25/34] Supports refactoring: Split FFF supports into multiple files, enclosed into namespaces. --- src/libslic3r/CMakeLists.txt | 5 + src/libslic3r/Support/SupportCommon.cpp | 1807 +++++++++++++++++ src/libslic3r/Support/SupportCommon.hpp | 138 ++ src/libslic3r/Support/SupportDebug.cpp | 108 + src/libslic3r/Support/SupportDebug.hpp | 18 + src/libslic3r/Support/SupportLayer.hpp | 26 +- src/libslic3r/Support/SupportParameters.cpp | 116 ++ src/libslic3r/Support/SupportParameters.hpp | 4 + src/libslic3r/SupportMaterial.cpp | 2013 +------------------ src/libslic3r/SupportMaterial.hpp | 52 +- src/libslic3r/TreeSupport.cpp | 44 +- 11 files changed, 2259 insertions(+), 2072 deletions(-) create mode 100644 src/libslic3r/Support/SupportCommon.cpp create mode 100644 src/libslic3r/Support/SupportCommon.hpp create mode 100644 src/libslic3r/Support/SupportDebug.cpp create mode 100644 src/libslic3r/Support/SupportDebug.hpp create mode 100644 src/libslic3r/Support/SupportParameters.cpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 32d44cb5c..11f7adf76 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -276,7 +276,12 @@ set(SLIC3R_SOURCES SlicingAdaptive.hpp Subdivide.cpp Subdivide.hpp + Support/SupportCommon.cpp + Support/SupportCommon.hpp + Support/SupportDebug.cpp + Support/SupportDebug.hpp Support/SupportLayer.hpp + Support/SupportParameters.cpp Support/SupportParameters.hpp SupportMaterial.cpp SupportMaterial.hpp diff --git a/src/libslic3r/Support/SupportCommon.cpp b/src/libslic3r/Support/SupportCommon.cpp new file mode 100644 index 000000000..8825d8dd2 --- /dev/null +++ b/src/libslic3r/Support/SupportCommon.cpp @@ -0,0 +1,1807 @@ +#include "../ClipperUtils.hpp" +#include "../ExtrusionEntityCollection.hpp" +#include "../Layer.hpp" +#include "../Print.hpp" +#include "../Fill/FillBase.hpp" +#include "../Geometry.hpp" +#include "../Point.hpp" + +#include +#include + +#include + +#include "SupportCommon.hpp" +#include "SupportLayer.hpp" +#include "SupportParameters.hpp" + +// #define SLIC3R_DEBUG + +// Make assert active if SLIC3R_DEBUG +#ifdef SLIC3R_DEBUG + #define DEBUG + #define _DEBUG + #undef NDEBUG + #include "utils.hpp" + #include "SVG.hpp" +#endif + +#include + +namespace Slic3r::FFFSupport { + +// how much we extend support around the actual contact area +//FIXME this should be dependent on the nozzle diameter! +#define SUPPORT_MATERIAL_MARGIN 1.5 + +//#define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 3. +//#define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 1.5 +#define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtSquare, 0. + +void remove_bridges_from_contacts( + const PrintConfig &print_config, + const Layer &lower_layer, + const LayerRegion &layerm, + float fw, + Polygons &contact_polygons) +{ + // compute the area of bridging perimeters + Polygons bridges; + { + // Surface supporting this layer, expanded by 0.5 * nozzle_diameter, as we consider this kind of overhang to be sufficiently supported. + Polygons lower_grown_slices = expand(lower_layer.lslices, + //FIXME to mimic the decision in the perimeter generator, we should use half the external perimeter width. + 0.5f * float(scale_(print_config.nozzle_diameter.get_at(layerm.region().config().perimeter_extruder-1))), + SUPPORT_SURFACES_OFFSET_PARAMETERS); + // Collect perimeters of this layer. + //FIXME split_at_first_point() could split a bridge mid-way + #if 0 + Polylines overhang_perimeters = layerm.perimeters.as_polylines(); + // workaround for Clipper bug, see Slic3r::Polygon::clip_as_polyline() + for (Polyline &polyline : overhang_perimeters) + polyline.points[0].x += 1; + // Trim the perimeters of this layer by the lower layer to get the unsupported pieces of perimeters. + overhang_perimeters = diff_pl(overhang_perimeters, lower_grown_slices); + #else + Polylines overhang_perimeters = diff_pl(layerm.perimeters().as_polylines(), lower_grown_slices); + #endif + + // only consider straight overhangs + // only consider overhangs having endpoints inside layer's slices + // convert bridging polylines into polygons by inflating them with their thickness + // since we're dealing with bridges, we can't assume width is larger than spacing, + // so we take the largest value and also apply safety offset to be ensure no gaps + // are left in between + Flow perimeter_bridge_flow = layerm.bridging_flow(frPerimeter); + //FIXME one may want to use a maximum of bridging flow width and normal flow width, as the perimeters are calculated using the normal flow + // and then turned to bridging flow, thus their centerlines are derived from non-bridging flow and expanding them by a bridging flow + // may not expand them to the edge of their respective islands. + const float w = float(0.5 * std::max(perimeter_bridge_flow.scaled_width(), perimeter_bridge_flow.scaled_spacing())) + scaled(0.001); + for (Polyline &polyline : overhang_perimeters) + if (polyline.is_straight()) { + // This is a bridge + polyline.extend_start(fw); + polyline.extend_end(fw); + // Is the straight perimeter segment supported at both sides? + Point pts[2] = { polyline.first_point(), polyline.last_point() }; + bool supported[2] = { false, false }; + for (size_t i = 0; i < lower_layer.lslices.size() && ! (supported[0] && supported[1]); ++ i) + for (int j = 0; j < 2; ++ j) + if (! supported[j] && lower_layer.lslices_ex[i].bbox.contains(pts[j]) && lower_layer.lslices[i].contains(pts[j])) + supported[j] = true; + if (supported[0] && supported[1]) + // Offset a polyline into a thick line. + polygons_append(bridges, offset(polyline, w)); + } + bridges = union_(bridges); + } + // remove the entire bridges and only support the unsupported edges + //FIXME the brided regions are already collected as layerm.bridged. Use it? + for (const Surface &surface : layerm.fill_surfaces()) + if (surface.surface_type == stBottomBridge && surface.bridge_angle >= 0.0) + polygons_append(bridges, surface.expolygon); + //FIXME add the gap filled areas. Extrude the gaps with a bridge flow? + // Remove the unsupported ends of the bridges from the bridged areas. + //FIXME add supports at regular intervals to support long bridges! + bridges = diff(bridges, + // Offset unsupported edges into polygons. + offset(layerm.unsupported_bridge_edges(), scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS)); + // Remove bridged areas from the supported areas. + contact_polygons = diff(contact_polygons, bridges, ApplySafetyOffset::Yes); + + #ifdef SLIC3R_DEBUG + static int iRun = 0; + SVG::export_expolygons(debug_out_path("support-top-contacts-remove-bridges-run%d.svg", iRun ++), + { { { union_ex(offset(layerm.unsupported_bridge_edges(), scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS)) }, { "unsupported_bridge_edges", "orange", 0.5f } }, + { { union_ex(contact_polygons) }, { "contact_polygons", "blue", 0.5f } }, + { { union_ex(bridges) }, { "bridges", "red", "black", "", scaled(0.1f), 0.5f } } }); + #endif /* SLIC3R_DEBUG */ +} + +SupportGeneratorLayersPtr generate_raft_base( + const PrintObject &object, + const SupportParameters &support_params, + const SlicingParameters &slicing_params, + const SupportGeneratorLayersPtr &top_contacts, + const SupportGeneratorLayersPtr &interface_layers, + const SupportGeneratorLayersPtr &base_interface_layers, + const SupportGeneratorLayersPtr &base_layers, + SupportGeneratorLayerStorage &layer_storage) +{ + // If there is brim to be generated, calculate the trimming regions. + Polygons brim; + if (object.has_brim()) { + // The object does not have a raft. + // Calculate the area covered by the brim. + const BrimType brim_type = object.config().brim_type; + const bool brim_outer = brim_type == btOuterOnly || brim_type == btOuterAndInner; + const bool brim_inner = brim_type == btInnerOnly || brim_type == btOuterAndInner; + const auto brim_separation = scaled(object.config().brim_separation.value + object.config().brim_width.value); + for (const ExPolygon &ex : object.layers().front()->lslices) { + if (brim_outer && brim_inner) + polygons_append(brim, offset(ex, brim_separation)); + else { + if (brim_outer) + polygons_append(brim, offset(ex.contour, brim_separation, ClipperLib::jtRound, float(scale_(0.1)))); + else + brim.emplace_back(ex.contour); + if (brim_inner) { + Polygons holes = ex.holes; + polygons_reverse(holes); + holes = shrink(holes, brim_separation, ClipperLib::jtRound, float(scale_(0.1))); + polygons_reverse(holes); + polygons_append(brim, std::move(holes)); + } else + polygons_append(brim, ex.holes); + } + } + brim = union_(brim); + } + + // How much to inflate the support columns to be stable. This also applies to the 1st layer, if no raft layers are to be printed. + const float inflate_factor_fine = float(scale_((slicing_params.raft_layers() > 1) ? 0.5 : EPSILON)); + const float inflate_factor_1st_layer = std::max(0.f, float(scale_(object.config().raft_first_layer_expansion)) - inflate_factor_fine); + SupportGeneratorLayer *contacts = top_contacts .empty() ? nullptr : top_contacts .front(); + SupportGeneratorLayer *interfaces = interface_layers .empty() ? nullptr : interface_layers .front(); + SupportGeneratorLayer *base_interfaces = base_interface_layers.empty() ? nullptr : base_interface_layers.front(); + SupportGeneratorLayer *columns_base = base_layers .empty() ? nullptr : base_layers .front(); + if (contacts != nullptr && contacts->print_z > std::max(slicing_params.first_print_layer_height, slicing_params.raft_contact_top_z) + EPSILON) + // This is not the raft contact layer. + contacts = nullptr; + if (interfaces != nullptr && interfaces->bottom_print_z() > slicing_params.raft_interface_top_z + EPSILON) + // This is not the raft column base layer. + interfaces = nullptr; + if (base_interfaces != nullptr && base_interfaces->bottom_print_z() > slicing_params.raft_interface_top_z + EPSILON) + // This is not the raft column base layer. + base_interfaces = nullptr; + if (columns_base != nullptr && columns_base->bottom_print_z() > slicing_params.raft_interface_top_z + EPSILON) + // This is not the raft interface layer. + columns_base = nullptr; + + Polygons interface_polygons; + if (contacts != nullptr && ! contacts->polygons.empty()) + polygons_append(interface_polygons, expand(contacts->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS)); + if (interfaces != nullptr && ! interfaces->polygons.empty()) + polygons_append(interface_polygons, expand(interfaces->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS)); + if (base_interfaces != nullptr && ! base_interfaces->polygons.empty()) + polygons_append(interface_polygons, expand(base_interfaces->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS)); + + // Output vector. + SupportGeneratorLayersPtr raft_layers; + + if (slicing_params.raft_layers() > 1) { + Polygons base; + Polygons columns; + Polygons first_layer; + if (columns_base != nullptr) { + if (columns_base->bottom_print_z() > slicing_params.raft_interface_top_z - EPSILON) { + // Classic supports with colums above the raft interface. + base = columns_base->polygons; + columns = base; + if (! interface_polygons.empty()) + // Trim the 1st layer columns with the inflated interface polygons. + columns = diff(columns, interface_polygons); + } else { + // Organic supports with raft on print bed. + assert(is_approx(columns_base->print_z, slicing_params.first_print_layer_height)); + first_layer = columns_base->polygons; + } + } + if (! interface_polygons.empty()) { + // Merge the untrimmed columns base with the expanded raft interface, to be used for the support base and interface. + base = union_(base, interface_polygons); + } + // Do not add the raft contact layer, only add the raft layers below the contact layer. + // Insert the 1st layer. + { + SupportGeneratorLayer &new_layer = layer_storage.allocate_unguarded(slicing_params.base_raft_layers > 0 ? SupporLayerType::RaftBase : SupporLayerType::RaftInterface); + raft_layers.push_back(&new_layer); + new_layer.print_z = slicing_params.first_print_layer_height; + new_layer.height = slicing_params.first_print_layer_height; + new_layer.bottom_z = 0.; + first_layer = union_(std::move(first_layer), base); + new_layer.polygons = inflate_factor_1st_layer > 0 ? expand(first_layer, inflate_factor_1st_layer) : first_layer; + } + // Insert the base layers. + for (size_t i = 1; i < slicing_params.base_raft_layers; ++ i) { + coordf_t print_z = raft_layers.back()->print_z; + SupportGeneratorLayer &new_layer = layer_storage.allocate_unguarded(SupporLayerType::RaftBase); + raft_layers.push_back(&new_layer); + new_layer.print_z = print_z + slicing_params.base_raft_layer_height; + new_layer.height = slicing_params.base_raft_layer_height; + new_layer.bottom_z = print_z; + new_layer.polygons = base; + } + // Insert the interface layers. + for (size_t i = 1; i < slicing_params.interface_raft_layers; ++ i) { + coordf_t print_z = raft_layers.back()->print_z; + SupportGeneratorLayer &new_layer = layer_storage.allocate_unguarded(SupporLayerType::RaftInterface); + raft_layers.push_back(&new_layer); + new_layer.print_z = print_z + slicing_params.interface_raft_layer_height; + new_layer.height = slicing_params.interface_raft_layer_height; + new_layer.bottom_z = print_z; + new_layer.polygons = interface_polygons; + //FIXME misusing contact_polygons for support columns. + new_layer.contact_polygons = std::make_unique(columns); + } + } else { + if (columns_base != nullptr) { + // Expand the bases of the support columns in the 1st layer. + Polygons &raft = columns_base->polygons; + Polygons trimming = offset(object.layers().front()->lslices, (float)scale_(support_params.gap_xy), SUPPORT_SURFACES_OFFSET_PARAMETERS); + if (inflate_factor_1st_layer > SCALED_EPSILON) { + // Inflate in multiple steps to avoid leaking of the support 1st layer through object walls. + auto nsteps = std::max(5, int(ceil(inflate_factor_1st_layer / support_params.first_layer_flow.scaled_width()))); + float step = inflate_factor_1st_layer / nsteps; + for (int i = 0; i < nsteps; ++ i) + raft = diff(expand(raft, step), trimming); + } else + raft = diff(raft, trimming); + if (! interface_polygons.empty()) + columns_base->polygons = diff(columns_base->polygons, interface_polygons); + } + if (! brim.empty()) { + if (columns_base) + columns_base->polygons = diff(columns_base->polygons, brim); + if (contacts) + contacts->polygons = diff(contacts->polygons, brim); + if (interfaces) + interfaces->polygons = diff(interfaces->polygons, brim); + if (base_interfaces) + base_interfaces->polygons = diff(base_interfaces->polygons, brim); + } + } + + return raft_layers; +} + +static inline void fill_expolygon_generate_paths( + ExtrusionEntitiesPtr &dst, + ExPolygon &&expolygon, + Fill *filler, + const FillParams &fill_params, + float density, + ExtrusionRole role, + const Flow &flow) +{ + Surface surface(stInternal, std::move(expolygon)); + Polylines polylines; + try { + assert(!fill_params.use_arachne); + polylines = filler->fill_surface(&surface, fill_params); + } catch (InfillFailedException &) { + } + extrusion_entities_append_paths( + dst, + std::move(polylines), + role, + flow.mm3_per_mm(), flow.width(), flow.height()); +} + +static inline void fill_expolygons_generate_paths( + ExtrusionEntitiesPtr &dst, + ExPolygons &&expolygons, + Fill *filler, + const FillParams &fill_params, + float density, + ExtrusionRole role, + const Flow &flow) +{ + for (ExPolygon &expoly : expolygons) + fill_expolygon_generate_paths(dst, std::move(expoly), filler, fill_params, density, role, flow); +} + +static inline void fill_expolygons_generate_paths( + ExtrusionEntitiesPtr &dst, + ExPolygons &&expolygons, + Fill *filler, + float density, + ExtrusionRole role, + const Flow &flow) +{ + FillParams fill_params; + fill_params.density = density; + fill_params.dont_adjust = true; + fill_expolygons_generate_paths(dst, std::move(expolygons), filler, fill_params, density, role, flow); +} + +static Polylines draw_perimeters(const ExPolygon &expoly, double clip_length) +{ + // Draw the perimeters. + Polylines polylines; + polylines.reserve(expoly.holes.size() + 1); + for (size_t i = 0; i <= expoly.holes.size(); ++ i) { + Polyline pl(i == 0 ? expoly.contour.points : expoly.holes[i - 1].points); + pl.points.emplace_back(pl.points.front()); + if (i > 0) + // It is a hole, reverse it. + pl.reverse(); + // so that all contours are CCW oriented. + pl.clip_end(clip_length); + polylines.emplace_back(std::move(pl)); + } + return polylines; +} + +static inline void tree_supports_generate_paths( + ExtrusionEntitiesPtr &dst, + const Polygons &polygons, + const Flow &flow) +{ + // Offset expolygon inside, returns number of expolygons collected (0 or 1). + // Vertices of output paths are marked with Z = source contour index of the expoly. + // Vertices at the intersection of source contours are marked with Z = -1. + auto shrink_expolygon_with_contour_idx = [](const Slic3r::ExPolygon &expoly, const float delta, ClipperLib::JoinType joinType, double miterLimit, ClipperLib_Z::Paths &out) -> int + { + assert(delta > 0); + auto append_paths_with_z = [](ClipperLib::Paths &src, coord_t contour_idx, ClipperLib_Z::Paths &dst) { + dst.reserve(next_highest_power_of_2(dst.size() + src.size())); + for (const ClipperLib::Path &contour : src) { + ClipperLib_Z::Path tmp; + tmp.reserve(contour.size()); + for (const Point &p : contour) + tmp.emplace_back(p.x(), p.y(), contour_idx); + dst.emplace_back(std::move(tmp)); + } + }; + + // 1) Offset the outer contour. + ClipperLib_Z::Paths contours; + { + ClipperLib::ClipperOffset co; + if (joinType == jtRound) + co.ArcTolerance = miterLimit; + else + co.MiterLimit = miterLimit; + co.ShortestEdgeLength = double(delta * 0.005); + co.AddPath(expoly.contour.points, joinType, ClipperLib::etClosedPolygon); + ClipperLib::Paths contours_raw; + co.Execute(contours_raw, - delta); + if (contours_raw.empty()) + // No need to try to offset the holes. + return 0; + append_paths_with_z(contours_raw, 0, contours); + } + + if (expoly.holes.empty()) { + // No need to subtract holes from the offsetted expolygon, we are done. + append(out, std::move(contours)); + } else { + // 2) Offset the holes one by one, collect the offsetted holes. + ClipperLib_Z::Paths holes; + { + for (const Polygon &hole : expoly.holes) { + ClipperLib::ClipperOffset co; + if (joinType == jtRound) + co.ArcTolerance = miterLimit; + else + co.MiterLimit = miterLimit; + co.ShortestEdgeLength = double(delta * 0.005); + co.AddPath(hole.points, joinType, ClipperLib::etClosedPolygon); + ClipperLib::Paths out2; + // Execute reorients the contours so that the outer most contour has a positive area. Thus the output + // contours will be CCW oriented even though the input paths are CW oriented. + // Offset is applied after contour reorientation, thus the signum of the offset value is reversed. + co.Execute(out2, delta); + append_paths_with_z(out2, 1 + (&hole - expoly.holes.data()), holes); + } + } + + // 3) Subtract holes from the contours. + if (holes.empty()) { + // No hole remaining after an offset. Just copy the outer contour. + append(out, std::move(contours)); + } else { + // Negative offset. There is a chance, that the offsetted hole intersects the outer contour. + // Subtract the offsetted holes from the offsetted contours. + ClipperLib_Z::Clipper clipper; + clipper.ZFillFunction([](const ClipperLib_Z::IntPoint &e1bot, const ClipperLib_Z::IntPoint &e1top, const ClipperLib_Z::IntPoint &e2bot, const ClipperLib_Z::IntPoint &e2top, ClipperLib_Z::IntPoint &pt) { + //pt.z() = std::max(std::max(e1bot.z(), e1top.z()), std::max(e2bot.z(), e2top.z())); + // Just mark the intersection. + pt.z() = -1; + }); + clipper.AddPaths(contours, ClipperLib_Z::ptSubject, true); + clipper.AddPaths(holes, ClipperLib_Z::ptClip, true); + ClipperLib_Z::Paths output; + clipper.Execute(ClipperLib_Z::ctDifference, output, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero); + if (! output.empty()) { + append(out, std::move(output)); + } else { + // The offsetted holes have eaten up the offsetted outer contour. + return 0; + } + } + } + + return 1; + }; + + const double spacing = flow.scaled_spacing(); + // Clip the sheath path to avoid the extruder to get exactly on the first point of the loop. + const double clip_length = spacing * 0.15; + const double anchor_length = spacing * 6.; + ClipperLib_Z::Paths anchor_candidates; + for (ExPolygon& expoly : closing_ex(polygons, float(SCALED_EPSILON), float(SCALED_EPSILON + 0.5 * flow.scaled_width()))) { + std::unique_ptr eec; + double area = expoly.area(); + if (area > sqr(scaled(5.))) { + eec = std::make_unique(); + // Don't reoder internal / external loops of the same island, always start with the internal loop. + eec->no_sort = true; + // Make the tree branch stable by adding another perimeter. + ExPolygons level2 = offset2_ex({ expoly }, -1.5 * flow.scaled_width(), 0.5 * flow.scaled_width()); + if (level2.size() == 1) { + Polylines polylines; + extrusion_entities_append_paths(eec->entities, draw_perimeters(expoly, clip_length), ExtrusionRole::SupportMaterial, flow.mm3_per_mm(), flow.width(), flow.height(), + // Disable reversal of the path, always start with the anchor, always print CCW. + false); + expoly = level2.front(); + } + } + + // Try to produce one more perimeter to place the seam anchor. + // First genrate a 2nd perimeter loop as a source for anchor candidates. + // The anchor candidate points are annotated with an index of the source contour or with -1 if on intersection. + anchor_candidates.clear(); + shrink_expolygon_with_contour_idx(expoly, flow.scaled_width(), DefaultJoinType, 1.2, anchor_candidates); + // Orient all contours CW. + for (auto &path : anchor_candidates) + if (ClipperLib_Z::Area(path) > 0) + std::reverse(path.begin(), path.end()); + + // Draw the perimeters. + Polylines polylines; + polylines.reserve(expoly.holes.size() + 1); + for (size_t idx_loop = 0; idx_loop < expoly.num_contours(); ++ idx_loop) { + // Open the loop with a seam. + const Polygon &loop = expoly.contour_or_hole(idx_loop); + Polyline pl(loop.points); + // Orient all contours CW, because the anchor will be added to the end of polyline while we want to start a loop with the anchor. + if (idx_loop == 0) + // It is an outer contour. + pl.reverse(); + pl.points.emplace_back(pl.points.front()); + pl.clip_end(clip_length); + if (pl.size() < 2) + continue; + // Find the foot of the seam point on anchor_candidates. Only pick an anchor point that was created by offsetting the source contour. + ClipperLib_Z::Path *closest_contour = nullptr; + Vec2d closest_point; + int closest_point_idx = -1; + double closest_point_t; + double d2min = std::numeric_limits::max(); + Vec2d seam_pt = pl.back().cast(); + for (ClipperLib_Z::Path &path : anchor_candidates) + for (int i = 0; i < path.size(); ++ i) { + int j = next_idx_modulo(i, path); + if (path[i].z() == idx_loop || path[j].z() == idx_loop) { + Vec2d pi(path[i].x(), path[i].y()); + Vec2d pj(path[j].x(), path[j].y()); + Vec2d v = pj - pi; + Vec2d w = seam_pt - pi; + auto l2 = v.squaredNorm(); + auto t = std::clamp((l2 == 0) ? 0 : v.dot(w) / l2, 0., 1.); + if ((path[i].z() == idx_loop || t > EPSILON) && (path[j].z() == idx_loop || t < 1. - EPSILON)) { + // Closest point. + Vec2d fp = pi + v * t; + double d2 = (fp - seam_pt).squaredNorm(); + if (d2 < d2min) { + d2min = d2; + closest_contour = &path; + closest_point = fp; + closest_point_idx = i; + closest_point_t = t; + } + } + } + } + if (d2min < sqr(flow.scaled_width() * 3.)) { + // Try to cut an anchor from the closest_contour. + // Both closest_contour and pl are CW oriented. + pl.points.emplace_back(closest_point.cast()); + const ClipperLib_Z::Path &path = *closest_contour; + double remaining_length = anchor_length - (seam_pt - closest_point).norm(); + int i = closest_point_idx; + int j = next_idx_modulo(i, *closest_contour); + Vec2d pi(path[i].x(), path[i].y()); + Vec2d pj(path[j].x(), path[j].y()); + Vec2d v = pj - pi; + double l = v.norm(); + if (remaining_length < (1. - closest_point_t) * l) { + // Just trim the current line. + pl.points.emplace_back((closest_point + v * (remaining_length / l)).cast()); + } else { + // Take the rest of the current line, continue with the other lines. + pl.points.emplace_back(path[j].x(), path[j].y()); + pi = pj; + for (i = j; path[i].z() == idx_loop && remaining_length > 0; i = j, pi = pj) { + j = next_idx_modulo(i, path); + pj = Vec2d(path[j].x(), path[j].y()); + v = pj - pi; + l = v.norm(); + if (i == closest_point_idx) { + // Back at the first segment. Most likely this should not happen and we may end the anchor. + break; + } + if (remaining_length <= l) { + pl.points.emplace_back((pi + v * (remaining_length / l)).cast()); + break; + } + pl.points.emplace_back(path[j].x(), path[j].y()); + remaining_length -= l; + } + } + } + // Start with the anchor. + pl.reverse(); + polylines.emplace_back(std::move(pl)); + } + + ExtrusionEntitiesPtr &out = eec ? eec->entities : dst; + extrusion_entities_append_paths(out, std::move(polylines), ExtrusionRole::SupportMaterial, flow.mm3_per_mm(), flow.width(), flow.height(), + // Disable reversal of the path, always start with the anchor, always print CCW. + false); + if (eec) { + std::reverse(eec->entities.begin(), eec->entities.end()); + dst.emplace_back(eec.release()); + } + } +} + +static inline void fill_expolygons_with_sheath_generate_paths( + ExtrusionEntitiesPtr &dst, + const Polygons &polygons, + Fill *filler, + float density, + ExtrusionRole role, + const Flow &flow, + bool with_sheath, + bool no_sort) +{ + if (polygons.empty()) + return; + + if (! with_sheath) { + fill_expolygons_generate_paths(dst, closing_ex(polygons, float(SCALED_EPSILON)), filler, density, role, flow); + return; + } + + FillParams fill_params; + fill_params.density = density; + fill_params.dont_adjust = true; + + const double spacing = flow.scaled_spacing(); + // Clip the sheath path to avoid the extruder to get exactly on the first point of the loop. + const double clip_length = spacing * 0.15; + + for (ExPolygon &expoly : closing_ex(polygons, float(SCALED_EPSILON), float(SCALED_EPSILON + 0.5*flow.scaled_width()))) { + // Don't reorder the skirt and its infills. + std::unique_ptr eec; + if (no_sort) { + eec = std::make_unique(); + eec->no_sort = true; + } + ExtrusionEntitiesPtr &out = no_sort ? eec->entities : dst; + extrusion_entities_append_paths(out, draw_perimeters(expoly, clip_length), ExtrusionRole::SupportMaterial, flow.mm3_per_mm(), flow.width(), flow.height()); + // Fill in the rest. + fill_expolygons_generate_paths(out, offset_ex(expoly, float(-0.4 * spacing)), filler, fill_params, density, role, flow); + if (no_sort && ! eec->empty()) + dst.emplace_back(eec.release()); + } +} + +// Support layers, partially processed. +struct SupportGeneratorLayerExtruded +{ + SupportGeneratorLayerExtruded& operator=(SupportGeneratorLayerExtruded &&rhs) { + this->layer = rhs.layer; + this->extrusions = std::move(rhs.extrusions); + m_polygons_to_extrude = std::move(rhs.m_polygons_to_extrude); + rhs.layer = nullptr; + return *this; + } + + bool empty() const { + return layer == nullptr || layer->polygons.empty(); + } + + void set_polygons_to_extrude(Polygons &&polygons) { + if (m_polygons_to_extrude == nullptr) + m_polygons_to_extrude = std::make_unique(std::move(polygons)); + else + *m_polygons_to_extrude = std::move(polygons); + } + Polygons& polygons_to_extrude() { return (m_polygons_to_extrude == nullptr) ? layer->polygons : *m_polygons_to_extrude; } + const Polygons& polygons_to_extrude() const { return (m_polygons_to_extrude == nullptr) ? layer->polygons : *m_polygons_to_extrude; } + + bool could_merge(const SupportGeneratorLayerExtruded &other) const { + return ! this->empty() && ! other.empty() && + std::abs(this->layer->height - other.layer->height) < EPSILON && + this->layer->bridging == other.layer->bridging; + } + + // Merge regions, perform boolean union over the merged polygons. + void merge(SupportGeneratorLayerExtruded &&other) { + assert(this->could_merge(other)); + // 1) Merge the rest polygons to extrude, if there are any. + if (other.m_polygons_to_extrude != nullptr) { + if (m_polygons_to_extrude == nullptr) { + // This layer has no extrusions generated yet, if it has no m_polygons_to_extrude (its area to extrude was not reduced yet). + assert(this->extrusions.empty()); + m_polygons_to_extrude = std::make_unique(this->layer->polygons); + } + Slic3r::polygons_append(*m_polygons_to_extrude, std::move(*other.m_polygons_to_extrude)); + *m_polygons_to_extrude = union_safety_offset(*m_polygons_to_extrude); + other.m_polygons_to_extrude.reset(); + } else if (m_polygons_to_extrude != nullptr) { + assert(other.m_polygons_to_extrude == nullptr); + // The other layer has no extrusions generated yet, if it has no m_polygons_to_extrude (its area to extrude was not reduced yet). + assert(other.extrusions.empty()); + Slic3r::polygons_append(*m_polygons_to_extrude, other.layer->polygons); + *m_polygons_to_extrude = union_safety_offset(*m_polygons_to_extrude); + } + // 2) Merge the extrusions. + this->extrusions.insert(this->extrusions.end(), other.extrusions.begin(), other.extrusions.end()); + other.extrusions.clear(); + // 3) Merge the infill polygons. + Slic3r::polygons_append(this->layer->polygons, std::move(other.layer->polygons)); + this->layer->polygons = union_safety_offset(this->layer->polygons); + other.layer->polygons.clear(); + } + + void polygons_append(Polygons &dst) const { + if (layer != NULL && ! layer->polygons.empty()) + Slic3r::polygons_append(dst, layer->polygons); + } + + // The source layer. It carries the height and extrusion type (bridging / non bridging, extrusion height). + SupportGeneratorLayer *layer { nullptr }; + // Collect extrusions. They will be exported sorted by the bottom height. + ExtrusionEntitiesPtr extrusions; + +private: + // In case the extrusions are non-empty, m_polygons_to_extrude may contain the rest areas yet to be filled by additional support. + // This is useful mainly for the loop interfaces, which are generated before the zig-zag infills. + std::unique_ptr m_polygons_to_extrude; +}; + +typedef std::vector SupportGeneratorLayerExtrudedPtrs; + +struct LoopInterfaceProcessor +{ + LoopInterfaceProcessor(coordf_t circle_r) : + n_contact_loops(0), + circle_radius(circle_r), + circle_distance(circle_r * 3.) + { + // Shape of the top contact area. + circle.points.reserve(6); + for (size_t i = 0; i < 6; ++ i) { + double angle = double(i) * M_PI / 3.; + circle.points.push_back(Point(circle_radius * cos(angle), circle_radius * sin(angle))); + } + } + + // Generate loop contacts at the top_contact_layer, + // trim the top_contact_layer->polygons with the areas covered by the loops. + void generate(SupportGeneratorLayerExtruded &top_contact_layer, const Flow &interface_flow_src) const; + + int n_contact_loops; + coordf_t circle_radius; + coordf_t circle_distance; + Polygon circle; +}; + +void LoopInterfaceProcessor::generate(SupportGeneratorLayerExtruded &top_contact_layer, const Flow &interface_flow_src) const +{ + if (n_contact_loops == 0 || top_contact_layer.empty()) + return; + + Flow flow = interface_flow_src.with_height(top_contact_layer.layer->height); + + Polygons overhang_polygons; + if (top_contact_layer.layer->overhang_polygons != nullptr) + overhang_polygons = std::move(*top_contact_layer.layer->overhang_polygons); + + // Generate the outermost loop. + // Find centerline of the external loop (or any other kind of extrusions should the loop be skipped) + ExPolygons top_contact_expolygons = offset_ex(union_ex(top_contact_layer.layer->polygons), - 0.5f * flow.scaled_width()); + + // Grid size and bit shifts for quick and exact to/from grid coordinates manipulation. + coord_t circle_grid_resolution = 1; + coord_t circle_grid_powerof2 = 0; + { + // epsilon to account for rounding errors + coord_t circle_grid_resolution_non_powerof2 = coord_t(2. * circle_distance + 3.); + while (circle_grid_resolution < circle_grid_resolution_non_powerof2) { + circle_grid_resolution <<= 1; + ++ circle_grid_powerof2; + } + } + + struct PointAccessor { + const Point* operator()(const Point &pt) const { return &pt; } + }; + typedef ClosestPointInRadiusLookup ClosestPointLookupType; + + Polygons loops0; + { + // find centerline of the external loop of the contours + // Only consider the loops facing the overhang. + Polygons external_loops; + // Holes in the external loops. + Polygons circles; + Polygons overhang_with_margin = offset(union_ex(overhang_polygons), 0.5f * flow.scaled_width()); + for (ExPolygons::iterator it_contact_expoly = top_contact_expolygons.begin(); it_contact_expoly != top_contact_expolygons.end(); ++ it_contact_expoly) { + // Store the circle centers placed for an expolygon into a regular grid, hashed by the circle centers. + ClosestPointLookupType circle_centers_lookup(coord_t(circle_distance - SCALED_EPSILON)); + Points circle_centers; + Point center_last; + // For each contour of the expolygon, start with the outer contour, continue with the holes. + for (size_t i_contour = 0; i_contour <= it_contact_expoly->holes.size(); ++ i_contour) { + Polygon &contour = (i_contour == 0) ? it_contact_expoly->contour : it_contact_expoly->holes[i_contour - 1]; + const Point *seg_current_pt = nullptr; + coordf_t seg_current_t = 0.; + if (! intersection_pl(contour.split_at_first_point(), overhang_with_margin).empty()) { + // The contour is below the overhang at least to some extent. + //FIXME ideally one would place the circles below the overhang only. + // Walk around the contour and place circles so their centers are not closer than circle_distance from each other. + if (circle_centers.empty()) { + // Place the first circle. + seg_current_pt = &contour.points.front(); + seg_current_t = 0.; + center_last = *seg_current_pt; + circle_centers_lookup.insert(center_last); + circle_centers.push_back(center_last); + } + for (Points::const_iterator it = contour.points.begin() + 1; it != contour.points.end(); ++it) { + // Is it possible to place a circle on this segment? Is it not too close to any of the circles already placed on this contour? + const Point &p1 = *(it-1); + const Point &p2 = *it; + // Intersection of a ray (p1, p2) with a circle placed at center_last, with radius of circle_distance. + const Vec2d v_seg(coordf_t(p2(0)) - coordf_t(p1(0)), coordf_t(p2(1)) - coordf_t(p1(1))); + const Vec2d v_cntr(coordf_t(p1(0) - center_last(0)), coordf_t(p1(1) - center_last(1))); + coordf_t a = v_seg.squaredNorm(); + coordf_t b = 2. * v_seg.dot(v_cntr); + coordf_t c = v_cntr.squaredNorm() - circle_distance * circle_distance; + coordf_t disc = b * b - 4. * a * c; + if (disc > 0.) { + // The circle intersects a ray. Avoid the parts of the segment inside the circle. + coordf_t t1 = (-b - sqrt(disc)) / (2. * a); + coordf_t t2 = (-b + sqrt(disc)) / (2. * a); + coordf_t t0 = (seg_current_pt == &p1) ? seg_current_t : 0.; + // Take the lowest t in , excluding . + coordf_t t; + if (t0 <= t1) + t = t0; + else if (t2 <= 1.) + t = t2; + else { + // Try the following segment. + seg_current_pt = nullptr; + continue; + } + seg_current_pt = &p1; + seg_current_t = t; + center_last = Point(p1(0) + coord_t(v_seg(0) * t), p1(1) + coord_t(v_seg(1) * t)); + // It has been verified that the new point is far enough from center_last. + // Ensure, that it is far enough from all the centers. + std::pair circle_closest = circle_centers_lookup.find(center_last); + if (circle_closest.first != nullptr) { + -- it; + continue; + } + } else { + // All of the segment is outside the circle. Take the first point. + seg_current_pt = &p1; + seg_current_t = 0.; + center_last = p1; + } + // Place the first circle. + circle_centers_lookup.insert(center_last); + circle_centers.push_back(center_last); + } + external_loops.push_back(std::move(contour)); + for (const Point ¢er : circle_centers) { + circles.push_back(circle); + circles.back().translate(center); + } + } + } + } + // Apply a pattern to the external loops. + loops0 = diff(external_loops, circles); + } + + Polylines loop_lines; + { + // make more loops + Polygons loop_polygons = loops0; + for (int i = 1; i < n_contact_loops; ++ i) + polygons_append(loop_polygons, + opening( + loops0, + i * flow.scaled_spacing() + 0.5f * flow.scaled_spacing(), + 0.5f * flow.scaled_spacing())); + // Clip such loops to the side oriented towards the object. + // Collect split points, so they will be recognized after the clipping. + // At the split points the clipped pieces will be stitched back together. + loop_lines.reserve(loop_polygons.size()); + std::unordered_map map_split_points; + for (Polygons::const_iterator it = loop_polygons.begin(); it != loop_polygons.end(); ++ it) { + assert(map_split_points.find(it->first_point()) == map_split_points.end()); + map_split_points[it->first_point()] = -1; + loop_lines.push_back(it->split_at_first_point()); + } + loop_lines = intersection_pl(loop_lines, expand(overhang_polygons, scale_(SUPPORT_MATERIAL_MARGIN))); + // Because a closed loop has been split to a line, loop_lines may contain continuous segments split to 2 pieces. + // Try to connect them. + for (int i_line = 0; i_line < int(loop_lines.size()); ++ i_line) { + Polyline &polyline = loop_lines[i_line]; + auto it = map_split_points.find(polyline.first_point()); + if (it != map_split_points.end()) { + // This is a stitching point. + // If this assert triggers, multiple source polygons likely intersected at this point. + assert(it->second != -2); + if (it->second < 0) { + // First occurence. + it->second = i_line; + } else { + // Second occurence. Join the lines. + Polyline &polyline_1st = loop_lines[it->second]; + assert(polyline_1st.first_point() == it->first || polyline_1st.last_point() == it->first); + if (polyline_1st.first_point() == it->first) + polyline_1st.reverse(); + polyline_1st.append(std::move(polyline)); + it->second = -2; + } + continue; + } + it = map_split_points.find(polyline.last_point()); + if (it != map_split_points.end()) { + // This is a stitching point. + // If this assert triggers, multiple source polygons likely intersected at this point. + assert(it->second != -2); + if (it->second < 0) { + // First occurence. + it->second = i_line; + } else { + // Second occurence. Join the lines. + Polyline &polyline_1st = loop_lines[it->second]; + assert(polyline_1st.first_point() == it->first || polyline_1st.last_point() == it->first); + if (polyline_1st.first_point() == it->first) + polyline_1st.reverse(); + polyline.reverse(); + polyline_1st.append(std::move(polyline)); + it->second = -2; + } + } + } + // Remove empty lines. + remove_degenerate(loop_lines); + } + + // add the contact infill area to the interface area + // note that growing loops by $circle_radius ensures no tiny + // extrusions are left inside the circles; however it creates + // a very large gap between loops and contact_infill_polygons, so maybe another + // solution should be found to achieve both goals + // Store the trimmed polygons into a separate polygon set, so the original infill area remains intact for + // "modulate by layer thickness". + top_contact_layer.set_polygons_to_extrude(diff(top_contact_layer.layer->polygons, offset(loop_lines, float(circle_radius * 1.1)))); + + // Transform loops into ExtrusionPath objects. + extrusion_entities_append_paths( + top_contact_layer.extrusions, + std::move(loop_lines), + ExtrusionRole::SupportMaterialInterface, flow.mm3_per_mm(), flow.width(), flow.height()); +} + +#ifdef SLIC3R_DEBUG +static std::string dbg_index_to_color(int idx) +{ + if (idx < 0) + return "yellow"; + idx = idx % 3; + switch (idx) { + case 0: return "red"; + case 1: return "green"; + default: return "blue"; + } +} +#endif /* SLIC3R_DEBUG */ + +// When extruding a bottom interface layer over an object, the bottom interface layer is extruded in a thin air, therefore +// it is being extruded with a bridging flow to not shrink excessively (the die swell effect). +// Tiny extrusions are better avoided and it is always better to anchor the thread to an existing support structure if possible. +// Therefore the bottom interface spots are expanded a bit. The expanded regions may overlap with another bottom interface layers, +// leading to over extrusion, where they overlap. The over extrusion is better avoided as it often makes the interface layers +// to stick too firmly to the object. +// +// Modulate thickness (increase bottom_z) of extrusions_in_out generated for this_layer +// if they overlap with overlapping_layers, whose print_z is above this_layer.bottom_z() and below this_layer.print_z. +static void modulate_extrusion_by_overlapping_layers( + // Extrusions generated for this_layer. + ExtrusionEntitiesPtr &extrusions_in_out, + const SupportGeneratorLayer &this_layer, + // Multiple layers overlapping with this_layer, sorted bottom up. + const SupportGeneratorLayersPtr &overlapping_layers) +{ + size_t n_overlapping_layers = overlapping_layers.size(); + if (n_overlapping_layers == 0 || extrusions_in_out.empty()) + // The extrusions do not overlap with any other extrusion. + return; + + // Get the initial extrusion parameters. + ExtrusionPath *extrusion_path_template = dynamic_cast(extrusions_in_out.front()); + assert(extrusion_path_template != nullptr); + ExtrusionRole extrusion_role = extrusion_path_template->role(); + float extrusion_width = extrusion_path_template->width; + + struct ExtrusionPathFragment + { + ExtrusionPathFragment() : mm3_per_mm(-1), width(-1), height(-1) {}; + ExtrusionPathFragment(double mm3_per_mm, float width, float height) : mm3_per_mm(mm3_per_mm), width(width), height(height) {}; + + Polylines polylines; + double mm3_per_mm; + float width; + float height; + }; + + // Split the extrusions by the overlapping layers, reduce their extrusion rate. + // The last path_fragment is from this_layer. + std::vector path_fragments( + n_overlapping_layers + 1, + ExtrusionPathFragment(extrusion_path_template->mm3_per_mm, extrusion_path_template->width, extrusion_path_template->height)); + // Don't use it, it will be released. + extrusion_path_template = nullptr; + +#ifdef SLIC3R_DEBUG + static int iRun = 0; + ++ iRun; + BoundingBox bbox; + for (size_t i_overlapping_layer = 0; i_overlapping_layer < n_overlapping_layers; ++ i_overlapping_layer) { + const SupportGeneratorLayer &overlapping_layer = *overlapping_layers[i_overlapping_layer]; + bbox.merge(get_extents(overlapping_layer.polygons)); + } + for (ExtrusionEntitiesPtr::const_iterator it = extrusions_in_out.begin(); it != extrusions_in_out.end(); ++ it) { + ExtrusionPath *path = dynamic_cast(*it); + assert(path != nullptr); + bbox.merge(get_extents(path->polyline)); + } + SVG svg(debug_out_path("support-fragments-%d-%lf.svg", iRun, this_layer.print_z).c_str(), bbox); + const float transparency = 0.5f; + // Filled polygons for the overlapping regions. + svg.draw(union_ex(this_layer.polygons), dbg_index_to_color(-1), transparency); + for (size_t i_overlapping_layer = 0; i_overlapping_layer < n_overlapping_layers; ++ i_overlapping_layer) { + const SupportGeneratorLayer &overlapping_layer = *overlapping_layers[i_overlapping_layer]; + svg.draw(union_ex(overlapping_layer.polygons), dbg_index_to_color(int(i_overlapping_layer)), transparency); + } + // Contours of the overlapping regions. + svg.draw(to_polylines(this_layer.polygons), dbg_index_to_color(-1), scale_(0.2)); + for (size_t i_overlapping_layer = 0; i_overlapping_layer < n_overlapping_layers; ++ i_overlapping_layer) { + const SupportGeneratorLayer &overlapping_layer = *overlapping_layers[i_overlapping_layer]; + svg.draw(to_polylines(overlapping_layer.polygons), dbg_index_to_color(int(i_overlapping_layer)), scale_(0.1)); + } + // Fill extrusion, the source. + for (ExtrusionEntitiesPtr::const_iterator it = extrusions_in_out.begin(); it != extrusions_in_out.end(); ++ it) { + ExtrusionPath *path = dynamic_cast(*it); + std::string color_name; + switch ((it - extrusions_in_out.begin()) % 9) { + case 0: color_name = "magenta"; break; + case 1: color_name = "deepskyblue"; break; + case 2: color_name = "coral"; break; + case 3: color_name = "goldenrod"; break; + case 4: color_name = "orange"; break; + case 5: color_name = "olivedrab"; break; + case 6: color_name = "blueviolet"; break; + case 7: color_name = "brown"; break; + default: color_name = "orchid"; break; + } + svg.draw(path->polyline, color_name, scale_(0.2)); + } +#endif /* SLIC3R_DEBUG */ + + // End points of the original paths. + std::vector> path_ends; + // Collect the paths of this_layer. + { + Polylines &polylines = path_fragments.back().polylines; + for (ExtrusionEntity *ee : extrusions_in_out) { + ExtrusionPath *path = dynamic_cast(ee); + assert(path != nullptr); + polylines.emplace_back(Polyline(std::move(path->polyline))); + path_ends.emplace_back(std::pair(polylines.back().points.front(), polylines.back().points.back())); + delete path; + } + } + // Destroy the original extrusion paths, their polylines were moved to path_fragments already. + // This will be the destination for the new paths. + extrusions_in_out.clear(); + + // Fragment the path segments by overlapping layers. The overlapping layers are sorted by an increasing print_z. + // Trim by the highest overlapping layer first. + for (int i_overlapping_layer = int(n_overlapping_layers) - 1; i_overlapping_layer >= 0; -- i_overlapping_layer) { + const SupportGeneratorLayer &overlapping_layer = *overlapping_layers[i_overlapping_layer]; + ExtrusionPathFragment &frag = path_fragments[i_overlapping_layer]; + Polygons polygons_trimming = offset(union_ex(overlapping_layer.polygons), float(scale_(0.5*extrusion_width))); + frag.polylines = intersection_pl(path_fragments.back().polylines, polygons_trimming); + path_fragments.back().polylines = diff_pl(path_fragments.back().polylines, polygons_trimming); + // Adjust the extrusion parameters for a reduced layer height and a non-bridging flow (nozzle_dmr = -1, does not matter). + assert(this_layer.print_z > overlapping_layer.print_z); + frag.height = float(this_layer.print_z - overlapping_layer.print_z); + frag.mm3_per_mm = Flow(frag.width, frag.height, -1.f).mm3_per_mm(); +#ifdef SLIC3R_DEBUG + svg.draw(frag.polylines, dbg_index_to_color(i_overlapping_layer), scale_(0.1)); +#endif /* SLIC3R_DEBUG */ + } + +#ifdef SLIC3R_DEBUG + svg.draw(path_fragments.back().polylines, dbg_index_to_color(-1), scale_(0.1)); + svg.Close(); +#endif /* SLIC3R_DEBUG */ + + // Now chain the split segments using hashing and a nearly exact match, maintaining the order of segments. + // Create a single ExtrusionPath or ExtrusionEntityCollection per source ExtrusionPath. + // Map of fragment start/end points to a pair of + // Because a non-exact matching is used for the end points, a multi-map is used. + // As the clipper library may reverse the order of some clipped paths, store both ends into the map. + struct ExtrusionPathFragmentEnd + { + ExtrusionPathFragmentEnd(size_t alayer_idx, size_t apolyline_idx, bool ais_start) : + layer_idx(alayer_idx), polyline_idx(apolyline_idx), is_start(ais_start) {} + size_t layer_idx; + size_t polyline_idx; + bool is_start; + }; + class ExtrusionPathFragmentEndPointAccessor { + public: + ExtrusionPathFragmentEndPointAccessor(const std::vector &path_fragments) : m_path_fragments(path_fragments) {} + // Return an end point of a fragment, or nullptr if the fragment has been consumed already. + const Point* operator()(const ExtrusionPathFragmentEnd &fragment_end) const { + const Polyline &polyline = m_path_fragments[fragment_end.layer_idx].polylines[fragment_end.polyline_idx]; + return polyline.points.empty() ? nullptr : + (fragment_end.is_start ? &polyline.points.front() : &polyline.points.back()); + } + private: + ExtrusionPathFragmentEndPointAccessor& operator=(const ExtrusionPathFragmentEndPointAccessor&) { + return *this; + } + + const std::vector &m_path_fragments; + }; + const coord_t search_radius = 7; + ClosestPointInRadiusLookup map_fragment_starts( + search_radius, ExtrusionPathFragmentEndPointAccessor(path_fragments)); + for (size_t i_overlapping_layer = 0; i_overlapping_layer <= n_overlapping_layers; ++ i_overlapping_layer) { + const Polylines &polylines = path_fragments[i_overlapping_layer].polylines; + for (size_t i_polyline = 0; i_polyline < polylines.size(); ++ i_polyline) { + // Map a starting point of a polyline to a pair of + if (polylines[i_polyline].points.size() >= 2) { + map_fragment_starts.insert(ExtrusionPathFragmentEnd(i_overlapping_layer, i_polyline, true)); + map_fragment_starts.insert(ExtrusionPathFragmentEnd(i_overlapping_layer, i_polyline, false)); + } + } + } + + // For each source path: + for (size_t i_path = 0; i_path < path_ends.size(); ++ i_path) { + const Point &pt_start = path_ends[i_path].first; + const Point &pt_end = path_ends[i_path].second; + Point pt_current = pt_start; + // Find a chain of fragments with the original / reduced print height. + ExtrusionMultiPath multipath; + for (;;) { + // Find a closest end point to pt_current. + std::pair end_and_dist2 = map_fragment_starts.find(pt_current); + // There may be a bug in Clipper flipping the order of two last points in a fragment? + // assert(end_and_dist2.first != nullptr); + assert(end_and_dist2.first == nullptr || end_and_dist2.second < search_radius * search_radius); + if (end_and_dist2.first == nullptr) { + // New fragment connecting to pt_current was not found. + // Verify that the last point found is close to the original end point of the unfragmented path. + //const double d2 = (pt_end - pt_current).cast.squaredNorm(); + //assert(d2 < coordf_t(search_radius * search_radius)); + // End of the path. + break; + } + const ExtrusionPathFragmentEnd &fragment_end_min = *end_and_dist2.first; + // Fragment to consume. + ExtrusionPathFragment &frag = path_fragments[fragment_end_min.layer_idx]; + Polyline &frag_polyline = frag.polylines[fragment_end_min.polyline_idx]; + // Path to append the fragment to. + ExtrusionPath *path = multipath.paths.empty() ? nullptr : &multipath.paths.back(); + if (path != nullptr) { + // Verify whether the path is compatible with the current fragment. + assert(this_layer.layer_type == SupporLayerType::BottomContact || path->height != frag.height || path->mm3_per_mm != frag.mm3_per_mm); + if (path->height != frag.height || path->mm3_per_mm != frag.mm3_per_mm) { + path = nullptr; + } + // Merging with the previous path. This can only happen if the current layer was reduced by a base layer, which was split into a base and interface layer. + } + if (path == nullptr) { + // Allocate a new path. + multipath.paths.push_back(ExtrusionPath(extrusion_role, frag.mm3_per_mm, frag.width, frag.height)); + path = &multipath.paths.back(); + } + // The Clipper library may flip the order of the clipped polylines arbitrarily. + // Reverse the source polyline, if connecting to the end. + if (! fragment_end_min.is_start) + frag_polyline.reverse(); + // Enforce exact overlap of the end points of successive fragments. + assert(frag_polyline.points.front() == pt_current); + frag_polyline.points.front() = pt_current; + // Don't repeat the first point. + if (! path->polyline.points.empty()) + path->polyline.points.pop_back(); + // Consume the fragment's polyline, remove it from the input fragments, so it will be ignored the next time. + path->polyline.append(std::move(frag_polyline)); + frag_polyline.points.clear(); + pt_current = path->polyline.points.back(); + if (pt_current == pt_end) { + // End of the path. + break; + } + } + if (!multipath.paths.empty()) { + if (multipath.paths.size() == 1) { + // This path was not fragmented. + extrusions_in_out.push_back(new ExtrusionPath(std::move(multipath.paths.front()))); + } else { + // This path was fragmented. Copy the collection as a whole object, so the order inside the collection will not be changed + // during the chaining of extrusions_in_out. + extrusions_in_out.push_back(new ExtrusionMultiPath(std::move(multipath))); + } + } + } + // If there are any non-consumed fragments, add them separately. + //FIXME this shall not happen, if the Clipper works as expected and all paths split to fragments could be re-connected. + for (auto it_fragment = path_fragments.begin(); it_fragment != path_fragments.end(); ++ it_fragment) + extrusion_entities_append_paths(extrusions_in_out, std::move(it_fragment->polylines), extrusion_role, it_fragment->mm3_per_mm, it_fragment->width, it_fragment->height); +} + +// Support layer that is covered by some form of dense interface. +static constexpr const std::initializer_list support_types_interface{ + SupporLayerType::RaftInterface, SupporLayerType::BottomContact, SupporLayerType::BottomInterface, SupporLayerType::TopContact, SupporLayerType::TopInterface +}; + +SupportGeneratorLayersPtr generate_support_layers( + PrintObject &object, + const SupportGeneratorLayersPtr &raft_layers, + const SupportGeneratorLayersPtr &bottom_contacts, + const SupportGeneratorLayersPtr &top_contacts, + const SupportGeneratorLayersPtr &intermediate_layers, + const SupportGeneratorLayersPtr &interface_layers, + const SupportGeneratorLayersPtr &base_interface_layers) +{ + // Install support layers into the object. + // A support layer installed on a PrintObject has a unique print_z. + SupportGeneratorLayersPtr layers_sorted; + layers_sorted.reserve(raft_layers.size() + bottom_contacts.size() + top_contacts.size() + intermediate_layers.size() + interface_layers.size() + base_interface_layers.size()); + append(layers_sorted, raft_layers); + append(layers_sorted, bottom_contacts); + append(layers_sorted, top_contacts); + append(layers_sorted, intermediate_layers); + append(layers_sorted, interface_layers); + append(layers_sorted, base_interface_layers); + // Sort the layers lexicographically by a raising print_z and a decreasing height. + std::sort(layers_sorted.begin(), layers_sorted.end(), [](auto *l1, auto *l2) { return *l1 < *l2; }); + int layer_id = 0; + int layer_id_interface = 0; + assert(object.support_layers().empty()); + for (size_t i = 0; i < layers_sorted.size();) { + // Find the last layer with roughly the same print_z, find the minimum layer height of all. + // Due to the floating point inaccuracies, the print_z may not be the same even if in theory they should. + size_t j = i + 1; + coordf_t zmax = layers_sorted[i]->print_z + EPSILON; + for (; j < layers_sorted.size() && layers_sorted[j]->print_z <= zmax; ++j) ; + // Assign an average print_z to the set of layers with nearly equal print_z. + coordf_t zavg = 0.5 * (layers_sorted[i]->print_z + layers_sorted[j - 1]->print_z); + coordf_t height_min = layers_sorted[i]->height; + bool empty = true; + // For snug supports, layers where the direction of the support interface shall change are accounted for. + size_t num_interfaces = 0; + size_t num_top_contacts = 0; + double top_contact_bottom_z = 0; + for (size_t u = i; u < j; ++u) { + SupportGeneratorLayer &layer = *layers_sorted[u]; + if (! layer.polygons.empty()) { + empty = false; + num_interfaces += one_of(layer.layer_type, support_types_interface); + if (layer.layer_type == SupporLayerType::TopContact) { + ++ num_top_contacts; + assert(num_top_contacts <= 1); + // All top contact layers sharing this print_z shall also share bottom_z. + //assert(num_top_contacts == 1 || (top_contact_bottom_z - layer.bottom_z) < EPSILON); + top_contact_bottom_z = layer.bottom_z; + } + } + layer.print_z = zavg; + height_min = std::min(height_min, layer.height); + } + if (! empty) { + // Here the upper_layer and lower_layer pointers are left to null at the support layers, + // as they are never used. These pointers are candidates for removal. + bool this_layer_contacts_only = num_top_contacts > 0 && num_top_contacts == num_interfaces; + size_t this_layer_id_interface = layer_id_interface; + if (this_layer_contacts_only) { + // Find a supporting layer for its interface ID. + for (auto it = object.support_layers().rbegin(); it != object.support_layers().rend(); ++ it) + if (const SupportLayer &other_layer = **it; std::abs(other_layer.print_z - top_contact_bottom_z) < EPSILON) { + // other_layer supports this top contact layer. Assign a different support interface direction to this layer + // from the layer that supports it. + this_layer_id_interface = other_layer.interface_id() + 1; + } + } + object.add_support_layer(layer_id ++, this_layer_id_interface, height_min, zavg); + if (num_interfaces && ! this_layer_contacts_only) + ++ layer_id_interface; + } + i = j; + } + return layers_sorted; +} + +void generate_support_toolpaths( + SupportLayerPtrs &support_layers, + const PrintObjectConfig &config, + const SupportParameters &support_params, + const SlicingParameters &slicing_params, + const SupportGeneratorLayersPtr &raft_layers, + const SupportGeneratorLayersPtr &bottom_contacts, + const SupportGeneratorLayersPtr &top_contacts, + const SupportGeneratorLayersPtr &intermediate_layers, + const SupportGeneratorLayersPtr &interface_layers, + const SupportGeneratorLayersPtr &base_interface_layers) +{ + // loop_interface_processor with a given circle radius. + LoopInterfaceProcessor loop_interface_processor(1.5 * support_params.support_material_interface_flow.scaled_width()); + loop_interface_processor.n_contact_loops = config.support_material_interface_contact_loops ? 1 : 0; + + std::vector angles { support_params.base_angle }; + if (config.support_material_pattern == smpRectilinearGrid) + angles.push_back(support_params.interface_angle); + + BoundingBox bbox_object(Point(-scale_(1.), -scale_(1.0)), Point(scale_(1.), scale_(1.))); + +// const coordf_t link_max_length_factor = 3.; + const coordf_t link_max_length_factor = 0.; + + // Insert the raft base layers. + auto n_raft_layers = std::min(support_layers.size(), std::max(0, int(slicing_params.raft_layers()) - 1)); + + tbb::parallel_for(tbb::blocked_range(0, n_raft_layers), + [&support_layers, &raft_layers, &intermediate_layers, &config, &support_params, &slicing_params, + &bbox_object, link_max_length_factor] + (const tbb::blocked_range& range) { + for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id) + { + assert(support_layer_id < raft_layers.size()); + SupportLayer &support_layer = *support_layers[support_layer_id]; + assert(support_layer.support_fills.entities.empty()); + SupportGeneratorLayer &raft_layer = *raft_layers[support_layer_id]; + + std::unique_ptr filler_interface = std::unique_ptr(Fill::new_from_type(support_params.raft_interface_fill_pattern)); + std::unique_ptr filler_support = std::unique_ptr(Fill::new_from_type(support_params.base_fill_pattern)); + filler_interface->set_bounding_box(bbox_object); + filler_support->set_bounding_box(bbox_object); + + // Print the tree supports cutting through the raft with the exception of the 1st layer, where a full support layer will be printed below + // both the raft and the trees. + // Trim the raft layers with the tree polygons. + const Polygons &tree_polygons = + support_layer_id > 0 && support_layer_id < intermediate_layers.size() && is_approx(intermediate_layers[support_layer_id]->print_z, support_layer.print_z) ? + intermediate_layers[support_layer_id]->polygons : Polygons(); + + // Print the support base below the support columns, or the support base for the support columns plus the contacts. + if (support_layer_id > 0) { + const Polygons &to_infill_polygons = (support_layer_id < slicing_params.base_raft_layers) ? + raft_layer.polygons : + //FIXME misusing contact_polygons for support columns. + ((raft_layer.contact_polygons == nullptr) ? Polygons() : *raft_layer.contact_polygons); + // Trees may cut through the raft layers down to a print bed. + Flow flow(float(support_params.support_material_flow.width()), float(raft_layer.height), support_params.support_material_flow.nozzle_diameter()); + assert(!raft_layer.bridging); + if (! to_infill_polygons.empty()) { + Fill *filler = filler_support.get(); + filler->angle = support_params.raft_angle_base; + filler->spacing = support_params.support_material_flow.spacing(); + filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_params.support_density)); + fill_expolygons_with_sheath_generate_paths( + // Destination + support_layer.support_fills.entities, + // Regions to fill + tree_polygons.empty() ? to_infill_polygons : diff(to_infill_polygons, tree_polygons), + // Filler and its parameters + filler, float(support_params.support_density), + // Extrusion parameters + ExtrusionRole::SupportMaterial, flow, + support_params.with_sheath, false); + } + if (! tree_polygons.empty()) + tree_supports_generate_paths(support_layer.support_fills.entities, tree_polygons, flow); + } + + Fill *filler = filler_interface.get(); + Flow flow = support_params.first_layer_flow; + float density = 0.f; + if (support_layer_id == 0) { + // Base flange. + filler->angle = support_params.raft_angle_1st_layer; + filler->spacing = support_params.first_layer_flow.spacing(); + density = float(config.raft_first_layer_density.value * 0.01); + } else if (support_layer_id >= slicing_params.base_raft_layers) { + filler->angle = support_params.raft_interface_angle(support_layer.interface_id()); + // We don't use $base_flow->spacing because we need a constant spacing + // value that guarantees that all layers are correctly aligned. + filler->spacing = support_params.support_material_flow.spacing(); + assert(! raft_layer.bridging); + flow = Flow(float(support_params.raft_interface_flow.width()), float(raft_layer.height), support_params.raft_interface_flow.nozzle_diameter()); + density = float(support_params.raft_interface_density); + } else + continue; + filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density)); + fill_expolygons_with_sheath_generate_paths( + // Destination + support_layer.support_fills.entities, + // Regions to fill + tree_polygons.empty() ? raft_layer.polygons : diff(raft_layer.polygons, tree_polygons), + // Filler and its parameters + filler, density, + // Extrusion parameters + (support_layer_id < slicing_params.base_raft_layers) ? ExtrusionRole::SupportMaterial : ExtrusionRole::SupportMaterialInterface, flow, + // sheath at first layer + support_layer_id == 0, support_layer_id == 0); + } + }); + + struct LayerCacheItem { + LayerCacheItem(SupportGeneratorLayerExtruded *layer_extruded = nullptr) : layer_extruded(layer_extruded) {} + SupportGeneratorLayerExtruded *layer_extruded; + std::vector overlapping; + }; + struct LayerCache { + SupportGeneratorLayerExtruded bottom_contact_layer; + SupportGeneratorLayerExtruded top_contact_layer; + SupportGeneratorLayerExtruded base_layer; + SupportGeneratorLayerExtruded interface_layer; + SupportGeneratorLayerExtruded base_interface_layer; + boost::container::static_vector nonempty; + + void add_nonempty_and_sort() { + for (SupportGeneratorLayerExtruded *item : { &bottom_contact_layer, &top_contact_layer, &interface_layer, &base_interface_layer, &base_layer }) + if (! item->empty()) + this->nonempty.emplace_back(item); + // Sort the layers with the same print_z coordinate by their heights, thickest first. + std::stable_sort(this->nonempty.begin(), this->nonempty.end(), [](const LayerCacheItem &lc1, const LayerCacheItem &lc2) { return lc1.layer_extruded->layer->height > lc2.layer_extruded->layer->height; }); + } + }; + std::vector layer_caches(support_layers.size()); + + tbb::parallel_for(tbb::blocked_range(n_raft_layers, support_layers.size()), + [&config, &slicing_params, &support_params, &support_layers, &bottom_contacts, &top_contacts, &intermediate_layers, &interface_layers, &base_interface_layers, &layer_caches, &loop_interface_processor, + &bbox_object, &angles, n_raft_layers, link_max_length_factor] + (const tbb::blocked_range& range) { + // Indices of the 1st layer in their respective container at the support layer height. + size_t idx_layer_bottom_contact = size_t(-1); + size_t idx_layer_top_contact = size_t(-1); + size_t idx_layer_intermediate = size_t(-1); + size_t idx_layer_interface = size_t(-1); + size_t idx_layer_base_interface = size_t(-1); + const auto fill_type_first_layer = ipRectilinear; + auto filler_interface = std::unique_ptr(Fill::new_from_type(support_params.contact_fill_pattern)); + // Filler for the 1st layer interface, if different from filler_interface. + auto filler_first_layer_ptr = std::unique_ptr(range.begin() == 0 && support_params.contact_fill_pattern != fill_type_first_layer ? Fill::new_from_type(fill_type_first_layer) : nullptr); + // Pointer to the 1st layer interface filler. + auto filler_first_layer = filler_first_layer_ptr ? filler_first_layer_ptr.get() : filler_interface.get(); + // Filler for the 1st layer interface, if different from filler_interface. + auto filler_raft_contact_ptr = std::unique_ptr(range.begin() == n_raft_layers && config.support_material_interface_layers.value == 0 ? + Fill::new_from_type(support_params.raft_interface_fill_pattern) : nullptr); + // Pointer to the 1st layer interface filler. + auto filler_raft_contact = filler_raft_contact_ptr ? filler_raft_contact_ptr.get() : filler_interface.get(); + // Filler for the base interface (to be used for soluble interface / non soluble base, to produce non soluble interface layer below soluble interface layer). + auto filler_base_interface = std::unique_ptr(base_interface_layers.empty() ? nullptr : + Fill::new_from_type(support_params.interface_density > 0.95 || support_params.with_sheath ? ipRectilinear : ipSupportBase)); + auto filler_support = std::unique_ptr(Fill::new_from_type(support_params.base_fill_pattern)); + filler_interface->set_bounding_box(bbox_object); + if (filler_first_layer_ptr) + filler_first_layer_ptr->set_bounding_box(bbox_object); + if (filler_raft_contact_ptr) + filler_raft_contact_ptr->set_bounding_box(bbox_object); + if (filler_base_interface) + filler_base_interface->set_bounding_box(bbox_object); + filler_support->set_bounding_box(bbox_object); + for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id) + { + SupportLayer &support_layer = *support_layers[support_layer_id]; + LayerCache &layer_cache = layer_caches[support_layer_id]; + const float support_interface_angle = config.support_material_style.value == smsGrid ? + support_params.interface_angle : support_params.raft_interface_angle(support_layer.interface_id()); + + // Find polygons with the same print_z. + SupportGeneratorLayerExtruded &bottom_contact_layer = layer_cache.bottom_contact_layer; + SupportGeneratorLayerExtruded &top_contact_layer = layer_cache.top_contact_layer; + SupportGeneratorLayerExtruded &base_layer = layer_cache.base_layer; + SupportGeneratorLayerExtruded &interface_layer = layer_cache.interface_layer; + SupportGeneratorLayerExtruded &base_interface_layer = layer_cache.base_interface_layer; + // Increment the layer indices to find a layer at support_layer.print_z. + { + auto fun = [&support_layer](const SupportGeneratorLayer *l){ return l->print_z >= support_layer.print_z - EPSILON; }; + idx_layer_bottom_contact = idx_higher_or_equal(bottom_contacts, idx_layer_bottom_contact, fun); + idx_layer_top_contact = idx_higher_or_equal(top_contacts, idx_layer_top_contact, fun); + idx_layer_intermediate = idx_higher_or_equal(intermediate_layers, idx_layer_intermediate, fun); + idx_layer_interface = idx_higher_or_equal(interface_layers, idx_layer_interface, fun); + idx_layer_base_interface = idx_higher_or_equal(base_interface_layers, idx_layer_base_interface,fun); + } + // Copy polygons from the layers. + if (idx_layer_bottom_contact < bottom_contacts.size() && bottom_contacts[idx_layer_bottom_contact]->print_z < support_layer.print_z + EPSILON) + bottom_contact_layer.layer = bottom_contacts[idx_layer_bottom_contact]; + if (idx_layer_top_contact < top_contacts.size() && top_contacts[idx_layer_top_contact]->print_z < support_layer.print_z + EPSILON) + top_contact_layer.layer = top_contacts[idx_layer_top_contact]; + if (idx_layer_interface < interface_layers.size() && interface_layers[idx_layer_interface]->print_z < support_layer.print_z + EPSILON) + interface_layer.layer = interface_layers[idx_layer_interface]; + if (idx_layer_base_interface < base_interface_layers.size() && base_interface_layers[idx_layer_base_interface]->print_z < support_layer.print_z + EPSILON) + base_interface_layer.layer = base_interface_layers[idx_layer_base_interface]; + if (idx_layer_intermediate < intermediate_layers.size() && intermediate_layers[idx_layer_intermediate]->print_z < support_layer.print_z + EPSILON) + base_layer.layer = intermediate_layers[idx_layer_intermediate]; + + // This layer is a raft contact layer. Any contact polygons at this layer are raft contacts. + bool raft_layer = slicing_params.interface_raft_layers && top_contact_layer.layer && is_approx(top_contact_layer.layer->print_z, slicing_params.raft_contact_top_z); + if (config.support_material_interface_layers == 0) { + // If no top interface layers were requested, we treat the contact layer exactly as a generic base layer. + // Don't merge the raft contact layer though. + if (support_params.can_merge_support_regions && ! raft_layer) { + if (base_layer.could_merge(top_contact_layer)) + base_layer.merge(std::move(top_contact_layer)); + else if (base_layer.empty()) + base_layer = std::move(top_contact_layer); + } + } else { + loop_interface_processor.generate(top_contact_layer, support_params.support_material_interface_flow); + // If no loops are allowed, we treat the contact layer exactly as a generic interface layer. + // Merge interface_layer into top_contact_layer, as the top_contact_layer is not synchronized and therefore it will be used + // to trim other layers. + if (top_contact_layer.could_merge(interface_layer) && ! raft_layer) + top_contact_layer.merge(std::move(interface_layer)); + } + if ((config.support_material_interface_layers == 0 || config.support_material_bottom_interface_layers == 0) && support_params.can_merge_support_regions) { + if (base_layer.could_merge(bottom_contact_layer)) + base_layer.merge(std::move(bottom_contact_layer)); + else if (base_layer.empty() && ! bottom_contact_layer.empty() && ! bottom_contact_layer.layer->bridging) + base_layer = std::move(bottom_contact_layer); + } else if (bottom_contact_layer.could_merge(top_contact_layer) && ! raft_layer) + top_contact_layer.merge(std::move(bottom_contact_layer)); + else if (bottom_contact_layer.could_merge(interface_layer)) + bottom_contact_layer.merge(std::move(interface_layer)); + +#if 0 + if ( ! interface_layer.empty() && ! base_layer.empty()) { + // turn base support into interface when it's contained in our holes + // (this way we get wider interface anchoring) + //FIXME The intention of the code below is unclear. One likely wanted to just merge small islands of base layers filling in the holes + // inside interface layers, but the code below fills just too much, see GH #4570 + Polygons islands = top_level_islands(interface_layer.layer->polygons); + polygons_append(interface_layer.layer->polygons, intersection(base_layer.layer->polygons, islands)); + base_layer.layer->polygons = diff(base_layer.layer->polygons, islands); + } +#endif + + // Top and bottom contacts, interface layers. + enum class InterfaceLayerType { TopContact, BottomContact, RaftContact, Interface, InterfaceAsBase }; + auto extrude_interface = [&](SupportGeneratorLayerExtruded &layer_ex, InterfaceLayerType interface_layer_type) { + if (! layer_ex.empty() && ! layer_ex.polygons_to_extrude().empty()) { + bool interface_as_base = interface_layer_type == InterfaceLayerType::InterfaceAsBase; + bool raft_contact = interface_layer_type == InterfaceLayerType::RaftContact; + //FIXME Bottom interfaces are extruded with the briding flow. Some bridging layers have its height slightly reduced, therefore + // the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b) + auto *filler = raft_contact ? filler_raft_contact : filler_interface.get(); + auto interface_flow = layer_ex.layer->bridging ? + Flow::bridging_flow(layer_ex.layer->height, support_params.support_material_bottom_interface_flow.nozzle_diameter()) : + (raft_contact ? &support_params.raft_interface_flow : + interface_as_base ? &support_params.support_material_flow : &support_params.support_material_interface_flow) + ->with_height(float(layer_ex.layer->height)); + filler->angle = interface_as_base ? + // If zero interface layers are configured, use the same angle as for the base layers. + angles[support_layer_id % angles.size()] : + // Use interface angle for the interface layers. + raft_contact ? + support_params.raft_interface_angle(support_layer.interface_id()) : + support_interface_angle; + double density = raft_contact ? support_params.raft_interface_density : interface_as_base ? support_params.support_density : support_params.interface_density; + filler->spacing = raft_contact ? support_params.raft_interface_flow.spacing() : + interface_as_base ? support_params.support_material_flow.spacing() : support_params.support_material_interface_flow.spacing(); + filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density)); + fill_expolygons_generate_paths( + // Destination + layer_ex.extrusions, + // Regions to fill + union_safety_offset_ex(layer_ex.polygons_to_extrude()), + // Filler and its parameters + filler, float(density), + // Extrusion parameters + ExtrusionRole::SupportMaterialInterface, interface_flow); + } + }; + const bool top_interfaces = config.support_material_interface_layers.value != 0; + const bool bottom_interfaces = top_interfaces && config.support_material_bottom_interface_layers != 0; + extrude_interface(top_contact_layer, raft_layer ? InterfaceLayerType::RaftContact : top_interfaces ? InterfaceLayerType::TopContact : InterfaceLayerType::InterfaceAsBase); + extrude_interface(bottom_contact_layer, bottom_interfaces ? InterfaceLayerType::BottomContact : InterfaceLayerType::InterfaceAsBase); + extrude_interface(interface_layer, top_interfaces ? InterfaceLayerType::Interface : InterfaceLayerType::InterfaceAsBase); + + // Base interface layers under soluble interfaces + if ( ! base_interface_layer.empty() && ! base_interface_layer.polygons_to_extrude().empty()) { + Fill *filler = filler_base_interface.get(); + //FIXME Bottom interfaces are extruded with the briding flow. Some bridging layers have its height slightly reduced, therefore + // the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b) + assert(! base_interface_layer.layer->bridging); + Flow interface_flow = support_params.support_material_flow.with_height(float(base_interface_layer.layer->height)); + filler->angle = support_interface_angle; + filler->spacing = support_params.support_material_interface_flow.spacing(); + filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_params.interface_density)); + fill_expolygons_generate_paths( + // Destination + base_interface_layer.extrusions, + //base_layer_interface.extrusions, + // Regions to fill + union_safety_offset_ex(base_interface_layer.polygons_to_extrude()), + // Filler and its parameters + filler, float(support_params.interface_density), + // Extrusion parameters + ExtrusionRole::SupportMaterial, interface_flow); + } + + // Base support or flange. + if (! base_layer.empty() && ! base_layer.polygons_to_extrude().empty()) { + Fill *filler = filler_support.get(); + filler->angle = angles[support_layer_id % angles.size()]; + // We don't use $base_flow->spacing because we need a constant spacing + // value that guarantees that all layers are correctly aligned. + assert(! base_layer.layer->bridging); + auto flow = support_params.support_material_flow.with_height(float(base_layer.layer->height)); + filler->spacing = support_params.support_material_flow.spacing(); + filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_params.support_density)); + float density = float(support_params.support_density); + bool sheath = support_params.with_sheath; + bool no_sort = false; + bool done = false; + if (base_layer.layer->bottom_z < EPSILON) { + // Base flange (the 1st layer). + filler = filler_first_layer; + filler->angle = Geometry::deg2rad(float(config.support_material_angle.value + 90.)); + density = float(config.raft_first_layer_density.value * 0.01); + flow = support_params.first_layer_flow; + // use the proper spacing for first layer as we don't need to align + // its pattern to the other layers + //FIXME When paralellizing, each thread shall have its own copy of the fillers. + filler->spacing = flow.spacing(); + filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density)); + sheath = true; + no_sort = true; + } else if (config.support_material_style == SupportMaterialStyle::smsOrganic) { + tree_supports_generate_paths(base_layer.extrusions, base_layer.polygons_to_extrude(), flow); + done = true; + } + if (! done) + fill_expolygons_with_sheath_generate_paths( + // Destination + base_layer.extrusions, + // Regions to fill + base_layer.polygons_to_extrude(), + // Filler and its parameters + filler, density, + // Extrusion parameters + ExtrusionRole::SupportMaterial, flow, + sheath, no_sort); + } + + // Merge base_interface_layers to base_layers to avoid unneccessary retractions + if (! base_layer.empty() && ! base_interface_layer.empty() && ! base_layer.polygons_to_extrude().empty() && ! base_interface_layer.polygons_to_extrude().empty() && + base_layer.could_merge(base_interface_layer)) + base_layer.merge(std::move(base_interface_layer)); + + layer_cache.add_nonempty_and_sort(); + + // Collect the support areas with this print_z into islands, as there is no need + // for retraction over these islands. + Polygons polys; + // Collect the extrusions, sorted by the bottom extrusion height. + for (LayerCacheItem &layer_cache_item : layer_cache.nonempty) { + // Collect islands to polys. + layer_cache_item.layer_extruded->polygons_append(polys); + // The print_z of the top contact surfaces and bottom_z of the bottom contact surfaces are "free" + // in a sense that they are not synchronized with other support layers. As the top and bottom contact surfaces + // are inflated to achieve a better anchoring, it may happen, that these surfaces will at least partially + // overlap in Z with another support layers, leading to over-extrusion. + // Mitigate the over-extrusion by modulating the extrusion rate over these regions. + // The print head will follow the same print_z, but the layer thickness will be reduced + // where it overlaps with another support layer. + //FIXME When printing a briging path, what is an equivalent height of the squished extrudate of the same width? + // Collect overlapping top/bottom surfaces. + layer_cache_item.overlapping.reserve(20); + coordf_t bottom_z = layer_cache_item.layer_extruded->layer->bottom_print_z() + EPSILON; + auto add_overlapping = [&layer_cache_item, bottom_z](const SupportGeneratorLayersPtr &layers, size_t idx_top) { + for (int i = int(idx_top) - 1; i >= 0 && layers[i]->print_z > bottom_z; -- i) + layer_cache_item.overlapping.push_back(layers[i]); + }; + add_overlapping(top_contacts, idx_layer_top_contact); + if (layer_cache_item.layer_extruded->layer->layer_type == SupporLayerType::BottomContact) { + // Bottom contact layer may overlap with a base layer, which may be changed to interface layer. + add_overlapping(intermediate_layers, idx_layer_intermediate); + add_overlapping(interface_layers, idx_layer_interface); + add_overlapping(base_interface_layers, idx_layer_base_interface); + } + // Order the layers by lexicographically by an increasing print_z and a decreasing layer height. + std::stable_sort(layer_cache_item.overlapping.begin(), layer_cache_item.overlapping.end(), [](auto *l1, auto *l2) { return *l1 < *l2; }); + } + assert(support_layer.support_islands.empty()); + if (! polys.empty()) { + support_layer.support_islands = union_ex(polys); + support_layer.support_islands_bboxes.reserve(support_layer.support_islands.size()); + for (const ExPolygon &expoly : support_layer.support_islands) + support_layer.support_islands_bboxes.emplace_back(get_extents(expoly).inflated(SCALED_EPSILON)); + } + } // for each support_layer_id + }); + + // Now modulate the support layer height in parallel. + tbb::parallel_for(tbb::blocked_range(n_raft_layers, support_layers.size()), + [&support_layers, &layer_caches] + (const tbb::blocked_range& range) { + for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id) { + SupportLayer &support_layer = *support_layers[support_layer_id]; + LayerCache &layer_cache = layer_caches[support_layer_id]; + // For all extrusion types at this print_z, ordered by decreasing layer height: + for (LayerCacheItem &layer_cache_item : layer_cache.nonempty) { + // Trim the extrusion height from the bottom by the overlapping layers. + modulate_extrusion_by_overlapping_layers(layer_cache_item.layer_extruded->extrusions, *layer_cache_item.layer_extruded->layer, layer_cache_item.overlapping); + support_layer.support_fills.append(std::move(layer_cache_item.layer_extruded->extrusions)); + } + } + }); + +#ifndef NDEBUG + struct Test { + static bool verify_nonempty(const ExtrusionEntityCollection *collection) { + for (const ExtrusionEntity *ee : collection->entities) { + if (const ExtrusionPath *path = dynamic_cast(ee)) + assert(! path->empty()); + else if (const ExtrusionMultiPath *multipath = dynamic_cast(ee)) + assert(! multipath->empty()); + else if (const ExtrusionEntityCollection *eecol = dynamic_cast(ee)) { + assert(! eecol->empty()); + return verify_nonempty(eecol); + } else + assert(false); + } + return true; + } + }; + for (const SupportLayer *support_layer : support_layers) + assert(Test::verify_nonempty(&support_layer->support_fills)); +#endif // NDEBUG +} + +/* +void PrintObjectSupportMaterial::clip_by_pillars( + const PrintObject &object, + LayersPtr &bottom_contacts, + LayersPtr &top_contacts, + LayersPtr &intermediate_contacts); + +{ + // this prevents supplying an empty point set to BoundingBox constructor + if (top_contacts.empty()) + return; + + coord_t pillar_size = scale_(PILLAR_SIZE); + coord_t pillar_spacing = scale_(PILLAR_SPACING); + + // A regular grid of pillars, filling the 2D bounding box. + Polygons grid; + { + // Rectangle with a side of 2.5x2.5mm. + Polygon pillar; + pillar.points.push_back(Point(0, 0)); + pillar.points.push_back(Point(pillar_size, 0)); + pillar.points.push_back(Point(pillar_size, pillar_size)); + pillar.points.push_back(Point(0, pillar_size)); + + // 2D bounding box of the projection of all contact polygons. + BoundingBox bbox; + for (LayersPtr::const_iterator it = top_contacts.begin(); it != top_contacts.end(); ++ it) + bbox.merge(get_extents((*it)->polygons)); + grid.reserve(size_t(ceil(bb.size()(0) / pillar_spacing)) * size_t(ceil(bb.size()(1) / pillar_spacing))); + for (coord_t x = bb.min(0); x <= bb.max(0) - pillar_size; x += pillar_spacing) { + for (coord_t y = bb.min(1); y <= bb.max(1) - pillar_size; y += pillar_spacing) { + grid.push_back(pillar); + for (size_t i = 0; i < pillar.points.size(); ++ i) + grid.back().points[i].translate(Point(x, y)); + } + } + } + + // add pillars to every layer + for my $i (0..n_support_z) { + $shape->[$i] = [ @$grid ]; + } + + // build capitals + for my $i (0..n_support_z) { + my $z = $support_z->[$i]; + + my $capitals = intersection( + $grid, + $contact->{$z} // [], + ); + + // work on one pillar at time (if any) to prevent the capitals from being merged + // but store the contact area supported by the capital because we need to make + // sure nothing is left + my $contact_supported_by_capitals = []; + foreach my $capital (@$capitals) { + // enlarge capital tops + $capital = offset([$capital], +($pillar_spacing - $pillar_size)/2); + push @$contact_supported_by_capitals, @$capital; + + for (my $j = $i-1; $j >= 0; $j--) { + my $jz = $support_z->[$j]; + $capital = offset($capital, -$self->interface_flow->scaled_width/2); + last if !@$capitals; + push @{ $shape->[$j] }, @$capital; + } + } + + // Capitals will not generally cover the whole contact area because there will be + // remainders. For now we handle this situation by projecting such unsupported + // areas to the ground, just like we would do with a normal support. + my $contact_not_supported_by_capitals = diff( + $contact->{$z} // [], + $contact_supported_by_capitals, + ); + if (@$contact_not_supported_by_capitals) { + for (my $j = $i-1; $j >= 0; $j--) { + push @{ $shape->[$j] }, @$contact_not_supported_by_capitals; + } + } + } +} + +sub clip_with_shape { + my ($self, $support, $shape) = @_; + + foreach my $i (keys %$support) { + // don't clip bottom layer with shape so that we + // can generate a continuous base flange + // also don't clip raft layers + next if $i == 0; + next if $i < $self->object_config->raft_layers; + $support->{$i} = intersection( + $support->{$i}, + $shape->[$i], + ); + } +} +*/ + +} // namespace Slic3r diff --git a/src/libslic3r/Support/SupportCommon.hpp b/src/libslic3r/Support/SupportCommon.hpp new file mode 100644 index 000000000..4eabce772 --- /dev/null +++ b/src/libslic3r/Support/SupportCommon.hpp @@ -0,0 +1,138 @@ +#ifndef slic3r_SupportCommon_hpp_ +#define slic3r_SupportCommon_hpp_ + +#include "../Polygon.hpp" +#include "SupportLayer.hpp" +#include "SupportParameters.hpp" + +namespace Slic3r { + +class PrintObject; +class SupportLayer; + +namespace FFFSupport { + +// Remove bridges from support contact areas. +// To be called if PrintObjectConfig::dont_support_bridges. +void remove_bridges_from_contacts( + const PrintConfig &print_config, + const Layer &lower_layer, + const LayerRegion &layerm, + float fw, + Polygons &contact_polygons); + +// Generate raft layers, also expand the 1st support layer +// in case there is no raft layer to improve support adhesion. +SupportGeneratorLayersPtr generate_raft_base( + const PrintObject &object, + const SupportParameters &support_params, + const SlicingParameters &slicing_params, + const SupportGeneratorLayersPtr &top_contacts, + const SupportGeneratorLayersPtr &interface_layers, + const SupportGeneratorLayersPtr &base_interface_layers, + const SupportGeneratorLayersPtr &base_layers, + SupportGeneratorLayerStorage &layer_storage); + +// returns sorted layers +SupportGeneratorLayersPtr generate_support_layers( + PrintObject &object, + const SupportGeneratorLayersPtr &raft_layers, + const SupportGeneratorLayersPtr &bottom_contacts, + const SupportGeneratorLayersPtr &top_contacts, + const SupportGeneratorLayersPtr &intermediate_layers, + const SupportGeneratorLayersPtr &interface_layers, + const SupportGeneratorLayersPtr &base_interface_layers); + +// Produce the support G-code. +// Used by both classic and tree supports. +void generate_support_toolpaths( + SupportLayerPtrs &support_layers, + const PrintObjectConfig &config, + const SupportParameters &support_params, + const SlicingParameters &slicing_params, + const SupportGeneratorLayersPtr &raft_layers, + const SupportGeneratorLayersPtr &bottom_contacts, + const SupportGeneratorLayersPtr &top_contacts, + const SupportGeneratorLayersPtr &intermediate_layers, + const SupportGeneratorLayersPtr &interface_layers, + const SupportGeneratorLayersPtr &base_interface_layers); + +// FN_HIGHER_EQUAL: the provided object pointer has a Z value >= of an internal threshold. +// Find the first item with Z value >= of an internal threshold of fn_higher_equal. +// If no vec item with Z value >= of an internal threshold of fn_higher_equal is found, return vec.size() +// If the initial idx is size_t(-1), then use binary search. +// Otherwise search linearly upwards. +template +IndexType idx_higher_or_equal(IteratorType begin, IteratorType end, IndexType idx, FN_HIGHER_EQUAL fn_higher_equal) +{ + auto size = int(end - begin); + if (size == 0) { + idx = 0; + } else if (idx == IndexType(-1)) { + // First of the batch of layers per thread pool invocation. Use binary search. + int idx_low = 0; + int idx_high = std::max(0, size - 1); + while (idx_low + 1 < idx_high) { + int idx_mid = (idx_low + idx_high) / 2; + if (fn_higher_equal(begin[idx_mid])) + idx_high = idx_mid; + else + idx_low = idx_mid; + } + idx = fn_higher_equal(begin[idx_low]) ? idx_low : + (fn_higher_equal(begin[idx_high]) ? idx_high : size); + } else { + // For the other layers of this batch of layers, search incrementally, which is cheaper than the binary search. + while (int(idx) < size && ! fn_higher_equal(begin[idx])) + ++ idx; + } + return idx; +} +template +IndexType idx_higher_or_equal(const std::vector& vec, IndexType idx, FN_HIGHER_EQUAL fn_higher_equal) +{ + return idx_higher_or_equal(vec.begin(), vec.end(), idx, fn_higher_equal); +} + +// FN_LOWER_EQUAL: the provided object pointer has a Z value <= of an internal threshold. +// Find the first item with Z value <= of an internal threshold of fn_lower_equal. +// If no vec item with Z value <= of an internal threshold of fn_lower_equal is found, return -1. +// If the initial idx is < -1, then use binary search. +// Otherwise search linearly downwards. +template +int idx_lower_or_equal(IT begin, IT end, int idx, FN_LOWER_EQUAL fn_lower_equal) +{ + auto size = int(end - begin); + if (size == 0) { + idx = -1; + } else if (idx < -1) { + // First of the batch of layers per thread pool invocation. Use binary search. + int idx_low = 0; + int idx_high = std::max(0, size - 1); + while (idx_low + 1 < idx_high) { + int idx_mid = (idx_low + idx_high) / 2; + if (fn_lower_equal(begin[idx_mid])) + idx_low = idx_mid; + else + idx_high = idx_mid; + } + idx = fn_lower_equal(begin[idx_high]) ? idx_high : + (fn_lower_equal(begin[idx_low ]) ? idx_low : -1); + } else { + // For the other layers of this batch of layers, search incrementally, which is cheaper than the binary search. + while (idx >= 0 && ! fn_lower_equal(begin[idx])) + -- idx; + } + return idx; +} +template +int idx_lower_or_equal(const std::vector &vec, int idx, FN_LOWER_EQUAL fn_lower_equal) +{ + return idx_lower_or_equal(vec.begin(), vec.end(), idx, fn_lower_equal); +} + +} // namespace FFFSupport + +} // namespace Slic3r + +#endif /* slic3r_SupportCommon_hpp_ */ diff --git a/src/libslic3r/Support/SupportDebug.cpp b/src/libslic3r/Support/SupportDebug.cpp new file mode 100644 index 000000000..8cec806c1 --- /dev/null +++ b/src/libslic3r/Support/SupportDebug.cpp @@ -0,0 +1,108 @@ +#if 1 //#ifdef SLIC3R_DEBUG + +#include "ClipperUtils.hpp" +#include "SVG.hpp" + +#include "../Layer.hpp" +#include "SupportLayer.hpp" + +namespace Slic3r::FFFSupport { + +const char* support_surface_type_to_color_name(const SupporLayerType surface_type) +{ + switch (surface_type) { + case SupporLayerType::TopContact: return "rgb(255,0,0)"; // "red"; + case SupporLayerType::TopInterface: return "rgb(0,255,0)"; // "green"; + case SupporLayerType::Base: return "rgb(0,0,255)"; // "blue"; + case SupporLayerType::BottomInterface:return "rgb(255,255,128)"; // yellow + case SupporLayerType::BottomContact: return "rgb(255,0,255)"; // magenta + case SupporLayerType::RaftInterface: return "rgb(0,255,255)"; + case SupporLayerType::RaftBase: return "rgb(128,128,128)"; + case SupporLayerType::Unknown: return "rgb(128,0,0)"; // maroon + default: return "rgb(64,64,64)"; + }; +} + +Point export_support_surface_type_legend_to_svg_box_size() +{ + return Point(scale_(1.+10.*8.), scale_(3.)); +} + +void export_support_surface_type_legend_to_svg(SVG &svg, const Point &pos) +{ + // 1st row + coord_t pos_x0 = pos(0) + scale_(1.); + coord_t pos_x = pos_x0; + coord_t pos_y = pos(1) + scale_(1.5); + coord_t step_x = scale_(10.); + svg.draw_legend(Point(pos_x, pos_y), "top contact" , support_surface_type_to_color_name(SupporLayerType::TopContact)); + pos_x += step_x; + svg.draw_legend(Point(pos_x, pos_y), "top iface" , support_surface_type_to_color_name(SupporLayerType::TopInterface)); + pos_x += step_x; + svg.draw_legend(Point(pos_x, pos_y), "base" , support_surface_type_to_color_name(SupporLayerType::Base)); + pos_x += step_x; + svg.draw_legend(Point(pos_x, pos_y), "bottom iface" , support_surface_type_to_color_name(SupporLayerType::BottomInterface)); + pos_x += step_x; + svg.draw_legend(Point(pos_x, pos_y), "bottom contact" , support_surface_type_to_color_name(SupporLayerType::BottomContact)); + // 2nd row + pos_x = pos_x0; + pos_y = pos(1)+scale_(2.8); + svg.draw_legend(Point(pos_x, pos_y), "raft interface" , support_surface_type_to_color_name(SupporLayerType::RaftInterface)); + pos_x += step_x; + svg.draw_legend(Point(pos_x, pos_y), "raft base" , support_surface_type_to_color_name(SupporLayerType::RaftBase)); + pos_x += step_x; + svg.draw_legend(Point(pos_x, pos_y), "unknown" , support_surface_type_to_color_name(SupporLayerType::Unknown)); + pos_x += step_x; + svg.draw_legend(Point(pos_x, pos_y), "intermediate" , support_surface_type_to_color_name(SupporLayerType::Intermediate)); +} + +void export_print_z_polygons_to_svg(const char *path, SupportGeneratorLayer ** const layers, int n_layers) +{ + BoundingBox bbox; + for (int i = 0; i < n_layers; ++ i) + bbox.merge(get_extents(layers[i]->polygons)); + Point legend_size = export_support_surface_type_legend_to_svg_box_size(); + Point legend_pos(bbox.min(0), bbox.max(1)); + bbox.merge(Point(std::max(bbox.min(0) + legend_size(0), bbox.max(0)), bbox.max(1) + legend_size(1))); + SVG svg(path, bbox); + const float transparency = 0.5f; + for (int i = 0; i < n_layers; ++ i) + svg.draw(union_ex(layers[i]->polygons), support_surface_type_to_color_name(layers[i]->layer_type), transparency); + for (int i = 0; i < n_layers; ++ i) + svg.draw(to_polylines(layers[i]->polygons), support_surface_type_to_color_name(layers[i]->layer_type)); + export_support_surface_type_legend_to_svg(svg, legend_pos); + svg.Close(); +} + +void export_print_z_polygons_and_extrusions_to_svg( + const char *path, + SupportGeneratorLayer ** const layers, + int n_layers, + SupportLayer &support_layer) +{ + BoundingBox bbox; + for (int i = 0; i < n_layers; ++ i) + bbox.merge(get_extents(layers[i]->polygons)); + Point legend_size = export_support_surface_type_legend_to_svg_box_size(); + Point legend_pos(bbox.min(0), bbox.max(1)); + bbox.merge(Point(std::max(bbox.min(0) + legend_size(0), bbox.max(0)), bbox.max(1) + legend_size(1))); + SVG svg(path, bbox); + const float transparency = 0.5f; + for (int i = 0; i < n_layers; ++ i) + svg.draw(union_ex(layers[i]->polygons), support_surface_type_to_color_name(layers[i]->layer_type), transparency); + for (int i = 0; i < n_layers; ++ i) + svg.draw(to_polylines(layers[i]->polygons), support_surface_type_to_color_name(layers[i]->layer_type)); + + Polygons polygons_support, polygons_interface; + support_layer.support_fills.polygons_covered_by_width(polygons_support, float(SCALED_EPSILON)); +// support_layer.support_interface_fills.polygons_covered_by_width(polygons_interface, SCALED_EPSILON); + svg.draw(union_ex(polygons_support), "brown"); + svg.draw(union_ex(polygons_interface), "black"); + + export_support_surface_type_legend_to_svg(svg, legend_pos); + svg.Close(); +} + +} // namespace Slic3r + +#endif /* SLIC3R_DEBUG */ diff --git a/src/libslic3r/Support/SupportDebug.hpp b/src/libslic3r/Support/SupportDebug.hpp new file mode 100644 index 000000000..22a43bc4e --- /dev/null +++ b/src/libslic3r/Support/SupportDebug.hpp @@ -0,0 +1,18 @@ +#ifndef slic3r_SupportCommon_hpp_ +#define slic3r_SupportCommon_hpp_ + +namespace Slic3r { + +class SupportGeneratorLayer; +class SupportLayer; + +namespace FFFSupport { + +void export_print_z_polygons_to_svg(const char *path, SupportGeneratorLayer ** const layers, size_t n_layers); +void export_print_z_polygons_and_extrusions_to_svg(const char *path, SupportGeneratorLayer ** const layers, size_t n_layers, SupportLayer& support_layer); + +} // namespace FFFSupport + +} // namespace Slic3r + +#endif /* slic3r_SupportCommon_hpp_ */ diff --git a/src/libslic3r/Support/SupportLayer.hpp b/src/libslic3r/Support/SupportLayer.hpp index 913e28136..82265881c 100644 --- a/src/libslic3r/Support/SupportLayer.hpp +++ b/src/libslic3r/Support/SupportLayer.hpp @@ -3,8 +3,10 @@ #include #include +// for Slic3r::deque +#include "../libslic3r.h" -namespace Slic3r { +namespace Slic3r::FFFSupport { // Support layer type to be used by SupportGeneratorLayer. This type carries a much more detailed information // about the support layer type than the final support layers stored in a PrintObject. @@ -111,18 +113,30 @@ public: // Layers are allocated and owned by a deque. Once a layer is allocated, it is maintained // up to the end of a generate() method. The layer storage may be replaced by an allocator class in the future, // which would allocate layers by multiple chunks. -#if 0 class SupportGeneratorLayerStorage { public: + SupportGeneratorLayer& allocate_unguarded(SupporLayerType layer_type) { + m_storage.emplace_back(); + m_storage.back().layer_type = layer_type; + return m_storage.back(); + } + + SupportGeneratorLayer& allocate(SupporLayerType layer_type) + { + m_mutex.lock(); + m_storage.emplace_back(); + SupportGeneratorLayer *layer_new = &m_storage.back(); + m_mutex.unlock(); + layer_new->layer_type = layer_type; + return *layer_new; + } + private: template using Allocator = tbb::scalable_allocator; - Slic3r::deque> m_data; + Slic3r::deque> m_storage; tbb::spin_mutex m_mutex; }; -#else -#endif -using SupportGeneratorLayerStorage = std::deque; using SupportGeneratorLayersPtr = std::vector; } // namespace Slic3r diff --git a/src/libslic3r/Support/SupportParameters.cpp b/src/libslic3r/Support/SupportParameters.cpp new file mode 100644 index 000000000..1c7f860b8 --- /dev/null +++ b/src/libslic3r/Support/SupportParameters.cpp @@ -0,0 +1,116 @@ +#include "../Print.hpp" +#include "../PrintConfig.hpp" +#include "../Slicing.hpp" +#include "SupportParameters.hpp" + +namespace Slic3r::FFFSupport { + +SupportParameters::SupportParameters(const PrintObject &object) +{ + const PrintConfig &print_config = object.print()->config(); + const PrintObjectConfig &object_config = object.config(); + const SlicingParameters &slicing_params = object.slicing_parameters(); + + this->first_layer_flow = Slic3r::support_material_1st_layer_flow(&object, float(slicing_params.first_print_layer_height)); + this->support_material_flow = Slic3r::support_material_flow(&object, float(slicing_params.layer_height)); + this->support_material_interface_flow = Slic3r::support_material_interface_flow(&object, float(slicing_params.layer_height)); + this->raft_interface_flow = support_material_interface_flow; + + // Calculate a minimum support layer height as a minimum over all extruders, but not smaller than 10um. + this->support_layer_height_min = scaled(0.01); + for (auto lh : print_config.min_layer_height.values) + this->support_layer_height_min = std::min(this->support_layer_height_min, std::max(0.01, lh)); + for (auto layer : object.layers()) + this->support_layer_height_min = std::min(this->support_layer_height_min, std::max(0.01, layer->height)); + + if (object_config.support_material_interface_layers.value == 0) { + // No interface layers allowed, print everything with the base support pattern. + this->support_material_interface_flow = this->support_material_flow; + } + + // Evaluate the XY gap between the object outer perimeters and the support structures. + // Evaluate the XY gap between the object outer perimeters and the support structures. + coordf_t external_perimeter_width = 0.; + coordf_t bridge_flow_ratio = 0; + for (size_t region_id = 0; region_id < object.num_printing_regions(); ++ region_id) { + const PrintRegion ®ion = object.printing_region(region_id); + external_perimeter_width = std::max(external_perimeter_width, coordf_t(region.flow(object, frExternalPerimeter, slicing_params.layer_height).width())); + bridge_flow_ratio += region.config().bridge_flow_ratio; + } + this->gap_xy = object_config.support_material_xy_spacing.get_abs_value(external_perimeter_width); + bridge_flow_ratio /= object.num_printing_regions(); + + this->support_material_bottom_interface_flow = slicing_params.soluble_interface || ! object_config.thick_bridges ? + this->support_material_interface_flow.with_flow_ratio(bridge_flow_ratio) : + Flow::bridging_flow(bridge_flow_ratio * this->support_material_interface_flow.nozzle_diameter(), this->support_material_interface_flow.nozzle_diameter()); + + this->can_merge_support_regions = object_config.support_material_extruder.value == object_config.support_material_interface_extruder.value; + if (!this->can_merge_support_regions && (object_config.support_material_extruder.value == 0 || object_config.support_material_interface_extruder.value == 0)) { + // One of the support extruders is of "don't care" type. + auto object_extruders = object.object_extruders(); + if (object_extruders.size() == 1 && + *object_extruders.begin() == std::max(object_config.support_material_extruder.value, object_config.support_material_interface_extruder.value)) + // Object is printed with the same extruder as the support. + this->can_merge_support_regions = true; + } + + + double interface_spacing = object_config.support_material_interface_spacing.value + this->support_material_interface_flow.spacing(); + this->interface_density = std::min(1., this->support_material_interface_flow.spacing() / interface_spacing); + double raft_interface_spacing = object_config.support_material_interface_spacing.value + this->raft_interface_flow.spacing(); + this->raft_interface_density = std::min(1., this->raft_interface_flow.spacing() / raft_interface_spacing); + double support_spacing = object_config.support_material_spacing.value + this->support_material_flow.spacing(); + this->support_density = std::min(1., this->support_material_flow.spacing() / support_spacing); + if (object_config.support_material_interface_layers.value == 0) { + // No interface layers allowed, print everything with the base support pattern. + this->interface_density = this->support_density; + } + + SupportMaterialPattern support_pattern = object_config.support_material_pattern; + this->with_sheath = object_config.support_material_with_sheath; + this->base_fill_pattern = + support_pattern == smpHoneycomb ? ipHoneycomb : + this->support_density > 0.95 || this->with_sheath ? ipRectilinear : ipSupportBase; + this->interface_fill_pattern = (this->interface_density > 0.95 ? ipRectilinear : ipSupportBase); + this->raft_interface_fill_pattern = this->raft_interface_density > 0.95 ? ipRectilinear : ipSupportBase; + this->contact_fill_pattern = + (object_config.support_material_interface_pattern == smipAuto && slicing_params.soluble_interface) || + object_config.support_material_interface_pattern == smipConcentric ? + ipConcentric : + (this->interface_density > 0.95 ? ipRectilinear : ipSupportBase); + + this->base_angle = Geometry::deg2rad(float(object_config.support_material_angle.value)); + this->interface_angle = Geometry::deg2rad(float(object_config.support_material_angle.value + 90.)); + this->raft_angle_1st_layer = 0.f; + this->raft_angle_base = 0.f; + this->raft_angle_interface = 0.f; + if (slicing_params.base_raft_layers > 1) { + assert(slicing_params.raft_layers() >= 4); + // There are all raft layer types (1st layer, base, interface & contact layers) available. + this->raft_angle_1st_layer = this->interface_angle; + this->raft_angle_base = this->base_angle; + this->raft_angle_interface = this->interface_angle; + if ((slicing_params.interface_raft_layers & 1) == 0) + // Allign the 1st raft interface layer so that the object 1st layer is hatched perpendicularly to the raft contact interface. + this->raft_angle_interface += float(0.5 * M_PI); + } else if (slicing_params.base_raft_layers == 1 || slicing_params.interface_raft_layers > 1) { + assert(slicing_params.raft_layers() == 2 || slicing_params.raft_layers() == 3); + // 1st layer, interface & contact layers available. + this->raft_angle_1st_layer = this->base_angle; + this->raft_angle_interface = this->interface_angle + 0.5 * M_PI; + } else if (slicing_params.interface_raft_layers == 1) { + // Only the contact raft layer is non-empty, which will be printed as the 1st layer. + assert(slicing_params.base_raft_layers == 0); + assert(slicing_params.interface_raft_layers == 1); + assert(slicing_params.raft_layers() == 1); + this->raft_angle_1st_layer = float(0.5 * M_PI); + this->raft_angle_interface = this->raft_angle_1st_layer; + } else { + // No raft. + assert(slicing_params.base_raft_layers == 0); + assert(slicing_params.interface_raft_layers == 0); + assert(slicing_params.raft_layers() == 0); + } +} + +} // namespace Slic3r diff --git a/src/libslic3r/Support/SupportParameters.hpp b/src/libslic3r/Support/SupportParameters.hpp index fd4f1f8b7..904e8ffe2 100644 --- a/src/libslic3r/Support/SupportParameters.hpp +++ b/src/libslic3r/Support/SupportParameters.hpp @@ -9,6 +9,8 @@ namespace Slic3r { class PrintObject; enum InfillPattern : int; +namespace FFFSupport { + struct SupportParameters { SupportParameters(const PrintObject &object); @@ -61,6 +63,8 @@ struct SupportParameters { { return this->raft_angle_interface + ((interface_id & 1) ? float(- M_PI / 4.) : float(+ M_PI / 4.)); } }; +} // namespace FFFSupport + } // namespace Slic3r #endif /* slic3r_SupportParameters_hpp_ */ diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index 224216466..40221ec2a 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -8,6 +8,8 @@ #include "Point.hpp" #include "MutablePolygon.hpp" +#include "Support/SupportCommon.hpp" + #include #include @@ -16,7 +18,6 @@ #include #include -#include #include #define SUPPORT_USE_AGG_RASTERIZER @@ -43,12 +44,10 @@ #include "SVG.hpp" #endif -#pragma message ("TODO: Wrap svg usages in DEBUG ifdef and remove the following include") -#include "SVG.hpp" - -// #undef NDEBUG #include +using namespace Slic3r::FFFSupport; + namespace Slic3r { // how much we extend support around the actual contact area @@ -66,103 +65,6 @@ namespace Slic3r { //#define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 1.5 #define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtSquare, 0. -#if 1 //#ifdef SLIC3R_DEBUG -const char* support_surface_type_to_color_name(const SupporLayerType surface_type) -{ - switch (surface_type) { - case SupporLayerType::TopContact: return "rgb(255,0,0)"; // "red"; - case SupporLayerType::TopInterface: return "rgb(0,255,0)"; // "green"; - case SupporLayerType::Base: return "rgb(0,0,255)"; // "blue"; - case SupporLayerType::BottomInterface:return "rgb(255,255,128)"; // yellow - case SupporLayerType::BottomContact: return "rgb(255,0,255)"; // magenta - case SupporLayerType::RaftInterface: return "rgb(0,255,255)"; - case SupporLayerType::RaftBase: return "rgb(128,128,128)"; - case SupporLayerType::Unknown: return "rgb(128,0,0)"; // maroon - default: return "rgb(64,64,64)"; - }; -} - -Point export_support_surface_type_legend_to_svg_box_size() -{ - return Point(scale_(1.+10.*8.), scale_(3.)); -} - -void export_support_surface_type_legend_to_svg(SVG &svg, const Point &pos) -{ - // 1st row - coord_t pos_x0 = pos(0) + scale_(1.); - coord_t pos_x = pos_x0; - coord_t pos_y = pos(1) + scale_(1.5); - coord_t step_x = scale_(10.); - svg.draw_legend(Point(pos_x, pos_y), "top contact" , support_surface_type_to_color_name(SupporLayerType::TopContact)); - pos_x += step_x; - svg.draw_legend(Point(pos_x, pos_y), "top iface" , support_surface_type_to_color_name(SupporLayerType::TopInterface)); - pos_x += step_x; - svg.draw_legend(Point(pos_x, pos_y), "base" , support_surface_type_to_color_name(SupporLayerType::Base)); - pos_x += step_x; - svg.draw_legend(Point(pos_x, pos_y), "bottom iface" , support_surface_type_to_color_name(SupporLayerType::BottomInterface)); - pos_x += step_x; - svg.draw_legend(Point(pos_x, pos_y), "bottom contact" , support_surface_type_to_color_name(SupporLayerType::BottomContact)); - // 2nd row - pos_x = pos_x0; - pos_y = pos(1)+scale_(2.8); - svg.draw_legend(Point(pos_x, pos_y), "raft interface" , support_surface_type_to_color_name(SupporLayerType::RaftInterface)); - pos_x += step_x; - svg.draw_legend(Point(pos_x, pos_y), "raft base" , support_surface_type_to_color_name(SupporLayerType::RaftBase)); - pos_x += step_x; - svg.draw_legend(Point(pos_x, pos_y), "unknown" , support_surface_type_to_color_name(SupporLayerType::Unknown)); - pos_x += step_x; - svg.draw_legend(Point(pos_x, pos_y), "intermediate" , support_surface_type_to_color_name(SupporLayerType::Intermediate)); -} - -void export_print_z_polygons_to_svg(const char *path, SupportGeneratorLayer ** const layers, int n_layers) -{ - BoundingBox bbox; - for (int i = 0; i < n_layers; ++ i) - bbox.merge(get_extents(layers[i]->polygons)); - Point legend_size = export_support_surface_type_legend_to_svg_box_size(); - Point legend_pos(bbox.min(0), bbox.max(1)); - bbox.merge(Point(std::max(bbox.min(0) + legend_size(0), bbox.max(0)), bbox.max(1) + legend_size(1))); - SVG svg(path, bbox); - const float transparency = 0.5f; - for (int i = 0; i < n_layers; ++ i) - svg.draw(union_ex(layers[i]->polygons), support_surface_type_to_color_name(layers[i]->layer_type), transparency); - for (int i = 0; i < n_layers; ++ i) - svg.draw(to_polylines(layers[i]->polygons), support_surface_type_to_color_name(layers[i]->layer_type)); - export_support_surface_type_legend_to_svg(svg, legend_pos); - svg.Close(); -} - -void export_print_z_polygons_and_extrusions_to_svg( - const char *path, - SupportGeneratorLayer ** const layers, - int n_layers, - SupportLayer &support_layer) -{ - BoundingBox bbox; - for (int i = 0; i < n_layers; ++ i) - bbox.merge(get_extents(layers[i]->polygons)); - Point legend_size = export_support_surface_type_legend_to_svg_box_size(); - Point legend_pos(bbox.min(0), bbox.max(1)); - bbox.merge(Point(std::max(bbox.min(0) + legend_size(0), bbox.max(0)), bbox.max(1) + legend_size(1))); - SVG svg(path, bbox); - const float transparency = 0.5f; - for (int i = 0; i < n_layers; ++ i) - svg.draw(union_ex(layers[i]->polygons), support_surface_type_to_color_name(layers[i]->layer_type), transparency); - for (int i = 0; i < n_layers; ++ i) - svg.draw(to_polylines(layers[i]->polygons), support_surface_type_to_color_name(layers[i]->layer_type)); - - Polygons polygons_support, polygons_interface; - support_layer.support_fills.polygons_covered_by_width(polygons_support, float(SCALED_EPSILON)); -// support_layer.support_interface_fills.polygons_covered_by_width(polygons_interface, SCALED_EPSILON); - svg.draw(union_ex(polygons_support), "brown"); - svg.draw(union_ex(polygons_interface), "black"); - - export_support_surface_type_legend_to_svg(svg, legend_pos); - svg.Close(); -} -#endif /* SLIC3R_DEBUG */ - #ifdef SUPPORT_USE_AGG_RASTERIZER static std::vector rasterize_polygons(const Vec2i &grid_size, const double pixel_size, const Point &left_bottom, const Polygons &polygons) { @@ -326,114 +228,6 @@ static Polygons contours_simplified(const Vec2i &grid_size, const double pixel_s } #endif // SUPPORT_USE_AGG_RASTERIZER -SupportParameters::SupportParameters(const PrintObject &object) -{ - const PrintConfig &print_config = object.print()->config(); - const PrintObjectConfig &object_config = object.config(); - const SlicingParameters &slicing_params = object.slicing_parameters(); - - this->first_layer_flow = Slic3r::support_material_1st_layer_flow(&object, float(slicing_params.first_print_layer_height)); - this->support_material_flow = Slic3r::support_material_flow(&object, float(slicing_params.layer_height)); - this->support_material_interface_flow = Slic3r::support_material_interface_flow(&object, float(slicing_params.layer_height)); - this->raft_interface_flow = support_material_interface_flow; - - // Calculate a minimum support layer height as a minimum over all extruders, but not smaller than 10um. - this->support_layer_height_min = scaled(0.01); - for (auto lh : print_config.min_layer_height.values) - this->support_layer_height_min = std::min(this->support_layer_height_min, std::max(0.01, lh)); - for (auto layer : object.layers()) - this->support_layer_height_min = std::min(this->support_layer_height_min, std::max(0.01, layer->height)); - - if (object_config.support_material_interface_layers.value == 0) { - // No interface layers allowed, print everything with the base support pattern. - this->support_material_interface_flow = this->support_material_flow; - } - - // Evaluate the XY gap between the object outer perimeters and the support structures. - // Evaluate the XY gap between the object outer perimeters and the support structures. - coordf_t external_perimeter_width = 0.; - coordf_t bridge_flow_ratio = 0; - for (size_t region_id = 0; region_id < object.num_printing_regions(); ++ region_id) { - const PrintRegion ®ion = object.printing_region(region_id); - external_perimeter_width = std::max(external_perimeter_width, coordf_t(region.flow(object, frExternalPerimeter, slicing_params.layer_height).width())); - bridge_flow_ratio += region.config().bridge_flow_ratio; - } - this->gap_xy = object_config.support_material_xy_spacing.get_abs_value(external_perimeter_width); - bridge_flow_ratio /= object.num_printing_regions(); - - this->support_material_bottom_interface_flow = slicing_params.soluble_interface || ! object_config.thick_bridges ? - this->support_material_interface_flow.with_flow_ratio(bridge_flow_ratio) : - Flow::bridging_flow(bridge_flow_ratio * this->support_material_interface_flow.nozzle_diameter(), this->support_material_interface_flow.nozzle_diameter()); - - this->can_merge_support_regions = object_config.support_material_extruder.value == object_config.support_material_interface_extruder.value; - if (!this->can_merge_support_regions && (object_config.support_material_extruder.value == 0 || object_config.support_material_interface_extruder.value == 0)) { - // One of the support extruders is of "don't care" type. - auto object_extruders = object.object_extruders(); - if (object_extruders.size() == 1 && - *object_extruders.begin() == std::max(object_config.support_material_extruder.value, object_config.support_material_interface_extruder.value)) - // Object is printed with the same extruder as the support. - this->can_merge_support_regions = true; - } - - - double interface_spacing = object_config.support_material_interface_spacing.value + this->support_material_interface_flow.spacing(); - this->interface_density = std::min(1., this->support_material_interface_flow.spacing() / interface_spacing); - double raft_interface_spacing = object_config.support_material_interface_spacing.value + this->raft_interface_flow.spacing(); - this->raft_interface_density = std::min(1., this->raft_interface_flow.spacing() / raft_interface_spacing); - double support_spacing = object_config.support_material_spacing.value + this->support_material_flow.spacing(); - this->support_density = std::min(1., this->support_material_flow.spacing() / support_spacing); - if (object_config.support_material_interface_layers.value == 0) { - // No interface layers allowed, print everything with the base support pattern. - this->interface_density = this->support_density; - } - - SupportMaterialPattern support_pattern = object_config.support_material_pattern; - this->with_sheath = object_config.support_material_with_sheath; - this->base_fill_pattern = - support_pattern == smpHoneycomb ? ipHoneycomb : - this->support_density > 0.95 || this->with_sheath ? ipRectilinear : ipSupportBase; - this->interface_fill_pattern = (this->interface_density > 0.95 ? ipRectilinear : ipSupportBase); - this->raft_interface_fill_pattern = this->raft_interface_density > 0.95 ? ipRectilinear : ipSupportBase; - this->contact_fill_pattern = - (object_config.support_material_interface_pattern == smipAuto && slicing_params.soluble_interface) || - object_config.support_material_interface_pattern == smipConcentric ? - ipConcentric : - (this->interface_density > 0.95 ? ipRectilinear : ipSupportBase); - - this->base_angle = Geometry::deg2rad(float(object_config.support_material_angle.value)); - this->interface_angle = Geometry::deg2rad(float(object_config.support_material_angle.value + 90.)); - this->raft_angle_1st_layer = 0.f; - this->raft_angle_base = 0.f; - this->raft_angle_interface = 0.f; - if (slicing_params.base_raft_layers > 1) { - assert(slicing_params.raft_layers() >= 4); - // There are all raft layer types (1st layer, base, interface & contact layers) available. - this->raft_angle_1st_layer = this->interface_angle; - this->raft_angle_base = this->base_angle; - this->raft_angle_interface = this->interface_angle; - if ((slicing_params.interface_raft_layers & 1) == 0) - // Allign the 1st raft interface layer so that the object 1st layer is hatched perpendicularly to the raft contact interface. - this->raft_angle_interface += float(0.5 * M_PI); - } else if (slicing_params.base_raft_layers == 1 || slicing_params.interface_raft_layers > 1) { - assert(slicing_params.raft_layers() == 2 || slicing_params.raft_layers() == 3); - // 1st layer, interface & contact layers available. - this->raft_angle_1st_layer = this->base_angle; - this->raft_angle_interface = this->interface_angle + 0.5 * M_PI; - } else if (slicing_params.interface_raft_layers == 1) { - // Only the contact raft layer is non-empty, which will be printed as the 1st layer. - assert(slicing_params.base_raft_layers == 0); - assert(slicing_params.interface_raft_layers == 1); - assert(slicing_params.raft_layers() == 1); - this->raft_angle_1st_layer = float(0.5 * M_PI); - this->raft_angle_interface = this->raft_angle_1st_layer; - } else { - // No raft. - assert(slicing_params.base_raft_layers == 0); - assert(slicing_params.interface_raft_layers == 0); - assert(slicing_params.raft_layers() == 0); - } -} - PrintObjectSupportMaterial::PrintObjectSupportMaterial(const PrintObject *object, const SlicingParameters &slicing_params) : m_print_config (&object->print()->config()), m_object_config (&object->config()), @@ -442,39 +236,6 @@ PrintObjectSupportMaterial::PrintObjectSupportMaterial(const PrintObject *object { } -// Using the std::deque as an allocator. -inline SupportGeneratorLayer& layer_allocate( - std::deque &layer_storage, - SupporLayerType layer_type) -{ - layer_storage.push_back(SupportGeneratorLayer()); - layer_storage.back().layer_type = layer_type; - return layer_storage.back(); -} - -inline SupportGeneratorLayer& layer_allocate( - std::deque &layer_storage, - tbb::spin_mutex &layer_storage_mutex, - SupporLayerType layer_type) -{ - layer_storage_mutex.lock(); - layer_storage.push_back(SupportGeneratorLayer()); - SupportGeneratorLayer *layer_new = &layer_storage.back(); - layer_storage_mutex.unlock(); - layer_new->layer_type = layer_type; - return *layer_new; -} - -inline void layers_append(SupportGeneratorLayersPtr &dst, const SupportGeneratorLayersPtr &src) -{ - dst.insert(dst.end(), src.begin(), src.end()); -} - -// Support layer that is covered by some form of dense interface. -static constexpr const std::initializer_list support_types_interface { - SupporLayerType::RaftInterface, SupporLayerType::BottomContact, SupporLayerType::BottomInterface, SupporLayerType::TopContact, SupporLayerType::TopInterface -}; - void PrintObjectSupportMaterial::generate(PrintObject &object) { BOOST_LOG_TRIVIAL(info) << "Support generator - Start"; @@ -1296,86 +1057,6 @@ namespace SupportMaterialInternal { } } -void remove_bridges_from_contacts( - const PrintConfig &print_config, - const Layer &lower_layer, - const LayerRegion &layerm, - float fw, - Polygons &contact_polygons) -{ - // compute the area of bridging perimeters - Polygons bridges; - { - // Surface supporting this layer, expanded by 0.5 * nozzle_diameter, as we consider this kind of overhang to be sufficiently supported. - Polygons lower_grown_slices = expand(lower_layer.lslices, - //FIXME to mimic the decision in the perimeter generator, we should use half the external perimeter width. - 0.5f * float(scale_(print_config.nozzle_diameter.get_at(layerm.region().config().perimeter_extruder-1))), - SUPPORT_SURFACES_OFFSET_PARAMETERS); - // Collect perimeters of this layer. - //FIXME split_at_first_point() could split a bridge mid-way - #if 0 - Polylines overhang_perimeters = layerm.perimeters.as_polylines(); - // workaround for Clipper bug, see Slic3r::Polygon::clip_as_polyline() - for (Polyline &polyline : overhang_perimeters) - polyline.points[0].x += 1; - // Trim the perimeters of this layer by the lower layer to get the unsupported pieces of perimeters. - overhang_perimeters = diff_pl(overhang_perimeters, lower_grown_slices); - #else - Polylines overhang_perimeters = diff_pl(layerm.perimeters().as_polylines(), lower_grown_slices); - #endif - - // only consider straight overhangs - // only consider overhangs having endpoints inside layer's slices - // convert bridging polylines into polygons by inflating them with their thickness - // since we're dealing with bridges, we can't assume width is larger than spacing, - // so we take the largest value and also apply safety offset to be ensure no gaps - // are left in between - Flow perimeter_bridge_flow = layerm.bridging_flow(frPerimeter); - //FIXME one may want to use a maximum of bridging flow width and normal flow width, as the perimeters are calculated using the normal flow - // and then turned to bridging flow, thus their centerlines are derived from non-bridging flow and expanding them by a bridging flow - // may not expand them to the edge of their respective islands. - const float w = float(0.5 * std::max(perimeter_bridge_flow.scaled_width(), perimeter_bridge_flow.scaled_spacing())) + scaled(0.001); - for (Polyline &polyline : overhang_perimeters) - if (polyline.is_straight()) { - // This is a bridge - polyline.extend_start(fw); - polyline.extend_end(fw); - // Is the straight perimeter segment supported at both sides? - Point pts[2] = { polyline.first_point(), polyline.last_point() }; - bool supported[2] = { false, false }; - for (size_t i = 0; i < lower_layer.lslices.size() && ! (supported[0] && supported[1]); ++ i) - for (int j = 0; j < 2; ++ j) - if (! supported[j] && lower_layer.lslices_ex[i].bbox.contains(pts[j]) && lower_layer.lslices[i].contains(pts[j])) - supported[j] = true; - if (supported[0] && supported[1]) - // Offset a polyline into a thick line. - polygons_append(bridges, offset(polyline, w)); - } - bridges = union_(bridges); - } - // remove the entire bridges and only support the unsupported edges - //FIXME the brided regions are already collected as layerm.bridged. Use it? - for (const Surface &surface : layerm.fill_surfaces()) - if (surface.surface_type == stBottomBridge && surface.bridge_angle >= 0.0) - polygons_append(bridges, surface.expolygon); - //FIXME add the gap filled areas. Extrude the gaps with a bridge flow? - // Remove the unsupported ends of the bridges from the bridged areas. - //FIXME add supports at regular intervals to support long bridges! - bridges = diff(bridges, - // Offset unsupported edges into polygons. - offset(layerm.unsupported_bridge_edges(), scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS)); - // Remove bridged areas from the supported areas. - contact_polygons = diff(contact_polygons, bridges, ApplySafetyOffset::Yes); - - #ifdef SLIC3R_DEBUG - static int iRun = 0; - SVG::export_expolygons(debug_out_path("support-top-contacts-remove-bridges-run%d.svg", iRun ++), - { { { union_ex(offset(layerm.unsupported_bridge_edges(), scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS)) }, { "unsupported_bridge_edges", "orange", 0.5f } }, - { { union_ex(contact_polygons) }, { "contact_polygons", "blue", 0.5f } }, - { { union_ex(bridges) }, { "bridges", "red", "black", "", scaled(0.1f), 0.5f } } }); - #endif /* SLIC3R_DEBUG */ -} - std::vector PrintObjectSupportMaterial::buildplate_covered(const PrintObject &object) const { // Build support on a build plate only? If so, then collect and union all the surfaces below the current layer. @@ -1674,8 +1355,7 @@ static inline std::pair new_cont const SlicingParameters &slicing_params, const coordf_t support_layer_height_min, const Layer &layer, - std::deque &layer_storage, - tbb::spin_mutex &layer_storage_mutex) + SupportGeneratorLayerStorage &layer_storage) { double print_z, bottom_z, height; SupportGeneratorLayer* bridging_layer = nullptr; @@ -1735,7 +1415,7 @@ static inline std::pair new_cont } if (bridging_print_z < print_z - EPSILON) { // Allocate the new layer. - bridging_layer = &layer_allocate(layer_storage, layer_storage_mutex, SupporLayerType::TopContact); + bridging_layer = &layer_storage.allocate(SupporLayerType::TopContact); bridging_layer->idx_object_layer_above = layer_id; bridging_layer->print_z = bridging_print_z; if (bridging_print_z == slicing_params.first_print_layer_height) { @@ -1751,7 +1431,7 @@ static inline std::pair new_cont } } - SupportGeneratorLayer &new_layer = layer_allocate(layer_storage, layer_storage_mutex, SupporLayerType::TopContact); + SupportGeneratorLayer &new_layer = layer_storage.allocate(SupporLayerType::TopContact); new_layer.idx_object_layer_above = layer_id; new_layer.print_z = print_z; new_layer.bottom_z = bottom_z; @@ -1983,9 +1663,8 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::top_contact_layers( // For each overhang layer, two supporting layers may be generated: One for the overhangs extruded with a bridging flow, // and the other for the overhangs extruded with a normal flow. contact_out.assign(num_layers * 2, nullptr); - tbb::spin_mutex layer_storage_mutex; tbb::parallel_for(tbb::blocked_range(this->has_raft() ? 0 : 1, num_layers), - [this, &object, &annotations, &layer_storage, &layer_storage_mutex, &contact_out] + [this, &object, &annotations, &layer_storage, &contact_out] (const tbb::blocked_range& range) { for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) { @@ -2003,7 +1682,7 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::top_contact_layers( // Now apply the contact areas to the layer where they need to be made. if (! contact_polygons.empty() || ! overhang_polygons.empty()) { // Allocate the two empty layers. - auto [new_layer, bridging_layer] = new_contact_layer(*m_print_config, *m_object_config, m_slicing_params, m_support_params.support_layer_height_min, layer, layer_storage, layer_storage_mutex); + 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); if (new_layer) { // Fill the non-bridging layer with polygons. fill_contact_layer(*new_layer, layer_id, m_slicing_params, @@ -2053,7 +1732,7 @@ static inline SupportGeneratorLayer* detect_bottom_contacts( // First top contact layer index overlapping with this new bottom interface layer. size_t contact_idx, // To allocate a new layer from. - std::deque &layer_storage, + SupportGeneratorLayerStorage &layer_storage, // To trim the support areas above this bottom interface layer with this newly created bottom interface layer. std::vector &layer_support_areas, // Support areas projected from top to bottom, starting with top support interfaces. @@ -2088,7 +1767,7 @@ static inline SupportGeneratorLayer* detect_bottom_contacts( size_t layer_id = layer.id() - slicing_params.raft_layers(); // Allocate a new bottom contact layer. - SupportGeneratorLayer &layer_new = layer_allocate(layer_storage, SupporLayerType::BottomContact); + SupportGeneratorLayer &layer_new = layer_storage.allocate_unguarded(SupporLayerType::BottomContact); // Grow top surfaces so that interface and support generation are generated // with some spacing from object - it looks we don't need the actual // top shapes so this can be done here @@ -2392,80 +2071,6 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::bottom_contact_layers_and_ return bottom_contacts; } -// FN_HIGHER_EQUAL: the provided object pointer has a Z value >= of an internal threshold. -// Find the first item with Z value >= of an internal threshold of fn_higher_equal. -// If no vec item with Z value >= of an internal threshold of fn_higher_equal is found, return vec.size() -// If the initial idx is size_t(-1), then use binary search. -// Otherwise search linearly upwards. -template -IndexType idx_higher_or_equal(IteratorType begin, IteratorType end, IndexType idx, FN_HIGHER_EQUAL fn_higher_equal) -{ - auto size = int(end - begin); - if (size == 0) { - idx = 0; - } else if (idx == IndexType(-1)) { - // First of the batch of layers per thread pool invocation. Use binary search. - int idx_low = 0; - int idx_high = std::max(0, size - 1); - while (idx_low + 1 < idx_high) { - int idx_mid = (idx_low + idx_high) / 2; - if (fn_higher_equal(begin[idx_mid])) - idx_high = idx_mid; - else - idx_low = idx_mid; - } - idx = fn_higher_equal(begin[idx_low]) ? idx_low : - (fn_higher_equal(begin[idx_high]) ? idx_high : size); - } else { - // For the other layers of this batch of layers, search incrementally, which is cheaper than the binary search. - while (int(idx) < size && ! fn_higher_equal(begin[idx])) - ++ idx; - } - return idx; -} -template -IndexType idx_higher_or_equal(const std::vector& vec, IndexType idx, FN_HIGHER_EQUAL fn_higher_equal) -{ - return idx_higher_or_equal(vec.begin(), vec.end(), idx, fn_higher_equal); -} - -// FN_LOWER_EQUAL: the provided object pointer has a Z value <= of an internal threshold. -// Find the first item with Z value <= of an internal threshold of fn_lower_equal. -// If no vec item with Z value <= of an internal threshold of fn_lower_equal is found, return -1. -// If the initial idx is < -1, then use binary search. -// Otherwise search linearly downwards. -template -int idx_lower_or_equal(IT begin, IT end, int idx, FN_LOWER_EQUAL fn_lower_equal) -{ - auto size = int(end - begin); - if (size == 0) { - idx = -1; - } else if (idx < -1) { - // First of the batch of layers per thread pool invocation. Use binary search. - int idx_low = 0; - int idx_high = std::max(0, size - 1); - while (idx_low + 1 < idx_high) { - int idx_mid = (idx_low + idx_high) / 2; - if (fn_lower_equal(begin[idx_mid])) - idx_low = idx_mid; - else - idx_high = idx_mid; - } - idx = fn_lower_equal(begin[idx_high]) ? idx_high : - (fn_lower_equal(begin[idx_low ]) ? idx_low : -1); - } else { - // For the other layers of this batch of layers, search incrementally, which is cheaper than the binary search. - while (idx >= 0 && ! fn_lower_equal(begin[idx])) - -- idx; - } - return idx; -} -template -int idx_lower_or_equal(const std::vector &vec, int idx, FN_LOWER_EQUAL fn_lower_equal) -{ - return idx_lower_or_equal(vec.begin(), vec.end(), idx, fn_lower_equal); -} - // Trim the top_contacts layers with the bottom_contacts layers if they overlap, so there would not be enough vertical space for both of them. void PrintObjectSupportMaterial::trim_top_contacts_by_bottom_contacts( const PrintObject &object, const SupportGeneratorLayersPtr &bottom_contacts, SupportGeneratorLayersPtr &top_contacts) const @@ -2561,7 +2166,7 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::raft_and_intermediate_supp assert(extr2->bottom_z == m_slicing_params.first_print_layer_height); assert(extr2->print_z >= m_slicing_params.first_print_layer_height + m_support_params.support_layer_height_min - EPSILON); if (intermediate_layers.empty() || intermediate_layers.back()->print_z < m_slicing_params.first_print_layer_height) { - SupportGeneratorLayer &layer_new = layer_allocate(layer_storage, SupporLayerType::Intermediate); + SupportGeneratorLayer &layer_new = layer_storage.allocate_unguarded(SupporLayerType::Intermediate); layer_new.bottom_z = 0.; layer_new.print_z = m_slicing_params.first_print_layer_height; layer_new.height = m_slicing_params.first_print_layer_height; @@ -2583,7 +2188,7 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::raft_and_intermediate_supp // At this point only layers above first_print_layer_heigth + EPSILON are expected as the other cases were captured earlier. assert(extr2z >= m_slicing_params.first_print_layer_height + EPSILON); // Generate a new intermediate layer. - SupportGeneratorLayer &layer_new = layer_allocate(layer_storage, SupporLayerType::Intermediate); + SupportGeneratorLayer &layer_new = layer_storage.allocate_unguarded(SupporLayerType::Intermediate); layer_new.bottom_z = 0.; layer_new.print_z = extr1z = m_slicing_params.first_print_layer_height; layer_new.height = extr1z; @@ -2603,7 +2208,7 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::raft_and_intermediate_supp ++ idx_layer_object; if (idx_layer_object == 0 && extr1z == m_slicing_params.raft_interface_top_z) { // Insert one base support layer below the object. - SupportGeneratorLayer &layer_new = layer_allocate(layer_storage, SupporLayerType::Intermediate); + SupportGeneratorLayer &layer_new = layer_storage.allocate_unguarded(SupporLayerType::Intermediate); layer_new.print_z = m_slicing_params.object_print_z_min; layer_new.bottom_z = m_slicing_params.raft_interface_top_z; layer_new.height = layer_new.print_z - layer_new.bottom_z; @@ -2611,7 +2216,7 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::raft_and_intermediate_supp } // Emit all intermediate support layers synchronized with object layers up to extr2z. for (; idx_layer_object < object.layers().size() && object.layers()[idx_layer_object]->print_z < extr2z + EPSILON; ++ idx_layer_object) { - SupportGeneratorLayer &layer_new = layer_allocate(layer_storage, SupporLayerType::Intermediate); + SupportGeneratorLayer &layer_new = layer_storage.allocate_unguarded(SupporLayerType::Intermediate); layer_new.print_z = object.layers()[idx_layer_object]->print_z; layer_new.height = object.layers()[idx_layer_object]->height; layer_new.bottom_z = (idx_layer_object > 0) ? object.layers()[idx_layer_object - 1]->print_z : (layer_new.print_z - layer_new.height); @@ -2629,7 +2234,7 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::raft_and_intermediate_supp // between the 1st intermediate layer print_z and extr1->print_z is not too small. assert(extr1->bottom_z + m_support_params.support_layer_height_min < extr1->print_z + EPSILON); // Generate the first intermediate layer. - SupportGeneratorLayer &layer_new = layer_allocate(layer_storage, SupporLayerType::Intermediate); + SupportGeneratorLayer &layer_new = layer_storage.allocate_unguarded(SupporLayerType::Intermediate); layer_new.bottom_z = extr1->bottom_z; layer_new.print_z = extr1z = extr1->print_z; layer_new.height = extr1->height; @@ -2653,7 +2258,7 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::raft_and_intermediate_supp coordf_t extr2z_large_steps = extr2z; // Take the largest allowed step in the Z axis until extr2z_large_steps is reached. for (size_t i = 0; i < n_layers_extra; ++ i) { - SupportGeneratorLayer &layer_new = layer_allocate(layer_storage, SupporLayerType::Intermediate); + SupportGeneratorLayer &layer_new = layer_storage.allocate_unguarded(SupporLayerType::Intermediate); if (i + 1 == n_layers_extra) { // Last intermediate layer added. Align the last entered layer with extr2z_large_steps exactly. layer_new.bottom_z = (i == 0) ? extr1z : intermediate_layers.back()->print_z; @@ -2909,163 +2514,6 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object( BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::trim_support_layers_by_object() in parallel - end"; } -SupportGeneratorLayersPtr generate_raft_base( - const PrintObject &object, - const SupportParameters &support_params, - const SlicingParameters &slicing_params, - const SupportGeneratorLayersPtr &top_contacts, - const SupportGeneratorLayersPtr &interface_layers, - const SupportGeneratorLayersPtr &base_interface_layers, - const SupportGeneratorLayersPtr &base_layers, - SupportGeneratorLayerStorage &layer_storage) -{ - // If there is brim to be generated, calculate the trimming regions. - Polygons brim; - if (object.has_brim()) { - // The object does not have a raft. - // Calculate the area covered by the brim. - const BrimType brim_type = object.config().brim_type; - const bool brim_outer = brim_type == btOuterOnly || brim_type == btOuterAndInner; - const bool brim_inner = brim_type == btInnerOnly || brim_type == btOuterAndInner; - const auto brim_separation = scaled(object.config().brim_separation.value + object.config().brim_width.value); - for (const ExPolygon &ex : object.layers().front()->lslices) { - if (brim_outer && brim_inner) - polygons_append(brim, offset(ex, brim_separation)); - else { - if (brim_outer) - polygons_append(brim, offset(ex.contour, brim_separation, ClipperLib::jtRound, float(scale_(0.1)))); - else - brim.emplace_back(ex.contour); - if (brim_inner) { - Polygons holes = ex.holes; - polygons_reverse(holes); - holes = shrink(holes, brim_separation, ClipperLib::jtRound, float(scale_(0.1))); - polygons_reverse(holes); - polygons_append(brim, std::move(holes)); - } else - polygons_append(brim, ex.holes); - } - } - brim = union_(brim); - } - - // How much to inflate the support columns to be stable. This also applies to the 1st layer, if no raft layers are to be printed. - const float inflate_factor_fine = float(scale_((slicing_params.raft_layers() > 1) ? 0.5 : EPSILON)); - const float inflate_factor_1st_layer = std::max(0.f, float(scale_(object.config().raft_first_layer_expansion)) - inflate_factor_fine); - SupportGeneratorLayer *contacts = top_contacts .empty() ? nullptr : top_contacts .front(); - SupportGeneratorLayer *interfaces = interface_layers .empty() ? nullptr : interface_layers .front(); - SupportGeneratorLayer *base_interfaces = base_interface_layers.empty() ? nullptr : base_interface_layers.front(); - SupportGeneratorLayer *columns_base = base_layers .empty() ? nullptr : base_layers .front(); - if (contacts != nullptr && contacts->print_z > std::max(slicing_params.first_print_layer_height, slicing_params.raft_contact_top_z) + EPSILON) - // This is not the raft contact layer. - contacts = nullptr; - if (interfaces != nullptr && interfaces->bottom_print_z() > slicing_params.raft_interface_top_z + EPSILON) - // This is not the raft column base layer. - interfaces = nullptr; - if (base_interfaces != nullptr && base_interfaces->bottom_print_z() > slicing_params.raft_interface_top_z + EPSILON) - // This is not the raft column base layer. - base_interfaces = nullptr; - if (columns_base != nullptr && columns_base->bottom_print_z() > slicing_params.raft_interface_top_z + EPSILON) - // This is not the raft interface layer. - columns_base = nullptr; - - Polygons interface_polygons; - if (contacts != nullptr && ! contacts->polygons.empty()) - polygons_append(interface_polygons, expand(contacts->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS)); - if (interfaces != nullptr && ! interfaces->polygons.empty()) - polygons_append(interface_polygons, expand(interfaces->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS)); - if (base_interfaces != nullptr && ! base_interfaces->polygons.empty()) - polygons_append(interface_polygons, expand(base_interfaces->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS)); - - // Output vector. - SupportGeneratorLayersPtr raft_layers; - - if (slicing_params.raft_layers() > 1) { - Polygons base; - Polygons columns; - Polygons first_layer; - if (columns_base != nullptr) { - if (columns_base->bottom_print_z() > slicing_params.raft_interface_top_z - EPSILON) { - // Classic supports with colums above the raft interface. - base = columns_base->polygons; - columns = base; - if (! interface_polygons.empty()) - // Trim the 1st layer columns with the inflated interface polygons. - columns = diff(columns, interface_polygons); - } else { - // Organic supports with raft on print bed. - assert(is_approx(columns_base->print_z, slicing_params.first_print_layer_height)); - first_layer = columns_base->polygons; - } - } - if (! interface_polygons.empty()) { - // Merge the untrimmed columns base with the expanded raft interface, to be used for the support base and interface. - base = union_(base, interface_polygons); - } - // Do not add the raft contact layer, only add the raft layers below the contact layer. - // Insert the 1st layer. - { - SupportGeneratorLayer &new_layer = layer_allocate(layer_storage, (slicing_params.base_raft_layers > 0) ? SupporLayerType::RaftBase : SupporLayerType::RaftInterface); - raft_layers.push_back(&new_layer); - new_layer.print_z = slicing_params.first_print_layer_height; - new_layer.height = slicing_params.first_print_layer_height; - new_layer.bottom_z = 0.; - first_layer = union_(std::move(first_layer), base); - new_layer.polygons = inflate_factor_1st_layer > 0 ? expand(first_layer, inflate_factor_1st_layer) : first_layer; - } - // Insert the base layers. - for (size_t i = 1; i < slicing_params.base_raft_layers; ++ i) { - coordf_t print_z = raft_layers.back()->print_z; - SupportGeneratorLayer &new_layer = layer_allocate(layer_storage, SupporLayerType::RaftBase); - raft_layers.push_back(&new_layer); - new_layer.print_z = print_z + slicing_params.base_raft_layer_height; - new_layer.height = slicing_params.base_raft_layer_height; - new_layer.bottom_z = print_z; - new_layer.polygons = base; - } - // Insert the interface layers. - for (size_t i = 1; i < slicing_params.interface_raft_layers; ++ i) { - coordf_t print_z = raft_layers.back()->print_z; - SupportGeneratorLayer &new_layer = layer_allocate(layer_storage, SupporLayerType::RaftInterface); - raft_layers.push_back(&new_layer); - new_layer.print_z = print_z + slicing_params.interface_raft_layer_height; - new_layer.height = slicing_params.interface_raft_layer_height; - new_layer.bottom_z = print_z; - new_layer.polygons = interface_polygons; - //FIXME misusing contact_polygons for support columns. - new_layer.contact_polygons = std::make_unique(columns); - } - } else { - if (columns_base != nullptr) { - // Expand the bases of the support columns in the 1st layer. - Polygons &raft = columns_base->polygons; - Polygons trimming = offset(object.layers().front()->lslices, (float)scale_(support_params.gap_xy), SUPPORT_SURFACES_OFFSET_PARAMETERS); - if (inflate_factor_1st_layer > SCALED_EPSILON) { - // Inflate in multiple steps to avoid leaking of the support 1st layer through object walls. - auto nsteps = std::max(5, int(ceil(inflate_factor_1st_layer / support_params.first_layer_flow.scaled_width()))); - float step = inflate_factor_1st_layer / nsteps; - for (int i = 0; i < nsteps; ++ i) - raft = diff(expand(raft, step), trimming); - } else - raft = diff(raft, trimming); - if (! interface_polygons.empty()) - columns_base->polygons = diff(columns_base->polygons, interface_polygons); - } - if (! brim.empty()) { - if (columns_base) - columns_base->polygons = diff(columns_base->polygons, brim); - if (contacts) - contacts->polygons = diff(contacts->polygons, brim); - if (interfaces) - interfaces->polygons = diff(interfaces->polygons, brim); - if (base_interfaces) - base_interfaces->polygons = diff(base_interfaces->polygons, brim); - } - } - - return raft_layers; -} - // Convert some of the intermediate layers into top/bottom interface layers as well as base interface layers. std::pair PrintObjectSupportMaterial::generate_interface_layers( const SupportGeneratorLayersPtr &bottom_contacts, @@ -3111,9 +2559,8 @@ std::pair PrintObjectSuppo auto smoothing_distance = m_support_params.support_material_interface_flow.scaled_spacing() * 1.5; auto minimum_island_radius = m_support_params.support_material_interface_flow.scaled_spacing() / m_support_params.interface_density; auto closing_distance = smoothing_distance; // scaled(m_object_config->support_material_closing_radius.value); - tbb::spin_mutex layer_storage_mutex; // Insert a new layer into base_interface_layers, if intersection with base exists. - auto insert_layer = [&layer_storage, &layer_storage_mutex, snug_supports, closing_distance, smoothing_distance, minimum_island_radius]( + auto insert_layer = [&layer_storage, snug_supports, closing_distance, smoothing_distance, minimum_island_radius]( SupportGeneratorLayer &intermediate_layer, Polygons &bottom, Polygons &&top, const Polygons *subtract, SupporLayerType type) -> SupportGeneratorLayer* { assert(! bottom.empty() || ! top.empty()); // Merge top into bottom, unite them with a safety offset. @@ -3128,7 +2575,7 @@ std::pair PrintObjectSuppo //FIXME Remove non-printable tiny islands, let them be printed using the base support. //bottom = opening(std::move(bottom), minimum_island_radius); if (! bottom.empty()) { - SupportGeneratorLayer &layer_new = layer_allocate(layer_storage, layer_storage_mutex, type); + SupportGeneratorLayer &layer_new = layer_storage.allocate(type); layer_new.polygons = std::move(bottom); layer_new.print_z = intermediate_layer.print_z; layer_new.bottom_z = intermediate_layer.bottom_z; @@ -3230,1428 +2677,6 @@ std::pair PrintObjectSuppo return base_and_interface_layers; } -static inline void fill_expolygon_generate_paths( - ExtrusionEntitiesPtr &dst, - ExPolygon &&expolygon, - Fill *filler, - const FillParams &fill_params, - float density, - ExtrusionRole role, - const Flow &flow) -{ - Surface surface(stInternal, std::move(expolygon)); - Polylines polylines; - try { - assert(!fill_params.use_arachne); - polylines = filler->fill_surface(&surface, fill_params); - } catch (InfillFailedException &) { - } - extrusion_entities_append_paths( - dst, - std::move(polylines), - role, - flow.mm3_per_mm(), flow.width(), flow.height()); -} - -static inline void fill_expolygons_generate_paths( - ExtrusionEntitiesPtr &dst, - ExPolygons &&expolygons, - Fill *filler, - const FillParams &fill_params, - float density, - ExtrusionRole role, - const Flow &flow) -{ - for (ExPolygon &expoly : expolygons) - fill_expolygon_generate_paths(dst, std::move(expoly), filler, fill_params, density, role, flow); -} - -static inline void fill_expolygons_generate_paths( - ExtrusionEntitiesPtr &dst, - ExPolygons &&expolygons, - Fill *filler, - float density, - ExtrusionRole role, - const Flow &flow) -{ - FillParams fill_params; - fill_params.density = density; - fill_params.dont_adjust = true; - fill_expolygons_generate_paths(dst, std::move(expolygons), filler, fill_params, density, role, flow); -} - -static Polylines draw_perimeters(const ExPolygon &expoly, double clip_length) -{ - // Draw the perimeters. - Polylines polylines; - polylines.reserve(expoly.holes.size() + 1); - for (size_t i = 0; i <= expoly.holes.size(); ++ i) { - Polyline pl(i == 0 ? expoly.contour.points : expoly.holes[i - 1].points); - pl.points.emplace_back(pl.points.front()); - if (i > 0) - // It is a hole, reverse it. - pl.reverse(); - // so that all contours are CCW oriented. - pl.clip_end(clip_length); - polylines.emplace_back(std::move(pl)); - } - return polylines; -} - -static inline void tree_supports_generate_paths( - ExtrusionEntitiesPtr &dst, - const Polygons &polygons, - const Flow &flow) -{ - // Offset expolygon inside, returns number of expolygons collected (0 or 1). - // Vertices of output paths are marked with Z = source contour index of the expoly. - // Vertices at the intersection of source contours are marked with Z = -1. - auto shrink_expolygon_with_contour_idx = [](const Slic3r::ExPolygon &expoly, const float delta, ClipperLib::JoinType joinType, double miterLimit, ClipperLib_Z::Paths &out) -> int - { - assert(delta > 0); - auto append_paths_with_z = [](ClipperLib::Paths &src, coord_t contour_idx, ClipperLib_Z::Paths &dst) { - dst.reserve(next_highest_power_of_2(dst.size() + src.size())); - for (const ClipperLib::Path &contour : src) { - ClipperLib_Z::Path tmp; - tmp.reserve(contour.size()); - for (const Point &p : contour) - tmp.emplace_back(p.x(), p.y(), contour_idx); - dst.emplace_back(std::move(tmp)); - } - }; - - // 1) Offset the outer contour. - ClipperLib_Z::Paths contours; - { - ClipperLib::ClipperOffset co; - if (joinType == jtRound) - co.ArcTolerance = miterLimit; - else - co.MiterLimit = miterLimit; - co.ShortestEdgeLength = double(delta * 0.005); - co.AddPath(expoly.contour.points, joinType, ClipperLib::etClosedPolygon); - ClipperLib::Paths contours_raw; - co.Execute(contours_raw, - delta); - if (contours_raw.empty()) - // No need to try to offset the holes. - return 0; - append_paths_with_z(contours_raw, 0, contours); - } - - if (expoly.holes.empty()) { - // No need to subtract holes from the offsetted expolygon, we are done. - append(out, std::move(contours)); - } else { - // 2) Offset the holes one by one, collect the offsetted holes. - ClipperLib_Z::Paths holes; - { - for (const Polygon &hole : expoly.holes) { - ClipperLib::ClipperOffset co; - if (joinType == jtRound) - co.ArcTolerance = miterLimit; - else - co.MiterLimit = miterLimit; - co.ShortestEdgeLength = double(delta * 0.005); - co.AddPath(hole.points, joinType, ClipperLib::etClosedPolygon); - ClipperLib::Paths out2; - // Execute reorients the contours so that the outer most contour has a positive area. Thus the output - // contours will be CCW oriented even though the input paths are CW oriented. - // Offset is applied after contour reorientation, thus the signum of the offset value is reversed. - co.Execute(out2, delta); - append_paths_with_z(out2, 1 + (&hole - expoly.holes.data()), holes); - } - } - - // 3) Subtract holes from the contours. - if (holes.empty()) { - // No hole remaining after an offset. Just copy the outer contour. - append(out, std::move(contours)); - } else { - // Negative offset. There is a chance, that the offsetted hole intersects the outer contour. - // Subtract the offsetted holes from the offsetted contours. - ClipperLib_Z::Clipper clipper; - clipper.ZFillFunction([](const ClipperLib_Z::IntPoint &e1bot, const ClipperLib_Z::IntPoint &e1top, const ClipperLib_Z::IntPoint &e2bot, const ClipperLib_Z::IntPoint &e2top, ClipperLib_Z::IntPoint &pt) { - //pt.z() = std::max(std::max(e1bot.z(), e1top.z()), std::max(e2bot.z(), e2top.z())); - // Just mark the intersection. - pt.z() = -1; - }); - clipper.AddPaths(contours, ClipperLib_Z::ptSubject, true); - clipper.AddPaths(holes, ClipperLib_Z::ptClip, true); - ClipperLib_Z::Paths output; - clipper.Execute(ClipperLib_Z::ctDifference, output, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero); - if (! output.empty()) { - append(out, std::move(output)); - } else { - // The offsetted holes have eaten up the offsetted outer contour. - return 0; - } - } - } - - return 1; - }; - - const double spacing = flow.scaled_spacing(); - // Clip the sheath path to avoid the extruder to get exactly on the first point of the loop. - const double clip_length = spacing * 0.15; - const double anchor_length = spacing * 6.; - ClipperLib_Z::Paths anchor_candidates; - for (ExPolygon& expoly : closing_ex(polygons, float(SCALED_EPSILON), float(SCALED_EPSILON + 0.5 * flow.scaled_width()))) { - std::unique_ptr eec; - double area = expoly.area(); - if (area > sqr(scaled(5.))) { - eec = std::make_unique(); - // Don't reoder internal / external loops of the same island, always start with the internal loop. - eec->no_sort = true; - // Make the tree branch stable by adding another perimeter. - ExPolygons level2 = offset2_ex({ expoly }, -1.5 * flow.scaled_width(), 0.5 * flow.scaled_width()); - if (level2.size() == 1) { - Polylines polylines; - extrusion_entities_append_paths(eec->entities, draw_perimeters(expoly, clip_length), ExtrusionRole::SupportMaterial, flow.mm3_per_mm(), flow.width(), flow.height(), - // Disable reversal of the path, always start with the anchor, always print CCW. - false); - expoly = level2.front(); - } - } - - // Try to produce one more perimeter to place the seam anchor. - // First genrate a 2nd perimeter loop as a source for anchor candidates. - // The anchor candidate points are annotated with an index of the source contour or with -1 if on intersection. - anchor_candidates.clear(); - shrink_expolygon_with_contour_idx(expoly, flow.scaled_width(), DefaultJoinType, 1.2, anchor_candidates); - // Orient all contours CW. - for (auto &path : anchor_candidates) - if (ClipperLib_Z::Area(path) > 0) - std::reverse(path.begin(), path.end()); - - // Draw the perimeters. - Polylines polylines; - polylines.reserve(expoly.holes.size() + 1); - for (size_t idx_loop = 0; idx_loop < expoly.num_contours(); ++ idx_loop) { - // Open the loop with a seam. - const Polygon &loop = expoly.contour_or_hole(idx_loop); - Polyline pl(loop.points); - // Orient all contours CW, because the anchor will be added to the end of polyline while we want to start a loop with the anchor. - if (idx_loop == 0) - // It is an outer contour. - pl.reverse(); - pl.points.emplace_back(pl.points.front()); - pl.clip_end(clip_length); - if (pl.size() < 2) - continue; - // Find the foot of the seam point on anchor_candidates. Only pick an anchor point that was created by offsetting the source contour. - ClipperLib_Z::Path *closest_contour = nullptr; - Vec2d closest_point; - int closest_point_idx = -1; - double closest_point_t; - double d2min = std::numeric_limits::max(); - Vec2d seam_pt = pl.back().cast(); - for (ClipperLib_Z::Path &path : anchor_candidates) - for (int i = 0; i < path.size(); ++ i) { - int j = next_idx_modulo(i, path); - if (path[i].z() == idx_loop || path[j].z() == idx_loop) { - Vec2d pi(path[i].x(), path[i].y()); - Vec2d pj(path[j].x(), path[j].y()); - Vec2d v = pj - pi; - Vec2d w = seam_pt - pi; - auto l2 = v.squaredNorm(); - auto t = std::clamp((l2 == 0) ? 0 : v.dot(w) / l2, 0., 1.); - if ((path[i].z() == idx_loop || t > EPSILON) && (path[j].z() == idx_loop || t < 1. - EPSILON)) { - // Closest point. - Vec2d fp = pi + v * t; - double d2 = (fp - seam_pt).squaredNorm(); - if (d2 < d2min) { - d2min = d2; - closest_contour = &path; - closest_point = fp; - closest_point_idx = i; - closest_point_t = t; - } - } - } - } - if (d2min < sqr(flow.scaled_width() * 3.)) { - // Try to cut an anchor from the closest_contour. - // Both closest_contour and pl are CW oriented. - pl.points.emplace_back(closest_point.cast()); - const ClipperLib_Z::Path &path = *closest_contour; - double remaining_length = anchor_length - (seam_pt - closest_point).norm(); - int i = closest_point_idx; - int j = next_idx_modulo(i, *closest_contour); - Vec2d pi(path[i].x(), path[i].y()); - Vec2d pj(path[j].x(), path[j].y()); - Vec2d v = pj - pi; - double l = v.norm(); - if (remaining_length < (1. - closest_point_t) * l) { - // Just trim the current line. - pl.points.emplace_back((closest_point + v * (remaining_length / l)).cast()); - } else { - // Take the rest of the current line, continue with the other lines. - pl.points.emplace_back(path[j].x(), path[j].y()); - pi = pj; - for (i = j; path[i].z() == idx_loop && remaining_length > 0; i = j, pi = pj) { - j = next_idx_modulo(i, path); - pj = Vec2d(path[j].x(), path[j].y()); - v = pj - pi; - l = v.norm(); - if (i == closest_point_idx) { - // Back at the first segment. Most likely this should not happen and we may end the anchor. - break; - } - if (remaining_length <= l) { - pl.points.emplace_back((pi + v * (remaining_length / l)).cast()); - break; - } - pl.points.emplace_back(path[j].x(), path[j].y()); - remaining_length -= l; - } - } - } - // Start with the anchor. - pl.reverse(); - polylines.emplace_back(std::move(pl)); - } - - ExtrusionEntitiesPtr &out = eec ? eec->entities : dst; - extrusion_entities_append_paths(out, std::move(polylines), ExtrusionRole::SupportMaterial, flow.mm3_per_mm(), flow.width(), flow.height(), - // Disable reversal of the path, always start with the anchor, always print CCW. - false); - if (eec) { - std::reverse(eec->entities.begin(), eec->entities.end()); - dst.emplace_back(eec.release()); - } - } -} - -static inline void fill_expolygons_with_sheath_generate_paths( - ExtrusionEntitiesPtr &dst, - const Polygons &polygons, - Fill *filler, - float density, - ExtrusionRole role, - const Flow &flow, - bool with_sheath, - bool no_sort) -{ - if (polygons.empty()) - return; - - if (! with_sheath) { - fill_expolygons_generate_paths(dst, closing_ex(polygons, float(SCALED_EPSILON)), filler, density, role, flow); - return; - } - - FillParams fill_params; - fill_params.density = density; - fill_params.dont_adjust = true; - - const double spacing = flow.scaled_spacing(); - // Clip the sheath path to avoid the extruder to get exactly on the first point of the loop. - const double clip_length = spacing * 0.15; - - for (ExPolygon &expoly : closing_ex(polygons, float(SCALED_EPSILON), float(SCALED_EPSILON + 0.5*flow.scaled_width()))) { - // Don't reorder the skirt and its infills. - std::unique_ptr eec; - if (no_sort) { - eec = std::make_unique(); - eec->no_sort = true; - } - ExtrusionEntitiesPtr &out = no_sort ? eec->entities : dst; - extrusion_entities_append_paths(out, draw_perimeters(expoly, clip_length), ExtrusionRole::SupportMaterial, flow.mm3_per_mm(), flow.width(), flow.height()); - // Fill in the rest. - fill_expolygons_generate_paths(out, offset_ex(expoly, float(-0.4 * spacing)), filler, fill_params, density, role, flow); - if (no_sort && ! eec->empty()) - dst.emplace_back(eec.release()); - } -} - -// Support layers, partially processed. -struct SupportGeneratorLayerExtruded -{ - SupportGeneratorLayerExtruded& operator=(SupportGeneratorLayerExtruded &&rhs) { - this->layer = rhs.layer; - this->extrusions = std::move(rhs.extrusions); - m_polygons_to_extrude = std::move(rhs.m_polygons_to_extrude); - rhs.layer = nullptr; - return *this; - } - - bool empty() const { - return layer == nullptr || layer->polygons.empty(); - } - - void set_polygons_to_extrude(Polygons &&polygons) { - if (m_polygons_to_extrude == nullptr) - m_polygons_to_extrude = std::make_unique(std::move(polygons)); - else - *m_polygons_to_extrude = std::move(polygons); - } - Polygons& polygons_to_extrude() { return (m_polygons_to_extrude == nullptr) ? layer->polygons : *m_polygons_to_extrude; } - const Polygons& polygons_to_extrude() const { return (m_polygons_to_extrude == nullptr) ? layer->polygons : *m_polygons_to_extrude; } - - bool could_merge(const SupportGeneratorLayerExtruded &other) const { - return ! this->empty() && ! other.empty() && - std::abs(this->layer->height - other.layer->height) < EPSILON && - this->layer->bridging == other.layer->bridging; - } - - // Merge regions, perform boolean union over the merged polygons. - void merge(SupportGeneratorLayerExtruded &&other) { - assert(this->could_merge(other)); - // 1) Merge the rest polygons to extrude, if there are any. - if (other.m_polygons_to_extrude != nullptr) { - if (m_polygons_to_extrude == nullptr) { - // This layer has no extrusions generated yet, if it has no m_polygons_to_extrude (its area to extrude was not reduced yet). - assert(this->extrusions.empty()); - m_polygons_to_extrude = std::make_unique(this->layer->polygons); - } - Slic3r::polygons_append(*m_polygons_to_extrude, std::move(*other.m_polygons_to_extrude)); - *m_polygons_to_extrude = union_safety_offset(*m_polygons_to_extrude); - other.m_polygons_to_extrude.reset(); - } else if (m_polygons_to_extrude != nullptr) { - assert(other.m_polygons_to_extrude == nullptr); - // The other layer has no extrusions generated yet, if it has no m_polygons_to_extrude (its area to extrude was not reduced yet). - assert(other.extrusions.empty()); - Slic3r::polygons_append(*m_polygons_to_extrude, other.layer->polygons); - *m_polygons_to_extrude = union_safety_offset(*m_polygons_to_extrude); - } - // 2) Merge the extrusions. - this->extrusions.insert(this->extrusions.end(), other.extrusions.begin(), other.extrusions.end()); - other.extrusions.clear(); - // 3) Merge the infill polygons. - Slic3r::polygons_append(this->layer->polygons, std::move(other.layer->polygons)); - this->layer->polygons = union_safety_offset(this->layer->polygons); - other.layer->polygons.clear(); - } - - void polygons_append(Polygons &dst) const { - if (layer != NULL && ! layer->polygons.empty()) - Slic3r::polygons_append(dst, layer->polygons); - } - - // The source layer. It carries the height and extrusion type (bridging / non bridging, extrusion height). - SupportGeneratorLayer *layer { nullptr }; - // Collect extrusions. They will be exported sorted by the bottom height. - ExtrusionEntitiesPtr extrusions; - -private: - // In case the extrusions are non-empty, m_polygons_to_extrude may contain the rest areas yet to be filled by additional support. - // This is useful mainly for the loop interfaces, which are generated before the zig-zag infills. - std::unique_ptr m_polygons_to_extrude; -}; - -typedef std::vector SupportGeneratorLayerExtrudedPtrs; - -struct LoopInterfaceProcessor -{ - LoopInterfaceProcessor(coordf_t circle_r) : - n_contact_loops(0), - circle_radius(circle_r), - circle_distance(circle_r * 3.) - { - // Shape of the top contact area. - circle.points.reserve(6); - for (size_t i = 0; i < 6; ++ i) { - double angle = double(i) * M_PI / 3.; - circle.points.push_back(Point(circle_radius * cos(angle), circle_radius * sin(angle))); - } - } - - // Generate loop contacts at the top_contact_layer, - // trim the top_contact_layer->polygons with the areas covered by the loops. - void generate(SupportGeneratorLayerExtruded &top_contact_layer, const Flow &interface_flow_src) const; - - int n_contact_loops; - coordf_t circle_radius; - coordf_t circle_distance; - Polygon circle; -}; - -void LoopInterfaceProcessor::generate(SupportGeneratorLayerExtruded &top_contact_layer, const Flow &interface_flow_src) const -{ - if (n_contact_loops == 0 || top_contact_layer.empty()) - return; - - Flow flow = interface_flow_src.with_height(top_contact_layer.layer->height); - - Polygons overhang_polygons; - if (top_contact_layer.layer->overhang_polygons != nullptr) - overhang_polygons = std::move(*top_contact_layer.layer->overhang_polygons); - - // Generate the outermost loop. - // Find centerline of the external loop (or any other kind of extrusions should the loop be skipped) - ExPolygons top_contact_expolygons = offset_ex(union_ex(top_contact_layer.layer->polygons), - 0.5f * flow.scaled_width()); - - // Grid size and bit shifts for quick and exact to/from grid coordinates manipulation. - coord_t circle_grid_resolution = 1; - coord_t circle_grid_powerof2 = 0; - { - // epsilon to account for rounding errors - coord_t circle_grid_resolution_non_powerof2 = coord_t(2. * circle_distance + 3.); - while (circle_grid_resolution < circle_grid_resolution_non_powerof2) { - circle_grid_resolution <<= 1; - ++ circle_grid_powerof2; - } - } - - struct PointAccessor { - const Point* operator()(const Point &pt) const { return &pt; } - }; - typedef ClosestPointInRadiusLookup ClosestPointLookupType; - - Polygons loops0; - { - // find centerline of the external loop of the contours - // Only consider the loops facing the overhang. - Polygons external_loops; - // Holes in the external loops. - Polygons circles; - Polygons overhang_with_margin = offset(union_ex(overhang_polygons), 0.5f * flow.scaled_width()); - for (ExPolygons::iterator it_contact_expoly = top_contact_expolygons.begin(); it_contact_expoly != top_contact_expolygons.end(); ++ it_contact_expoly) { - // Store the circle centers placed for an expolygon into a regular grid, hashed by the circle centers. - ClosestPointLookupType circle_centers_lookup(coord_t(circle_distance - SCALED_EPSILON)); - Points circle_centers; - Point center_last; - // For each contour of the expolygon, start with the outer contour, continue with the holes. - for (size_t i_contour = 0; i_contour <= it_contact_expoly->holes.size(); ++ i_contour) { - Polygon &contour = (i_contour == 0) ? it_contact_expoly->contour : it_contact_expoly->holes[i_contour - 1]; - const Point *seg_current_pt = nullptr; - coordf_t seg_current_t = 0.; - if (! intersection_pl(contour.split_at_first_point(), overhang_with_margin).empty()) { - // The contour is below the overhang at least to some extent. - //FIXME ideally one would place the circles below the overhang only. - // Walk around the contour and place circles so their centers are not closer than circle_distance from each other. - if (circle_centers.empty()) { - // Place the first circle. - seg_current_pt = &contour.points.front(); - seg_current_t = 0.; - center_last = *seg_current_pt; - circle_centers_lookup.insert(center_last); - circle_centers.push_back(center_last); - } - for (Points::const_iterator it = contour.points.begin() + 1; it != contour.points.end(); ++it) { - // Is it possible to place a circle on this segment? Is it not too close to any of the circles already placed on this contour? - const Point &p1 = *(it-1); - const Point &p2 = *it; - // Intersection of a ray (p1, p2) with a circle placed at center_last, with radius of circle_distance. - const Vec2d v_seg(coordf_t(p2(0)) - coordf_t(p1(0)), coordf_t(p2(1)) - coordf_t(p1(1))); - const Vec2d v_cntr(coordf_t(p1(0) - center_last(0)), coordf_t(p1(1) - center_last(1))); - coordf_t a = v_seg.squaredNorm(); - coordf_t b = 2. * v_seg.dot(v_cntr); - coordf_t c = v_cntr.squaredNorm() - circle_distance * circle_distance; - coordf_t disc = b * b - 4. * a * c; - if (disc > 0.) { - // The circle intersects a ray. Avoid the parts of the segment inside the circle. - coordf_t t1 = (-b - sqrt(disc)) / (2. * a); - coordf_t t2 = (-b + sqrt(disc)) / (2. * a); - coordf_t t0 = (seg_current_pt == &p1) ? seg_current_t : 0.; - // Take the lowest t in , excluding . - coordf_t t; - if (t0 <= t1) - t = t0; - else if (t2 <= 1.) - t = t2; - else { - // Try the following segment. - seg_current_pt = nullptr; - continue; - } - seg_current_pt = &p1; - seg_current_t = t; - center_last = Point(p1(0) + coord_t(v_seg(0) * t), p1(1) + coord_t(v_seg(1) * t)); - // It has been verified that the new point is far enough from center_last. - // Ensure, that it is far enough from all the centers. - std::pair circle_closest = circle_centers_lookup.find(center_last); - if (circle_closest.first != nullptr) { - -- it; - continue; - } - } else { - // All of the segment is outside the circle. Take the first point. - seg_current_pt = &p1; - seg_current_t = 0.; - center_last = p1; - } - // Place the first circle. - circle_centers_lookup.insert(center_last); - circle_centers.push_back(center_last); - } - external_loops.push_back(std::move(contour)); - for (const Point ¢er : circle_centers) { - circles.push_back(circle); - circles.back().translate(center); - } - } - } - } - // Apply a pattern to the external loops. - loops0 = diff(external_loops, circles); - } - - Polylines loop_lines; - { - // make more loops - Polygons loop_polygons = loops0; - for (int i = 1; i < n_contact_loops; ++ i) - polygons_append(loop_polygons, - opening( - loops0, - i * flow.scaled_spacing() + 0.5f * flow.scaled_spacing(), - 0.5f * flow.scaled_spacing())); - // Clip such loops to the side oriented towards the object. - // Collect split points, so they will be recognized after the clipping. - // At the split points the clipped pieces will be stitched back together. - loop_lines.reserve(loop_polygons.size()); - std::unordered_map map_split_points; - for (Polygons::const_iterator it = loop_polygons.begin(); it != loop_polygons.end(); ++ it) { - assert(map_split_points.find(it->first_point()) == map_split_points.end()); - map_split_points[it->first_point()] = -1; - loop_lines.push_back(it->split_at_first_point()); - } - loop_lines = intersection_pl(loop_lines, expand(overhang_polygons, scale_(SUPPORT_MATERIAL_MARGIN))); - // Because a closed loop has been split to a line, loop_lines may contain continuous segments split to 2 pieces. - // Try to connect them. - for (int i_line = 0; i_line < int(loop_lines.size()); ++ i_line) { - Polyline &polyline = loop_lines[i_line]; - auto it = map_split_points.find(polyline.first_point()); - if (it != map_split_points.end()) { - // This is a stitching point. - // If this assert triggers, multiple source polygons likely intersected at this point. - assert(it->second != -2); - if (it->second < 0) { - // First occurence. - it->second = i_line; - } else { - // Second occurence. Join the lines. - Polyline &polyline_1st = loop_lines[it->second]; - assert(polyline_1st.first_point() == it->first || polyline_1st.last_point() == it->first); - if (polyline_1st.first_point() == it->first) - polyline_1st.reverse(); - polyline_1st.append(std::move(polyline)); - it->second = -2; - } - continue; - } - it = map_split_points.find(polyline.last_point()); - if (it != map_split_points.end()) { - // This is a stitching point. - // If this assert triggers, multiple source polygons likely intersected at this point. - assert(it->second != -2); - if (it->second < 0) { - // First occurence. - it->second = i_line; - } else { - // Second occurence. Join the lines. - Polyline &polyline_1st = loop_lines[it->second]; - assert(polyline_1st.first_point() == it->first || polyline_1st.last_point() == it->first); - if (polyline_1st.first_point() == it->first) - polyline_1st.reverse(); - polyline.reverse(); - polyline_1st.append(std::move(polyline)); - it->second = -2; - } - } - } - // Remove empty lines. - remove_degenerate(loop_lines); - } - - // add the contact infill area to the interface area - // note that growing loops by $circle_radius ensures no tiny - // extrusions are left inside the circles; however it creates - // a very large gap between loops and contact_infill_polygons, so maybe another - // solution should be found to achieve both goals - // Store the trimmed polygons into a separate polygon set, so the original infill area remains intact for - // "modulate by layer thickness". - top_contact_layer.set_polygons_to_extrude(diff(top_contact_layer.layer->polygons, offset(loop_lines, float(circle_radius * 1.1)))); - - // Transform loops into ExtrusionPath objects. - extrusion_entities_append_paths( - top_contact_layer.extrusions, - std::move(loop_lines), - ExtrusionRole::SupportMaterialInterface, flow.mm3_per_mm(), flow.width(), flow.height()); -} - -#ifdef SLIC3R_DEBUG -static std::string dbg_index_to_color(int idx) -{ - if (idx < 0) - return "yellow"; - idx = idx % 3; - switch (idx) { - case 0: return "red"; - case 1: return "green"; - default: return "blue"; - } -} -#endif /* SLIC3R_DEBUG */ - -// When extruding a bottom interface layer over an object, the bottom interface layer is extruded in a thin air, therefore -// it is being extruded with a bridging flow to not shrink excessively (the die swell effect). -// Tiny extrusions are better avoided and it is always better to anchor the thread to an existing support structure if possible. -// Therefore the bottom interface spots are expanded a bit. The expanded regions may overlap with another bottom interface layers, -// leading to over extrusion, where they overlap. The over extrusion is better avoided as it often makes the interface layers -// to stick too firmly to the object. -// -// Modulate thickness (increase bottom_z) of extrusions_in_out generated for this_layer -// if they overlap with overlapping_layers, whose print_z is above this_layer.bottom_z() and below this_layer.print_z. -void modulate_extrusion_by_overlapping_layers( - // Extrusions generated for this_layer. - ExtrusionEntitiesPtr &extrusions_in_out, - const SupportGeneratorLayer &this_layer, - // Multiple layers overlapping with this_layer, sorted bottom up. - const SupportGeneratorLayersPtr &overlapping_layers) -{ - size_t n_overlapping_layers = overlapping_layers.size(); - if (n_overlapping_layers == 0 || extrusions_in_out.empty()) - // The extrusions do not overlap with any other extrusion. - return; - - // Get the initial extrusion parameters. - ExtrusionPath *extrusion_path_template = dynamic_cast(extrusions_in_out.front()); - assert(extrusion_path_template != nullptr); - ExtrusionRole extrusion_role = extrusion_path_template->role(); - float extrusion_width = extrusion_path_template->width; - - struct ExtrusionPathFragment - { - ExtrusionPathFragment() : mm3_per_mm(-1), width(-1), height(-1) {}; - ExtrusionPathFragment(double mm3_per_mm, float width, float height) : mm3_per_mm(mm3_per_mm), width(width), height(height) {}; - - Polylines polylines; - double mm3_per_mm; - float width; - float height; - }; - - // Split the extrusions by the overlapping layers, reduce their extrusion rate. - // The last path_fragment is from this_layer. - std::vector path_fragments( - n_overlapping_layers + 1, - ExtrusionPathFragment(extrusion_path_template->mm3_per_mm, extrusion_path_template->width, extrusion_path_template->height)); - // Don't use it, it will be released. - extrusion_path_template = nullptr; - -#ifdef SLIC3R_DEBUG - static int iRun = 0; - ++ iRun; - BoundingBox bbox; - for (size_t i_overlapping_layer = 0; i_overlapping_layer < n_overlapping_layers; ++ i_overlapping_layer) { - const SupportGeneratorLayer &overlapping_layer = *overlapping_layers[i_overlapping_layer]; - bbox.merge(get_extents(overlapping_layer.polygons)); - } - for (ExtrusionEntitiesPtr::const_iterator it = extrusions_in_out.begin(); it != extrusions_in_out.end(); ++ it) { - ExtrusionPath *path = dynamic_cast(*it); - assert(path != nullptr); - bbox.merge(get_extents(path->polyline)); - } - SVG svg(debug_out_path("support-fragments-%d-%lf.svg", iRun, this_layer.print_z).c_str(), bbox); - const float transparency = 0.5f; - // Filled polygons for the overlapping regions. - svg.draw(union_ex(this_layer.polygons), dbg_index_to_color(-1), transparency); - for (size_t i_overlapping_layer = 0; i_overlapping_layer < n_overlapping_layers; ++ i_overlapping_layer) { - const SupportGeneratorLayer &overlapping_layer = *overlapping_layers[i_overlapping_layer]; - svg.draw(union_ex(overlapping_layer.polygons), dbg_index_to_color(int(i_overlapping_layer)), transparency); - } - // Contours of the overlapping regions. - svg.draw(to_polylines(this_layer.polygons), dbg_index_to_color(-1), scale_(0.2)); - for (size_t i_overlapping_layer = 0; i_overlapping_layer < n_overlapping_layers; ++ i_overlapping_layer) { - const SupportGeneratorLayer &overlapping_layer = *overlapping_layers[i_overlapping_layer]; - svg.draw(to_polylines(overlapping_layer.polygons), dbg_index_to_color(int(i_overlapping_layer)), scale_(0.1)); - } - // Fill extrusion, the source. - for (ExtrusionEntitiesPtr::const_iterator it = extrusions_in_out.begin(); it != extrusions_in_out.end(); ++ it) { - ExtrusionPath *path = dynamic_cast(*it); - std::string color_name; - switch ((it - extrusions_in_out.begin()) % 9) { - case 0: color_name = "magenta"; break; - case 1: color_name = "deepskyblue"; break; - case 2: color_name = "coral"; break; - case 3: color_name = "goldenrod"; break; - case 4: color_name = "orange"; break; - case 5: color_name = "olivedrab"; break; - case 6: color_name = "blueviolet"; break; - case 7: color_name = "brown"; break; - default: color_name = "orchid"; break; - } - svg.draw(path->polyline, color_name, scale_(0.2)); - } -#endif /* SLIC3R_DEBUG */ - - // End points of the original paths. - std::vector> path_ends; - // Collect the paths of this_layer. - { - Polylines &polylines = path_fragments.back().polylines; - for (ExtrusionEntity *ee : extrusions_in_out) { - ExtrusionPath *path = dynamic_cast(ee); - assert(path != nullptr); - polylines.emplace_back(Polyline(std::move(path->polyline))); - path_ends.emplace_back(std::pair(polylines.back().points.front(), polylines.back().points.back())); - delete path; - } - } - // Destroy the original extrusion paths, their polylines were moved to path_fragments already. - // This will be the destination for the new paths. - extrusions_in_out.clear(); - - // Fragment the path segments by overlapping layers. The overlapping layers are sorted by an increasing print_z. - // Trim by the highest overlapping layer first. - for (int i_overlapping_layer = int(n_overlapping_layers) - 1; i_overlapping_layer >= 0; -- i_overlapping_layer) { - const SupportGeneratorLayer &overlapping_layer = *overlapping_layers[i_overlapping_layer]; - ExtrusionPathFragment &frag = path_fragments[i_overlapping_layer]; - Polygons polygons_trimming = offset(union_ex(overlapping_layer.polygons), float(scale_(0.5*extrusion_width))); - frag.polylines = intersection_pl(path_fragments.back().polylines, polygons_trimming); - path_fragments.back().polylines = diff_pl(path_fragments.back().polylines, polygons_trimming); - // Adjust the extrusion parameters for a reduced layer height and a non-bridging flow (nozzle_dmr = -1, does not matter). - assert(this_layer.print_z > overlapping_layer.print_z); - frag.height = float(this_layer.print_z - overlapping_layer.print_z); - frag.mm3_per_mm = Flow(frag.width, frag.height, -1.f).mm3_per_mm(); -#ifdef SLIC3R_DEBUG - svg.draw(frag.polylines, dbg_index_to_color(i_overlapping_layer), scale_(0.1)); -#endif /* SLIC3R_DEBUG */ - } - -#ifdef SLIC3R_DEBUG - svg.draw(path_fragments.back().polylines, dbg_index_to_color(-1), scale_(0.1)); - svg.Close(); -#endif /* SLIC3R_DEBUG */ - - // Now chain the split segments using hashing and a nearly exact match, maintaining the order of segments. - // Create a single ExtrusionPath or ExtrusionEntityCollection per source ExtrusionPath. - // Map of fragment start/end points to a pair of - // Because a non-exact matching is used for the end points, a multi-map is used. - // As the clipper library may reverse the order of some clipped paths, store both ends into the map. - struct ExtrusionPathFragmentEnd - { - ExtrusionPathFragmentEnd(size_t alayer_idx, size_t apolyline_idx, bool ais_start) : - layer_idx(alayer_idx), polyline_idx(apolyline_idx), is_start(ais_start) {} - size_t layer_idx; - size_t polyline_idx; - bool is_start; - }; - class ExtrusionPathFragmentEndPointAccessor { - public: - ExtrusionPathFragmentEndPointAccessor(const std::vector &path_fragments) : m_path_fragments(path_fragments) {} - // Return an end point of a fragment, or nullptr if the fragment has been consumed already. - const Point* operator()(const ExtrusionPathFragmentEnd &fragment_end) const { - const Polyline &polyline = m_path_fragments[fragment_end.layer_idx].polylines[fragment_end.polyline_idx]; - return polyline.points.empty() ? nullptr : - (fragment_end.is_start ? &polyline.points.front() : &polyline.points.back()); - } - private: - ExtrusionPathFragmentEndPointAccessor& operator=(const ExtrusionPathFragmentEndPointAccessor&) { - return *this; - } - - const std::vector &m_path_fragments; - }; - const coord_t search_radius = 7; - ClosestPointInRadiusLookup map_fragment_starts( - search_radius, ExtrusionPathFragmentEndPointAccessor(path_fragments)); - for (size_t i_overlapping_layer = 0; i_overlapping_layer <= n_overlapping_layers; ++ i_overlapping_layer) { - const Polylines &polylines = path_fragments[i_overlapping_layer].polylines; - for (size_t i_polyline = 0; i_polyline < polylines.size(); ++ i_polyline) { - // Map a starting point of a polyline to a pair of - if (polylines[i_polyline].points.size() >= 2) { - map_fragment_starts.insert(ExtrusionPathFragmentEnd(i_overlapping_layer, i_polyline, true)); - map_fragment_starts.insert(ExtrusionPathFragmentEnd(i_overlapping_layer, i_polyline, false)); - } - } - } - - // For each source path: - for (size_t i_path = 0; i_path < path_ends.size(); ++ i_path) { - const Point &pt_start = path_ends[i_path].first; - const Point &pt_end = path_ends[i_path].second; - Point pt_current = pt_start; - // Find a chain of fragments with the original / reduced print height. - ExtrusionMultiPath multipath; - for (;;) { - // Find a closest end point to pt_current. - std::pair end_and_dist2 = map_fragment_starts.find(pt_current); - // There may be a bug in Clipper flipping the order of two last points in a fragment? - // assert(end_and_dist2.first != nullptr); - assert(end_and_dist2.first == nullptr || end_and_dist2.second < search_radius * search_radius); - if (end_and_dist2.first == nullptr) { - // New fragment connecting to pt_current was not found. - // Verify that the last point found is close to the original end point of the unfragmented path. - //const double d2 = (pt_end - pt_current).cast.squaredNorm(); - //assert(d2 < coordf_t(search_radius * search_radius)); - // End of the path. - break; - } - const ExtrusionPathFragmentEnd &fragment_end_min = *end_and_dist2.first; - // Fragment to consume. - ExtrusionPathFragment &frag = path_fragments[fragment_end_min.layer_idx]; - Polyline &frag_polyline = frag.polylines[fragment_end_min.polyline_idx]; - // Path to append the fragment to. - ExtrusionPath *path = multipath.paths.empty() ? nullptr : &multipath.paths.back(); - if (path != nullptr) { - // Verify whether the path is compatible with the current fragment. - assert(this_layer.layer_type == SupporLayerType::BottomContact || path->height != frag.height || path->mm3_per_mm != frag.mm3_per_mm); - if (path->height != frag.height || path->mm3_per_mm != frag.mm3_per_mm) { - path = nullptr; - } - // Merging with the previous path. This can only happen if the current layer was reduced by a base layer, which was split into a base and interface layer. - } - if (path == nullptr) { - // Allocate a new path. - multipath.paths.push_back(ExtrusionPath(extrusion_role, frag.mm3_per_mm, frag.width, frag.height)); - path = &multipath.paths.back(); - } - // The Clipper library may flip the order of the clipped polylines arbitrarily. - // Reverse the source polyline, if connecting to the end. - if (! fragment_end_min.is_start) - frag_polyline.reverse(); - // Enforce exact overlap of the end points of successive fragments. - assert(frag_polyline.points.front() == pt_current); - frag_polyline.points.front() = pt_current; - // Don't repeat the first point. - if (! path->polyline.points.empty()) - path->polyline.points.pop_back(); - // Consume the fragment's polyline, remove it from the input fragments, so it will be ignored the next time. - path->polyline.append(std::move(frag_polyline)); - frag_polyline.points.clear(); - pt_current = path->polyline.points.back(); - if (pt_current == pt_end) { - // End of the path. - break; - } - } - if (!multipath.paths.empty()) { - if (multipath.paths.size() == 1) { - // This path was not fragmented. - extrusions_in_out.push_back(new ExtrusionPath(std::move(multipath.paths.front()))); - } else { - // This path was fragmented. Copy the collection as a whole object, so the order inside the collection will not be changed - // during the chaining of extrusions_in_out. - extrusions_in_out.push_back(new ExtrusionMultiPath(std::move(multipath))); - } - } - } - // If there are any non-consumed fragments, add them separately. - //FIXME this shall not happen, if the Clipper works as expected and all paths split to fragments could be re-connected. - for (auto it_fragment = path_fragments.begin(); it_fragment != path_fragments.end(); ++ it_fragment) - extrusion_entities_append_paths(extrusions_in_out, std::move(it_fragment->polylines), extrusion_role, it_fragment->mm3_per_mm, it_fragment->width, it_fragment->height); -} - -SupportGeneratorLayersPtr generate_support_layers( - PrintObject &object, - const SupportGeneratorLayersPtr &raft_layers, - const SupportGeneratorLayersPtr &bottom_contacts, - const SupportGeneratorLayersPtr &top_contacts, - const SupportGeneratorLayersPtr &intermediate_layers, - const SupportGeneratorLayersPtr &interface_layers, - const SupportGeneratorLayersPtr &base_interface_layers) -{ - // Install support layers into the object. - // A support layer installed on a PrintObject has a unique print_z. - SupportGeneratorLayersPtr layers_sorted; - layers_sorted.reserve(raft_layers.size() + bottom_contacts.size() + top_contacts.size() + intermediate_layers.size() + interface_layers.size() + base_interface_layers.size()); - layers_append(layers_sorted, raft_layers); - layers_append(layers_sorted, bottom_contacts); - layers_append(layers_sorted, top_contacts); - layers_append(layers_sorted, intermediate_layers); - layers_append(layers_sorted, interface_layers); - layers_append(layers_sorted, base_interface_layers); - // Sort the layers lexicographically by a raising print_z and a decreasing height. - std::sort(layers_sorted.begin(), layers_sorted.end(), [](auto *l1, auto *l2) { return *l1 < *l2; }); - int layer_id = 0; - int layer_id_interface = 0; - assert(object.support_layers().empty()); - for (size_t i = 0; i < layers_sorted.size();) { - // Find the last layer with roughly the same print_z, find the minimum layer height of all. - // Due to the floating point inaccuracies, the print_z may not be the same even if in theory they should. - size_t j = i + 1; - coordf_t zmax = layers_sorted[i]->print_z + EPSILON; - for (; j < layers_sorted.size() && layers_sorted[j]->print_z <= zmax; ++j) ; - // Assign an average print_z to the set of layers with nearly equal print_z. - coordf_t zavg = 0.5 * (layers_sorted[i]->print_z + layers_sorted[j - 1]->print_z); - coordf_t height_min = layers_sorted[i]->height; - bool empty = true; - // For snug supports, layers where the direction of the support interface shall change are accounted for. - size_t num_interfaces = 0; - size_t num_top_contacts = 0; - double top_contact_bottom_z = 0; - for (size_t u = i; u < j; ++u) { - SupportGeneratorLayer &layer = *layers_sorted[u]; - if (! layer.polygons.empty()) { - empty = false; - num_interfaces += one_of(layer.layer_type, support_types_interface); - if (layer.layer_type == SupporLayerType::TopContact) { - ++ num_top_contacts; - assert(num_top_contacts <= 1); - // All top contact layers sharing this print_z shall also share bottom_z. - //assert(num_top_contacts == 1 || (top_contact_bottom_z - layer.bottom_z) < EPSILON); - top_contact_bottom_z = layer.bottom_z; - } - } - layer.print_z = zavg; - height_min = std::min(height_min, layer.height); - } - if (! empty) { - // Here the upper_layer and lower_layer pointers are left to null at the support layers, - // as they are never used. These pointers are candidates for removal. - bool this_layer_contacts_only = num_top_contacts > 0 && num_top_contacts == num_interfaces; - size_t this_layer_id_interface = layer_id_interface; - if (this_layer_contacts_only) { - // Find a supporting layer for its interface ID. - for (auto it = object.support_layers().rbegin(); it != object.support_layers().rend(); ++ it) - if (const SupportLayer &other_layer = **it; std::abs(other_layer.print_z - top_contact_bottom_z) < EPSILON) { - // other_layer supports this top contact layer. Assign a different support interface direction to this layer - // from the layer that supports it. - this_layer_id_interface = other_layer.interface_id() + 1; - } - } - object.add_support_layer(layer_id ++, this_layer_id_interface, height_min, zavg); - if (num_interfaces && ! this_layer_contacts_only) - ++ layer_id_interface; - } - i = j; - } - return layers_sorted; -} - -void generate_support_toolpaths( - SupportLayerPtrs &support_layers, - const PrintObjectConfig &config, - const SupportParameters &support_params, - const SlicingParameters &slicing_params, - const SupportGeneratorLayersPtr &raft_layers, - const SupportGeneratorLayersPtr &bottom_contacts, - const SupportGeneratorLayersPtr &top_contacts, - const SupportGeneratorLayersPtr &intermediate_layers, - const SupportGeneratorLayersPtr &interface_layers, - const SupportGeneratorLayersPtr &base_interface_layers) -{ - // loop_interface_processor with a given circle radius. - LoopInterfaceProcessor loop_interface_processor(1.5 * support_params.support_material_interface_flow.scaled_width()); - loop_interface_processor.n_contact_loops = config.support_material_interface_contact_loops ? 1 : 0; - - std::vector angles { support_params.base_angle }; - if (config.support_material_pattern == smpRectilinearGrid) - angles.push_back(support_params.interface_angle); - - BoundingBox bbox_object(Point(-scale_(1.), -scale_(1.0)), Point(scale_(1.), scale_(1.))); - -// const coordf_t link_max_length_factor = 3.; - const coordf_t link_max_length_factor = 0.; - - // Insert the raft base layers. - auto n_raft_layers = std::min(support_layers.size(), std::max(0, int(slicing_params.raft_layers()) - 1)); - - tbb::parallel_for(tbb::blocked_range(0, n_raft_layers), - [&support_layers, &raft_layers, &intermediate_layers, &config, &support_params, &slicing_params, - &bbox_object, link_max_length_factor] - (const tbb::blocked_range& range) { - for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id) - { - assert(support_layer_id < raft_layers.size()); - SupportLayer &support_layer = *support_layers[support_layer_id]; - assert(support_layer.support_fills.entities.empty()); - SupportGeneratorLayer &raft_layer = *raft_layers[support_layer_id]; - - std::unique_ptr filler_interface = std::unique_ptr(Fill::new_from_type(support_params.raft_interface_fill_pattern)); - std::unique_ptr filler_support = std::unique_ptr(Fill::new_from_type(support_params.base_fill_pattern)); - filler_interface->set_bounding_box(bbox_object); - filler_support->set_bounding_box(bbox_object); - - // Print the tree supports cutting through the raft with the exception of the 1st layer, where a full support layer will be printed below - // both the raft and the trees. - // Trim the raft layers with the tree polygons. - const Polygons &tree_polygons = - support_layer_id > 0 && support_layer_id < intermediate_layers.size() && is_approx(intermediate_layers[support_layer_id]->print_z, support_layer.print_z) ? - intermediate_layers[support_layer_id]->polygons : Polygons(); - - // Print the support base below the support columns, or the support base for the support columns plus the contacts. - if (support_layer_id > 0) { - const Polygons &to_infill_polygons = (support_layer_id < slicing_params.base_raft_layers) ? - raft_layer.polygons : - //FIXME misusing contact_polygons for support columns. - ((raft_layer.contact_polygons == nullptr) ? Polygons() : *raft_layer.contact_polygons); - // Trees may cut through the raft layers down to a print bed. - Flow flow(float(support_params.support_material_flow.width()), float(raft_layer.height), support_params.support_material_flow.nozzle_diameter()); - assert(!raft_layer.bridging); - if (! to_infill_polygons.empty()) { - Fill *filler = filler_support.get(); - filler->angle = support_params.raft_angle_base; - filler->spacing = support_params.support_material_flow.spacing(); - filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_params.support_density)); - fill_expolygons_with_sheath_generate_paths( - // Destination - support_layer.support_fills.entities, - // Regions to fill - tree_polygons.empty() ? to_infill_polygons : diff(to_infill_polygons, tree_polygons), - // Filler and its parameters - filler, float(support_params.support_density), - // Extrusion parameters - ExtrusionRole::SupportMaterial, flow, - support_params.with_sheath, false); - } - if (! tree_polygons.empty()) - tree_supports_generate_paths(support_layer.support_fills.entities, tree_polygons, flow); - } - - Fill *filler = filler_interface.get(); - Flow flow = support_params.first_layer_flow; - float density = 0.f; - if (support_layer_id == 0) { - // Base flange. - filler->angle = support_params.raft_angle_1st_layer; - filler->spacing = support_params.first_layer_flow.spacing(); - density = float(config.raft_first_layer_density.value * 0.01); - } else if (support_layer_id >= slicing_params.base_raft_layers) { - filler->angle = support_params.raft_interface_angle(support_layer.interface_id()); - // We don't use $base_flow->spacing because we need a constant spacing - // value that guarantees that all layers are correctly aligned. - filler->spacing = support_params.support_material_flow.spacing(); - assert(! raft_layer.bridging); - flow = Flow(float(support_params.raft_interface_flow.width()), float(raft_layer.height), support_params.raft_interface_flow.nozzle_diameter()); - density = float(support_params.raft_interface_density); - } else - continue; - filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density)); - fill_expolygons_with_sheath_generate_paths( - // Destination - support_layer.support_fills.entities, - // Regions to fill - tree_polygons.empty() ? raft_layer.polygons : diff(raft_layer.polygons, tree_polygons), - // Filler and its parameters - filler, density, - // Extrusion parameters - (support_layer_id < slicing_params.base_raft_layers) ? ExtrusionRole::SupportMaterial : ExtrusionRole::SupportMaterialInterface, flow, - // sheath at first layer - support_layer_id == 0, support_layer_id == 0); - } - }); - - struct LayerCacheItem { - LayerCacheItem(SupportGeneratorLayerExtruded *layer_extruded = nullptr) : layer_extruded(layer_extruded) {} - SupportGeneratorLayerExtruded *layer_extruded; - std::vector overlapping; - }; - struct LayerCache { - SupportGeneratorLayerExtruded bottom_contact_layer; - SupportGeneratorLayerExtruded top_contact_layer; - SupportGeneratorLayerExtruded base_layer; - SupportGeneratorLayerExtruded interface_layer; - SupportGeneratorLayerExtruded base_interface_layer; - boost::container::static_vector nonempty; - - void add_nonempty_and_sort() { - for (SupportGeneratorLayerExtruded *item : { &bottom_contact_layer, &top_contact_layer, &interface_layer, &base_interface_layer, &base_layer }) - if (! item->empty()) - this->nonempty.emplace_back(item); - // Sort the layers with the same print_z coordinate by their heights, thickest first. - std::stable_sort(this->nonempty.begin(), this->nonempty.end(), [](const LayerCacheItem &lc1, const LayerCacheItem &lc2) { return lc1.layer_extruded->layer->height > lc2.layer_extruded->layer->height; }); - } - }; - std::vector layer_caches(support_layers.size()); - - tbb::parallel_for(tbb::blocked_range(n_raft_layers, support_layers.size()), - [&config, &slicing_params, &support_params, &support_layers, &bottom_contacts, &top_contacts, &intermediate_layers, &interface_layers, &base_interface_layers, &layer_caches, &loop_interface_processor, - &bbox_object, &angles, n_raft_layers, link_max_length_factor] - (const tbb::blocked_range& range) { - // Indices of the 1st layer in their respective container at the support layer height. - size_t idx_layer_bottom_contact = size_t(-1); - size_t idx_layer_top_contact = size_t(-1); - size_t idx_layer_intermediate = size_t(-1); - size_t idx_layer_interface = size_t(-1); - size_t idx_layer_base_interface = size_t(-1); - const auto fill_type_first_layer = ipRectilinear; - auto filler_interface = std::unique_ptr(Fill::new_from_type(support_params.contact_fill_pattern)); - // Filler for the 1st layer interface, if different from filler_interface. - auto filler_first_layer_ptr = std::unique_ptr(range.begin() == 0 && support_params.contact_fill_pattern != fill_type_first_layer ? Fill::new_from_type(fill_type_first_layer) : nullptr); - // Pointer to the 1st layer interface filler. - auto filler_first_layer = filler_first_layer_ptr ? filler_first_layer_ptr.get() : filler_interface.get(); - // Filler for the 1st layer interface, if different from filler_interface. - auto filler_raft_contact_ptr = std::unique_ptr(range.begin() == n_raft_layers && config.support_material_interface_layers.value == 0 ? - Fill::new_from_type(support_params.raft_interface_fill_pattern) : nullptr); - // Pointer to the 1st layer interface filler. - auto filler_raft_contact = filler_raft_contact_ptr ? filler_raft_contact_ptr.get() : filler_interface.get(); - // Filler for the base interface (to be used for soluble interface / non soluble base, to produce non soluble interface layer below soluble interface layer). - auto filler_base_interface = std::unique_ptr(base_interface_layers.empty() ? nullptr : - Fill::new_from_type(support_params.interface_density > 0.95 || support_params.with_sheath ? ipRectilinear : ipSupportBase)); - auto filler_support = std::unique_ptr(Fill::new_from_type(support_params.base_fill_pattern)); - filler_interface->set_bounding_box(bbox_object); - if (filler_first_layer_ptr) - filler_first_layer_ptr->set_bounding_box(bbox_object); - if (filler_raft_contact_ptr) - filler_raft_contact_ptr->set_bounding_box(bbox_object); - if (filler_base_interface) - filler_base_interface->set_bounding_box(bbox_object); - filler_support->set_bounding_box(bbox_object); - for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id) - { - SupportLayer &support_layer = *support_layers[support_layer_id]; - LayerCache &layer_cache = layer_caches[support_layer_id]; - const float support_interface_angle = config.support_material_style.value == smsGrid ? - support_params.interface_angle : support_params.raft_interface_angle(support_layer.interface_id()); - - // Find polygons with the same print_z. - SupportGeneratorLayerExtruded &bottom_contact_layer = layer_cache.bottom_contact_layer; - SupportGeneratorLayerExtruded &top_contact_layer = layer_cache.top_contact_layer; - SupportGeneratorLayerExtruded &base_layer = layer_cache.base_layer; - SupportGeneratorLayerExtruded &interface_layer = layer_cache.interface_layer; - SupportGeneratorLayerExtruded &base_interface_layer = layer_cache.base_interface_layer; - // Increment the layer indices to find a layer at support_layer.print_z. - { - auto fun = [&support_layer](const SupportGeneratorLayer *l){ return l->print_z >= support_layer.print_z - EPSILON; }; - idx_layer_bottom_contact = idx_higher_or_equal(bottom_contacts, idx_layer_bottom_contact, fun); - idx_layer_top_contact = idx_higher_or_equal(top_contacts, idx_layer_top_contact, fun); - idx_layer_intermediate = idx_higher_or_equal(intermediate_layers, idx_layer_intermediate, fun); - idx_layer_interface = idx_higher_or_equal(interface_layers, idx_layer_interface, fun); - idx_layer_base_interface = idx_higher_or_equal(base_interface_layers, idx_layer_base_interface,fun); - } - // Copy polygons from the layers. - if (idx_layer_bottom_contact < bottom_contacts.size() && bottom_contacts[idx_layer_bottom_contact]->print_z < support_layer.print_z + EPSILON) - bottom_contact_layer.layer = bottom_contacts[idx_layer_bottom_contact]; - if (idx_layer_top_contact < top_contacts.size() && top_contacts[idx_layer_top_contact]->print_z < support_layer.print_z + EPSILON) - top_contact_layer.layer = top_contacts[idx_layer_top_contact]; - if (idx_layer_interface < interface_layers.size() && interface_layers[idx_layer_interface]->print_z < support_layer.print_z + EPSILON) - interface_layer.layer = interface_layers[idx_layer_interface]; - if (idx_layer_base_interface < base_interface_layers.size() && base_interface_layers[idx_layer_base_interface]->print_z < support_layer.print_z + EPSILON) - base_interface_layer.layer = base_interface_layers[idx_layer_base_interface]; - if (idx_layer_intermediate < intermediate_layers.size() && intermediate_layers[idx_layer_intermediate]->print_z < support_layer.print_z + EPSILON) - base_layer.layer = intermediate_layers[idx_layer_intermediate]; - - // This layer is a raft contact layer. Any contact polygons at this layer are raft contacts. - bool raft_layer = slicing_params.interface_raft_layers && top_contact_layer.layer && is_approx(top_contact_layer.layer->print_z, slicing_params.raft_contact_top_z); - if (config.support_material_interface_layers == 0) { - // If no top interface layers were requested, we treat the contact layer exactly as a generic base layer. - // Don't merge the raft contact layer though. - if (support_params.can_merge_support_regions && ! raft_layer) { - if (base_layer.could_merge(top_contact_layer)) - base_layer.merge(std::move(top_contact_layer)); - else if (base_layer.empty()) - base_layer = std::move(top_contact_layer); - } - } else { - loop_interface_processor.generate(top_contact_layer, support_params.support_material_interface_flow); - // If no loops are allowed, we treat the contact layer exactly as a generic interface layer. - // Merge interface_layer into top_contact_layer, as the top_contact_layer is not synchronized and therefore it will be used - // to trim other layers. - if (top_contact_layer.could_merge(interface_layer) && ! raft_layer) - top_contact_layer.merge(std::move(interface_layer)); - } - if ((config.support_material_interface_layers == 0 || config.support_material_bottom_interface_layers == 0) && support_params.can_merge_support_regions) { - if (base_layer.could_merge(bottom_contact_layer)) - base_layer.merge(std::move(bottom_contact_layer)); - else if (base_layer.empty() && ! bottom_contact_layer.empty() && ! bottom_contact_layer.layer->bridging) - base_layer = std::move(bottom_contact_layer); - } else if (bottom_contact_layer.could_merge(top_contact_layer) && ! raft_layer) - top_contact_layer.merge(std::move(bottom_contact_layer)); - else if (bottom_contact_layer.could_merge(interface_layer)) - bottom_contact_layer.merge(std::move(interface_layer)); - -#if 0 - if ( ! interface_layer.empty() && ! base_layer.empty()) { - // turn base support into interface when it's contained in our holes - // (this way we get wider interface anchoring) - //FIXME The intention of the code below is unclear. One likely wanted to just merge small islands of base layers filling in the holes - // inside interface layers, but the code below fills just too much, see GH #4570 - Polygons islands = top_level_islands(interface_layer.layer->polygons); - polygons_append(interface_layer.layer->polygons, intersection(base_layer.layer->polygons, islands)); - base_layer.layer->polygons = diff(base_layer.layer->polygons, islands); - } -#endif - - // Top and bottom contacts, interface layers. - enum class InterfaceLayerType { TopContact, BottomContact, RaftContact, Interface, InterfaceAsBase }; - auto extrude_interface = [&](SupportGeneratorLayerExtruded &layer_ex, InterfaceLayerType interface_layer_type) { - if (! layer_ex.empty() && ! layer_ex.polygons_to_extrude().empty()) { - bool interface_as_base = interface_layer_type == InterfaceLayerType::InterfaceAsBase; - bool raft_contact = interface_layer_type == InterfaceLayerType::RaftContact; - //FIXME Bottom interfaces are extruded with the briding flow. Some bridging layers have its height slightly reduced, therefore - // the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b) - auto *filler = raft_contact ? filler_raft_contact : filler_interface.get(); - auto interface_flow = layer_ex.layer->bridging ? - Flow::bridging_flow(layer_ex.layer->height, support_params.support_material_bottom_interface_flow.nozzle_diameter()) : - (raft_contact ? &support_params.raft_interface_flow : - interface_as_base ? &support_params.support_material_flow : &support_params.support_material_interface_flow) - ->with_height(float(layer_ex.layer->height)); - filler->angle = interface_as_base ? - // If zero interface layers are configured, use the same angle as for the base layers. - angles[support_layer_id % angles.size()] : - // Use interface angle for the interface layers. - raft_contact ? - support_params.raft_interface_angle(support_layer.interface_id()) : - support_interface_angle; - double density = raft_contact ? support_params.raft_interface_density : interface_as_base ? support_params.support_density : support_params.interface_density; - filler->spacing = raft_contact ? support_params.raft_interface_flow.spacing() : - interface_as_base ? support_params.support_material_flow.spacing() : support_params.support_material_interface_flow.spacing(); - filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density)); - fill_expolygons_generate_paths( - // Destination - layer_ex.extrusions, - // Regions to fill - union_safety_offset_ex(layer_ex.polygons_to_extrude()), - // Filler and its parameters - filler, float(density), - // Extrusion parameters - ExtrusionRole::SupportMaterialInterface, interface_flow); - } - }; - const bool top_interfaces = config.support_material_interface_layers.value != 0; - const bool bottom_interfaces = top_interfaces && config.support_material_bottom_interface_layers != 0; - extrude_interface(top_contact_layer, raft_layer ? InterfaceLayerType::RaftContact : top_interfaces ? InterfaceLayerType::TopContact : InterfaceLayerType::InterfaceAsBase); - extrude_interface(bottom_contact_layer, bottom_interfaces ? InterfaceLayerType::BottomContact : InterfaceLayerType::InterfaceAsBase); - extrude_interface(interface_layer, top_interfaces ? InterfaceLayerType::Interface : InterfaceLayerType::InterfaceAsBase); - - // Base interface layers under soluble interfaces - if ( ! base_interface_layer.empty() && ! base_interface_layer.polygons_to_extrude().empty()) { - Fill *filler = filler_base_interface.get(); - //FIXME Bottom interfaces are extruded with the briding flow. Some bridging layers have its height slightly reduced, therefore - // the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b) - assert(! base_interface_layer.layer->bridging); - Flow interface_flow = support_params.support_material_flow.with_height(float(base_interface_layer.layer->height)); - filler->angle = support_interface_angle; - filler->spacing = support_params.support_material_interface_flow.spacing(); - filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_params.interface_density)); - fill_expolygons_generate_paths( - // Destination - base_interface_layer.extrusions, - //base_layer_interface.extrusions, - // Regions to fill - union_safety_offset_ex(base_interface_layer.polygons_to_extrude()), - // Filler and its parameters - filler, float(support_params.interface_density), - // Extrusion parameters - ExtrusionRole::SupportMaterial, interface_flow); - } - - // Base support or flange. - if (! base_layer.empty() && ! base_layer.polygons_to_extrude().empty()) { - Fill *filler = filler_support.get(); - filler->angle = angles[support_layer_id % angles.size()]; - // We don't use $base_flow->spacing because we need a constant spacing - // value that guarantees that all layers are correctly aligned. - assert(! base_layer.layer->bridging); - auto flow = support_params.support_material_flow.with_height(float(base_layer.layer->height)); - filler->spacing = support_params.support_material_flow.spacing(); - filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_params.support_density)); - float density = float(support_params.support_density); - bool sheath = support_params.with_sheath; - bool no_sort = false; - bool done = false; - if (base_layer.layer->bottom_z < EPSILON) { - // Base flange (the 1st layer). - filler = filler_first_layer; - filler->angle = Geometry::deg2rad(float(config.support_material_angle.value + 90.)); - density = float(config.raft_first_layer_density.value * 0.01); - flow = support_params.first_layer_flow; - // use the proper spacing for first layer as we don't need to align - // its pattern to the other layers - //FIXME When paralellizing, each thread shall have its own copy of the fillers. - filler->spacing = flow.spacing(); - filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density)); - sheath = true; - no_sort = true; - } else if (config.support_material_style == SupportMaterialStyle::smsOrganic) { - tree_supports_generate_paths(base_layer.extrusions, base_layer.polygons_to_extrude(), flow); - done = true; - } - if (! done) - fill_expolygons_with_sheath_generate_paths( - // Destination - base_layer.extrusions, - // Regions to fill - base_layer.polygons_to_extrude(), - // Filler and its parameters - filler, density, - // Extrusion parameters - ExtrusionRole::SupportMaterial, flow, - sheath, no_sort); - } - - // Merge base_interface_layers to base_layers to avoid unneccessary retractions - if (! base_layer.empty() && ! base_interface_layer.empty() && ! base_layer.polygons_to_extrude().empty() && ! base_interface_layer.polygons_to_extrude().empty() && - base_layer.could_merge(base_interface_layer)) - base_layer.merge(std::move(base_interface_layer)); - - layer_cache.add_nonempty_and_sort(); - - // Collect the support areas with this print_z into islands, as there is no need - // for retraction over these islands. - Polygons polys; - // Collect the extrusions, sorted by the bottom extrusion height. - for (LayerCacheItem &layer_cache_item : layer_cache.nonempty) { - // Collect islands to polys. - layer_cache_item.layer_extruded->polygons_append(polys); - // The print_z of the top contact surfaces and bottom_z of the bottom contact surfaces are "free" - // in a sense that they are not synchronized with other support layers. As the top and bottom contact surfaces - // are inflated to achieve a better anchoring, it may happen, that these surfaces will at least partially - // overlap in Z with another support layers, leading to over-extrusion. - // Mitigate the over-extrusion by modulating the extrusion rate over these regions. - // The print head will follow the same print_z, but the layer thickness will be reduced - // where it overlaps with another support layer. - //FIXME When printing a briging path, what is an equivalent height of the squished extrudate of the same width? - // Collect overlapping top/bottom surfaces. - layer_cache_item.overlapping.reserve(20); - coordf_t bottom_z = layer_cache_item.layer_extruded->layer->bottom_print_z() + EPSILON; - auto add_overlapping = [&layer_cache_item, bottom_z](const SupportGeneratorLayersPtr &layers, size_t idx_top) { - for (int i = int(idx_top) - 1; i >= 0 && layers[i]->print_z > bottom_z; -- i) - layer_cache_item.overlapping.push_back(layers[i]); - }; - add_overlapping(top_contacts, idx_layer_top_contact); - if (layer_cache_item.layer_extruded->layer->layer_type == SupporLayerType::BottomContact) { - // Bottom contact layer may overlap with a base layer, which may be changed to interface layer. - add_overlapping(intermediate_layers, idx_layer_intermediate); - add_overlapping(interface_layers, idx_layer_interface); - add_overlapping(base_interface_layers, idx_layer_base_interface); - } - // Order the layers by lexicographically by an increasing print_z and a decreasing layer height. - std::stable_sort(layer_cache_item.overlapping.begin(), layer_cache_item.overlapping.end(), [](auto *l1, auto *l2) { return *l1 < *l2; }); - } - assert(support_layer.support_islands.empty()); - if (! polys.empty()) { - support_layer.support_islands = union_ex(polys); - support_layer.support_islands_bboxes.reserve(support_layer.support_islands.size()); - for (const ExPolygon &expoly : support_layer.support_islands) - support_layer.support_islands_bboxes.emplace_back(get_extents(expoly).inflated(SCALED_EPSILON)); - } - } // for each support_layer_id - }); - - // Now modulate the support layer height in parallel. - tbb::parallel_for(tbb::blocked_range(n_raft_layers, support_layers.size()), - [&support_layers, &layer_caches] - (const tbb::blocked_range& range) { - for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id) { - SupportLayer &support_layer = *support_layers[support_layer_id]; - LayerCache &layer_cache = layer_caches[support_layer_id]; - // For all extrusion types at this print_z, ordered by decreasing layer height: - for (LayerCacheItem &layer_cache_item : layer_cache.nonempty) { - // Trim the extrusion height from the bottom by the overlapping layers. - modulate_extrusion_by_overlapping_layers(layer_cache_item.layer_extruded->extrusions, *layer_cache_item.layer_extruded->layer, layer_cache_item.overlapping); - support_layer.support_fills.append(std::move(layer_cache_item.layer_extruded->extrusions)); - } - } - }); - -#ifndef NDEBUG - struct Test { - static bool verify_nonempty(const ExtrusionEntityCollection *collection) { - for (const ExtrusionEntity *ee : collection->entities) { - if (const ExtrusionPath *path = dynamic_cast(ee)) - assert(! path->empty()); - else if (const ExtrusionMultiPath *multipath = dynamic_cast(ee)) - assert(! multipath->empty()); - else if (const ExtrusionEntityCollection *eecol = dynamic_cast(ee)) { - assert(! eecol->empty()); - return verify_nonempty(eecol); - } else - assert(false); - } - return true; - } - }; - for (const SupportLayer *support_layer : support_layers) - assert(Test::verify_nonempty(&support_layer->support_fills)); -#endif // NDEBUG -} - /* void PrintObjectSupportMaterial::clip_by_pillars( const PrintObject &object, diff --git a/src/libslic3r/SupportMaterial.hpp b/src/libslic3r/SupportMaterial.hpp index 24dc8507e..bbb991c3e 100644 --- a/src/libslic3r/SupportMaterial.hpp +++ b/src/libslic3r/SupportMaterial.hpp @@ -12,54 +12,6 @@ namespace Slic3r { class PrintObject; -// Remove bridges from support contact areas. -// To be called if PrintObjectConfig::dont_support_bridges. -void remove_bridges_from_contacts( - const PrintConfig &print_config, - const Layer &lower_layer, - const LayerRegion &layerm, - float fw, - Polygons &contact_polygons); - -// Generate raft layers, also expand the 1st support layer -// in case there is no raft layer to improve support adhesion. -SupportGeneratorLayersPtr generate_raft_base( - const PrintObject &object, - const SupportParameters &support_params, - const SlicingParameters &slicing_params, - const SupportGeneratorLayersPtr &top_contacts, - const SupportGeneratorLayersPtr &interface_layers, - const SupportGeneratorLayersPtr &base_interface_layers, - const SupportGeneratorLayersPtr &base_layers, - SupportGeneratorLayerStorage &layer_storage); - -// returns sorted layers -SupportGeneratorLayersPtr generate_support_layers( - PrintObject &object, - const SupportGeneratorLayersPtr &raft_layers, - const SupportGeneratorLayersPtr &bottom_contacts, - const SupportGeneratorLayersPtr &top_contacts, - const SupportGeneratorLayersPtr &intermediate_layers, - const SupportGeneratorLayersPtr &interface_layers, - const SupportGeneratorLayersPtr &base_interface_layers); - -// Produce the support G-code. -// Used by both classic and tree supports. -void generate_support_toolpaths( - SupportLayerPtrs &support_layers, - const PrintObjectConfig &config, - const SupportParameters &support_params, - const SlicingParameters &slicing_params, - const SupportGeneratorLayersPtr &raft_layers, - const SupportGeneratorLayersPtr &bottom_contacts, - const SupportGeneratorLayersPtr &top_contacts, - const SupportGeneratorLayersPtr &intermediate_layers, - const SupportGeneratorLayersPtr &interface_layers, - const SupportGeneratorLayersPtr &base_interface_layers); - -void export_print_z_polygons_to_svg(const char *path, SupportGeneratorLayer ** const layers, size_t n_layers); -void export_print_z_polygons_and_extrusions_to_svg(const char *path, SupportGeneratorLayer ** const layers, size_t n_layers, SupportLayer& support_layer); - // This class manages raft and supports for a single PrintObject. // Instantiated by Slic3r::Print::Object->_support_material() // This class is instantiated before the slicing starts as Object.pm will query @@ -84,6 +36,10 @@ public: void generate(PrintObject &object); private: + using SupportGeneratorLayersPtr = FFFSupport::SupportGeneratorLayersPtr; + using SupportGeneratorLayerStorage = FFFSupport::SupportGeneratorLayerStorage; + using SupportParameters = FFFSupport::SupportParameters; + std::vector buildplate_covered(const PrintObject &object) const; // Generate top contact layers supporting overhangs. diff --git a/src/libslic3r/TreeSupport.cpp b/src/libslic3r/TreeSupport.cpp index 9a7b203c6..a3264990a 100644 --- a/src/libslic3r/TreeSupport.cpp +++ b/src/libslic3r/TreeSupport.cpp @@ -19,9 +19,10 @@ #include "Polygon.hpp" #include "Polyline.hpp" #include "MutablePolygon.hpp" -#include "SupportMaterial.hpp" #include "TriangleMeshSlicer.hpp" +#include "Support/SupportCommon.hpp" + #include #include #include @@ -37,7 +38,6 @@ #include #include -#include #if defined(TREE_SUPPORT_SHOW_ERRORS) && defined(_WIN32) #define TREE_SUPPORT_SHOW_ERRORS_WIN32 @@ -53,6 +53,8 @@ // #define TREESUPPORT_DEBUG_SVG +using namespace Slic3r::FFFSupport; + namespace Slic3r { @@ -962,13 +964,11 @@ static LayerIndex layer_idx_floor(const SlicingParameters &slicing_params, const } static inline SupportGeneratorLayer& layer_initialize( - SupportGeneratorLayer &layer_new, - const SupporLayerType layer_type, - const SlicingParameters &slicing_params, + SupportGeneratorLayer &layer_new, + const SlicingParameters &slicing_params, const TreeSupportSettings &config, - const size_t layer_idx) + const size_t layer_idx) { - layer_new.layer_type = layer_type; layer_new.print_z = layer_z(slicing_params, config, layer_idx); layer_new.bottom_z = layer_idx > 0 ? layer_z(slicing_params, config, layer_idx - 1) : 0; layer_new.height = layer_new.print_z - layer_new.bottom_z; @@ -976,29 +976,26 @@ static inline SupportGeneratorLayer& layer_initialize( } // Using the std::deque as an allocator. -inline SupportGeneratorLayer& layer_allocate( - std::deque &layer_storage, +inline SupportGeneratorLayer& layer_allocate_unguarded( + SupportGeneratorLayerStorage &layer_storage, SupporLayerType layer_type, const SlicingParameters &slicing_params, const TreeSupportSettings &config, size_t layer_idx) { - //FIXME take raft into account. - layer_storage.push_back(SupportGeneratorLayer()); - return layer_initialize(layer_storage.back(), layer_type, slicing_params, config, layer_idx); + SupportGeneratorLayer &layer = layer_storage.allocate_unguarded(layer_type); + return layer_initialize(layer, slicing_params, config, layer_idx); } inline SupportGeneratorLayer& layer_allocate( - std::deque &layer_storage, - tbb::spin_mutex& layer_storage_mutex, + SupportGeneratorLayerStorage &layer_storage, SupporLayerType layer_type, const SlicingParameters &slicing_params, const TreeSupportSettings &config, size_t layer_idx) { - tbb::spin_mutex::scoped_lock lock(layer_storage_mutex); - layer_storage.push_back(SupportGeneratorLayer()); - return layer_initialize(layer_storage.back(), layer_type, slicing_params, config, layer_idx); + SupportGeneratorLayer &layer = layer_storage.allocate(layer_type); + return layer_initialize(layer, slicing_params, config, layer_idx); } int generate_raft_contact( @@ -1016,7 +1013,7 @@ int generate_raft_contact( while (raft_contact_layer_idx > 0 && config.raft_layers[raft_contact_layer_idx] > print_object.slicing_parameters().raft_contact_top_z + EPSILON) -- raft_contact_layer_idx; // Create the raft contact layer. - SupportGeneratorLayer &raft_contact_layer = layer_allocate(layer_storage, SupporLayerType::TopContact, print_object.slicing_parameters(), config, raft_contact_layer_idx); + SupportGeneratorLayer &raft_contact_layer = layer_allocate_unguarded(layer_storage, SupporLayerType::TopContact, print_object.slicing_parameters(), config, raft_contact_layer_idx); top_contacts[raft_contact_layer_idx] = &raft_contact_layer; const ExPolygons &lslices = print_object.get_layer(0)->lslices; double expansion = print_object.config().raft_expansion.value; @@ -1115,7 +1112,7 @@ public: { SupportGeneratorLayer*& l = top_contacts[insert_layer_idx]; if (l == nullptr) - l = &layer_allocate(layer_storage, SupporLayerType::TopContact, slicing_parameters, config, insert_layer_idx); + l = &layer_allocate_unguarded(layer_storage, SupporLayerType::TopContact, slicing_parameters, config, insert_layer_idx); // will be unioned in finalize_interface_and_support_areas() append(l->polygons, std::move(new_roofs)); } @@ -1141,7 +1138,7 @@ public: std::lock_guard lock(m_mutex_layer_storage); SupportGeneratorLayer*& l = top_contacts[0]; if (l == nullptr) - l = &layer_allocate(layer_storage, SupporLayerType::TopContact, slicing_parameters, config, 0); + l = &layer_allocate_unguarded(layer_storage, SupporLayerType::TopContact, slicing_parameters, config, 0); append(l->polygons, std::move(overhang_areas)); } @@ -3309,7 +3306,6 @@ static void finalize_interface_and_support_areas( #endif // SLIC3R_TREESUPPORTS_PROGRESS // Iterate over the generated circles in parallel and clean them up. Also add support floor. - tbb::spin_mutex layer_storage_mutex; tbb::parallel_for(tbb::blocked_range(0, support_layer_storage.size()), [&](const tbb::blocked_range &range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { @@ -3402,7 +3398,7 @@ static void finalize_interface_and_support_areas( } if (! floor_layer.empty()) { if (support_bottom == nullptr) - support_bottom = &layer_allocate(layer_storage, layer_storage_mutex, SupporLayerType::BottomContact, print_object.slicing_parameters(), config, layer_idx); + support_bottom = &layer_allocate(layer_storage, SupporLayerType::BottomContact, print_object.slicing_parameters(), config, layer_idx); support_bottom->polygons = union_(floor_layer, support_bottom->polygons); base_layer_polygons = diff_clipped(base_layer_polygons, offset(support_bottom->polygons, scaled(0.01), jtMiter, 1.2)); // Subtract the support floor from the normal support. } @@ -3410,11 +3406,11 @@ static void finalize_interface_and_support_areas( if (! support_roof_polygons.empty()) { if (support_roof == nullptr) - support_roof = top_contacts[layer_idx] = &layer_allocate(layer_storage, layer_storage_mutex, SupporLayerType::TopContact, print_object.slicing_parameters(), config, layer_idx); + support_roof = top_contacts[layer_idx] = &layer_allocate(layer_storage, SupporLayerType::TopContact, print_object.slicing_parameters(), config, layer_idx); support_roof->polygons = union_(support_roof_polygons); } if (! base_layer_polygons.empty()) { - SupportGeneratorLayer *base_layer = intermediate_layers[layer_idx] = &layer_allocate(layer_storage, layer_storage_mutex, SupporLayerType::Base, print_object.slicing_parameters(), config, layer_idx); + SupportGeneratorLayer *base_layer = intermediate_layers[layer_idx] = &layer_allocate(layer_storage, SupporLayerType::Base, print_object.slicing_parameters(), config, layer_idx); base_layer->polygons = union_(base_layer_polygons); } From 287304214828288ede6c76073b0ba12589b8e7c1 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Fri, 5 May 2023 13:52:20 +0200 Subject: [PATCH 26/34] SPE-1684: Fix issue where the automatic painting did not trigger paint-on-supports notification in the right panel --- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 7700e1eef..557cdee8e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -434,18 +434,15 @@ void GLGizmoFdmSupports::apply_data_from_backend() mesh_id++; auto selector = selectors.find(mv->id().id); if (selector != selectors.end()) { - mv->supported_facets.set(selector->second.selector); - m_triangle_selectors[mesh_id]->deserialize(mv->supported_facets.get_data(), true); + m_triangle_selectors[mesh_id]->deserialize(selector->second.selector.serialize(), true); m_triangle_selectors[mesh_id]->request_update_render_data(); } } } - - m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); - m_parent.set_as_dirty(); } - this->waiting_for_autogenerated_supports = false; } + this->waiting_for_autogenerated_supports = false; + update_model_object(); } void GLGizmoFdmSupports::update_model_object() const From ad203baf77d152c25d718bf270df3d5dc38991fe Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Fri, 5 May 2023 14:05:22 +0200 Subject: [PATCH 27/34] Moved FFF support code to libslic3r/Support directory --- src/libslic3r/CMakeLists.txt | 12 +++++----- src/libslic3r/Print.cpp | 1 - src/libslic3r/PrintObject.cpp | 6 ++--- src/libslic3r/Support/SupportCommon.cpp | 4 ++-- src/libslic3r/Support/SupportDebug.cpp | 6 ++--- .../{ => Support}/SupportMaterial.cpp | 22 +++++++++---------- .../{ => Support}/SupportMaterial.hpp | 10 ++++----- .../{ => Support}/TreeModelVolumes.cpp | 18 +++++++-------- .../{ => Support}/TreeModelVolumes.hpp | 6 ++--- src/libslic3r/{ => Support}/TreeSupport.cpp | 0 src/libslic3r/{ => Support}/TreeSupport.hpp | 0 11 files changed, 42 insertions(+), 43 deletions(-) rename src/libslic3r/{ => Support}/SupportMaterial.cpp (99%) rename src/libslic3r/{ => Support}/SupportMaterial.hpp (97%) rename src/libslic3r/{ => Support}/TreeModelVolumes.cpp (99%) rename src/libslic3r/{ => Support}/TreeModelVolumes.hpp (99%) rename src/libslic3r/{ => Support}/TreeSupport.cpp (100%) rename src/libslic3r/{ => Support}/TreeSupport.hpp (100%) diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 11f7adf76..d44bfcde0 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -281,10 +281,14 @@ set(SLIC3R_SOURCES Support/SupportDebug.cpp Support/SupportDebug.hpp Support/SupportLayer.hpp + Support/SupportMaterial.cpp + Support/SupportMaterial.hpp Support/SupportParameters.cpp Support/SupportParameters.hpp - SupportMaterial.cpp - SupportMaterial.hpp + Support/TreeSupport.cpp + Support/TreeSupport.hpp + Support/TreeModelVolumes.cpp + Support/TreeModelVolumes.hpp SupportSpotsGenerator.cpp SupportSpotsGenerator.hpp Surface.cpp @@ -298,10 +302,6 @@ set(SLIC3R_SOURCES Tesselate.cpp Tesselate.hpp TextConfiguration.hpp - TreeSupport.cpp - TreeSupport.hpp - TreeModelVolumes.cpp - TreeModelVolumes.hpp TriangleMesh.cpp TriangleMesh.hpp TriangleMeshSlicer.cpp diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index cf8b5c577..5b23dc260 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -8,7 +8,6 @@ #include "Geometry/ConvexHull.hpp" #include "I18N.hpp" #include "ShortestPath.hpp" -#include "SupportMaterial.hpp" #include "Thread.hpp" #include "GCode.hpp" #include "GCode/WipeTower.hpp" diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 46588b9b0..b43afd6be 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -17,8 +17,8 @@ #include "MutablePolygon.hpp" #include "PrintBase.hpp" #include "PrintConfig.hpp" -#include "SupportMaterial.hpp" -#include "TreeSupport.hpp" +#include "Support/SupportMaterial.hpp" +#include "Support/TreeSupport.hpp" #include "Surface.hpp" #include "Slicing.hpp" #include "Tesselate.hpp" @@ -27,7 +27,7 @@ #include "Fill/FillAdaptive.hpp" #include "Fill/FillLightning.hpp" #include "Format/STL.hpp" -#include "SupportMaterial.hpp" +#include "Support/SupportMaterial.hpp" #include "SupportSpotsGenerator.hpp" #include "TriangleSelectorWrapper.hpp" #include "format.hpp" diff --git a/src/libslic3r/Support/SupportCommon.cpp b/src/libslic3r/Support/SupportCommon.cpp index 8825d8dd2..f76ebaa0e 100644 --- a/src/libslic3r/Support/SupportCommon.cpp +++ b/src/libslic3r/Support/SupportCommon.cpp @@ -22,8 +22,8 @@ #define DEBUG #define _DEBUG #undef NDEBUG - #include "utils.hpp" - #include "SVG.hpp" + #include "../utils.hpp" + #include "../SVG.hpp" #endif #include diff --git a/src/libslic3r/Support/SupportDebug.cpp b/src/libslic3r/Support/SupportDebug.cpp index 8cec806c1..5c18bc769 100644 --- a/src/libslic3r/Support/SupportDebug.cpp +++ b/src/libslic3r/Support/SupportDebug.cpp @@ -1,9 +1,9 @@ #if 1 //#ifdef SLIC3R_DEBUG -#include "ClipperUtils.hpp" -#include "SVG.hpp" - +#include "../ClipperUtils.hpp" +#include "../SVG.hpp" #include "../Layer.hpp" + #include "SupportLayer.hpp" namespace Slic3r::FFFSupport { diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/Support/SupportMaterial.cpp similarity index 99% rename from src/libslic3r/SupportMaterial.cpp rename to src/libslic3r/Support/SupportMaterial.cpp index 40221ec2a..347278911 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/Support/SupportMaterial.cpp @@ -1,14 +1,14 @@ -#include "ClipperUtils.hpp" -#include "ExtrusionEntityCollection.hpp" -#include "Layer.hpp" -#include "Print.hpp" -#include "SupportMaterial.hpp" -#include "Fill/FillBase.hpp" -#include "Geometry.hpp" -#include "Point.hpp" -#include "MutablePolygon.hpp" +#include "../ClipperUtils.hpp" +#include "../ExtrusionEntityCollection.hpp" +#include "../Layer.hpp" +#include "../Print.hpp" +#include "../Fill/FillBase.hpp" +#include "../Geometry.hpp" +#include "../Point.hpp" +#include "../MutablePolygon.hpp" #include "Support/SupportCommon.hpp" +#include "SupportMaterial.hpp" #include @@ -40,8 +40,8 @@ #define DEBUG #define _DEBUG #undef NDEBUG - #include "utils.hpp" - #include "SVG.hpp" + #include "../utils.hpp" + #include "../SVG.hpp" #endif #include diff --git a/src/libslic3r/SupportMaterial.hpp b/src/libslic3r/Support/SupportMaterial.hpp similarity index 97% rename from src/libslic3r/SupportMaterial.hpp rename to src/libslic3r/Support/SupportMaterial.hpp index bbb991c3e..924ed1a05 100644 --- a/src/libslic3r/SupportMaterial.hpp +++ b/src/libslic3r/Support/SupportMaterial.hpp @@ -1,12 +1,12 @@ #ifndef slic3r_SupportMaterial_hpp_ #define slic3r_SupportMaterial_hpp_ -#include "Flow.hpp" -#include "PrintConfig.hpp" -#include "Slicing.hpp" +#include "../Flow.hpp" +#include "../PrintConfig.hpp" +#include "../Slicing.hpp" -#include "Support/SupportLayer.hpp" -#include "Support/SupportParameters.hpp" +#include "SupportLayer.hpp" +#include "SupportParameters.hpp" namespace Slic3r { diff --git a/src/libslic3r/TreeModelVolumes.cpp b/src/libslic3r/Support/TreeModelVolumes.cpp similarity index 99% rename from src/libslic3r/TreeModelVolumes.cpp rename to src/libslic3r/Support/TreeModelVolumes.cpp index 96f61a07d..fd5437f9e 100644 --- a/src/libslic3r/TreeModelVolumes.cpp +++ b/src/libslic3r/Support/TreeModelVolumes.cpp @@ -9,15 +9,15 @@ #include "TreeModelVolumes.hpp" #include "TreeSupport.hpp" -#include "BuildVolume.hpp" -#include "ClipperUtils.hpp" -#include "Flow.hpp" -#include "Layer.hpp" -#include "Point.hpp" -#include "Print.hpp" -#include "PrintConfig.hpp" -#include "Utils.hpp" -#include "format.hpp" +#include "../BuildVolume.hpp" +#include "../ClipperUtils.hpp" +#include "../Flow.hpp" +#include "../Layer.hpp" +#include "../Point.hpp" +#include "../Print.hpp" +#include "../PrintConfig.hpp" +#include "../Utils.hpp" +#include "../format.hpp" #include diff --git a/src/libslic3r/TreeModelVolumes.hpp b/src/libslic3r/Support/TreeModelVolumes.hpp similarity index 99% rename from src/libslic3r/TreeModelVolumes.hpp rename to src/libslic3r/Support/TreeModelVolumes.hpp index 659baf1ff..2b7ab5e1b 100644 --- a/src/libslic3r/TreeModelVolumes.hpp +++ b/src/libslic3r/Support/TreeModelVolumes.hpp @@ -14,9 +14,9 @@ #include -#include "Point.hpp" -#include "Polygon.hpp" -#include "PrintConfig.hpp" +#include "../Point.hpp" +#include "../Polygon.hpp" +#include "../PrintConfig.hpp" namespace Slic3r { diff --git a/src/libslic3r/TreeSupport.cpp b/src/libslic3r/Support/TreeSupport.cpp similarity index 100% rename from src/libslic3r/TreeSupport.cpp rename to src/libslic3r/Support/TreeSupport.cpp diff --git a/src/libslic3r/TreeSupport.hpp b/src/libslic3r/Support/TreeSupport.hpp similarity index 100% rename from src/libslic3r/TreeSupport.hpp rename to src/libslic3r/Support/TreeSupport.hpp From 89f0895dd67f6ec94732ca98c4e0f69dd1645870 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Fri, 5 May 2023 14:23:09 +0200 Subject: [PATCH 28/34] WIP: Organic supports - bottom interfaces --- src/libslic3r/Support/TreeModelVolumes.cpp | 4 +++- src/libslic3r/Support/TreeModelVolumes.hpp | 4 +++- src/libslic3r/Support/TreeSupport.cpp | 18 +++++++++++++----- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/libslic3r/Support/TreeModelVolumes.cpp b/src/libslic3r/Support/TreeModelVolumes.cpp index fd5437f9e..5df56cd62 100644 --- a/src/libslic3r/Support/TreeModelVolumes.cpp +++ b/src/libslic3r/Support/TreeModelVolumes.cpp @@ -79,7 +79,9 @@ TreeSupportMeshGroupSettings::TreeSupportMeshGroupSettings(const PrintObject &pr // this->support_interface_skip_height = // this->support_infill_angles = this->support_roof_enable = config.support_material_interface_layers.value > 0; - this->support_roof_height = config.support_material_interface_layers.value * this->layer_height; + this->support_roof_layers = this->support_roof_enable ? config.support_material_interface_layers.value : 0; + this->support_floor_enable = config.support_material_interface_layers.value > 0 && config.support_material_bottom_interface_layers.value > 0; + this->support_floor_layers = this->support_floor_enable ? config.support_material_bottom_interface_layers.value : 0; // this->minimum_roof_area = // this->support_roof_angles = this->support_roof_pattern = config.support_material_interface_pattern; diff --git a/src/libslic3r/Support/TreeModelVolumes.hpp b/src/libslic3r/Support/TreeModelVolumes.hpp index 2b7ab5e1b..ecfa99971 100644 --- a/src/libslic3r/Support/TreeModelVolumes.hpp +++ b/src/libslic3r/Support/TreeModelVolumes.hpp @@ -94,7 +94,9 @@ struct TreeSupportMeshGroupSettings { bool support_roof_enable { false }; // Support Roof Thickness // The thickness of the support roofs. This controls the amount of dense layers at the top of the support on which the model rests. - coord_t support_roof_height { scaled(1.) }; + coord_t support_roof_layers { 2 }; + bool support_floor_enable { false }; + coord_t support_floor_layers { 2 }; // Minimum Support Roof Area // Minimum area size for the roofs of the support. Polygons which have an area smaller than this value will be printed as normal support. double minimum_roof_area { scaled(scaled(1.)) }; diff --git a/src/libslic3r/Support/TreeSupport.cpp b/src/libslic3r/Support/TreeSupport.cpp index a3264990a..d1306962e 100644 --- a/src/libslic3r/Support/TreeSupport.cpp +++ b/src/libslic3r/Support/TreeSupport.cpp @@ -1460,7 +1460,7 @@ static void generate_initial_areas( //FIXME this is a heuristic value for support enforcers to work. // + 10 * config.support_line_width; ; - const size_t num_support_roof_layers = mesh_group_settings.support_roof_enable ? (mesh_group_settings.support_roof_height + config.layer_height / 2) / config.layer_height : 0; + const size_t num_support_roof_layers = mesh_group_settings.support_roof_layers; const bool roof_enabled = num_support_roof_layers > 0; const bool force_tip_to_roof = sqr(config.min_radius) * M_PI > mesh_group_settings.minimum_roof_area && roof_enabled; // cap for how much layer below the overhang a new support point may be added, as other than with regular support every new inserted point @@ -4211,6 +4211,7 @@ static std::vector draw_branches( struct Slice { Polygons polygons; + Polygons bottom_interfaces; size_t num_branches{ 0 }; }; @@ -4310,6 +4311,7 @@ static std::vector draw_branches( const SlicingParameters &slicing_params = print_object.slicing_parameters(); MeshSlicingParams mesh_slicing_params; mesh_slicing_params.mode = MeshSlicingParams::SlicingMode::Positive; + tbb::parallel_for(tbb::blocked_range(0, trees.size(), 1), [&trees, &volumes, &config, &slicing_params, &move_bounds, &mesh_slicing_params, &throw_on_cancel](const tbb::blocked_range &range) { indexed_triangle_set partial_mesh; @@ -4343,6 +4345,7 @@ static std::vector draw_branches( struct BottomExtraSlice { Polygons polygons; Polygons supported; + Polygons bottom_interfaces; double area; double supported_area; }; @@ -4371,12 +4374,17 @@ static std::vector draw_branches( */ Polygons supported; double supported_area; - bottom_extra_slices.push_back({ rest_support, std::move(supported), rest_support_area, supported_area }); + bottom_extra_slices.push_back({ rest_support, std::move(supported), {}, rest_support_area, supported_area }); } // Now remove those bottom slices that are not supported at all. - while (! bottom_extra_slices.empty() && - area(intersection_clipped(bottom_extra_slices.back().polygons, volumes.getPlaceableAreas(0, layer_begin - LayerIndex(bottom_extra_slices.size()), [] {}))) < support_area_min) - bottom_extra_slices.pop_back(); + while (! bottom_extra_slices.empty()) { + Polygons bottom_interfaces = intersection_clipped(bottom_extra_slices.back().polygons, volumes.getPlaceableAreas(0, layer_begin - LayerIndex(bottom_extra_slices.size()), [] {})); + if (area(bottom_interfaces) < support_area_min) + bottom_extra_slices.pop_back(); + else { + bottom_extra_slices.back().bottom_interfaces = std::move(bottom_interfaces); + } + } layer_begin -= LayerIndex(bottom_extra_slices.size()); slices.insert(slices.begin(), bottom_extra_slices.size(), {}); size_t i = 0; From ce4cf95067d0bd51824d3951f51881645ab2605b Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Fri, 5 May 2023 16:31:27 +0200 Subject: [PATCH 29/34] Fix missing includes --- src/libslic3r/Support/SupportCommon.cpp | 1 + src/libslic3r/Support/SupportLayer.hpp | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/libslic3r/Support/SupportCommon.cpp b/src/libslic3r/Support/SupportCommon.cpp index f76ebaa0e..20e8558dc 100644 --- a/src/libslic3r/Support/SupportCommon.cpp +++ b/src/libslic3r/Support/SupportCommon.cpp @@ -1,4 +1,5 @@ #include "../ClipperUtils.hpp" +#include "../ClipperZUtils.hpp" #include "../ExtrusionEntityCollection.hpp" #include "../Layer.hpp" #include "../Print.hpp" diff --git a/src/libslic3r/Support/SupportLayer.hpp b/src/libslic3r/Support/SupportLayer.hpp index 82265881c..155de70ce 100644 --- a/src/libslic3r/Support/SupportLayer.hpp +++ b/src/libslic3r/Support/SupportLayer.hpp @@ -5,6 +5,8 @@ #include // for Slic3r::deque #include "../libslic3r.h" +#include "ClipperUtils.hpp" +#include "Polygon.hpp" namespace Slic3r::FFFSupport { From 1e7a3216ca87d9fdeaa5b12f813e5900a77d49bc Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 8 May 2023 09:19:31 +0200 Subject: [PATCH 30/34] WIP Organic supports intefaces: Further refactor of FDM support code - extracted interface routine to common. Implemented support for soluble interfaces & half soluble / half non-soluble interfaces. --- src/libslic3r/Support/SupportCommon.cpp | 189 +++++++++ src/libslic3r/Support/SupportCommon.hpp | 17 + src/libslic3r/Support/SupportMaterial.cpp | 169 +------- src/libslic3r/Support/SupportMaterial.hpp | 10 - src/libslic3r/Support/SupportParameters.cpp | 28 +- src/libslic3r/Support/SupportParameters.hpp | 24 ++ src/libslic3r/Support/TreeSupport.cpp | 405 ++++++++++++-------- src/libslic3r/Support/TreeSupport.hpp | 4 - 8 files changed, 507 insertions(+), 339 deletions(-) diff --git a/src/libslic3r/Support/SupportCommon.cpp b/src/libslic3r/Support/SupportCommon.cpp index f76ebaa0e..4978b9e40 100644 --- a/src/libslic3r/Support/SupportCommon.cpp +++ b/src/libslic3r/Support/SupportCommon.cpp @@ -3,6 +3,7 @@ #include "../Layer.hpp" #include "../Print.hpp" #include "../Fill/FillBase.hpp" +#include "../MutablePolygon.hpp" #include "../Geometry.hpp" #include "../Point.hpp" @@ -118,6 +119,194 @@ void remove_bridges_from_contacts( #endif /* SLIC3R_DEBUG */ } +// Convert some of the intermediate layers into top/bottom interface layers as well as base interface layers. +std::pair generate_interface_layers( + const PrintObjectConfig &config, + const SupportParameters &support_params, + const SupportGeneratorLayersPtr &bottom_contacts, + const SupportGeneratorLayersPtr &top_contacts, + // Input / output, will be merged with output. Only provided for Organic supports. + SupportGeneratorLayersPtr &top_interface_layers, + SupportGeneratorLayersPtr &top_base_interface_layers, + // Input, will be trimmed with the newly created interface layers. + SupportGeneratorLayersPtr &intermediate_layers, + SupportGeneratorLayerStorage &layer_storage) +{ + std::pair base_and_interface_layers; + + if (! intermediate_layers.empty() && support_params.has_interfaces()) { + // For all intermediate layers, collect top contact surfaces, which are not further than support_material_interface_layers. + BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::generate_interface_layers() in parallel - start"; + const bool snug_supports = config.support_material_style.value != smsGrid; + SupportGeneratorLayersPtr &interface_layers = base_and_interface_layers.first; + SupportGeneratorLayersPtr &base_interface_layers = base_and_interface_layers.second; + interface_layers.assign(intermediate_layers.size(), nullptr); + if (support_params.has_base_interfaces()) + base_interface_layers.assign(intermediate_layers.size(), nullptr); + const auto smoothing_distance = support_params.support_material_interface_flow.scaled_spacing() * 1.5; + const auto minimum_island_radius = support_params.support_material_interface_flow.scaled_spacing() / support_params.interface_density; + const auto closing_distance = smoothing_distance; // scaled(config.support_material_closing_radius.value); + // Insert a new layer into base_interface_layers, if intersection with base exists. + auto insert_layer = [&layer_storage, snug_supports, closing_distance, smoothing_distance, minimum_island_radius]( + SupportGeneratorLayer &intermediate_layer, Polygons &bottom, Polygons &&top, SupportGeneratorLayer *top_interface_layer, + const Polygons *subtract, SupporLayerType type) -> SupportGeneratorLayer* { + bool has_top_interface = top_interface_layer && ! top_interface_layer->polygons.empty(); + assert(! bottom.empty() || ! top.empty() || has_top_interface); + // Merge top into bottom, unite them with a safety offset. + append(bottom, std::move(top)); + // Merge top / bottom interfaces. For snug supports, merge using closing distance and regularize (close concave corners). + bottom = intersection( + snug_supports ? + smooth_outward(closing(std::move(bottom), closing_distance + minimum_island_radius, closing_distance, SUPPORT_SURFACES_OFFSET_PARAMETERS), smoothing_distance) : + union_safety_offset(std::move(bottom)), + intermediate_layer.polygons); + if (has_top_interface) + // Don't trim the precomputed Organic supports top interface with base layer + // as the precomputed top interface likely expands over multiple tree tips. + bottom = union_(std::move(top_interface_layer->polygons), bottom); + if (! bottom.empty()) { + //FIXME Remove non-printable tiny islands, let them be printed using the base support. + //bottom = opening(std::move(bottom), minimum_island_radius); + if (! bottom.empty()) { + SupportGeneratorLayer &layer_new = top_interface_layer ? *top_interface_layer : layer_storage.allocate(type); + layer_new.polygons = std::move(bottom); + layer_new.print_z = intermediate_layer.print_z; + layer_new.bottom_z = intermediate_layer.bottom_z; + layer_new.height = intermediate_layer.height; + layer_new.bridging = intermediate_layer.bridging; + // Subtract the interface from the base regions. + intermediate_layer.polygons = diff(intermediate_layer.polygons, layer_new.polygons); + if (subtract) + // Trim the base interface layer with the interface layer. + layer_new.polygons = diff(std::move(layer_new.polygons), *subtract); + //FIXME filter layer_new.polygons islands by a minimum area? + // $interface_area = [ grep abs($_->area) >= $area_threshold, @$interface_area ]; + return &layer_new; + } + } + return nullptr; + }; + tbb::parallel_for(tbb::blocked_range(0, int(intermediate_layers.size())), + [&bottom_contacts, &top_contacts, &top_interface_layers, &top_base_interface_layers, &intermediate_layers, &insert_layer, &support_params, + snug_supports, &interface_layers, &base_interface_layers](const tbb::blocked_range& range) { + // Gather the top / bottom contact layers intersecting with num_interface_layers resp. num_interface_layers_only intermediate layers above / below + // this intermediate layer. + // Index of the first top contact layer intersecting the current intermediate layer. + auto idx_top_contact_first = -1; + // Index of the first bottom contact layer intersecting the current intermediate layer. + auto idx_bottom_contact_first = -1; + // Index of the first top interface layer intersecting the current intermediate layer. + auto idx_top_interface_first = -1; + // Index of the first top contact interface layer intersecting the current intermediate layer. + auto idx_top_base_interface_first = -1; + auto num_intermediate = int(intermediate_layers.size()); + for (int idx_intermediate_layer = range.begin(); idx_intermediate_layer < range.end(); ++ idx_intermediate_layer) { + SupportGeneratorLayer &intermediate_layer = *intermediate_layers[idx_intermediate_layer]; + Polygons polygons_top_contact_projected_interface; + Polygons polygons_top_contact_projected_base; + Polygons polygons_bottom_contact_projected_interface; + Polygons polygons_bottom_contact_projected_base; + if (support_params.num_top_interface_layers > 0) { + // Top Z coordinate of a slab, over which we are collecting the top / bottom contact surfaces + coordf_t top_z = intermediate_layers[std::min(num_intermediate - 1, idx_intermediate_layer + int(support_params.num_top_interface_layers) - 1)]->print_z; + coordf_t top_inteface_z = std::numeric_limits::max(); + if (support_params.num_top_base_interface_layers > 0) + // Some top base interface layers will be generated. + top_inteface_z = support_params.num_top_interface_layers_only() == 0 ? + // Only base interface layers to generate. + - std::numeric_limits::max() : + intermediate_layers[std::min(num_intermediate - 1, idx_intermediate_layer + int(support_params.num_top_interface_layers_only()) - 1)]->print_z; + // Move idx_top_contact_first up until above the current print_z. + idx_top_contact_first = idx_higher_or_equal(top_contacts, idx_top_contact_first, [&intermediate_layer](const SupportGeneratorLayer *layer){ return layer->print_z >= intermediate_layer.print_z; }); // - EPSILON + // Collect the top contact areas above this intermediate layer, below top_z. + for (int idx_top_contact = idx_top_contact_first; idx_top_contact < int(top_contacts.size()); ++ idx_top_contact) { + const SupportGeneratorLayer &top_contact_layer = *top_contacts[idx_top_contact]; + //FIXME maybe this adds one interface layer in excess? + if (top_contact_layer.bottom_z - EPSILON > top_z) + break; + polygons_append(top_contact_layer.bottom_z - EPSILON > top_inteface_z ? polygons_top_contact_projected_base : polygons_top_contact_projected_interface, + // For snug supports, project the overhang polygons covering the whole overhang, so that they will merge without a gap with support polygons of the other layers. + // For grid supports, merging of support regions will be performed by the projection into grid. + snug_supports ? *top_contact_layer.overhang_polygons : top_contact_layer.polygons); + } + } + if (support_params.num_bottom_interface_layers > 0) { + // Bottom Z coordinate of a slab, over which we are collecting the top / bottom contact surfaces + coordf_t bottom_z = intermediate_layers[std::max(0, idx_intermediate_layer - int(support_params.num_bottom_interface_layers) + 1)]->bottom_z; + coordf_t bottom_interface_z = - std::numeric_limits::max(); + if (support_params.num_bottom_base_interface_layers > 0) + // Some bottom base interface layers will be generated. + bottom_interface_z = support_params.num_bottom_interface_layers_only() == 0 ? + // Only base interface layers to generate. + std::numeric_limits::max() : + intermediate_layers[std::max(0, idx_intermediate_layer - int(support_params.num_bottom_interface_layers_only()))]->bottom_z; + // Move idx_bottom_contact_first up until touching bottom_z. + idx_bottom_contact_first = idx_higher_or_equal(bottom_contacts, idx_bottom_contact_first, [bottom_z](const SupportGeneratorLayer *layer){ return layer->print_z >= bottom_z - EPSILON; }); + // Collect the top contact areas above this intermediate layer, below top_z. + for (int idx_bottom_contact = idx_bottom_contact_first; idx_bottom_contact < int(bottom_contacts.size()); ++ idx_bottom_contact) { + const SupportGeneratorLayer &bottom_contact_layer = *bottom_contacts[idx_bottom_contact]; + if (bottom_contact_layer.print_z - EPSILON > intermediate_layer.bottom_z) + break; + polygons_append(bottom_contact_layer.print_z - EPSILON > bottom_interface_z ? polygons_bottom_contact_projected_interface : polygons_bottom_contact_projected_base, bottom_contact_layer.polygons); + } + } + auto resolve_same_layer = [](SupportGeneratorLayersPtr &layers, int &idx, coordf_t print_z) -> SupportGeneratorLayer* { + if (! layers.empty()) { + idx = idx_higher_or_equal(layers, idx, [print_z](const SupportGeneratorLayer *layer) { return layer->print_z > print_z - EPSILON; }); + if (idx < int(layers.size()) && layers[idx]->print_z < print_z + EPSILON) { + SupportGeneratorLayer *l = layers[idx]; + // Remove the layer from the source container, as it will be consumed here: It will be merged + // with the newly produced interfaces. + layers[idx] = nullptr; + return l; + } + } + return nullptr; + }; + SupportGeneratorLayer *top_interface_layer = resolve_same_layer(top_interface_layers, idx_top_interface_first, intermediate_layer.print_z); + SupportGeneratorLayer *top_base_interface_layer = resolve_same_layer(top_base_interface_layers, idx_top_base_interface_first, intermediate_layer.print_z); + SupportGeneratorLayer *interface_layer = nullptr; + if (! polygons_bottom_contact_projected_interface.empty() || ! polygons_top_contact_projected_interface.empty() || + (top_interface_layer && ! top_interface_layer->polygons.empty())) { + interface_layer = insert_layer( + intermediate_layer, polygons_bottom_contact_projected_interface, std::move(polygons_top_contact_projected_interface), top_interface_layer, + nullptr, polygons_top_contact_projected_interface.empty() ? SupporLayerType::BottomInterface : SupporLayerType::TopInterface); + interface_layers[idx_intermediate_layer] = interface_layer; + } + if (! polygons_bottom_contact_projected_base.empty() || ! polygons_top_contact_projected_base.empty() || + (top_base_interface_layer && ! top_base_interface_layer->polygons.empty())) + base_interface_layers[idx_intermediate_layer] = insert_layer( + intermediate_layer, polygons_bottom_contact_projected_base, std::move(polygons_top_contact_projected_base), top_base_interface_layer, + interface_layer ? &interface_layer->polygons : nullptr, SupporLayerType::Base); + } + }); + + // Compress contact_out, remove the nullptr items. + // The parallel_for above may not have merged all the interface and base_interface layers + // generated by the Organic supports code, do it here. + auto merge_remove_nulls = [](SupportGeneratorLayersPtr &in1, SupportGeneratorLayersPtr &in2) { + size_t nonzeros = std::count_if(in1.begin(), in1.end(), [](auto *l) { return l != nullptr; }) + + std::count_if(in2.begin(), in2.end(), [](auto *l) { return l != nullptr; }); + remove_nulls(in1); + remove_nulls(in2); + if (in2.empty()) + return std::move(in1); + else if (in1.empty()) + return std::move(in2); + else { + SupportGeneratorLayersPtr out(in1.size() + in2.size(), nullptr); + std::merge(in1.begin(), in1.end(), in2.begin(), in2.end(), out.begin(), [](auto* l, auto* r) { return l->print_z < r->print_z; }); + return std::move(out); + } + }; + interface_layers = merge_remove_nulls(interface_layers, top_interface_layers); + base_interface_layers = merge_remove_nulls(base_interface_layers, top_base_interface_layers); + BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::generate_interface_layers() in parallel - end"; + } + + return base_and_interface_layers; +} + SupportGeneratorLayersPtr generate_raft_base( const PrintObject &object, const SupportParameters &support_params, diff --git a/src/libslic3r/Support/SupportCommon.hpp b/src/libslic3r/Support/SupportCommon.hpp index 4eabce772..19f5822fe 100644 --- a/src/libslic3r/Support/SupportCommon.hpp +++ b/src/libslic3r/Support/SupportCommon.hpp @@ -21,6 +21,23 @@ void remove_bridges_from_contacts( float fw, Polygons &contact_polygons); +// Turn some of the base layers into base interface layers. +// For soluble interfaces with non-soluble bases, print maximum two first interface layers with the base +// extruder to improve adhesion of the soluble filament to the base. +// For Organic supports, merge top_interface_layers & top_base_interface_layers with the interfaces +// produced by this function. +std::pair generate_interface_layers( + const PrintObjectConfig &config, + const SupportParameters &support_params, + const SupportGeneratorLayersPtr &bottom_contacts, + const SupportGeneratorLayersPtr &top_contacts, + // Input / output, will be merged with output + SupportGeneratorLayersPtr &top_interface_layers, + SupportGeneratorLayersPtr &top_base_interface_layers, + // Input, will be trimmed with the newly created interface layers. + SupportGeneratorLayersPtr &intermediate_layers, + SupportGeneratorLayerStorage &layer_storage); + // Generate raft layers, also expand the 1st support layer // in case there is no raft layer to improve support adhesion. SupportGeneratorLayersPtr generate_raft_base( diff --git a/src/libslic3r/Support/SupportMaterial.cpp b/src/libslic3r/Support/SupportMaterial.cpp index 347278911..a21a48b9a 100644 --- a/src/libslic3r/Support/SupportMaterial.cpp +++ b/src/libslic3r/Support/SupportMaterial.cpp @@ -335,14 +335,16 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) // Propagate top / bottom contact layers to generate interface layers // and base interface layers (for soluble interface / non souble base only) - auto [interface_layers, base_interface_layers] = this->generate_interface_layers(bottom_contacts, top_contacts, intermediate_layers, layer_storage); + SupportGeneratorLayersPtr empty_layers; + auto [interface_layers, base_interface_layers] = FFFSupport::generate_interface_layers( + *m_object_config, m_support_params, bottom_contacts, top_contacts, empty_layers, empty_layers, intermediate_layers, layer_storage); BOOST_LOG_TRIVIAL(info) << "Support generator - Creating raft"; // If raft is to be generated, the 1st top_contact layer will contain the 1st object layer silhouette with holes filled. // There is also a 1st intermediate layer containing bases of support columns. // Inflate the bases of the support columns and create the raft base under the object. - SupportGeneratorLayersPtr raft_layers = generate_raft_base(object, m_support_params, m_slicing_params, top_contacts, interface_layers, base_interface_layers, intermediate_layers, layer_storage); + SupportGeneratorLayersPtr raft_layers = FFFSupport::generate_raft_base(object, m_support_params, m_slicing_params, top_contacts, interface_layers, base_interface_layers, intermediate_layers, layer_storage); #ifdef SLIC3R_DEBUG for (const SupportGeneratorLayer *l : interface_layers) @@ -2514,169 +2516,6 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object( BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::trim_support_layers_by_object() in parallel - end"; } -// Convert some of the intermediate layers into top/bottom interface layers as well as base interface layers. -std::pair PrintObjectSupportMaterial::generate_interface_layers( - const SupportGeneratorLayersPtr &bottom_contacts, - const SupportGeneratorLayersPtr &top_contacts, - SupportGeneratorLayersPtr &intermediate_layers, - SupportGeneratorLayerStorage &layer_storage) const -{ -// my $area_threshold = $self->interface_flow->scaled_spacing ** 2; - - std::pair base_and_interface_layers; - SupportGeneratorLayersPtr &interface_layers = base_and_interface_layers.first; - SupportGeneratorLayersPtr &base_interface_layers = base_and_interface_layers.second; - - // distinguish between interface and base interface layers - // Contact layer is considered an interface layer, therefore run the following block only if support_material_interface_layers > 1. - // Contact layer needs a base_interface layer, therefore run the following block if support_material_interface_layers > 0, has soluble support and extruders are different. - bool soluble_interface_non_soluble_base = - // Zero z-gap between the overhangs and the support interface. - m_slicing_params.soluble_interface && - // Interface extruder soluble. - m_object_config->support_material_interface_extruder.value > 0 && m_print_config->filament_soluble.get_at(m_object_config->support_material_interface_extruder.value - 1) && - // Base extruder: Either "print with active extruder" not soluble. - (m_object_config->support_material_extruder.value == 0 || ! m_print_config->filament_soluble.get_at(m_object_config->support_material_extruder.value - 1)); - bool snug_supports = m_object_config->support_material_style.value != smsGrid; - int num_interface_layers_top = m_object_config->support_material_interface_layers; - int num_interface_layers_bottom = m_object_config->support_material_bottom_interface_layers; - if (num_interface_layers_bottom < 0) - num_interface_layers_bottom = num_interface_layers_top; - int num_base_interface_layers_top = soluble_interface_non_soluble_base ? std::min(num_interface_layers_top / 2, 2) : 0; - int num_base_interface_layers_bottom = soluble_interface_non_soluble_base ? std::min(num_interface_layers_bottom / 2, 2) : 0; - - if (! intermediate_layers.empty() && (num_interface_layers_top > 1 || num_interface_layers_bottom > 1)) { - // For all intermediate layers, collect top contact surfaces, which are not further than support_material_interface_layers. - BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::generate_interface_layers() in parallel - start"; - // Since the intermediate layer index starts at zero the number of interface layer needs to be reduced by 1. - -- num_interface_layers_top; - -- num_interface_layers_bottom; - int num_interface_layers_only_top = num_interface_layers_top - num_base_interface_layers_top; - int num_interface_layers_only_bottom = num_interface_layers_bottom - num_base_interface_layers_bottom; - interface_layers.assign(intermediate_layers.size(), nullptr); - if (num_base_interface_layers_top || num_base_interface_layers_bottom) - base_interface_layers.assign(intermediate_layers.size(), nullptr); - auto smoothing_distance = m_support_params.support_material_interface_flow.scaled_spacing() * 1.5; - auto minimum_island_radius = m_support_params.support_material_interface_flow.scaled_spacing() / m_support_params.interface_density; - auto closing_distance = smoothing_distance; // scaled(m_object_config->support_material_closing_radius.value); - // Insert a new layer into base_interface_layers, if intersection with base exists. - auto insert_layer = [&layer_storage, snug_supports, closing_distance, smoothing_distance, minimum_island_radius]( - SupportGeneratorLayer &intermediate_layer, Polygons &bottom, Polygons &&top, const Polygons *subtract, SupporLayerType type) -> SupportGeneratorLayer* { - assert(! bottom.empty() || ! top.empty()); - // Merge top into bottom, unite them with a safety offset. - append(bottom, std::move(top)); - // Merge top / bottom interfaces. For snug supports, merge using closing distance and regularize (close concave corners). - bottom = intersection( - snug_supports ? - smooth_outward(closing(std::move(bottom), closing_distance + minimum_island_radius, closing_distance, SUPPORT_SURFACES_OFFSET_PARAMETERS), smoothing_distance) : - union_safety_offset(std::move(bottom)), - intermediate_layer.polygons); - if (! bottom.empty()) { - //FIXME Remove non-printable tiny islands, let them be printed using the base support. - //bottom = opening(std::move(bottom), minimum_island_radius); - if (! bottom.empty()) { - SupportGeneratorLayer &layer_new = layer_storage.allocate(type); - layer_new.polygons = std::move(bottom); - layer_new.print_z = intermediate_layer.print_z; - layer_new.bottom_z = intermediate_layer.bottom_z; - layer_new.height = intermediate_layer.height; - layer_new.bridging = intermediate_layer.bridging; - // Subtract the interface from the base regions. - intermediate_layer.polygons = diff(intermediate_layer.polygons, layer_new.polygons); - if (subtract) - // Trim the base interface layer with the interface layer. - layer_new.polygons = diff(std::move(layer_new.polygons), *subtract); - //FIXME filter layer_new.polygons islands by a minimum area? - // $interface_area = [ grep abs($_->area) >= $area_threshold, @$interface_area ]; - return &layer_new; - } - } - return nullptr; - }; - tbb::parallel_for(tbb::blocked_range(0, int(intermediate_layers.size())), - [&bottom_contacts, &top_contacts, &intermediate_layers, &insert_layer, - num_interface_layers_top, num_interface_layers_bottom, num_base_interface_layers_top, num_base_interface_layers_bottom, num_interface_layers_only_top, num_interface_layers_only_bottom, - snug_supports, &interface_layers, &base_interface_layers](const tbb::blocked_range& range) { - // Gather the top / bottom contact layers intersecting with num_interface_layers resp. num_interface_layers_only intermediate layers above / below - // this intermediate layer. - // Index of the first top contact layer intersecting the current intermediate layer. - auto idx_top_contact_first = -1; - // Index of the first bottom contact layer intersecting the current intermediate layer. - auto idx_bottom_contact_first = -1; - auto num_intermediate = int(intermediate_layers.size()); - for (int idx_intermediate_layer = range.begin(); idx_intermediate_layer < range.end(); ++ idx_intermediate_layer) { - SupportGeneratorLayer &intermediate_layer = *intermediate_layers[idx_intermediate_layer]; - Polygons polygons_top_contact_projected_interface; - Polygons polygons_top_contact_projected_base; - Polygons polygons_bottom_contact_projected_interface; - Polygons polygons_bottom_contact_projected_base; - if (num_interface_layers_top > 0) { - // Top Z coordinate of a slab, over which we are collecting the top / bottom contact surfaces - coordf_t top_z = intermediate_layers[std::min(num_intermediate - 1, idx_intermediate_layer + num_interface_layers_top - 1)]->print_z; - coordf_t top_inteface_z = std::numeric_limits::max(); - if (num_base_interface_layers_top > 0) - // Some top base interface layers will be generated. - top_inteface_z = num_interface_layers_only_top == 0 ? - // Only base interface layers to generate. - - std::numeric_limits::max() : - intermediate_layers[std::min(num_intermediate - 1, idx_intermediate_layer + num_interface_layers_only_top - 1)]->print_z; - // Move idx_top_contact_first up until above the current print_z. - idx_top_contact_first = idx_higher_or_equal(top_contacts, idx_top_contact_first, [&intermediate_layer](const SupportGeneratorLayer *layer){ return layer->print_z >= intermediate_layer.print_z; }); // - EPSILON - // Collect the top contact areas above this intermediate layer, below top_z. - for (int idx_top_contact = idx_top_contact_first; idx_top_contact < int(top_contacts.size()); ++ idx_top_contact) { - const SupportGeneratorLayer &top_contact_layer = *top_contacts[idx_top_contact]; - //FIXME maybe this adds one interface layer in excess? - if (top_contact_layer.bottom_z - EPSILON > top_z) - break; - polygons_append(top_contact_layer.bottom_z - EPSILON > top_inteface_z ? polygons_top_contact_projected_base : polygons_top_contact_projected_interface, - // For snug supports, project the overhang polygons covering the whole overhang, so that they will merge without a gap with support polygons of the other layers. - // For grid supports, merging of support regions will be performed by the projection into grid. - snug_supports ? *top_contact_layer.overhang_polygons : top_contact_layer.polygons); - } - } - if (num_interface_layers_bottom > 0) { - // Bottom Z coordinate of a slab, over which we are collecting the top / bottom contact surfaces - coordf_t bottom_z = intermediate_layers[std::max(0, idx_intermediate_layer - num_interface_layers_bottom + 1)]->bottom_z; - coordf_t bottom_interface_z = - std::numeric_limits::max(); - if (num_base_interface_layers_bottom > 0) - // Some bottom base interface layers will be generated. - bottom_interface_z = num_interface_layers_only_bottom == 0 ? - // Only base interface layers to generate. - std::numeric_limits::max() : - intermediate_layers[std::max(0, idx_intermediate_layer - num_interface_layers_only_bottom)]->bottom_z; - // Move idx_bottom_contact_first up until touching bottom_z. - idx_bottom_contact_first = idx_higher_or_equal(bottom_contacts, idx_bottom_contact_first, [bottom_z](const SupportGeneratorLayer *layer){ return layer->print_z >= bottom_z - EPSILON; }); - // Collect the top contact areas above this intermediate layer, below top_z. - for (int idx_bottom_contact = idx_bottom_contact_first; idx_bottom_contact < int(bottom_contacts.size()); ++ idx_bottom_contact) { - const SupportGeneratorLayer &bottom_contact_layer = *bottom_contacts[idx_bottom_contact]; - if (bottom_contact_layer.print_z - EPSILON > intermediate_layer.bottom_z) - break; - polygons_append(bottom_contact_layer.print_z - EPSILON > bottom_interface_z ? polygons_bottom_contact_projected_interface : polygons_bottom_contact_projected_base, bottom_contact_layer.polygons); - } - } - SupportGeneratorLayer *interface_layer = nullptr; - if (! polygons_bottom_contact_projected_interface.empty() || ! polygons_top_contact_projected_interface.empty()) { - interface_layer = insert_layer( - intermediate_layer, polygons_bottom_contact_projected_interface, std::move(polygons_top_contact_projected_interface), nullptr, - polygons_top_contact_projected_interface.empty() ? SupporLayerType::BottomInterface : SupporLayerType::TopInterface); - interface_layers[idx_intermediate_layer] = interface_layer; - } - if (! polygons_bottom_contact_projected_base.empty() || ! polygons_top_contact_projected_base.empty()) - base_interface_layers[idx_intermediate_layer] = insert_layer( - intermediate_layer, polygons_bottom_contact_projected_base, std::move(polygons_top_contact_projected_base), - interface_layer ? &interface_layer->polygons : nullptr, SupporLayerType::Base); - } - }); - - // Compress contact_out, remove the nullptr items. - remove_nulls(interface_layers); - remove_nulls(base_interface_layers); - BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::generate_interface_layers() in parallel - end"; - } - - return base_and_interface_layers; -} - /* void PrintObjectSupportMaterial::clip_by_pillars( const PrintObject &object, diff --git a/src/libslic3r/Support/SupportMaterial.hpp b/src/libslic3r/Support/SupportMaterial.hpp index 924ed1a05..4f1768fb1 100644 --- a/src/libslic3r/Support/SupportMaterial.hpp +++ b/src/libslic3r/Support/SupportMaterial.hpp @@ -72,16 +72,6 @@ private: SupportGeneratorLayersPtr &intermediate_layers, const std::vector &layer_support_areas) const; - // Turn some of the base layers into base interface layers. - // For soluble interfaces with non-soluble bases, print maximum two first interface layers with the base - // extruder to improve adhesion of the soluble filament to the base. - std::pair generate_interface_layers( - const SupportGeneratorLayersPtr &bottom_contacts, - const SupportGeneratorLayersPtr &top_contacts, - SupportGeneratorLayersPtr &intermediate_layers, - SupportGeneratorLayerStorage &layer_storage) const; - - // Trim support layers by an object to leave a defined gap between // the support volume and the object. void trim_support_layers_by_object( diff --git a/src/libslic3r/Support/SupportParameters.cpp b/src/libslic3r/Support/SupportParameters.cpp index 1c7f860b8..531e8dcaf 100644 --- a/src/libslic3r/Support/SupportParameters.cpp +++ b/src/libslic3r/Support/SupportParameters.cpp @@ -11,6 +11,33 @@ SupportParameters::SupportParameters(const PrintObject &object) const PrintObjectConfig &object_config = object.config(); const SlicingParameters &slicing_params = object.slicing_parameters(); + this->soluble_interface = slicing_params.soluble_interface; + this->soluble_interface_non_soluble_base = + // Zero z-gap between the overhangs and the support interface. + slicing_params.soluble_interface && + // Interface extruder soluble. + object_config.support_material_interface_extruder.value > 0 && print_config.filament_soluble.get_at(object_config.support_material_interface_extruder.value - 1) && + // Base extruder: Either "print with active extruder" not soluble. + (object_config.support_material_extruder.value == 0 || ! print_config.filament_soluble.get_at(object_config.support_material_extruder.value - 1)); + + { + int num_top_interface_layers = std::max(0, object_config.support_material_interface_layers.value); + int num_bottom_interface_layers = object_config.support_material_bottom_interface_layers < 0 ? + num_top_interface_layers : object_config.support_material_bottom_interface_layers; + this->has_top_contacts = num_top_interface_layers > 0; + this->has_bottom_contacts = num_bottom_interface_layers > 0; + this->num_top_interface_layers = this->has_top_contacts ? size_t(num_top_interface_layers - 1) : 0; + this->num_bottom_interface_layers = this->has_bottom_contacts ? size_t(num_bottom_interface_layers - 1) : 0; + if (this->soluble_interface_non_soluble_base) { + // Try to support soluble dense interfaces with non-soluble dense interfaces. + this->num_top_base_interface_layers = size_t(std::min(num_top_interface_layers / 2, 2)); + this->num_bottom_base_interface_layers = size_t(std::min(num_bottom_interface_layers / 2, 2)); + } else { + this->num_top_base_interface_layers = 0; + this->num_bottom_base_interface_layers = 0; + } + } + this->first_layer_flow = Slic3r::support_material_1st_layer_flow(&object, float(slicing_params.first_print_layer_height)); this->support_material_flow = Slic3r::support_material_flow(&object, float(slicing_params.layer_height)); this->support_material_interface_flow = Slic3r::support_material_interface_flow(&object, float(slicing_params.layer_height)); @@ -54,7 +81,6 @@ SupportParameters::SupportParameters(const PrintObject &object) this->can_merge_support_regions = true; } - double interface_spacing = object_config.support_material_interface_spacing.value + this->support_material_interface_flow.spacing(); this->interface_density = std::min(1., this->support_material_interface_flow.spacing() / interface_spacing); double raft_interface_spacing = object_config.support_material_interface_spacing.value + this->raft_interface_flow.spacing(); diff --git a/src/libslic3r/Support/SupportParameters.hpp b/src/libslic3r/Support/SupportParameters.hpp index 904e8ffe2..be38e9650 100644 --- a/src/libslic3r/Support/SupportParameters.hpp +++ b/src/libslic3r/Support/SupportParameters.hpp @@ -14,6 +14,30 @@ namespace FFFSupport { struct SupportParameters { SupportParameters(const PrintObject &object); + // Both top / bottom contacts and interfaces are soluble. + bool soluble_interface; + // Support contact & interface are soluble, but support base is non-soluble. + bool soluble_interface_non_soluble_base; + + // Is there at least a top contact layer extruded above support base? + bool has_top_contacts; + // Is there at least a bottom contact layer extruded below support base? + bool has_bottom_contacts; + // Number of top interface layers without counting the contact layer. + size_t num_top_interface_layers; + // Number of bottom interface layers without counting the contact layer. + size_t num_bottom_interface_layers; + // Number of top base interface layers. Zero if not soluble_interface_non_soluble_base. + size_t num_top_base_interface_layers; + // Number of bottom base interface layers. Zero if not soluble_interface_non_soluble_base. + size_t num_bottom_base_interface_layers; + + bool has_contacts() const { return this->has_top_contacts || this->has_bottom_contacts; } + bool has_interfaces() const { return this->num_top_interface_layers + this->num_bottom_interface_layers > 0; } + bool has_base_interfaces() const { return this->num_top_base_interface_layers + this->num_bottom_base_interface_layers > 0; } + size_t num_top_interface_layers_only() const { return this->num_top_interface_layers - this->num_top_base_interface_layers; } + size_t num_bottom_interface_layers_only() const { return this->num_bottom_interface_layers - this->num_bottom_base_interface_layers; } + // Flow at the 1st print layer. Flow first_layer_flow; // Flow at the support base (neither top, nor bottom interface). diff --git a/src/libslic3r/Support/TreeSupport.cpp b/src/libslic3r/Support/TreeSupport.cpp index d1306962e..dfeb3fdf9 100644 --- a/src/libslic3r/Support/TreeSupport.cpp +++ b/src/libslic3r/Support/TreeSupport.cpp @@ -87,7 +87,6 @@ TreeSupportSettings::TreeSupportSettings(const TreeSupportMeshGroupSettings& mes bp_radius_increase_per_layer(std::min(tan(0.7) * layer_height, 0.5 * support_line_width)), z_distance_bottom_layers(size_t(round(double(mesh_group_settings.support_bottom_distance) / double(layer_height)))), z_distance_top_layers(size_t(round(double(mesh_group_settings.support_top_distance) / double(layer_height)))), - performance_interface_skip_layers(round_up_divide(mesh_group_settings.support_interface_skip_height, layer_height)), // support_infill_angles(mesh_group_settings.support_infill_angles), support_roof_angles(mesh_group_settings.support_roof_angles), roof_pattern(mesh_group_settings.support_roof_pattern), @@ -1077,71 +1076,53 @@ void finalize_raft_contact( } } +// Used by generate_initial_areas() in parallel by multiple layers. class InterfacePlacer { public: - InterfacePlacer(const SlicingParameters &slicing_parameters, const TreeModelVolumes &volumes, const TreeSupportSettings &config, bool force_tip_to_roof, size_t num_support_layers, - std::vector &move_bounds, SupportGeneratorLayerStorage &layer_storage, SupportGeneratorLayersPtr &top_contacts) : - slicing_parameters(slicing_parameters), volumes(volumes), config(config), force_tip_to_roof(force_tip_to_roof), - move_bounds(move_bounds), layer_storage(layer_storage), top_contacts(top_contacts) { + InterfacePlacer( + const SlicingParameters &slicing_parameters, + const SupportParameters &support_parameters, + const TreeModelVolumes &volumes, const TreeSupportSettings &config, + bool force_tip_to_roof, size_t num_support_layers, + std::vector &move_bounds, + SupportGeneratorLayerStorage &layer_storage, + SupportGeneratorLayersPtr &top_contacts, SupportGeneratorLayersPtr &top_interfaces, SupportGeneratorLayersPtr &top_base_interfaces) + : + slicing_parameters(slicing_parameters), support_parameters(support_parameters), volumes(volumes), config(config), force_tip_to_roof(force_tip_to_roof), + move_bounds(move_bounds), + layer_storage(layer_storage), top_contacts(top_contacts), top_interfaces(top_interfaces), top_base_interfaces(top_base_interfaces) { m_already_inserted.assign(num_support_layers, {}); this->min_xy_dist = config.xy_distance > config.xy_min_distance; } const SlicingParameters &slicing_parameters; + const SupportParameters &support_parameters; const TreeModelVolumes &volumes; const TreeSupportSettings &config; - bool force_tip_to_roof; + // Radius of the tree tip is large enough to be covered by an interface. + const bool force_tip_to_roof; bool min_xy_dist; - // Outputs - std::vector &move_bounds; - SupportGeneratorLayerStorage &layer_storage; - SupportGeneratorLayersPtr &top_contacts; - -private: - // Temps - static constexpr const auto m_base_radius = scaled(0.01); - const Polygon m_base_circle { make_circle(m_base_radius, SUPPORT_TREE_CIRCLE_RESOLUTION) }; - - // Mutexes, guards - std::mutex m_mutex_movebounds; - std::mutex m_mutex_layer_storage; - std::vector> m_already_inserted; - public: - void add_roof_unguarded(Polygons &&new_roofs, const size_t insert_layer_idx) - { - SupportGeneratorLayer*& l = top_contacts[insert_layer_idx]; - if (l == nullptr) - l = &layer_allocate_unguarded(layer_storage, SupporLayerType::TopContact, slicing_parameters, config, insert_layer_idx); - // will be unioned in finalize_interface_and_support_areas() - append(l->polygons, std::move(new_roofs)); - } - - void add_roof(Polygons &&new_roofs, const size_t insert_layer_idx) - { - std::lock_guard lock(m_mutex_layer_storage); - add_roof_unguarded(std::move(new_roofs), insert_layer_idx); - } - - void add_roofs(std::vector &&new_roofs, const size_t insert_layer_idx, const size_t dtt_roof) + // called by sample_overhang_area() + // Insert the contact layer and some of the inteface and base interface layers below. + void add_roofs(std::vector &&new_roofs, const size_t insert_layer_idx) { if (! new_roofs.empty()) { std::lock_guard lock(m_mutex_layer_storage); - for (size_t idx = 0; idx < dtt_roof; ++ idx) + for (size_t idx = 0; idx < new_roofs.size(); ++ idx) if (! new_roofs[idx].empty()) - add_roof_unguarded(std::move(new_roofs[idx]), insert_layer_idx - idx); + add_roof_unguarded(std::move(new_roofs[idx]), insert_layer_idx - idx, idx); } } - void add_roof_build_plate(Polygons &&overhang_areas) + // called by sample_overhang_area() + void add_roof_build_plate(Polygons &&overhang_areas, size_t dtt_roof) { std::lock_guard lock(m_mutex_layer_storage); - SupportGeneratorLayer*& l = top_contacts[0]; - if (l == nullptr) - l = &layer_allocate_unguarded(layer_storage, SupporLayerType::TopContact, slicing_parameters, config, 0); - append(l->polygons, std::move(overhang_areas)); + this->add_roof_unguarded(std::move(overhang_areas), 0, dtt_roof); } + // called by sample_overhang_area() void add_points_along_lines( // Insert points (tree tips or top contact interfaces) along these lines. LineInformations lines, @@ -1150,7 +1131,7 @@ public: // Insert this number of interface layers. size_t roof_tip_layers, // True if an interface is already generated above these lines. - bool supports_roof, + size_t supports_roof_layers, // The element tries to not move until this dtt is reached. size_t dont_move_until) { @@ -1203,7 +1184,7 @@ public: roof_circle.translate(p.first); new_roofs.emplace_back(std::move(roof_circle)); } - this->add_roof(std::move(new_roofs), this_layer_idx); + this->add_roof(std::move(new_roofs), this_layer_idx, dtt_roof_tip + supports_roof_layers); } for (const LineInformation &line : lines) { @@ -1215,11 +1196,33 @@ public: // don't move until dont_move_until > dtt_roof_tip ? dont_move_until - dtt_roof_tip : 0, // supports roof - dtt_roof_tip > 0 || supports_roof, + dtt_roof_tip + supports_roof_layers > 0, disable_ovalistation); } } +private: + void add_roof_unguarded(Polygons &&new_roofs, const size_t insert_layer_idx, const size_t dtt_roof) + { + SupportGeneratorLayersPtr &layers = + dtt_roof == 0 ? this->top_contacts : + dtt_roof <= support_parameters.num_top_interface_layers_only() ? this->top_interfaces : this->top_base_interfaces; + SupportGeneratorLayer*& l = layers[insert_layer_idx]; + if (l == nullptr) + l = &layer_allocate_unguarded(layer_storage, dtt_roof == 0 ? SupporLayerType::TopContact : SupporLayerType::TopInterface, + slicing_parameters, config, insert_layer_idx); + // will be unioned in finalize_interface_and_support_areas() + append(l->polygons, std::move(new_roofs)); + } + + // called by this->add_points_along_lines() + void add_roof(Polygons &&new_roof, const size_t insert_layer_idx, const size_t dtt_tip) + { + std::lock_guard lock(m_mutex_layer_storage); + add_roof_unguarded(std::move(new_roof), insert_layer_idx, dtt_tip); + } + + // called by this->add_points_along_lines() void add_point_as_influence_area(std::pair p, LayerIndex insert_layer, size_t dont_move_until, bool roof, bool skip_ovalisation) { bool to_bp = p.second == LineStatus::TO_BP || p.second == LineStatus::TO_BP_SAFE; @@ -1233,8 +1236,8 @@ public: Polygons circle{ m_base_circle }; circle.front().translate(p.first); { - std::lock_guard critical_section_movebounds(m_mutex_movebounds); Point hash_pos = p.first / ((config.min_radius + 1) / 10); + std::lock_guard critical_section_movebounds(m_mutex_movebounds); if (!m_already_inserted[insert_layer].count(hash_pos)) { // normalize the point a bit to also catch points which are so close that inserting it would achieve nothing m_already_inserted[insert_layer].emplace(hash_pos); @@ -1261,9 +1264,26 @@ public: move_bounds[insert_layer].emplace_back(state, std::move(circle)); } } - }; + } + + // Outputs + std::vector &move_bounds; + SupportGeneratorLayerStorage &layer_storage; + SupportGeneratorLayersPtr &top_contacts; + SupportGeneratorLayersPtr &top_interfaces; + SupportGeneratorLayersPtr &top_base_interfaces; + + // Temps + static constexpr const auto m_base_radius = scaled(0.01); + const Polygon m_base_circle { make_circle(m_base_radius, SUPPORT_TREE_CIRCLE_RESOLUTION) }; + + // Mutexes, guards + std::mutex m_mutex_movebounds; + std::mutex m_mutex_layer_storage; + std::vector> m_already_inserted; }; +// Called by generate_initial_areas(), used in parallel by multiple layers. // Produce // 1) Maximum num_support_roof_layers roof (top interface & contact) layers. // 2) Tree tips supporting either the roof layers or the object itself. @@ -1280,13 +1300,12 @@ void sample_overhang_area( const bool large_horizontal_roof, // Index of the top suport layer generated by this function. const size_t layer_idx, - // Number of roof (contact, interface) layers between the overhang and tree tips. + // Maximum number of roof (contact, interface) layers between the overhang and tree tips to be generated. const size_t num_support_roof_layers, // const coord_t connect_length, // Configuration classes const TreeSupportMeshGroupSettings &mesh_group_settings, - const SupportParameters &support_params, // Configuration & Output InterfacePlacer &interface_placer) { @@ -1297,11 +1316,12 @@ void sample_overhang_area( // as the pattern may be different one layer below. Same with calculating which points are now no longer being generated as result from // a decreasing roof, as there is no guarantee that a line will be above these points. Implementing a separate roof support behavior // for each pattern harms maintainability as it very well could be >100 LOC - auto generate_roof_lines = [&support_params, &mesh_group_settings](const Polygons &area, LayerIndex layer_idx) -> Polylines { - return generate_support_infill_lines(area, support_params, true, layer_idx, mesh_group_settings.support_roof_line_distance); + auto generate_roof_lines = [&interface_placer, &mesh_group_settings](const Polygons &area, LayerIndex layer_idx) -> Polylines { + return generate_support_infill_lines(area, interface_placer.support_parameters, true, layer_idx, mesh_group_settings.support_roof_line_distance); }; LineInformations overhang_lines; + // Track how many top contact / interface layers were already generated. size_t dtt_roof = 0; size_t layer_generation_dtt = 0; @@ -1325,9 +1345,9 @@ void sample_overhang_area( } Polygons overhang_area_next = diff(overhang_area, forbidden_next); if (area(overhang_area_next) < mesh_group_settings.minimum_roof_area) { - // next layer down the roof area would be to small so we have to insert our roof support here. Also convert squaremicrons to squaremilimeter - if (dtt_roof != 0) { - size_t dtt_before = dtt_roof > 0 ? dtt_roof - 1 : 0; + // Next layer down the roof area would be to small so we have to insert our roof support here. + if (dtt_roof > 0) { + size_t dtt_before = dtt_roof - 1; // Produce support head points supporting an interface layer: First produce the interface lines, then sample them. overhang_lines = split_lines( convert_lines_to_internal(interface_placer.volumes, interface_placer.config, @@ -1354,7 +1374,8 @@ void sample_overhang_area( break; } } - interface_placer.add_roofs(std::move(added_roofs), layer_idx, dtt_roof); + added_roofs.erase(added_roofs.begin() + dtt_roof, added_roofs.end()); + interface_placer.add_roofs(std::move(added_roofs), layer_idx); } if (overhang_lines.empty()) { @@ -1364,7 +1385,7 @@ void sample_overhang_area( bool supports_roof = dtt_roof > 0; bool continuous_tips = ! supports_roof && large_horizontal_roof; Polylines polylines = ensure_maximum_distance_polyline( - generate_support_infill_lines(overhang_area, support_params, supports_roof, layer_idx - layer_generation_dtt, + generate_support_infill_lines(overhang_area, interface_placer.support_parameters, supports_roof, layer_idx - layer_generation_dtt, supports_roof ? mesh_group_settings.support_roof_line_distance : mesh_group_settings.support_tree_branch_distance), continuous_tips ? interface_placer.config.min_radius / 2 : connect_length, 1); size_t point_count = 0; @@ -1388,9 +1409,10 @@ void sample_overhang_area( overhang_lines = convert_lines_to_internal(interface_placer.volumes, interface_placer.config, polylines, layer_idx - dtt_roof); } + assert(dtt_roof <= layer_idx); if (int(dtt_roof) >= layer_idx && large_horizontal_roof) - // reached buildplate - interface_placer.add_roof_build_plate(std::move(overhang_area)); + // Reached buildplate when generating contact, interface and base interface layers. + interface_placer.add_roof_build_plate(std::move(overhang_area), dtt_roof); else { // normal trees have to be generated const bool roof_enabled = num_support_roof_layers > 0; @@ -1401,8 +1423,8 @@ void sample_overhang_area( layer_idx - dtt_roof, // Remaining roof tip layers. interface_placer.force_tip_to_roof ? num_support_roof_layers - dtt_roof : 0, - // Supports roof already? - dtt_roof > 0, + // Supports roof already? How many roof layers were already produced above these tips? + dtt_roof, // Don't move until the following distance to top is reached. roof_enabled ? num_support_roof_layers - dtt_roof : 0); } @@ -1424,7 +1446,8 @@ static void generate_initial_areas( const std::vector &overhangs, std::vector &move_bounds, SupportGeneratorLayersPtr &top_contacts, - SupportGeneratorLayersPtr &top_interface_layers, + SupportGeneratorLayersPtr &top_interfaces, + SupportGeneratorLayersPtr &top_base_interfaces, SupportGeneratorLayerStorage &layer_storage, std::function throw_on_cancel) { @@ -1462,7 +1485,7 @@ static void generate_initial_areas( ; const size_t num_support_roof_layers = mesh_group_settings.support_roof_layers; const bool roof_enabled = num_support_roof_layers > 0; - const bool force_tip_to_roof = sqr(config.min_radius) * M_PI > mesh_group_settings.minimum_roof_area && roof_enabled; + const bool force_tip_to_roof = roof_enabled && sqr(config.min_radius) * M_PI > mesh_group_settings.minimum_roof_area; // cap for how much layer below the overhang a new support point may be added, as other than with regular support every new inserted point // may cause extra material and time cost. Could also be an user setting or differently calculated. Idea is that if an overhang // does not turn valid in double the amount of layers a slope of support angle would take to travel xy_distance, nothing reasonable will come from it. @@ -1496,9 +1519,9 @@ static void generate_initial_areas( raw_overhangs.push_back({ layer_idx, &overhangs[overhang_idx] }); } - InterfacePlacer interface_placer{ print_object.slicing_parameters(), volumes, config, force_tip_to_roof, num_support_layers, + InterfacePlacer interface_placer{ print_object.slicing_parameters(), support_params, volumes, config, force_tip_to_roof, num_support_layers, // Outputs - move_bounds, layer_storage, top_contacts }; + move_bounds, layer_storage, top_contacts, top_interfaces, top_base_interfaces }; tbb::parallel_for(tbb::blocked_range(0, raw_overhangs.size()), [&volumes, &config, &raw_overhangs, &mesh_group_settings, &support_params, @@ -1613,7 +1636,7 @@ static void generate_initial_areas( //check_self_intersections(overhang_regular, "overhang_regular3"); for (ExPolygon &roof_part : union_ex(overhang_roofs)) { sample_overhang_area(to_polygons(std::move(roof_part)), true, layer_idx, num_support_roof_layers, connect_length, - mesh_group_settings, support_params, interface_placer); + mesh_group_settings, interface_placer); throw_on_cancel(); } } @@ -1623,9 +1646,8 @@ static void generate_initial_areas( remove_small(overhang_regular, mesh_group_settings.minimum_support_area); for (ExPolygon &support_part : union_ex(overhang_regular)) { sample_overhang_area(to_polygons(std::move(support_part)), - // Don't false, layer_idx, num_support_roof_layers, connect_length, - mesh_group_settings, support_params, interface_placer); + mesh_group_settings, interface_placer); throw_on_cancel(); } } @@ -3392,7 +3414,7 @@ static void finalize_interface_and_support_areas( //FIXME subtract the wipe tower append(floor_layer, intersection(layer_outset, overhangs[sample_layer])); if (layers_below < config.support_bottom_layers) - layers_below = std::min(layers_below + config.performance_interface_skip_layers, config.support_bottom_layers); + layers_below = std::min(layers_below + 1, config.support_bottom_layers); else break; } @@ -4139,11 +4161,20 @@ static void organic_smooth_branches_avoid_collisions( #endif // TREE_SUPPORT_ORGANIC_NUDGE_NEW // Organic specific: Smooth branches and produce one cummulative mesh to be sliced. -static std::vector draw_branches( +static void draw_branches( PrintObject &print_object, TreeModelVolumes &volumes, const TreeSupportSettings &config, std::vector &move_bounds, + + // I/O: + SupportGeneratorLayersPtr &bottom_contacts, + SupportGeneratorLayersPtr &top_contacts, + + // Output: + SupportGeneratorLayersPtr &intermediate_layers, + SupportGeneratorLayerStorage &layer_storage, + std::function throw_on_cancel) { // All SupportElements are put into a layer independent storage to improve parallelization. @@ -4211,7 +4242,7 @@ static std::vector draw_branches( struct Slice { Polygons polygons; - Polygons bottom_interfaces; + Polygons bottom_contacts; size_t num_branches{ 0 }; }; @@ -4316,6 +4347,7 @@ static std::vector draw_branches( [&trees, &volumes, &config, &slicing_params, &move_bounds, &mesh_slicing_params, &throw_on_cancel](const tbb::blocked_range &range) { indexed_triangle_set partial_mesh; std::vector slice_z; + std::vector bottom_contacts; for (size_t tree_id = range.begin(); tree_id < range.end(); ++ tree_id) { Tree &tree = trees[tree_id]; for (const Branch &branch : tree.branches) { @@ -4335,63 +4367,71 @@ static std::vector draw_branches( slice_z.emplace_back(float(0.5 * (bottom_z + print_z))); } std::vector slices = slice_mesh(partial_mesh, slice_z, mesh_slicing_params, throw_on_cancel); + bottom_contacts.clear(); //FIXME parallelize? for (LayerIndex i = 0; i < LayerIndex(slices.size()); ++ i) slices[i] = diff_clipped(slices[i], volumes.getCollision(0, layer_begin + i, true)); //FIXME parent_uses_min || draw_area.element->state.use_min_xy_dist); size_t num_empty = 0; - if (layer_begin > 0 && branch.has_root && ! branch.path.front()->state.to_model_gracious && ! slices.front().empty()) { - // Drop down areas that do rest non - gracefully on the model to ensure the branch actually rests on something. - struct BottomExtraSlice { - Polygons polygons; - Polygons supported; - Polygons bottom_interfaces; - double area; - double supported_area; - }; - std::vector bottom_extra_slices; - Polygons rest_support; - coord_t bottom_radius = config.getRadius(branch.path.front()->state); - // Don't propagate further than 1.5 * bottom radius. - //LayerIndex layers_propagate_max = 2 * bottom_radius / config.layer_height; - LayerIndex layers_propagate_max = 5 * bottom_radius / config.layer_height; - LayerIndex layer_bottommost = std::max(0, layer_begin - layers_propagate_max); - // Only propagate until the rest area is smaller than this threshold. - double support_area_stop = 0.2 * M_PI * sqr(double(bottom_radius)); - // Only propagate until the rest area is smaller than this threshold. - double support_area_min = 0.1 * M_PI * sqr(double(config.min_radius)); - for (LayerIndex layer_idx = layer_begin - 1; layer_idx >= layer_bottommost; -- layer_idx) { - rest_support = diff_clipped(rest_support.empty() ? slices.front() : rest_support, volumes.getCollision(0, layer_idx, false)); - double rest_support_area = area(rest_support); - if (rest_support_area < support_area_stop) - // Don't propagate a fraction of the tree contact surface. - break; - // Measure how much the rest_support is actually supported. - /* - Polygons supported = intersection_clipped(rest_support, volumes.getPlaceableAreas(0, layer_idx, []{})); - double supported_area = area(supported); - printf("Supported area: %d, %lf\n", layer_idx, supported_area); - */ - Polygons supported; - double supported_area; - bottom_extra_slices.push_back({ rest_support, std::move(supported), {}, rest_support_area, supported_area }); - } - // Now remove those bottom slices that are not supported at all. - while (! bottom_extra_slices.empty()) { - Polygons bottom_interfaces = intersection_clipped(bottom_extra_slices.back().polygons, volumes.getPlaceableAreas(0, layer_begin - LayerIndex(bottom_extra_slices.size()), [] {})); - if (area(bottom_interfaces) < support_area_min) - bottom_extra_slices.pop_back(); - else { - bottom_extra_slices.back().bottom_interfaces = std::move(bottom_interfaces); + if (slices.front().empty()) { + // Some of the initial layers are empty. + num_empty = std::find_if(slices.begin(), slices.end(), [](auto &s) { return !s.empty(); }) - slices.begin(); + } else { + if (branch.has_root) { + if (branch.path.front()->state.to_model_gracious) { + if (config.settings.support_floor_layers > 0) + //FIXME one may just take the whole tree slice as bottom interface. + bottom_contacts.emplace_back(intersection_clipped(slices.front(), volumes.getPlaceableAreas(0, layer_begin, [] {}))); + } else if (layer_begin > 0) { + // Drop down areas that do rest non - gracefully on the model to ensure the branch actually rests on something. + struct BottomExtraSlice { + Polygons polygons; + double area; + }; + std::vector bottom_extra_slices; + Polygons rest_support; + coord_t bottom_radius = config.getRadius(branch.path.front()->state); + // Don't propagate further than 1.5 * bottom radius. + //LayerIndex layers_propagate_max = 2 * bottom_radius / config.layer_height; + LayerIndex layers_propagate_max = 5 * bottom_radius / config.layer_height; + LayerIndex layer_bottommost = std::max(0, layer_begin - layers_propagate_max); + // Only propagate until the rest area is smaller than this threshold. + double support_area_stop = 0.2 * M_PI * sqr(double(bottom_radius)); + // Only propagate until the rest area is smaller than this threshold. + double support_area_min = 0.1 * M_PI * sqr(double(config.min_radius)); + for (LayerIndex layer_idx = layer_begin - 1; layer_idx >= layer_bottommost; -- layer_idx) { + rest_support = diff_clipped(rest_support.empty() ? slices.front() : rest_support, volumes.getCollision(0, layer_idx, false)); + double rest_support_area = area(rest_support); + if (rest_support_area < support_area_stop) + // Don't propagate a fraction of the tree contact surface. + break; + bottom_extra_slices.push_back({ rest_support, rest_support_area }); + } + // Now remove those bottom slices that are not supported at all. + while (! bottom_extra_slices.empty()) { + Polygons this_bottom_contacts = intersection_clipped( + bottom_extra_slices.back().polygons, volumes.getPlaceableAreas(0, layer_begin - LayerIndex(bottom_extra_slices.size()), [] {})); + if (area(this_bottom_contacts) < support_area_min) + bottom_extra_slices.pop_back(); + else if (config.settings.support_floor_layers > 0) + bottom_contacts.emplace_back(std::move(this_bottom_contacts)); + } + if (config.settings.support_floor_layers > 0) + for (int i = int(bottom_extra_slices.size()) - 2; i >= 0; -- i) + bottom_contacts.emplace_back( + intersection_clipped(bottom_extra_slices[i].polygons, volumes.getPlaceableAreas(0, layer_begin - i - 1, [] {}))); + layer_begin -= LayerIndex(bottom_extra_slices.size()); + slices.insert(slices.begin(), bottom_extra_slices.size(), {}); + auto it_dst = slices.begin(); + for (auto it_src = bottom_extra_slices.rbegin(); it_src != bottom_extra_slices.rend(); ++ it_src) + *it_dst ++ = std::move(it_src->polygons); } } - layer_begin -= LayerIndex(bottom_extra_slices.size()); - slices.insert(slices.begin(), bottom_extra_slices.size(), {}); - size_t i = 0; - for (auto it = bottom_extra_slices.rbegin(); it != bottom_extra_slices.rend(); ++it, ++i) - slices[i] = std::move(it->polygons); - } else - num_empty = std::find_if(slices.begin(), slices.end(), [](auto &s) { return !s.empty(); }) - slices.begin(); + if (branch.has_tip) { + // Add top slices to top contacts / interfaces / base interfaces. + //slices; + } + } layer_begin += LayerIndex(num_empty); while (! slices.empty() && slices.back().empty()) { @@ -4414,14 +4454,21 @@ static std::vector draw_branches( tree.slices.insert(tree.slices.begin(), tree.first_layer_id - new_begin, {}); tree.slices.insert(tree.slices.end(), new_size - tree.slices.size(), {}); layer_begin -= LayerIndex(num_empty); - for (LayerIndex i = layer_begin; i != layer_end; ++ i) - if (Polygons &src = slices[i - layer_begin]; ! src.empty()) { + for (LayerIndex i = layer_begin; i != layer_end; ++ i) { + int j = i - layer_begin; + if (Polygons &src = slices[j]; ! src.empty()) { Slice &dst = tree.slices[i - new_begin]; - if (++ dst.num_branches > 1) + if (++ dst.num_branches > 1) { append(dst.polygons, std::move(src)); - else + if (j < bottom_contacts.size()) + append(dst.bottom_contacts, std::move(bottom_contacts[j])); + } else { dst.polygons = std::move(std::move(src)); + if (j < bottom_contacts.size()) + dst.bottom_contacts = std::move(bottom_contacts[j]); + } } + } tree.first_layer_id = new_begin; } } @@ -4434,7 +4481,8 @@ static std::vector draw_branches( Tree &tree = trees[tree_id]; for (Slice &slice : tree.slices) if (slice.num_branches > 1) { - slice.polygons = union_(slice.polygons); + slice.polygons = union_(slice.polygons); + slice.bottom_contacts = union_(slice.bottom_contacts); slice.num_branches = 1; } throw_on_cancel(); @@ -4452,25 +4500,55 @@ static std::vector draw_branches( for (LayerIndex i = tree.first_layer_id; i != tree.first_layer_id + LayerIndex(tree.slices.size()); ++ i) if (Slice &src = tree.slices[i - tree.first_layer_id]; ! src.polygons.empty()) { Slice &dst = slices[i]; - if (++ dst.num_branches > 1) - append(dst.polygons, std::move(src.polygons)); - else - dst.polygons = std::move(src.polygons); + if (++ dst.num_branches > 1) { + append(dst.polygons, std::move(src.polygons)); + append(dst.bottom_contacts, std::move(src.bottom_contacts)); + } else { + dst.polygons = std::move(src.polygons); + dst.bottom_contacts = std::move(src.bottom_contacts); + } } } - std::vector support_layer_storage(move_bounds.size()); tbb::parallel_for(tbb::blocked_range(0, std::min(move_bounds.size(), slices.size()), 1), - [&slices, &support_layer_storage, &throw_on_cancel](const tbb::blocked_range &range) { - for (size_t slice_id = range.begin(); slice_id < range.end(); ++ slice_id) { - Slice &slice = slices[slice_id]; - support_layer_storage[slice_id] = slice.num_branches > 1 ? union_(slice.polygons) : std::move(slice.polygons); + [&print_object, &config, &slices, &bottom_contacts, &top_contacts, &intermediate_layers, &layer_storage, &throw_on_cancel](const tbb::blocked_range &range) { + for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { + Slice &slice = slices[layer_idx]; + assert(intermediate_layers[layer_idx] == nullptr); + Polygons base_layer_polygons = slice.num_branches > 1 ? union_(slice.polygons) : std::move(slice.polygons); + Polygons bottom_contact_polygons = slice.num_branches > 1 ? union_(slice.bottom_contacts) : std::move(slice.bottom_contacts); + + if (! base_layer_polygons.empty()) { + // Most of the time in this function is this union call. Can take 300+ ms when a lot of areas are to be unioned. + base_layer_polygons = smooth_outward(union_(base_layer_polygons), config.support_line_width); //FIXME was .smooth(50); + //smooth_outward(closing(std::move(bottom), closing_distance + minimum_island_radius, closing_distance, SUPPORT_SURFACES_OFFSET_PARAMETERS), smoothing_distance) : + // simplify a bit, to ensure the output does not contain outrageous amounts of vertices. Should not be necessary, just a precaution. + base_layer_polygons = polygons_simplify(base_layer_polygons, std::min(scaled(0.03), double(config.resolution)), polygons_strictly_simple); + } + + // Subtract top contact layer polygons from support base. + SupportGeneratorLayer *top_contact_layer = top_contacts[layer_idx]; + if (top_contact_layer && ! top_contact_layer->polygons.empty() && ! base_layer_polygons.empty()) { + base_layer_polygons = diff(base_layer_polygons, top_contact_layer->polygons); + if (! bottom_contact_polygons.empty()) + //FIXME it may be better to clip bottom contacts with top contacts first after they are propagated to produce interface layers. + bottom_contact_polygons = diff(bottom_contact_polygons, top_contact_layer->polygons); + } + if (! bottom_contact_polygons.empty()) { + base_layer_polygons = diff(base_layer_polygons, bottom_contact_polygons); + SupportGeneratorLayer *bottom_contact_layer = bottom_contacts[layer_idx] = &layer_allocate( + layer_storage, SupporLayerType::BottomContact, print_object.slicing_parameters(), config, layer_idx); + bottom_contact_layer->polygons = std::move(bottom_contact_polygons); + } + if (! base_layer_polygons.empty()) { + SupportGeneratorLayer *base_layer = intermediate_layers[layer_idx] = &layer_allocate( + layer_storage, SupporLayerType::Base, print_object.slicing_parameters(), config, layer_idx); + base_layer->polygons = union_(base_layer_polygons); + } + throw_on_cancel(); } }, tbb::simple_partitioner()); - - //FIXME simplify! - return support_layer_storage; } /*! @@ -4538,9 +4616,12 @@ static void generate_support_areas(Print &print, const BuildVolume &build_volume SupportGeneratorLayerStorage layer_storage; SupportGeneratorLayersPtr top_contacts; SupportGeneratorLayersPtr bottom_contacts; - SupportGeneratorLayersPtr top_interface_layers; + SupportGeneratorLayersPtr interface_layers; + SupportGeneratorLayersPtr base_interface_layers; SupportGeneratorLayersPtr intermediate_layers; + SupportParameters support_params(print_object); + if (size_t num_support_layers = precalculate(print, overhangs, processing.first, processing.second, volumes, throw_on_cancel); num_support_layers > 0) { @@ -4550,13 +4631,15 @@ static void generate_support_areas(Print &print, const BuildVolume &build_volume std::vector move_bounds(num_support_layers); // ### Place tips of the support tree - top_contacts .assign(num_support_layers, nullptr); - bottom_contacts .assign(num_support_layers, nullptr); - top_interface_layers.assign(num_support_layers, nullptr); - intermediate_layers .assign(num_support_layers, nullptr); + bottom_contacts .assign(num_support_layers, nullptr); + top_contacts .assign(num_support_layers, nullptr); + interface_layers .assign(num_support_layers, nullptr); + base_interface_layers.assign(num_support_layers, nullptr); + intermediate_layers .assign(num_support_layers, nullptr); for (size_t mesh_idx : processing.second) - generate_initial_areas(*print.get_object(mesh_idx), volumes, config, overhangs, move_bounds, top_contacts, top_interface_layers, layer_storage, throw_on_cancel); + generate_initial_areas(*print.get_object(mesh_idx), volumes, config, overhangs, + move_bounds, top_contacts, interface_layers, base_interface_layers, layer_storage, throw_on_cancel); auto t_gen = std::chrono::high_resolution_clock::now(); #ifdef TREESUPPORT_DEBUG_SVG @@ -4587,12 +4670,24 @@ static void generate_support_areas(Print &print, const BuildVolume &build_volume bottom_contacts, top_contacts, intermediate_layers, layer_storage, throw_on_cancel); else { assert(print_object.config().support_material_style == smsOrganic); - std::vector support_layer_storage = draw_branches(*print.get_object(processing.second.front()), volumes, config, move_bounds, throw_on_cancel); - std::vector support_roof_storage(support_layer_storage.size()); - finalize_interface_and_support_areas(print_object, volumes, config, overhangs, support_layer_storage, support_roof_storage, - bottom_contacts, top_contacts, intermediate_layers, layer_storage, throw_on_cancel); + draw_branches( + *print.get_object(processing.second.front()), volumes, config, move_bounds, + bottom_contacts, top_contacts, intermediate_layers, layer_storage, + throw_on_cancel); } + auto remove_undefined_layers = [](SupportGeneratorLayersPtr& layers) { + layers.erase(std::remove_if(layers.begin(), layers.end(), [](const SupportGeneratorLayer* ptr) { return ptr == nullptr; }), layers.end()); + }; + remove_undefined_layers(bottom_contacts); + remove_undefined_layers(top_contacts); + remove_undefined_layers(interface_layers); + remove_undefined_layers(base_interface_layers); + remove_undefined_layers(intermediate_layers); + + std::tie(interface_layers, base_interface_layers) = generate_interface_layers(print_object.config(), support_params, + bottom_contacts, top_contacts, interface_layers, base_interface_layers, intermediate_layers, layer_storage); + auto t_draw = std::chrono::high_resolution_clock::now(); auto dur_pre_gen = 0.001 * std::chrono::duration_cast(t_precalc - t_start).count(); auto dur_gen = 0.001 * std::chrono::duration_cast(t_gen - t_precalc).count(); @@ -4618,18 +4713,10 @@ static void generate_support_areas(Print &print, const BuildVolume &build_volume continue; } - auto remove_undefined_layers = [](SupportGeneratorLayersPtr &layers) { - layers.erase(std::remove_if(layers.begin(), layers.end(), [](const SupportGeneratorLayer* ptr) { return ptr == nullptr; }), layers.end()); - }; - remove_undefined_layers(bottom_contacts); - remove_undefined_layers(top_contacts); - remove_undefined_layers(intermediate_layers); - // Produce the support G-code. // Used by both classic and tree supports. - SupportParameters support_params(print_object); - SupportGeneratorLayersPtr interface_layers, base_interface_layers; - SupportGeneratorLayersPtr raft_layers = generate_raft_base(print_object, support_params, print_object.slicing_parameters(), top_contacts, interface_layers, base_interface_layers, intermediate_layers, layer_storage); + SupportGeneratorLayersPtr raft_layers = generate_raft_base(print_object, support_params, print_object.slicing_parameters(), + top_contacts, interface_layers, base_interface_layers, intermediate_layers, layer_storage); #if 1 //#ifdef SLIC3R_DEBUG SupportGeneratorLayersPtr layers_sorted = #endif // SLIC3R_DEBUG diff --git a/src/libslic3r/Support/TreeSupport.hpp b/src/libslic3r/Support/TreeSupport.hpp index 2ed5d50ec..899f02724 100644 --- a/src/libslic3r/Support/TreeSupport.hpp +++ b/src/libslic3r/Support/TreeSupport.hpp @@ -364,10 +364,6 @@ public: * \brief Amount of layers distance required from the top of the model to the bottom of a support structure. */ size_t z_distance_bottom_layers; - /*! - * \brief used for performance optimization at the support floor. Should have no impact on the resulting tree. - */ - size_t performance_interface_skip_layers; /*! * \brief User specified angles for the support infill. */ From c838fc92fc1ebb19791e003dd4481f21d5ee12c8 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 8 May 2023 10:25:28 +0200 Subject: [PATCH 31/34] Follow-up to 1e7a3216ca87d9fdeaa5b12f813e5900a77d49bc WIP Organic supports intefaces: bugfixes --- src/libslic3r/Support/SupportCommon.cpp | 37 +++++++++++++------------ src/libslic3r/Support/TreeSupport.cpp | 13 +++++++-- 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/src/libslic3r/Support/SupportCommon.cpp b/src/libslic3r/Support/SupportCommon.cpp index 4978b9e40..951ae740d 100644 --- a/src/libslic3r/Support/SupportCommon.cpp +++ b/src/libslic3r/Support/SupportCommon.cpp @@ -137,7 +137,8 @@ std::pair generate_interfa if (! intermediate_layers.empty() && support_params.has_interfaces()) { // For all intermediate layers, collect top contact surfaces, which are not further than support_material_interface_layers. BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::generate_interface_layers() in parallel - start"; - const bool snug_supports = config.support_material_style.value != smsGrid; + const bool snug_supports = config.support_material_style.value == smsSnug; + const bool smooth_supports = config.support_material_style.value != smsGrid; SupportGeneratorLayersPtr &interface_layers = base_and_interface_layers.first; SupportGeneratorLayersPtr &base_interface_layers = base_and_interface_layers.second; interface_layers.assign(intermediate_layers.size(), nullptr); @@ -147,7 +148,7 @@ std::pair generate_interfa const auto minimum_island_radius = support_params.support_material_interface_flow.scaled_spacing() / support_params.interface_density; const auto closing_distance = smoothing_distance; // scaled(config.support_material_closing_radius.value); // Insert a new layer into base_interface_layers, if intersection with base exists. - auto insert_layer = [&layer_storage, snug_supports, closing_distance, smoothing_distance, minimum_island_radius]( + auto insert_layer = [&layer_storage, smooth_supports, closing_distance, smoothing_distance, minimum_island_radius]( SupportGeneratorLayer &intermediate_layer, Polygons &bottom, Polygons &&top, SupportGeneratorLayer *top_interface_layer, const Polygons *subtract, SupporLayerType type) -> SupportGeneratorLayer* { bool has_top_interface = top_interface_layer && ! top_interface_layer->polygons.empty(); @@ -156,14 +157,16 @@ std::pair generate_interfa append(bottom, std::move(top)); // Merge top / bottom interfaces. For snug supports, merge using closing distance and regularize (close concave corners). bottom = intersection( - snug_supports ? + smooth_supports ? smooth_outward(closing(std::move(bottom), closing_distance + minimum_island_radius, closing_distance, SUPPORT_SURFACES_OFFSET_PARAMETERS), smoothing_distance) : union_safety_offset(std::move(bottom)), intermediate_layer.polygons); - if (has_top_interface) + if (has_top_interface) { // Don't trim the precomputed Organic supports top interface with base layer // as the precomputed top interface likely expands over multiple tree tips. bottom = union_(std::move(top_interface_layer->polygons), bottom); + top_interface_layer->polygons.clear(); + } if (! bottom.empty()) { //FIXME Remove non-printable tiny islands, let them be printed using the base support. //bottom = opening(std::move(bottom), minimum_island_radius); @@ -253,13 +256,8 @@ std::pair generate_interfa auto resolve_same_layer = [](SupportGeneratorLayersPtr &layers, int &idx, coordf_t print_z) -> SupportGeneratorLayer* { if (! layers.empty()) { idx = idx_higher_or_equal(layers, idx, [print_z](const SupportGeneratorLayer *layer) { return layer->print_z > print_z - EPSILON; }); - if (idx < int(layers.size()) && layers[idx]->print_z < print_z + EPSILON) { - SupportGeneratorLayer *l = layers[idx]; - // Remove the layer from the source container, as it will be consumed here: It will be merged - // with the newly produced interfaces. - layers[idx] = nullptr; - return l; - } + if (idx < int(layers.size()) && layers[idx]->print_z < print_z + EPSILON) + return layers[idx]; } return nullptr; }; @@ -284,11 +282,14 @@ std::pair generate_interfa // Compress contact_out, remove the nullptr items. // The parallel_for above may not have merged all the interface and base_interface layers // generated by the Organic supports code, do it here. - auto merge_remove_nulls = [](SupportGeneratorLayersPtr &in1, SupportGeneratorLayersPtr &in2) { - size_t nonzeros = std::count_if(in1.begin(), in1.end(), [](auto *l) { return l != nullptr; }) + - std::count_if(in2.begin(), in2.end(), [](auto *l) { return l != nullptr; }); - remove_nulls(in1); - remove_nulls(in2); + auto merge_remove_empty = [](SupportGeneratorLayersPtr &in1, SupportGeneratorLayersPtr &in2) { + auto remove_empty = [](SupportGeneratorLayersPtr &vec) { + vec.erase( + std::remove_if(vec.begin(), vec.end(), [](const SupportGeneratorLayer *ptr) { return ptr == nullptr || ptr->polygons.empty(); }), + vec.end()); + }; + remove_empty(in1); + remove_empty(in2); if (in2.empty()) return std::move(in1); else if (in1.empty()) @@ -299,8 +300,8 @@ std::pair generate_interfa return std::move(out); } }; - interface_layers = merge_remove_nulls(interface_layers, top_interface_layers); - base_interface_layers = merge_remove_nulls(base_interface_layers, top_base_interface_layers); + interface_layers = merge_remove_empty(interface_layers, top_interface_layers); + base_interface_layers = merge_remove_empty(base_interface_layers, top_base_interface_layers); BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::generate_interface_layers() in parallel - end"; } diff --git a/src/libslic3r/Support/TreeSupport.cpp b/src/libslic3r/Support/TreeSupport.cpp index dfeb3fdf9..e27661384 100644 --- a/src/libslic3r/Support/TreeSupport.cpp +++ b/src/libslic3r/Support/TreeSupport.cpp @@ -1119,7 +1119,7 @@ public: void add_roof_build_plate(Polygons &&overhang_areas, size_t dtt_roof) { std::lock_guard lock(m_mutex_layer_storage); - this->add_roof_unguarded(std::move(overhang_areas), 0, dtt_roof); + this->add_roof_unguarded(std::move(overhang_areas), 0, std::min(dtt_roof, this->support_parameters.num_top_interface_layers)); } // called by sample_overhang_area() @@ -1204,6 +1204,8 @@ public: private: void add_roof_unguarded(Polygons &&new_roofs, const size_t insert_layer_idx, const size_t dtt_roof) { + assert(support_parameters.has_top_contacts); + assert(dtt_roof <= support_parameters.num_top_interface_layers); SupportGeneratorLayersPtr &layers = dtt_roof == 0 ? this->top_contacts : dtt_roof <= support_parameters.num_top_interface_layers_only() ? this->top_interfaces : this->top_base_interfaces; @@ -4413,8 +4415,13 @@ static void draw_branches( bottom_extra_slices.back().polygons, volumes.getPlaceableAreas(0, layer_begin - LayerIndex(bottom_extra_slices.size()), [] {})); if (area(this_bottom_contacts) < support_area_min) bottom_extra_slices.pop_back(); - else if (config.settings.support_floor_layers > 0) - bottom_contacts.emplace_back(std::move(this_bottom_contacts)); + else { + // At least a fraction of the tree bottom is considered to be supported. + if (config.settings.support_floor_layers > 0) + // Turn this fraction of the tree bottom into a contact layer. + bottom_contacts.emplace_back(std::move(this_bottom_contacts)); + break; + } } if (config.settings.support_floor_layers > 0) for (int i = int(bottom_extra_slices.size()) - 2; i >= 0; -- i) From 9d495f2413c215c80de9eead736e78c1c9142453 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 9 May 2023 09:46:27 +0200 Subject: [PATCH 32/34] Organic supports: Refactoring for adding interfaces to tree tips. --- src/libslic3r/Support/TreeSupport.cpp | 549 ++++++++++++++------------ 1 file changed, 290 insertions(+), 259 deletions(-) diff --git a/src/libslic3r/Support/TreeSupport.cpp b/src/libslic3r/Support/TreeSupport.cpp index e27661384..0eb009c77 100644 --- a/src/libslic3r/Support/TreeSupport.cpp +++ b/src/libslic3r/Support/TreeSupport.cpp @@ -997,11 +997,245 @@ inline SupportGeneratorLayer& layer_allocate( return layer_initialize(layer, slicing_params, config, layer_idx); } +using SupportElements = std::deque; + +// Used by generate_initial_areas() in parallel by multiple layers. +class InterfacePlacer { +public: + InterfacePlacer( + const SlicingParameters &slicing_parameters, + const SupportParameters &support_parameters, + const TreeSupportSettings &config, + SupportGeneratorLayerStorage &layer_storage, + SupportGeneratorLayersPtr &top_contacts, + SupportGeneratorLayersPtr &top_interfaces, + SupportGeneratorLayersPtr &top_base_interfaces) + : + slicing_parameters(slicing_parameters), support_parameters(support_parameters), config(config), + layer_storage(layer_storage), top_contacts(top_contacts), top_interfaces(top_interfaces), top_base_interfaces(top_base_interfaces) + {} + InterfacePlacer(const InterfacePlacer& rhs) : + slicing_parameters(rhs.slicing_parameters), support_parameters(rhs.support_parameters), config(rhs.config), + layer_storage(rhs.layer_storage), top_contacts(rhs.top_contacts), top_interfaces(rhs.top_interfaces), top_base_interfaces(rhs.top_base_interfaces) + {} + + const SlicingParameters &slicing_parameters; + const SupportParameters &support_parameters; + const TreeSupportSettings &config; + SupportGeneratorLayersPtr& top_contacts_mutable() { return this->top_contacts; } + +public: + // Insert the contact layer and some of the inteface and base interface layers below. + void add_roofs(std::vector &&new_roofs, const size_t insert_layer_idx) + { + if (! new_roofs.empty()) { + std::lock_guard lock(m_mutex_layer_storage); + for (size_t idx = 0; idx < new_roofs.size(); ++ idx) + if (! new_roofs[idx].empty()) + add_roof_unguarded(std::move(new_roofs[idx]), insert_layer_idx - idx, idx); + } + } + + void add_roof(Polygons &&new_roof, const size_t insert_layer_idx, const size_t dtt_tip) + { + std::lock_guard lock(m_mutex_layer_storage); + add_roof_unguarded(std::move(new_roof), insert_layer_idx, dtt_tip); + } + + // called by sample_overhang_area() + void add_roof_build_plate(Polygons &&overhang_areas, size_t dtt_roof) + { + std::lock_guard lock(m_mutex_layer_storage); + this->add_roof_unguarded(std::move(overhang_areas), 0, std::min(dtt_roof, this->support_parameters.num_top_interface_layers)); + } + + void add_roof_unguarded(Polygons &&new_roofs, const size_t insert_layer_idx, const size_t dtt_roof) + { + assert(support_parameters.has_top_contacts); + assert(dtt_roof <= support_parameters.num_top_interface_layers); + SupportGeneratorLayersPtr &layers = + dtt_roof == 0 ? this->top_contacts : + dtt_roof <= support_parameters.num_top_interface_layers_only() ? this->top_interfaces : this->top_base_interfaces; + SupportGeneratorLayer*& l = layers[insert_layer_idx]; + if (l == nullptr) + l = &layer_allocate_unguarded(layer_storage, dtt_roof == 0 ? SupporLayerType::TopContact : SupporLayerType::TopInterface, + slicing_parameters, config, insert_layer_idx); + // will be unioned in finalize_interface_and_support_areas() + append(l->polygons, std::move(new_roofs)); + } + +private: + // Outputs + SupportGeneratorLayerStorage &layer_storage; + SupportGeneratorLayersPtr &top_contacts; + SupportGeneratorLayersPtr &top_interfaces; + SupportGeneratorLayersPtr &top_base_interfaces; + + // Mutexes, guards + std::mutex m_mutex_layer_storage; +}; + +class RichInterfacePlacer : public InterfacePlacer { +public: + RichInterfacePlacer( + const InterfacePlacer &interface_placer, + const TreeModelVolumes &volumes, + bool force_tip_to_roof, + size_t num_support_layers, + std::vector &move_bounds) + : + InterfacePlacer(interface_placer), + volumes(volumes), force_tip_to_roof(force_tip_to_roof), move_bounds(move_bounds) + { + m_already_inserted.assign(num_support_layers, {}); + this->min_xy_dist = this->config.xy_distance > this->config.xy_min_distance; + } + const TreeModelVolumes &volumes; + // Radius of the tree tip is large enough to be covered by an interface. + const bool force_tip_to_roof; + bool min_xy_dist; + +public: + // called by sample_overhang_area() + void add_points_along_lines( + // Insert points (tree tips or top contact interfaces) along these lines. + LineInformations lines, + // Start at this layer. + LayerIndex insert_layer_idx, + // Insert this number of interface layers. + size_t roof_tip_layers, + // True if an interface is already generated above these lines. + size_t supports_roof_layers, + // The element tries to not move until this dtt is reached. + size_t dont_move_until) + { + validate_range(lines); + // Add tip area as roof (happens when minimum roof area > minimum tip area) if possible + size_t dtt_roof_tip; + for (dtt_roof_tip = 0; dtt_roof_tip < roof_tip_layers && insert_layer_idx - dtt_roof_tip >= 1; ++ dtt_roof_tip) { + size_t this_layer_idx = insert_layer_idx - dtt_roof_tip; + auto evaluateRoofWillGenerate = [&](const std::pair &p) { + //FIXME Vojtech: The circle is just shifted, it has a known size, the infill should fit all the time! + #if 0 + Polygon roof_circle; + for (Point corner : base_circle) + roof_circle.points.emplace_back(p.first + corner * config.min_radius); + return !generate_support_infill_lines({ roof_circle }, config, true, insert_layer_idx - dtt_roof_tip, config.support_roof_line_distance).empty(); + #else + return true; + #endif + }; + + { + std::pair split = + // keep all lines that are still valid on the next layer + split_lines(lines, [this, this_layer_idx](const std::pair &p) + { return evaluate_point_for_next_layer_function(volumes, config, this_layer_idx, p); }); + LineInformations points = std::move(split.second); + // Not all roofs are guaranteed to actually generate lines, so filter these out and add them as points. + split = split_lines(split.first, evaluateRoofWillGenerate); + lines = std::move(split.first); + append(points, split.second); + // add all points that would not be valid + for (const LineInformation &line : points) + for (const std::pair &point_data : line) + add_point_as_influence_area(point_data, this_layer_idx, + // don't move until + roof_tip_layers - dtt_roof_tip, + // supports roof + dtt_roof_tip + supports_roof_layers > 0, + // disable ovalization + false); + } + + // add all tips as roof to the roof storage + Polygons new_roofs; + for (const LineInformation &line : lines) + //FIXME sweep the tip radius along the line? + for (const std::pair &p : line) { + Polygon roof_circle{ m_base_circle }; + roof_circle.scale(config.min_radius / m_base_radius); + roof_circle.translate(p.first); + new_roofs.emplace_back(std::move(roof_circle)); + } + this->add_roof(std::move(new_roofs), this_layer_idx, dtt_roof_tip + supports_roof_layers); + } + + for (const LineInformation &line : lines) { + // If a line consists of enough tips, the assumption is that it is not a single tip, but part of a simulated support pattern. + // Ovalisation should be disabled for these to improve the quality of the lines when tip_diameter=line_width + bool disable_ovalistation = config.min_radius < 3 * config.support_line_width && roof_tip_layers == 0 && dtt_roof_tip == 0 && line.size() > 5; + for (const std::pair &point_data : line) + add_point_as_influence_area(point_data, insert_layer_idx - dtt_roof_tip, + // don't move until + dont_move_until > dtt_roof_tip ? dont_move_until - dtt_roof_tip : 0, + // supports roof + dtt_roof_tip + supports_roof_layers > 0, + disable_ovalistation); + } + } + +private: + // called by this->add_points_along_lines() + void add_point_as_influence_area(std::pair p, LayerIndex insert_layer, size_t dont_move_until, bool roof, bool skip_ovalisation) + { + bool to_bp = p.second == LineStatus::TO_BP || p.second == LineStatus::TO_BP_SAFE; + bool gracious = to_bp || p.second == LineStatus::TO_MODEL_GRACIOUS || p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE; + bool safe_radius = p.second == LineStatus::TO_BP_SAFE || p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE; + if (! config.support_rests_on_model && ! to_bp) { + BOOST_LOG_TRIVIAL(warning) << "Tried to add an invalid support point"; + tree_supports_show_error("Unable to add tip. Some overhang may not be supported correctly."sv, true); + return; + } + Polygons circle{ m_base_circle }; + circle.front().translate(p.first); + { + Point hash_pos = p.first / ((config.min_radius + 1) / 10); + std::lock_guard critical_section_movebounds(m_mutex_movebounds); + if (!m_already_inserted[insert_layer].count(hash_pos)) { + // normalize the point a bit to also catch points which are so close that inserting it would achieve nothing + m_already_inserted[insert_layer].emplace(hash_pos); + static constexpr const size_t dtt = 0; + SupportElementState state; + state.target_height = insert_layer; + state.target_position = p.first; + state.next_position = p.first; + state.layer_idx = insert_layer; + state.effective_radius_height = dtt; + state.to_buildplate = to_bp; + state.distance_to_top = dtt; + state.result_on_layer = p.first; + assert(state.result_on_layer_is_set()); + state.increased_to_model_radius = 0; + state.to_model_gracious = gracious; + state.elephant_foot_increases = 0; + state.use_min_xy_dist = min_xy_dist; + state.supports_roof = roof; + state.dont_move_until = dont_move_until; + state.can_use_safe_radius = safe_radius; + state.missing_roof_layers = force_tip_to_roof ? dont_move_until : 0; + state.skip_ovalisation = skip_ovalisation; + move_bounds[insert_layer].emplace_back(state, std::move(circle)); + } + } + } + + // Outputs + std::vector &move_bounds; + + // Temps + static constexpr const auto m_base_radius = scaled(0.01); + const Polygon m_base_circle { make_circle(m_base_radius, SUPPORT_TREE_CIRCLE_RESOLUTION) }; + + // Mutexes, guards + std::mutex m_mutex_movebounds; + std::vector> m_already_inserted; +}; + int generate_raft_contact( const PrintObject &print_object, const TreeSupportSettings &config, - SupportGeneratorLayersPtr &top_contacts, - SupportGeneratorLayerStorage &layer_storage) + InterfacePlacer &interface_placer) { int raft_contact_layer_idx = -1; if (print_object.has_raft() && print_object.layer_count() > 0) { @@ -1012,17 +1246,13 @@ int generate_raft_contact( while (raft_contact_layer_idx > 0 && config.raft_layers[raft_contact_layer_idx] > print_object.slicing_parameters().raft_contact_top_z + EPSILON) -- raft_contact_layer_idx; // Create the raft contact layer. - SupportGeneratorLayer &raft_contact_layer = layer_allocate_unguarded(layer_storage, SupporLayerType::TopContact, print_object.slicing_parameters(), config, raft_contact_layer_idx); - top_contacts[raft_contact_layer_idx] = &raft_contact_layer; const ExPolygons &lslices = print_object.get_layer(0)->lslices; double expansion = print_object.config().raft_expansion.value; - raft_contact_layer.polygons = expansion > 0 ? expand(lslices, scaled(expansion)) : to_polygons(lslices); + interface_placer.add_roof_unguarded(expansion > 0 ? expand(lslices, scaled(expansion)) : to_polygons(lslices), raft_contact_layer_idx, 0); } return raft_contact_layer_idx; } -using SupportElements = std::deque; - void finalize_raft_contact( const PrintObject &print_object, const int raft_contact_layer_idx, @@ -1076,215 +1306,6 @@ void finalize_raft_contact( } } -// Used by generate_initial_areas() in parallel by multiple layers. -class InterfacePlacer { -public: - InterfacePlacer( - const SlicingParameters &slicing_parameters, - const SupportParameters &support_parameters, - const TreeModelVolumes &volumes, const TreeSupportSettings &config, - bool force_tip_to_roof, size_t num_support_layers, - std::vector &move_bounds, - SupportGeneratorLayerStorage &layer_storage, - SupportGeneratorLayersPtr &top_contacts, SupportGeneratorLayersPtr &top_interfaces, SupportGeneratorLayersPtr &top_base_interfaces) - : - slicing_parameters(slicing_parameters), support_parameters(support_parameters), volumes(volumes), config(config), force_tip_to_roof(force_tip_to_roof), - move_bounds(move_bounds), - layer_storage(layer_storage), top_contacts(top_contacts), top_interfaces(top_interfaces), top_base_interfaces(top_base_interfaces) { - m_already_inserted.assign(num_support_layers, {}); - this->min_xy_dist = config.xy_distance > config.xy_min_distance; - } - const SlicingParameters &slicing_parameters; - const SupportParameters &support_parameters; - const TreeModelVolumes &volumes; - const TreeSupportSettings &config; - // Radius of the tree tip is large enough to be covered by an interface. - const bool force_tip_to_roof; - bool min_xy_dist; - -public: - // called by sample_overhang_area() - // Insert the contact layer and some of the inteface and base interface layers below. - void add_roofs(std::vector &&new_roofs, const size_t insert_layer_idx) - { - if (! new_roofs.empty()) { - std::lock_guard lock(m_mutex_layer_storage); - for (size_t idx = 0; idx < new_roofs.size(); ++ idx) - if (! new_roofs[idx].empty()) - add_roof_unguarded(std::move(new_roofs[idx]), insert_layer_idx - idx, idx); - } - } - - // called by sample_overhang_area() - void add_roof_build_plate(Polygons &&overhang_areas, size_t dtt_roof) - { - std::lock_guard lock(m_mutex_layer_storage); - this->add_roof_unguarded(std::move(overhang_areas), 0, std::min(dtt_roof, this->support_parameters.num_top_interface_layers)); - } - - // called by sample_overhang_area() - void add_points_along_lines( - // Insert points (tree tips or top contact interfaces) along these lines. - LineInformations lines, - // Start at this layer. - LayerIndex insert_layer_idx, - // Insert this number of interface layers. - size_t roof_tip_layers, - // True if an interface is already generated above these lines. - size_t supports_roof_layers, - // The element tries to not move until this dtt is reached. - size_t dont_move_until) - { - validate_range(lines); - // Add tip area as roof (happens when minimum roof area > minimum tip area) if possible - size_t dtt_roof_tip; - for (dtt_roof_tip = 0; dtt_roof_tip < roof_tip_layers && insert_layer_idx - dtt_roof_tip >= 1; ++ dtt_roof_tip) { - size_t this_layer_idx = insert_layer_idx - dtt_roof_tip; - auto evaluateRoofWillGenerate = [&](const std::pair &p) { - //FIXME Vojtech: The circle is just shifted, it has a known size, the infill should fit all the time! - #if 0 - Polygon roof_circle; - for (Point corner : base_circle) - roof_circle.points.emplace_back(p.first + corner * config.min_radius); - return !generate_support_infill_lines({ roof_circle }, config, true, insert_layer_idx - dtt_roof_tip, config.support_roof_line_distance).empty(); - #else - return true; - #endif - }; - - { - std::pair split = - // keep all lines that are still valid on the next layer - split_lines(lines, [this, this_layer_idx](const std::pair &p) - { return evaluate_point_for_next_layer_function(volumes, config, this_layer_idx, p); }); - LineInformations points = std::move(split.second); - // Not all roofs are guaranteed to actually generate lines, so filter these out and add them as points. - split = split_lines(split.first, evaluateRoofWillGenerate); - lines = std::move(split.first); - append(points, split.second); - // add all points that would not be valid - for (const LineInformation &line : points) - for (const std::pair &point_data : line) - add_point_as_influence_area(point_data, this_layer_idx, - // don't move until - roof_tip_layers - dtt_roof_tip, - // supports roof - dtt_roof_tip > 0, - // disable ovalization - false); - } - - // add all tips as roof to the roof storage - Polygons new_roofs; - for (const LineInformation &line : lines) - //FIXME sweep the tip radius along the line? - for (const std::pair &p : line) { - Polygon roof_circle{ m_base_circle }; - roof_circle.scale(config.min_radius / m_base_radius); - roof_circle.translate(p.first); - new_roofs.emplace_back(std::move(roof_circle)); - } - this->add_roof(std::move(new_roofs), this_layer_idx, dtt_roof_tip + supports_roof_layers); - } - - for (const LineInformation &line : lines) { - // If a line consists of enough tips, the assumption is that it is not a single tip, but part of a simulated support pattern. - // Ovalisation should be disabled for these to improve the quality of the lines when tip_diameter=line_width - bool disable_ovalistation = config.min_radius < 3 * config.support_line_width && roof_tip_layers == 0 && dtt_roof_tip == 0 && line.size() > 5; - for (const std::pair &point_data : line) - add_point_as_influence_area(point_data, insert_layer_idx - dtt_roof_tip, - // don't move until - dont_move_until > dtt_roof_tip ? dont_move_until - dtt_roof_tip : 0, - // supports roof - dtt_roof_tip + supports_roof_layers > 0, - disable_ovalistation); - } - } - -private: - void add_roof_unguarded(Polygons &&new_roofs, const size_t insert_layer_idx, const size_t dtt_roof) - { - assert(support_parameters.has_top_contacts); - assert(dtt_roof <= support_parameters.num_top_interface_layers); - SupportGeneratorLayersPtr &layers = - dtt_roof == 0 ? this->top_contacts : - dtt_roof <= support_parameters.num_top_interface_layers_only() ? this->top_interfaces : this->top_base_interfaces; - SupportGeneratorLayer*& l = layers[insert_layer_idx]; - if (l == nullptr) - l = &layer_allocate_unguarded(layer_storage, dtt_roof == 0 ? SupporLayerType::TopContact : SupporLayerType::TopInterface, - slicing_parameters, config, insert_layer_idx); - // will be unioned in finalize_interface_and_support_areas() - append(l->polygons, std::move(new_roofs)); - } - - // called by this->add_points_along_lines() - void add_roof(Polygons &&new_roof, const size_t insert_layer_idx, const size_t dtt_tip) - { - std::lock_guard lock(m_mutex_layer_storage); - add_roof_unguarded(std::move(new_roof), insert_layer_idx, dtt_tip); - } - - // called by this->add_points_along_lines() - void add_point_as_influence_area(std::pair p, LayerIndex insert_layer, size_t dont_move_until, bool roof, bool skip_ovalisation) - { - bool to_bp = p.second == LineStatus::TO_BP || p.second == LineStatus::TO_BP_SAFE; - bool gracious = to_bp || p.second == LineStatus::TO_MODEL_GRACIOUS || p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE; - bool safe_radius = p.second == LineStatus::TO_BP_SAFE || p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE; - if (! config.support_rests_on_model && ! to_bp) { - BOOST_LOG_TRIVIAL(warning) << "Tried to add an invalid support point"; - tree_supports_show_error("Unable to add tip. Some overhang may not be supported correctly."sv, true); - return; - } - Polygons circle{ m_base_circle }; - circle.front().translate(p.first); - { - Point hash_pos = p.first / ((config.min_radius + 1) / 10); - std::lock_guard critical_section_movebounds(m_mutex_movebounds); - if (!m_already_inserted[insert_layer].count(hash_pos)) { - // normalize the point a bit to also catch points which are so close that inserting it would achieve nothing - m_already_inserted[insert_layer].emplace(hash_pos); - static constexpr const size_t dtt = 0; - SupportElementState state; - state.target_height = insert_layer; - state.target_position = p.first; - state.next_position = p.first; - state.layer_idx = insert_layer; - state.effective_radius_height = dtt; - state.to_buildplate = to_bp; - state.distance_to_top = dtt; - state.result_on_layer = p.first; - assert(state.result_on_layer_is_set()); - state.increased_to_model_radius = 0; - state.to_model_gracious = gracious; - state.elephant_foot_increases = 0; - state.use_min_xy_dist = min_xy_dist; - state.supports_roof = roof; - state.dont_move_until = dont_move_until; - state.can_use_safe_radius = safe_radius; - state.missing_roof_layers = force_tip_to_roof ? dont_move_until : 0; - state.skip_ovalisation = skip_ovalisation; - move_bounds[insert_layer].emplace_back(state, std::move(circle)); - } - } - } - - // Outputs - std::vector &move_bounds; - SupportGeneratorLayerStorage &layer_storage; - SupportGeneratorLayersPtr &top_contacts; - SupportGeneratorLayersPtr &top_interfaces; - SupportGeneratorLayersPtr &top_base_interfaces; - - // Temps - static constexpr const auto m_base_radius = scaled(0.01); - const Polygon m_base_circle { make_circle(m_base_radius, SUPPORT_TREE_CIRCLE_RESOLUTION) }; - - // Mutexes, guards - std::mutex m_mutex_movebounds; - std::mutex m_mutex_layer_storage; - std::vector> m_already_inserted; -}; - // Called by generate_initial_areas(), used in parallel by multiple layers. // Produce // 1) Maximum num_support_roof_layers roof (top interface & contact) layers. @@ -1309,7 +1330,7 @@ void sample_overhang_area( // Configuration classes const TreeSupportMeshGroupSettings &mesh_group_settings, // Configuration & Output - InterfacePlacer &interface_placer) + RichInterfacePlacer &interface_placer) { // Assumption is that roof will support roof further up to avoid a lot of unnecessary branches. Each layer down it is checked whether the roof area // is still large enough to be a roof and aborted as soon as it is not. This part was already reworked a few times, and there could be an argument @@ -1447,17 +1468,11 @@ static void generate_initial_areas( const TreeSupportSettings &config, const std::vector &overhangs, std::vector &move_bounds, - SupportGeneratorLayersPtr &top_contacts, - SupportGeneratorLayersPtr &top_interfaces, - SupportGeneratorLayersPtr &top_base_interfaces, - SupportGeneratorLayerStorage &layer_storage, + InterfacePlacer &interface_placer, std::function throw_on_cancel) { using AvoidanceType = TreeModelVolumes::AvoidanceType; TreeSupportMeshGroupSettings mesh_group_settings(print_object); - SupportParameters support_params(print_object); - support_params.with_sheath = true; - support_params.support_density = 0; // To ensure z_distance_top_layers are left empty between the overhang (zeroth empty layer), the support has to be added z_distance_top_layers+1 layers below const size_t z_distance_delta = config.z_distance_top_layers + 1; @@ -1487,7 +1502,7 @@ static void generate_initial_areas( ; const size_t num_support_roof_layers = mesh_group_settings.support_roof_layers; const bool roof_enabled = num_support_roof_layers > 0; - const bool force_tip_to_roof = roof_enabled && sqr(config.min_radius) * M_PI > mesh_group_settings.minimum_roof_area; + const bool force_tip_to_roof = roof_enabled && (interface_placer.support_parameters.soluble_interface || sqr(config.min_radius) * M_PI > mesh_group_settings.minimum_roof_area); // cap for how much layer below the overhang a new support point may be added, as other than with regular support every new inserted point // may cause extra material and time cost. Could also be an user setting or differently calculated. Idea is that if an overhang // does not turn valid in double the amount of layers a slope of support angle would take to travel xy_distance, nothing reasonable will come from it. @@ -1513,7 +1528,7 @@ static void generate_initial_areas( const size_t num_raft_layers = config.raft_layers.size(); const size_t first_support_layer = std::max(int(num_raft_layers) - int(z_distance_delta), 1); num_support_layers = size_t(std::max(0, int(print_object.layer_count()) + int(num_raft_layers) - int(z_distance_delta))); - raft_contact_layer_idx = generate_raft_contact(print_object, config, top_contacts, layer_storage); + raft_contact_layer_idx = generate_raft_contact(print_object, config, interface_placer); // Enumerate layers for which the support tips may be generated from overhangs above. raw_overhangs.reserve(num_support_layers - first_support_layer); for (size_t layer_idx = first_support_layer; layer_idx < num_support_layers; ++ layer_idx) @@ -1521,14 +1536,12 @@ static void generate_initial_areas( raw_overhangs.push_back({ layer_idx, &overhangs[overhang_idx] }); } - InterfacePlacer interface_placer{ print_object.slicing_parameters(), support_params, volumes, config, force_tip_to_roof, num_support_layers, - // Outputs - move_bounds, layer_storage, top_contacts, top_interfaces, top_base_interfaces }; + RichInterfacePlacer rich_interface_placer{ interface_placer, volumes, force_tip_to_roof, num_support_layers, move_bounds }; tbb::parallel_for(tbb::blocked_range(0, raw_overhangs.size()), - [&volumes, &config, &raw_overhangs, &mesh_group_settings, &support_params, + [&volumes, &config, &raw_overhangs, &mesh_group_settings, min_xy_dist, force_tip_to_roof, roof_enabled, num_support_roof_layers, extra_outset, circle_length_to_half_linewidth_change, connect_length, max_overhang_insert_lag, - &interface_placer, &throw_on_cancel](const tbb::blocked_range &range) { + &rich_interface_placer, &throw_on_cancel](const tbb::blocked_range &range) { for (size_t raw_overhang_idx = range.begin(); raw_overhang_idx < range.end(); ++ raw_overhang_idx) { size_t layer_idx = raw_overhangs[raw_overhang_idx].first; const Polygons &overhang_raw = *raw_overhangs[raw_overhang_idx].second; @@ -1620,7 +1633,7 @@ static void generate_initial_areas( LineInformations fresh_valid_points = convert_lines_to_internal(volumes, config, convert_internal_to_lines(split.second), layer_idx - lag_ctr); validate_range(fresh_valid_points); - interface_placer.add_points_along_lines(fresh_valid_points, (force_tip_to_roof && lag_ctr <= num_support_roof_layers) ? num_support_roof_layers : 0, layer_idx - lag_ctr, false, roof_enabled ? num_support_roof_layers : 0); + rich_interface_placer.add_points_along_lines(fresh_valid_points, (force_tip_to_roof && lag_ctr <= num_support_roof_layers) ? num_support_roof_layers : 0, layer_idx - lag_ctr, false, roof_enabled ? num_support_roof_layers : 0); } } #endif @@ -1638,7 +1651,7 @@ static void generate_initial_areas( //check_self_intersections(overhang_regular, "overhang_regular3"); for (ExPolygon &roof_part : union_ex(overhang_roofs)) { sample_overhang_area(to_polygons(std::move(roof_part)), true, layer_idx, num_support_roof_layers, connect_length, - mesh_group_settings, interface_placer); + mesh_group_settings, rich_interface_placer); throw_on_cancel(); } } @@ -1649,13 +1662,13 @@ static void generate_initial_areas( for (ExPolygon &support_part : union_ex(overhang_regular)) { sample_overhang_area(to_polygons(std::move(support_part)), false, layer_idx, num_support_roof_layers, connect_length, - mesh_group_settings, interface_placer); + mesh_group_settings, rich_interface_placer); throw_on_cancel(); } } }); - finalize_raft_contact(print_object, raft_contact_layer_idx, top_contacts, move_bounds); + finalize_raft_contact(print_object, raft_contact_layer_idx, interface_placer.top_contacts_mutable(), move_bounds); } static unsigned int move_inside(const Polygons &polygons, Point &from, int distance = 0, int64_t maxDist2 = std::numeric_limits::max()) @@ -4172,6 +4185,7 @@ static void draw_branches( // I/O: SupportGeneratorLayersPtr &bottom_contacts, SupportGeneratorLayersPtr &top_contacts, + InterfacePlacer &interface_placer, // Output: SupportGeneratorLayersPtr &intermediate_layers, @@ -4346,7 +4360,7 @@ static void draw_branches( mesh_slicing_params.mode = MeshSlicingParams::SlicingMode::Positive; tbb::parallel_for(tbb::blocked_range(0, trees.size(), 1), - [&trees, &volumes, &config, &slicing_params, &move_bounds, &mesh_slicing_params, &throw_on_cancel](const tbb::blocked_range &range) { + [&trees, &volumes, &config, &slicing_params, &move_bounds, &interface_placer, &mesh_slicing_params, &throw_on_cancel](const tbb::blocked_range &range) { indexed_triangle_set partial_mesh; std::vector slice_z; std::vector bottom_contacts; @@ -4434,10 +4448,20 @@ static void draw_branches( *it_dst ++ = std::move(it_src->polygons); } } - if (branch.has_tip) { + +#if 0 + //FIXME branch.has_tip seems to not be reliable. + if (branch.has_tip && interface_placer.support_parameters.has_top_contacts) // Add top slices to top contacts / interfaces / base interfaces. - //slices; - } + for (int i = int(branch.path.size()) - 1; i >= 0; -- i) { + const SupportElement &el = *branch.path[i]; + if (el.state.missing_roof_layers == 0) + break; + //FIXME Move or not? + interface_placer.add_roof(std::move(slices[int(slices.size()) - i - 1]), el.state.layer_idx, + interface_placer.support_parameters.num_top_interface_layers + 1 - el.state.missing_roof_layers); + } +#endif } layer_begin += LayerIndex(num_empty); @@ -4619,34 +4643,44 @@ static void generate_support_areas(Print &print, const BuildVolume &build_volume std::vector overhangs = generate_overhangs(config, *print.get_object(processing.second.front()), throw_on_cancel); // ### Precalculate avoidances, collision etc. - + size_t num_support_layers = precalculate(print, overhangs, processing.first, processing.second, volumes, throw_on_cancel); + bool has_support = num_support_layers > 0; + num_support_layers = std::max(num_support_layers, config.raft_layers.size()); + + SupportParameters support_params(print_object); + support_params.with_sheath = true; + support_params.support_density = 0; + SupportGeneratorLayerStorage layer_storage; SupportGeneratorLayersPtr top_contacts; SupportGeneratorLayersPtr bottom_contacts; SupportGeneratorLayersPtr interface_layers; SupportGeneratorLayersPtr base_interface_layers; - SupportGeneratorLayersPtr intermediate_layers; + SupportGeneratorLayersPtr intermediate_layers(num_support_layers, nullptr); + if (support_params.has_top_contacts) + top_contacts.assign(num_support_layers, nullptr); + if (support_params.has_bottom_contacts) + bottom_contacts.assign(num_support_layers, nullptr); + if (support_params.has_interfaces()) + interface_layers.assign(num_support_layers, nullptr); + if (support_params.has_base_interfaces()) + base_interface_layers.assign(num_support_layers, nullptr); - SupportParameters support_params(print_object); - - if (size_t num_support_layers = precalculate(print, overhangs, processing.first, processing.second, volumes, throw_on_cancel); - num_support_layers > 0) { + InterfacePlacer interface_placer{ + print_object.slicing_parameters(), support_params, config, + // Outputs + layer_storage, top_contacts, interface_layers, base_interface_layers }; + if (has_support) { auto t_precalc = std::chrono::high_resolution_clock::now(); // value is the area where support may be placed. As this is calculated in CreateLayerPathing it is saved and reused in draw_areas std::vector move_bounds(num_support_layers); // ### Place tips of the support tree - bottom_contacts .assign(num_support_layers, nullptr); - top_contacts .assign(num_support_layers, nullptr); - interface_layers .assign(num_support_layers, nullptr); - base_interface_layers.assign(num_support_layers, nullptr); - intermediate_layers .assign(num_support_layers, nullptr); - for (size_t mesh_idx : processing.second) generate_initial_areas(*print.get_object(mesh_idx), volumes, config, overhangs, - move_bounds, top_contacts, interface_layers, base_interface_layers, layer_storage, throw_on_cancel); + move_bounds, interface_placer, throw_on_cancel); auto t_gen = std::chrono::high_resolution_clock::now(); #ifdef TREESUPPORT_DEBUG_SVG @@ -4679,7 +4713,7 @@ static void generate_support_areas(Print &print, const BuildVolume &build_volume assert(print_object.config().support_material_style == smsOrganic); draw_branches( *print.get_object(processing.second.front()), volumes, config, move_bounds, - bottom_contacts, top_contacts, intermediate_layers, layer_storage, + bottom_contacts, top_contacts, interface_placer, intermediate_layers, layer_storage, throw_on_cancel); } @@ -4713,12 +4747,9 @@ static void generate_support_areas(Print &print, const BuildVolume &build_volume // BOOST_LOG_TRIVIAL(error) << "Why ask questions when you already know the answer twice.\n (This is not a real bug, please dont report it.)"; move_bounds.clear(); - } else { - top_contacts.assign(config.raft_layers.size(), nullptr); - if (generate_raft_contact(print_object, config, top_contacts, layer_storage) < 0) - // No raft. - continue; - } + } else if (generate_raft_contact(print_object, config, interface_placer) < 0) + // No raft. + continue; // Produce the support G-code. // Used by both classic and tree supports. From 84db6356b3c7a27a5c1bd2762fa93a17ed9bbe00 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 9 May 2023 10:46:56 +0200 Subject: [PATCH 33/34] Organic Supports: support_tree_branch_diameter_double_wall to control when the 2nd wall kicks in. --- src/libslic3r/Preset.cpp | 3 +- src/libslic3r/PrintConfig.cpp | 12 +++++++ src/libslic3r/PrintConfig.hpp | 1 + src/libslic3r/PrintObject.cpp | 1 + src/libslic3r/Support/SupportCommon.cpp | 35 +++++++++++---------- src/libslic3r/Support/SupportParameters.cpp | 2 ++ src/libslic3r/Support/SupportParameters.hpp | 2 ++ src/slic3r/GUI/ConfigManipulation.cpp | 3 +- src/slic3r/GUI/Tab.cpp | 1 + 9 files changed, 41 insertions(+), 19 deletions(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 23f1438c4..d20514bba 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -444,7 +444,8 @@ static std::vector s_Preset_print_options { "support_material_interface_pattern", "support_material_interface_spacing", "support_material_interface_contact_loops", "support_material_contact_distance", "support_material_bottom_contact_distance", "support_material_buildplate_only", - "support_tree_angle", "support_tree_angle_slow", "support_tree_branch_diameter", "support_tree_branch_diameter_angle", "support_tree_top_rate", "support_tree_branch_distance", "support_tree_tip_diameter", + "support_tree_angle", "support_tree_angle_slow", "support_tree_branch_diameter", "support_tree_branch_diameter_angle", "support_tree_branch_diameter_double_wall", + "support_tree_top_rate", "support_tree_branch_distance", "support_tree_tip_diameter", "dont_support_bridges", "thick_bridges", "notes", "complete_objects", "extruder_clearance_radius", "extruder_clearance_height", "gcode_comments", "gcode_label_objects", "output_filename_format", "post_process", "gcode_substitutions", "perimeter_extruder", "infill_extruder", "solid_infill_extruder", "support_material_extruder", "support_material_interface_extruder", diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index d48f18aa5..77120bfdf 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2939,6 +2939,18 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(5)); + def = this->add("support_tree_branch_diameter_double_wall", coFloat); + def->label = L("Branch Diameter with double walls"); + def->category = L("Support material"); + // TRN PrintSettings: "Organic supports" > "Branch Diameter" + def->tooltip = L("Branches with area larger than the area of a circle of this diameter will be printed with double walls for stability. " + "Set this value to zero for no double walls."); + def->sidetext = L("mm"); + def->min = 0; + def->max = 100.f; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(3)); + // Tree Support Branch Distance // How far apart the branches need to be when they touch the model. Making this distance small will cause // the tree support to touch the model at more points, causing better overhang but making support harder to remove. diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index b9ca95a15..2a4b3258d 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -554,6 +554,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloat, support_tree_angle_slow)) ((ConfigOptionFloat, support_tree_branch_diameter)) ((ConfigOptionFloat, support_tree_branch_diameter_angle)) + ((ConfigOptionFloat, support_tree_branch_diameter_double_wall)) ((ConfigOptionPercent, support_tree_top_rate)) ((ConfigOptionFloat, support_tree_branch_distance)) ((ConfigOptionFloat, support_tree_tip_diameter)) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index b43afd6be..1c37339a2 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -712,6 +712,7 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "support_tree_angle_slow" || opt_key == "support_tree_branch_diameter" || opt_key == "support_tree_branch_diameter_angle" + || opt_key == "support_tree_branch_diameter_double_wall" || opt_key == "support_tree_top_rate" || opt_key == "support_tree_branch_distance" || opt_key == "support_tree_tip_diameter" diff --git a/src/libslic3r/Support/SupportCommon.cpp b/src/libslic3r/Support/SupportCommon.cpp index c01e49bff..2035f9ea3 100644 --- a/src/libslic3r/Support/SupportCommon.cpp +++ b/src/libslic3r/Support/SupportCommon.cpp @@ -537,7 +537,8 @@ static Polylines draw_perimeters(const ExPolygon &expoly, double clip_length) static inline void tree_supports_generate_paths( ExtrusionEntitiesPtr &dst, const Polygons &polygons, - const Flow &flow) + const Flow &flow, + const SupportParameters &support_params) { // Offset expolygon inside, returns number of expolygons collected (0 or 1). // Vertices of output paths are marked with Z = source contour index of the expoly. @@ -634,21 +635,21 @@ static inline void tree_supports_generate_paths( ClipperLib_Z::Paths anchor_candidates; for (ExPolygon& expoly : closing_ex(polygons, float(SCALED_EPSILON), float(SCALED_EPSILON + 0.5 * flow.scaled_width()))) { std::unique_ptr eec; - double area = expoly.area(); - if (area > sqr(scaled(5.))) { - eec = std::make_unique(); - // Don't reoder internal / external loops of the same island, always start with the internal loop. - eec->no_sort = true; - // Make the tree branch stable by adding another perimeter. - ExPolygons level2 = offset2_ex({ expoly }, -1.5 * flow.scaled_width(), 0.5 * flow.scaled_width()); - if (level2.size() == 1) { - Polylines polylines; - extrusion_entities_append_paths(eec->entities, draw_perimeters(expoly, clip_length), ExtrusionRole::SupportMaterial, flow.mm3_per_mm(), flow.width(), flow.height(), - // Disable reversal of the path, always start with the anchor, always print CCW. - false); - expoly = level2.front(); + if (support_params.tree_branch_diameter_double_wall_area_scaled > 0) + if (double area = expoly.area(); area > support_params.tree_branch_diameter_double_wall_area_scaled) { + eec = std::make_unique(); + // Don't reoder internal / external loops of the same island, always start with the internal loop. + eec->no_sort = true; + // Make the tree branch stable by adding another perimeter. + ExPolygons level2 = offset2_ex({ expoly }, -1.5 * flow.scaled_width(), 0.5 * flow.scaled_width()); + if (level2.size() == 1) { + Polylines polylines; + extrusion_entities_append_paths(eec->entities, draw_perimeters(expoly, clip_length), ExtrusionRole::SupportMaterial, flow.mm3_per_mm(), flow.width(), flow.height(), + // Disable reversal of the path, always start with the anchor, always print CCW. + false); + expoly = level2.front(); + } } - } // Try to produce one more perimeter to place the seam anchor. // First genrate a 2nd perimeter loop as a source for anchor candidates. @@ -1531,7 +1532,7 @@ void generate_support_toolpaths( support_params.with_sheath, false); } if (! tree_polygons.empty()) - tree_supports_generate_paths(support_layer.support_fills.entities, tree_polygons, flow); + tree_supports_generate_paths(support_layer.support_fills.entities, tree_polygons, flow, support_params); } Fill *filler = filler_interface.get(); @@ -1790,7 +1791,7 @@ void generate_support_toolpaths( sheath = true; no_sort = true; } else if (config.support_material_style == SupportMaterialStyle::smsOrganic) { - tree_supports_generate_paths(base_layer.extrusions, base_layer.polygons_to_extrude(), flow); + tree_supports_generate_paths(base_layer.extrusions, base_layer.polygons_to_extrude(), flow, support_params); done = true; } if (! done) diff --git a/src/libslic3r/Support/SupportParameters.cpp b/src/libslic3r/Support/SupportParameters.cpp index 531e8dcaf..09eca9610 100644 --- a/src/libslic3r/Support/SupportParameters.cpp +++ b/src/libslic3r/Support/SupportParameters.cpp @@ -137,6 +137,8 @@ SupportParameters::SupportParameters(const PrintObject &object) assert(slicing_params.interface_raft_layers == 0); assert(slicing_params.raft_layers() == 0); } + + this->tree_branch_diameter_double_wall_area_scaled = 0.25 * sqr(scaled(object_config.support_tree_branch_diameter_double_wall.value)) * M_PI; } } // namespace Slic3r diff --git a/src/libslic3r/Support/SupportParameters.hpp b/src/libslic3r/Support/SupportParameters.hpp index be38e9650..8a63d9f3f 100644 --- a/src/libslic3r/Support/SupportParameters.hpp +++ b/src/libslic3r/Support/SupportParameters.hpp @@ -77,6 +77,8 @@ struct SupportParameters { InfillPattern contact_fill_pattern; // Shall the sparse (base) layers be printed with a single perimeter line (sheath) for robustness? bool with_sheath; + // Branches of organic supports with area larger than this threshold will be extruded with double lines. + double tree_branch_diameter_double_wall_area_scaled; float raft_angle_1st_layer; float raft_angle_base; diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 4c5b0fd8e..f645e8a0d 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -291,7 +291,8 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config) (config->opt_bool("support_material") || config->opt_int("support_material_enforce_layers") > 0); for (const std::string& key : { "support_tree_angle", "support_tree_angle_slow", "support_tree_branch_diameter", - "support_tree_branch_diameter_angle", "support_tree_tip_diameter", "support_tree_branch_distance", "support_tree_top_rate" }) + "support_tree_branch_diameter_angle", "support_tree_branch_diameter_double_wall", + "support_tree_tip_diameter", "support_tree_branch_distance", "support_tree_top_rate" }) toggle_field(key, has_organic_supports); for (auto el : { "support_material_bottom_interface_layers", "support_material_interface_spacing", "support_material_interface_extruder", diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 99843e541..2cf4969cb 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1536,6 +1536,7 @@ void TabPrint::build() optgroup->append_single_option_line("support_tree_angle_slow", category_path + "tree_angle_slow"); optgroup->append_single_option_line("support_tree_branch_diameter", category_path + "tree_branch_diameter"); optgroup->append_single_option_line("support_tree_branch_diameter_angle", category_path + "tree_branch_diameter_angle"); + optgroup->append_single_option_line("support_tree_branch_diameter_double_wall", category_path + "tree_branch_diameter_double_wall"); optgroup->append_single_option_line("support_tree_tip_diameter", category_path + "tree_tip_diameter"); optgroup->append_single_option_line("support_tree_branch_distance", category_path + "tree_branch_distance"); optgroup->append_single_option_line("support_tree_top_rate", category_path + "tree_top_rate"); From c9f449bcb2df5a9cce6dd7c97b78fc585e6bc757 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 9 May 2023 11:14:36 +0200 Subject: [PATCH 34/34] PlaceholderParser: fixed reporting of x value outside of the interpolated table. --- src/libslic3r/PlaceholderParser.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/PlaceholderParser.cpp b/src/libslic3r/PlaceholderParser.cpp index 1dbf4120e..348825106 100644 --- a/src/libslic3r/PlaceholderParser.cpp +++ b/src/libslic3r/PlaceholderParser.cpp @@ -1644,9 +1644,9 @@ namespace client } if (! evaluated) { // Clamp x into the table range with EPSILON. - if (x > table.table.front().x - EPSILON) + if (double x0 = table.table.front().x; x > x0 - EPSILON && x < x0) out.set_d(table.table.front().y); - else if (x < table.table.back().x + EPSILON) + else if (double x1 = table.table.back().x; x > x1 && x < x1 + EPSILON) out.set_d(table.table.back().y); else // The value is really outside the table range.