diff --git a/CMakeLists.txt b/CMakeLists.txt index 66b9a777b0..49fe3437f7 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) diff --git a/src/clipper/clipper.cpp b/src/clipper/clipper.cpp index c775a3226c..3691877eea 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) @@ -131,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; } @@ -693,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; } @@ -915,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; } @@ -968,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; @@ -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.emplace_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.emplace_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); @@ -2600,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; @@ -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)) @@ -3373,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; @@ -3386,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; @@ -3514,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; } @@ -3556,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; @@ -3569,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; @@ -3577,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]; @@ -3612,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 { @@ -3625,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 { @@ -3652,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 { @@ -3665,7 +3641,7 @@ void ClipperOffset::DoOffset(double delta) else DoRound(0, 1); } - m_destPolys.push_back(m_destPoly); + m_destPolys.emplace_back(m_destPoly); } } } @@ -3681,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; } @@ -3692,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 @@ -3719,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)))); } @@ -3731,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))); } //------------------------------------------------------------------------------ @@ -3745,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))); } @@ -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); @@ -4020,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) @@ -4029,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(); @@ -4040,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); } } //------------------------------------------------------------------------------ @@ -4105,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); } @@ -4117,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); } @@ -4155,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); } //------------------------------------------------------------------------------ diff --git a/src/clipper/clipper.hpp b/src/clipper/clipper.hpp index d190d09b5d..c88545454e 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 fce69d5e4c..6c5dafdac4 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/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 53074d3bd8..d44bfcde09 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -276,10 +276,21 @@ 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/SupportMaterial.cpp + Support/SupportMaterial.hpp + Support/SupportParameters.cpp + Support/SupportParameters.hpp + Support/TreeSupport.cpp + Support/TreeSupport.hpp + Support/TreeModelVolumes.cpp + Support/TreeModelVolumes.hpp SupportSpotsGenerator.cpp SupportSpotsGenerator.hpp - SupportMaterial.cpp - SupportMaterial.hpp Surface.cpp Surface.hpp SurfaceCollection.cpp @@ -291,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/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index 95a5337185..85ef53c888 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) @@ -964,21 +966,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 +986,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 aaa06107da..774e9cb42f 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); @@ -596,8 +599,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); diff --git a/src/libslic3r/ExPolygon.cpp b/src/libslic3r/ExPolygon.cpp index 42f026e0b2..19489bddbc 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/Flow.cpp b/src/libslic3r/Flow.cpp index 1084e6f102..bc3a2d7775 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 diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index 5542d73eee..f2860ea8e2 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/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index d722f1e9ce..ab52dd962b 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; } } } diff --git a/src/libslic3r/Line.hpp b/src/libslic3r/Line.hpp index 1edd1f5e9f..d90757bedd 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. diff --git a/src/libslic3r/MultiPoint.cpp b/src/libslic3r/MultiPoint.cpp index bb4d62cc0b..fb4727abe5 100644 --- a/src/libslic3r/MultiPoint.cpp +++ b/src/libslic3r/MultiPoint.cpp @@ -103,10 +103,10 @@ 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; + auto tolerance_sq = int64_t(sqr(tolerance)); if (! pts.empty()) { const Point *anchor = &pts.front(); size_t anchor_idx = 0; @@ -120,14 +120,40 @@ 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(); + 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; + 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 diff --git a/src/libslic3r/MultiPoint.hpp b/src/libslic3r/MultiPoint.hpp index 4cf4b5e140..62b53255b4 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/PlaceholderParser.cpp b/src/libslic3r/PlaceholderParser.cpp index 1dbf4120ea..348825106e 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. diff --git a/src/libslic3r/Polygon.cpp b/src/libslic3r/Polygon.cpp index 299e22adcd..88ac1b03f8 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 bf4a087b0d..e0c3958fd9 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 5743e38bd5..5247365752 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 diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 23f1438c4c..d20514bba3 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/Print.cpp b/src/libslic3r/Print.cpp index cf8b5c577d..5b23dc2606 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/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index d48f18aa5d..77120bfdfd 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 b9ca95a150..2a4b3258da 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 46588b9b02..1c37339a23 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" @@ -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 new file mode 100644 index 0000000000..2035f9ea3b --- /dev/null +++ b/src/libslic3r/Support/SupportCommon.cpp @@ -0,0 +1,1999 @@ +#include "../ClipperUtils.hpp" +#include "../ClipperZUtils.hpp" +#include "../ExtrusionEntityCollection.hpp" +#include "../Layer.hpp" +#include "../Print.hpp" +#include "../Fill/FillBase.hpp" +#include "../MutablePolygon.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 */ +} + +// 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 == 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); + 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, 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(); + 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( + 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) { + // 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); + 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) + return layers[idx]; + } + 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_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()) + 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_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"; + } + + return base_and_interface_layers; +} + +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, + 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. + // 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; + 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. + // 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, support_params); + } + + 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, support_params); + 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 0000000000..19f5822fe5 --- /dev/null +++ b/src/libslic3r/Support/SupportCommon.hpp @@ -0,0 +1,155 @@ +#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); + +// 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( + 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 0000000000..5c18bc7694 --- /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 0000000000..22a43bc4ea --- /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 new file mode 100644 index 0000000000..155de70ce6 --- /dev/null +++ b/src/libslic3r/Support/SupportLayer.hpp @@ -0,0 +1,146 @@ +#ifndef slic3r_SupportLayer_hpp_ +#define slic3r_SupportLayer_hpp_ + +#include +#include +// for Slic3r::deque +#include "../libslic3r.h" +#include "ClipperUtils.hpp" +#include "Polygon.hpp" + +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. +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. +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_storage; + tbb::spin_mutex m_mutex; +}; +using SupportGeneratorLayersPtr = std::vector; + +} // namespace Slic3r + +#endif /* slic3r_SupportLayer_hpp_ */ diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/Support/SupportMaterial.cpp similarity index 52% rename from src/libslic3r/SupportMaterial.cpp rename to src/libslic3r/Support/SupportMaterial.cpp index 224216466d..a21a48b9a3 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/Support/SupportMaterial.cpp @@ -1,12 +1,14 @@ -#include "ClipperUtils.hpp" -#include "ExtrusionEntityCollection.hpp" -#include "Layer.hpp" -#include "Print.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 "Fill/FillBase.hpp" -#include "Geometry.hpp" -#include "Point.hpp" -#include "MutablePolygon.hpp" #include @@ -16,7 +18,6 @@ #include #include -#include #include #define SUPPORT_USE_AGG_RASTERIZER @@ -39,16 +40,14 @@ #define DEBUG #define _DEBUG #undef NDEBUG - #include "utils.hpp" - #include "SVG.hpp" + #include "../utils.hpp" + #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"; @@ -574,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) @@ -1296,86 +1059,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 +1357,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 +1417,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 +1433,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 +1665,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 +1684,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 +1734,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 +1769,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 +2073,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 +2168,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 +2190,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 +2210,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 +2218,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 +2236,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 +2260,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,1749 +2516,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, - 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); - 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]( - 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_allocate(layer_storage, layer_storage_mutex, type); - layer_new.polygons = std::move(bottom); - layer_new.print_z = intermediate_layer.print_z; - layer_new.bottom_z = intermediate_layer.bottom_z; - layer_new.height = intermediate_layer.height; - layer_new.bridging = intermediate_layer.bridging; - // Subtract the interface from the base regions. - intermediate_layer.polygons = diff(intermediate_layer.polygons, layer_new.polygons); - if (subtract) - // Trim the base interface layer with the interface layer. - layer_new.polygons = diff(std::move(layer_new.polygons), *subtract); - //FIXME filter layer_new.polygons islands by a minimum area? - // $interface_area = [ grep abs($_->area) >= $area_threshold, @$interface_area ]; - return &layer_new; - } - } - return nullptr; - }; - tbb::parallel_for(tbb::blocked_range(0, int(intermediate_layers.size())), - [&bottom_contacts, &top_contacts, &intermediate_layers, &insert_layer, - 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; -} - -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/Support/SupportMaterial.hpp b/src/libslic3r/Support/SupportMaterial.hpp new file mode 100644 index 0000000000..4f1768fb1e --- /dev/null +++ b/src/libslic3r/Support/SupportMaterial.hpp @@ -0,0 +1,101 @@ +#ifndef slic3r_SupportMaterial_hpp_ +#define slic3r_SupportMaterial_hpp_ + +#include "../Flow.hpp" +#include "../PrintConfig.hpp" +#include "../Slicing.hpp" + +#include "SupportLayer.hpp" +#include "SupportParameters.hpp" + +namespace Slic3r { + +class PrintObject; + +// 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 +// the parameters of the raft to determine the 1st layer height and thickness. +class PrintObjectSupportMaterial +{ +public: + PrintObjectSupportMaterial(const PrintObject *object, const SlicingParameters &slicing_params); + + // Is raft enabled? + bool has_raft() const { return m_slicing_params.has_raft(); } + // Has any support? + bool has_support() const { return m_object_config->support_material.value || m_object_config->support_material_enforce_layers; } + bool build_plate_only() const { return this->has_support() && m_object_config->support_material_buildplate_only.value; } + + bool synchronize_layers() const { return m_slicing_params.soluble_interface && m_object_config->support_material_synchronize_layers.value; } + bool has_contact_loops() const { return m_object_config->support_material_interface_contact_loops.value; } + + // Generate support material for the object. + // New support layers will be added to the object, + // with extrusion paths and islands filled in for each support layer. + 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. + // For a soluble interface material synchronize the layer heights with the object, otherwise leave the layer height undefined. + // If supports over bed surface only are requested, don't generate contact layers over an object. + SupportGeneratorLayersPtr top_contact_layers(const PrintObject &object, const std::vector &buildplate_covered, SupportGeneratorLayerStorage &layer_storage) const; + + // Generate bottom contact layers supporting the top contact layers. + // For a soluble interface material synchronize the layer heights with the object, + // otherwise set the layer height to a bridging flow of a support interface nozzle. + SupportGeneratorLayersPtr bottom_contact_layers_and_layer_support_areas( + const PrintObject &object, const SupportGeneratorLayersPtr &top_contacts, std::vector &buildplate_covered, + SupportGeneratorLayerStorage &layer_storage, std::vector &layer_support_areas) const; + + // 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 trim_top_contacts_by_bottom_contacts(const PrintObject &object, const SupportGeneratorLayersPtr &bottom_contacts, SupportGeneratorLayersPtr &top_contacts) const; + + // Generate raft layers and the intermediate support layers between the bottom contact and top contact surfaces. + SupportGeneratorLayersPtr raft_and_intermediate_support_layers( + const PrintObject &object, + const SupportGeneratorLayersPtr &bottom_contacts, + const SupportGeneratorLayersPtr &top_contacts, + SupportGeneratorLayerStorage &layer_storage) const; + + // Fill in the base layers with polygons. + void generate_base_layers( + const PrintObject &object, + const SupportGeneratorLayersPtr &bottom_contacts, + const SupportGeneratorLayersPtr &top_contacts, + SupportGeneratorLayersPtr &intermediate_layers, + const std::vector &layer_support_areas) 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( + const PrintObject &object, + SupportGeneratorLayersPtr &support_layers, + const coordf_t gap_extra_above, + const coordf_t gap_extra_below, + const coordf_t gap_xy) const; + +/* + void generate_pillars_shape(); + void clip_with_shape(); +*/ + + // Following objects are not owned by SupportMaterial class. + const PrintConfig *m_print_config; + const PrintObjectConfig *m_object_config; + // Pre-calculated parameters shared between the object slicer and the support generator, + // carrying information on a raft, 1st layer height, 1st object layer height, gap between the raft and object etc. + SlicingParameters m_slicing_params; + // Various precomputed support parameters to be shared with external functions. + SupportParameters m_support_params; +}; + +} // namespace Slic3r + +#endif /* slic3r_SupportMaterial_hpp_ */ diff --git a/src/libslic3r/Support/SupportParameters.cpp b/src/libslic3r/Support/SupportParameters.cpp new file mode 100644 index 0000000000..09eca9610d --- /dev/null +++ b/src/libslic3r/Support/SupportParameters.cpp @@ -0,0 +1,144 @@ +#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->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)); + 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); + } + + 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 new file mode 100644 index 0000000000..8a63d9f3f0 --- /dev/null +++ b/src/libslic3r/Support/SupportParameters.hpp @@ -0,0 +1,96 @@ +#ifndef slic3r_SupportParameters_hpp_ +#define slic3r_SupportParameters_hpp_ + +#include "../libslic3r.h" +#include "../Flow.hpp" + +namespace Slic3r { + +class PrintObject; +enum InfillPattern : int; + +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). + // 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; + // 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; + 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 FFFSupport + +} // namespace Slic3r + +#endif /* slic3r_SupportParameters_hpp_ */ diff --git a/src/libslic3r/TreeModelVolumes.cpp b/src/libslic3r/Support/TreeModelVolumes.cpp similarity index 96% rename from src/libslic3r/TreeModelVolumes.cpp rename to src/libslic3r/Support/TreeModelVolumes.cpp index 5a1fcec561..5df56cd62d 100644 --- a/src/libslic3r/TreeModelVolumes.cpp +++ b/src/libslic3r/Support/TreeModelVolumes.cpp @@ -9,14 +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 "../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 @@ -34,6 +35,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(); @@ -76,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; @@ -175,7 +180,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 @@ -424,7 +429,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. @@ -585,7 +590,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(); @@ -595,21 +600,24 @@ 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()) 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 +665,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 +752,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 +873,12 @@ void TreeModelVolumes::calculateWallRestrictions(const std::vector -#include "Point.hpp" -#include "Polygon.hpp" -#include "PrintConfig.hpp" +#include "../Point.hpp" +#include "../Polygon.hpp" +#include "../PrintConfig.hpp" namespace Slic3r { @@ -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.)) }; @@ -215,6 +217,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 +226,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/Support/TreeSupport.cpp similarity index 91% rename from src/libslic3r/TreeSupport.cpp rename to src/libslic3r/Support/TreeSupport.cpp index 174e91a9cd..0eb009c777 100644 --- a/src/libslic3r/TreeSupport.cpp +++ b/src/libslic3r/Support/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,12 +53,16 @@ // #define TREESUPPORT_DEBUG_SVG +using namespace Slic3r::FFFSupport; + 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), @@ -83,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), @@ -297,7 +300,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; @@ -925,13 +928,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()); @@ -960,13 +963,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; @@ -974,36 +975,267 @@ 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); } +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) { @@ -1014,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(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, @@ -1078,193 +1306,7 @@ void finalize_raft_contact( } } -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) { - m_already_inserted.assign(num_support_layers, {}); - this->min_xy_dist = config.xy_distance > config.xy_min_distance; - } - const SlicingParameters &slicing_parameters; - const TreeModelVolumes &volumes; - const TreeSupportSettings &config; - 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(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) - { - if (! new_roofs.empty()) { - std::lock_guard lock(m_mutex_layer_storage); - for (size_t idx = 0; idx < dtt_roof; ++ idx) - if (! new_roofs[idx].empty()) - add_roof_unguarded(std::move(new_roofs[idx]), insert_layer_idx - idx); - } - } - - void add_roof_build_plate(Polygons &&overhang_areas) - { - 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); - append(l->polygons, std::move(overhang_areas)); - } - - 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. - bool supports_roof, - // 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); - } - - 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 > 0 || supports_roof, - disable_ovalistation); - } - } - - 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); - { - std::lock_guard critical_section_movebounds(m_mutex_movebounds); - Point hash_pos = p.first / ((config.min_radius + 1) / 10); - 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)); - } - } - }; -}; - +// 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. @@ -1281,15 +1323,14 @@ 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) + 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 @@ -1298,11 +1339,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; @@ -1326,9 +1368,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, @@ -1355,7 +1397,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()) { @@ -1365,7 +1408,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; @@ -1389,9 +1432,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; @@ -1402,8 +1446,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,16 +1468,11 @@ static void generate_initial_areas( const TreeSupportSettings &config, const std::vector &overhangs, std::vector &move_bounds, - SupportGeneratorLayersPtr &top_contacts, - SupportGeneratorLayersPtr &top_interface_layers, - 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; @@ -1461,9 +1500,9 @@ 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; + 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. @@ -1489,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) @@ -1497,14 +1536,12 @@ 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, - // Outputs - move_bounds, layer_storage, top_contacts }; + 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; @@ -1596,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 @@ -1614,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, support_params, interface_placer); + mesh_group_settings, rich_interface_placer); throw_on_cancel(); } } @@ -1624,15 +1661,14 @@ 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, 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()) @@ -1797,7 +1833,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 +1857,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 +2003,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]; @@ -2155,6 +2192,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); @@ -2208,10 +2249,14 @@ 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(); } - }); + }, tbb::simple_partitioner()); } [[nodiscard]] static SupportElementState merge_support_element_states( @@ -3298,7 +3343,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) { @@ -3325,7 +3369,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()) { @@ -3385,13 +3429,13 @@ 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; } 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. } @@ -3399,11 +3443,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); } @@ -4132,11 +4176,21 @@ 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, + InterfacePlacer &interface_placer, + + // Output: + SupportGeneratorLayersPtr &intermediate_layers, + SupportGeneratorLayerStorage &layer_storage, + std::function throw_on_cancel) { // All SupportElements are put into a layer independent storage to improve parallelization. @@ -4204,6 +4258,7 @@ static std::vector draw_branches( struct Slice { Polygons polygons; + Polygons bottom_contacts; size_t num_branches{ 0 }; }; @@ -4230,26 +4285,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)); @@ -4259,21 +4323,47 @@ 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; mesh_slicing_params.mode = MeshSlicingParams::SlicingMode::Positive; - tbb::parallel_for(tbb::blocked_range(0, trees.size()), - [&trees, &config, &slicing_params, &move_bounds, &mesh_slicing_params, &throw_on_cancel](const tbb::blocked_range &range) { + + tbb::parallel_for(tbb::blocked_range(0, trees.size(), 1), + [&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; for (size_t tree_id = range.begin(); tree_id < range.end(); ++ tree_id) { Tree &tree = trees[tree_id]; for (const Branch &branch : tree.branches) { @@ -4293,49 +4383,142 @@ 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(); - 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)); + 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 (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 { + // 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) + 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); + } } - tree.first_layer_id = new_begin; + +#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. + 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); + 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) { + int j = i - layer_begin; + if (Polygons &src = slices[j]; ! src.empty()) { + Slice &dst = tree.slices[i - new_begin]; + if (++ dst.num_branches > 1) { + append(dst.polygons, std::move(src)); + 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; + } } } - }); + }, 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]; 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(); } - }); + }, tbb::simple_partitioner()); size_t num_layers = 0; for (Tree &tree : trees) @@ -4348,25 +4531,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())), - [&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); + tbb::parallel_for(tbb::blocked_range(0, std::min(move_bounds.size(), slices.size()), 1), + [&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(); } - }); - - //FIXME simplify! - return support_layer_storage; + }, tbb::simple_partitioner()); } /*! @@ -4430,29 +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 top_interface_layers; - SupportGeneratorLayersPtr intermediate_layers; + SupportGeneratorLayersPtr interface_layers; + SupportGeneratorLayersPtr base_interface_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); - 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 - 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); - 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, interface_placer, throw_on_cancel); auto t_gen = std::chrono::high_resolution_clock::now(); #ifdef TREESUPPORT_DEBUG_SVG @@ -4483,12 +4711,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, interface_placer, 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(); @@ -4507,25 +4747,14 @@ 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; - } - - 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); + } 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. - 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/TreeSupport.hpp b/src/libslic3r/Support/TreeSupport.hpp similarity index 98% rename from src/libslic3r/TreeSupport.hpp rename to src/libslic3r/Support/TreeSupport.hpp index 070903096f..899f027242 100644 --- a/src/libslic3r/TreeSupport.hpp +++ b/src/libslic3r/Support/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 { @@ -93,6 +91,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 +102,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 +140,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; @@ -354,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. */ diff --git a/src/libslic3r/SupportMaterial.hpp b/src/libslic3r/SupportMaterial.hpp deleted file mode 100644 index 2bd3211445..0000000000 --- a/src/libslic3r/SupportMaterial.hpp +++ /dev/null @@ -1,314 +0,0 @@ -#ifndef slic3r_SupportMaterial_hpp_ -#define slic3r_SupportMaterial_hpp_ - -#include "Flow.hpp" -#include "PrintConfig.hpp" -#include "Slicing.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. -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 -// the parameters of the raft to determine the 1st layer height and thickness. -class PrintObjectSupportMaterial -{ -public: - PrintObjectSupportMaterial(const PrintObject *object, const SlicingParameters &slicing_params); - - // Is raft enabled? - bool has_raft() const { return m_slicing_params.has_raft(); } - // Has any support? - bool has_support() const { return m_object_config->support_material.value || m_object_config->support_material_enforce_layers; } - bool build_plate_only() const { return this->has_support() && m_object_config->support_material_buildplate_only.value; } - - bool synchronize_layers() const { return m_slicing_params.soluble_interface && m_object_config->support_material_synchronize_layers.value; } - bool has_contact_loops() const { return m_object_config->support_material_interface_contact_loops.value; } - - // Generate support material for the object. - // New support layers will be added to the object, - // with extrusion paths and islands filled in for each support layer. - void generate(PrintObject &object); - -private: - std::vector buildplate_covered(const PrintObject &object) const; - - // Generate top contact layers supporting overhangs. - // For a soluble interface material synchronize the layer heights with the object, otherwise leave the layer height undefined. - // If supports over bed surface only are requested, don't generate contact layers over an object. - SupportGeneratorLayersPtr top_contact_layers(const PrintObject &object, const std::vector &buildplate_covered, SupportGeneratorLayerStorage &layer_storage) const; - - // Generate bottom contact layers supporting the top contact layers. - // For a soluble interface material synchronize the layer heights with the object, - // otherwise set the layer height to a bridging flow of a support interface nozzle. - SupportGeneratorLayersPtr bottom_contact_layers_and_layer_support_areas( - const PrintObject &object, const SupportGeneratorLayersPtr &top_contacts, std::vector &buildplate_covered, - SupportGeneratorLayerStorage &layer_storage, std::vector &layer_support_areas) const; - - // 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 trim_top_contacts_by_bottom_contacts(const PrintObject &object, const SupportGeneratorLayersPtr &bottom_contacts, SupportGeneratorLayersPtr &top_contacts) const; - - // Generate raft layers and the intermediate support layers between the bottom contact and top contact surfaces. - SupportGeneratorLayersPtr raft_and_intermediate_support_layers( - const PrintObject &object, - const SupportGeneratorLayersPtr &bottom_contacts, - const SupportGeneratorLayersPtr &top_contacts, - SupportGeneratorLayerStorage &layer_storage) const; - - // Fill in the base layers with polygons. - void generate_base_layers( - const PrintObject &object, - const SupportGeneratorLayersPtr &bottom_contacts, - const SupportGeneratorLayersPtr &top_contacts, - 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( - const PrintObject &object, - SupportGeneratorLayersPtr &support_layers, - const coordf_t gap_extra_above, - const coordf_t gap_extra_below, - const coordf_t gap_xy) const; - -/* - void generate_pillars_shape(); - void clip_with_shape(); -*/ - - // Following objects are not owned by SupportMaterial class. - const PrintConfig *m_print_config; - const PrintObjectConfig *m_object_config; - // Pre-calculated parameters shared between the object slicer and the support generator, - // carrying information on a raft, 1st layer height, 1st object layer height, gap between the raft and object etc. - SlicingParameters m_slicing_params; - // Various precomputed support parameters to be shared with external functions. - SupportParameters m_support_params; -}; - -} // namespace Slic3r - -#endif /* slic3r_SupportMaterial_hpp_ */ diff --git a/src/libslic3r/TriangleMeshSlicer.cpp b/src/libslic3r/TriangleMeshSlicer.cpp index 460cd901e9..da696e1ecb 100644 --- a/src/libslic3r/TriangleMeshSlicer.cpp +++ b/src/libslic3r/TriangleMeshSlicer.cpp @@ -10,11 +10,15 @@ #include #include #include +#include #include #include #include +#include + +#include #ifndef NDEBUG // #define EXPENSIVE_DEBUG_CHECKS @@ -32,6 +36,13 @@ #include #include +#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 │ ... + constexpr std::size_t hardware_destructive_interference_size = 64; +#endif + // #define SLIC3R_DEBUG_SLICE_PROCESSING #ifdef SLIC3R_DEBUG_SLICE_PROCESSING @@ -139,7 +150,7 @@ public: #endif }; -using IntersectionLines = std::vector; +using IntersectionLines = std::vector>; enum class FacetSliceType { NoSlice = 0, @@ -351,6 +362,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(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 +387,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 +406,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 +421,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 +501,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 +520,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 +556,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 +675,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 +830,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 +1566,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; diff --git a/src/libslic3r/pchheader.hpp b/src/libslic3r/pchheader.hpp index 9017a5deac..e71b1461ce 100644 --- a/src/libslic3r/pchheader.hpp +++ b/src/libslic3r/pchheader.hpp @@ -107,6 +107,7 @@ #include #include +#include #include #include diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 4c5b0fd8e2..f645e8a0dd 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/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index d6765a046f..804f6058d2 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3916,6 +3916,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); @@ -3924,8 +3925,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() @@ -4003,6 +4007,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); @@ -4013,8 +4018,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)); @@ -4072,6 +4080,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); @@ -4081,8 +4090,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)); @@ -4135,6 +4147,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); @@ -4144,8 +4157,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)); @@ -4197,6 +4213,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); @@ -4206,8 +4223,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 1a3563a5d2..a5584692ce 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2998,21 +2998,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); + } } } } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 7700e1eef7..557cdee8e3 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 diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 7cebff141b..841ae20483 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -1634,6 +1634,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. diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 99843e541d..2cf4969cb8 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");