mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-08-13 23:15:58 +08:00
Merge branch 'lh_ms_seam_improvements'
This commit is contained in:
commit
d8bf4fed18
@ -1,10 +1,10 @@
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Author : Angus Johnson *
|
||||
* Version : 6.2.9 *
|
||||
* Date : 16 February 2015 *
|
||||
* Version : 6.4.2 *
|
||||
* Date : 27 February 2017 *
|
||||
* Website : http://www.angusj.com *
|
||||
* Copyright : Angus Johnson 2010-2015 *
|
||||
* Copyright : Angus Johnson 2010-2017 *
|
||||
* *
|
||||
* License: *
|
||||
* Use, modification & distribution is subject to Boost Software License Ver 1. *
|
||||
@ -325,7 +325,7 @@ inline bool SlopesEqual(const cInt dx1, const cInt dy1, const cInt dx2, const cI
|
||||
#endif
|
||||
|
||||
inline bool SlopesEqual(const TEdge &e1, const TEdge &e2, bool UseFullInt64Range)
|
||||
{ return SlopesEqual(e1.Delta.x(), e1.Delta.y(), e2.Delta.x(), e2.Delta.y(), UseFullInt64Range); }
|
||||
{ return SlopesEqual(e1.Top.x() - e1.Bot.x(), e1.Top.y() - e1.Bot.y(), e2.Top.x() - e2.Bot.x(), e2.Top.y() - e2.Bot.y(), UseFullInt64Range); }
|
||||
inline bool SlopesEqual(const IntPoint &pt1, const IntPoint &pt2, const IntPoint &pt3, bool UseFullInt64Range)
|
||||
{ return SlopesEqual(pt1.x()-pt2.x(), pt1.y()-pt2.y(), pt2.x()-pt3.x(), pt2.y()-pt3.y(), UseFullInt64Range); }
|
||||
inline bool SlopesEqual(const IntPoint &pt1, const IntPoint &pt2, const IntPoint &pt3, const IntPoint &pt4, bool UseFullInt64Range)
|
||||
@ -335,7 +335,7 @@ inline bool SlopesEqual(const IntPoint &pt1, const IntPoint &pt2, const IntPoint
|
||||
|
||||
inline bool IsHorizontal(TEdge &e)
|
||||
{
|
||||
return e.Delta.y() == 0;
|
||||
return e.Dx == HORIZONTAL;
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@ -368,7 +368,7 @@ void IntersectPoint(TEdge &Edge1, TEdge &Edge2, IntPoint &ip)
|
||||
}
|
||||
|
||||
int64_t y;
|
||||
if (Edge1.Delta.x() == 0)
|
||||
if (Edge1.Dx == 0)
|
||||
{
|
||||
ip.x() = Edge1.Bot.x();
|
||||
y = IsHorizontal(Edge2) ?
|
||||
@ -376,7 +376,7 @@ void IntersectPoint(TEdge &Edge1, TEdge &Edge2, IntPoint &ip)
|
||||
Round<int64_t>(ip.x() / Edge2.Dx + Edge2.Bot.y() - (Edge2.Bot.x() / Edge2.Dx));
|
||||
|
||||
}
|
||||
else if (Edge2.Delta.x() == 0)
|
||||
else if (Edge2.Dx == 0)
|
||||
{
|
||||
ip.x() = Edge2.Bot.x();
|
||||
y = IsHorizontal(Edge1) ?
|
||||
@ -448,11 +448,9 @@ void InitEdge2(TEdge& e, PolyType Pt)
|
||||
e.Bot = e.Next->Curr;
|
||||
}
|
||||
|
||||
e.Delta.x() = (e.Top.x() - e.Bot.x());
|
||||
e.Delta.y() = (e.Top.y() - e.Bot.y());
|
||||
|
||||
if (e.Delta.y() == 0) e.Dx = HORIZONTAL;
|
||||
else e.Dx = (double)(e.Delta.x()) / e.Delta.y();
|
||||
cInt dy = (e.Top.y() - e.Bot.y());
|
||||
if (dy == 0) e.Dx = HORIZONTAL;
|
||||
else e.Dx = (double)(e.Top.x() - e.Bot.x()) / dy;
|
||||
|
||||
e.PolyTyp = Pt;
|
||||
}
|
||||
@ -934,8 +932,6 @@ bool ClipperBase::AddPathInternal(const Path &pg, int highI, PolyType PolyTyp, b
|
||||
locMin.RightBound = E->Prev;
|
||||
leftBoundIsForward = true; //Q.nextInLML = Q.next
|
||||
}
|
||||
locMin.LeftBound->Side = esLeft;
|
||||
locMin.RightBound->Side = esRight;
|
||||
|
||||
if (!Closed) locMin.LeftBound->WindDelta = 0;
|
||||
else if (locMin.LeftBound->Next == locMin.RightBound)
|
||||
@ -1204,7 +1200,13 @@ void Clipper::SetWindingCount(TEdge &edge) const
|
||||
while (e && ((e->PolyTyp != edge.PolyTyp) || (e->WindDelta == 0))) e = e->PrevInAEL;
|
||||
if (!e)
|
||||
{
|
||||
edge.WindCnt = (edge.WindDelta == 0 ? 1 : edge.WindDelta);
|
||||
if (edge.WindDelta == 0)
|
||||
{
|
||||
PolyFillType pft = (edge.PolyTyp == ptSubject ? m_SubjFillType : m_ClipFillType);
|
||||
edge.WindCnt = (pft == pftNegative ? -1 : 1);
|
||||
}
|
||||
else
|
||||
edge.WindCnt = edge.WindDelta;
|
||||
edge.WindCnt2 = 0;
|
||||
e = m_ActiveEdges; //ie get ready to calc WindCnt2
|
||||
}
|
||||
@ -1421,14 +1423,17 @@ OutPt* Clipper::AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &Pt)
|
||||
prevE = e->PrevInAEL;
|
||||
}
|
||||
|
||||
if (prevE && prevE->OutIdx >= 0 &&
|
||||
(TopX(*prevE, Pt.y()) == TopX(*e, Pt.y())) &&
|
||||
SlopesEqual(*e, *prevE, m_UseFullRange) &&
|
||||
(e->WindDelta != 0) && (prevE->WindDelta != 0))
|
||||
if (prevE && prevE->OutIdx >= 0 && prevE->Top.y() < Pt.y() && e->Top.y() < Pt.y())
|
||||
{
|
||||
cInt xPrev = TopX(*prevE, Pt.y());
|
||||
cInt xE = TopX(*e, Pt.y());
|
||||
if (xPrev == xE && (e->WindDelta != 0) && (prevE->WindDelta != 0) &&
|
||||
SlopesEqual(IntPoint2d(xPrev, Pt.y()), prevE->Top, IntPoint2d(xE, Pt.y()), e->Top, m_UseFullRange))
|
||||
{
|
||||
OutPt* outPt = AddOutPt(prevE, Pt);
|
||||
m_Joins.emplace_back(Join(result, outPt, e->Top));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
@ -1523,7 +1528,12 @@ void Clipper::InsertLocalMinimaIntoAEL(const cInt botY)
|
||||
|
||||
if (rb)
|
||||
{
|
||||
if(IsHorizontal(*rb)) AddEdgeToSEL(rb);
|
||||
if (IsHorizontal(*rb))
|
||||
{
|
||||
AddEdgeToSEL(rb);
|
||||
if (rb->NextInLML)
|
||||
m_Scanbeam.push(rb->NextInLML->Top.y());
|
||||
}
|
||||
else m_Scanbeam.push(rb->Top.y());
|
||||
}
|
||||
|
||||
@ -1543,7 +1553,7 @@ void Clipper::InsertLocalMinimaIntoAEL(const cInt botY)
|
||||
if (lb->OutIdx >= 0 && lb->PrevInAEL &&
|
||||
lb->PrevInAEL->Curr.x() == lb->Bot.x() &&
|
||||
lb->PrevInAEL->OutIdx >= 0 &&
|
||||
SlopesEqual(*lb->PrevInAEL, *lb, m_UseFullRange) &&
|
||||
SlopesEqual(lb->PrevInAEL->Bot, lb->PrevInAEL->Top, lb->Curr, lb->Top, m_UseFullRange) &&
|
||||
(lb->WindDelta != 0) && (lb->PrevInAEL->WindDelta != 0))
|
||||
{
|
||||
OutPt *Op2 = AddOutPt(lb->PrevInAEL, lb->Bot);
|
||||
@ -1554,7 +1564,7 @@ void Clipper::InsertLocalMinimaIntoAEL(const cInt botY)
|
||||
{
|
||||
|
||||
if (rb->OutIdx >= 0 && rb->PrevInAEL->OutIdx >= 0 &&
|
||||
SlopesEqual(*rb->PrevInAEL, *rb, m_UseFullRange) &&
|
||||
SlopesEqual(rb->PrevInAEL->Curr, rb->PrevInAEL->Top, rb->Curr, rb->Top, m_UseFullRange) &&
|
||||
(rb->WindDelta != 0) && (rb->PrevInAEL->WindDelta != 0))
|
||||
{
|
||||
OutPt *Op2 = AddOutPt(rb->PrevInAEL, rb->Bot);
|
||||
@ -1814,19 +1824,27 @@ void Clipper::IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &Pt)
|
||||
|
||||
void Clipper::SetHoleState(TEdge *e, OutRec *outrec)
|
||||
{
|
||||
bool IsHole = false;
|
||||
TEdge *e2 = e->PrevInAEL;
|
||||
TEdge *eTmp = 0;
|
||||
while (e2)
|
||||
{
|
||||
if (e2->OutIdx >= 0 && e2->WindDelta != 0)
|
||||
{
|
||||
IsHole = !IsHole;
|
||||
if (! outrec->FirstLeft)
|
||||
outrec->FirstLeft = &m_PolyOuts[e2->OutIdx];
|
||||
if (!eTmp) eTmp = e2;
|
||||
else if (eTmp->OutIdx == e2->OutIdx) eTmp = 0;
|
||||
}
|
||||
e2 = e2->PrevInAEL;
|
||||
}
|
||||
if (IsHole) outrec->IsHole = true;
|
||||
if (!eTmp)
|
||||
{
|
||||
outrec->FirstLeft = 0;
|
||||
outrec->IsHole = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
outrec->FirstLeft = &m_PolyOuts[eTmp->OutIdx];
|
||||
outrec->IsHole = !outrec->FirstLeft->IsHole;
|
||||
}
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@ -1850,7 +1868,7 @@ OutRec* GetLowermostRec(OutRec *outRec1, OutRec *outRec2)
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
bool Param1RightOfParam2(OutRec* outRec1, OutRec* outRec2)
|
||||
bool OutRec1RightOfOutRec2(OutRec* outRec1, OutRec* outRec2)
|
||||
{
|
||||
do
|
||||
{
|
||||
@ -1877,9 +1895,9 @@ void Clipper::AppendPolygon(TEdge *e1, TEdge *e2)
|
||||
OutRec *outRec2 = &m_PolyOuts[e2->OutIdx];
|
||||
|
||||
OutRec *holeStateRec;
|
||||
if (Param1RightOfParam2(outRec1, outRec2))
|
||||
if (OutRec1RightOfOutRec2(outRec1, outRec2))
|
||||
holeStateRec = outRec2;
|
||||
else if (Param1RightOfParam2(outRec2, outRec1))
|
||||
else if (OutRec1RightOfOutRec2(outRec2, outRec1))
|
||||
holeStateRec = outRec1;
|
||||
else
|
||||
holeStateRec = GetLowermostRec(outRec1, outRec2);
|
||||
@ -1892,7 +1910,6 @@ void Clipper::AppendPolygon(TEdge *e1, TEdge *e2)
|
||||
OutPt* p2_lft = outRec2->Pts;
|
||||
OutPt* p2_rt = p2_lft->Prev;
|
||||
|
||||
EdgeSide Side;
|
||||
//join e2 poly onto e1 poly and delete pointers to e2 ...
|
||||
if( e1->Side == esLeft )
|
||||
{
|
||||
@ -1914,7 +1931,6 @@ void Clipper::AppendPolygon(TEdge *e1, TEdge *e2)
|
||||
p1_rt->Next = p2_lft;
|
||||
outRec1->Pts = p2_lft;
|
||||
}
|
||||
Side = esLeft;
|
||||
} else
|
||||
{
|
||||
if( e2->Side == esRight )
|
||||
@ -1933,7 +1949,6 @@ void Clipper::AppendPolygon(TEdge *e1, TEdge *e2)
|
||||
p1_lft->Prev = p2_rt;
|
||||
p2_rt->Next = p1_lft;
|
||||
}
|
||||
Side = esRight;
|
||||
}
|
||||
|
||||
outRec1->BottomPt = 0;
|
||||
@ -1959,7 +1974,7 @@ void Clipper::AppendPolygon(TEdge *e1, TEdge *e2)
|
||||
if( e->OutIdx == ObsoleteIdx )
|
||||
{
|
||||
e->OutIdx = OKIdx;
|
||||
e->Side = Side;
|
||||
e->Side = e1->Side;
|
||||
break;
|
||||
}
|
||||
e = e->NextInAEL;
|
||||
@ -2059,16 +2074,20 @@ inline bool IsIntermediate(TEdge *e, const cInt Y)
|
||||
|
||||
inline TEdge *GetMaximaPair(TEdge *e)
|
||||
{
|
||||
TEdge* result = 0;
|
||||
if ((e->Next->Top == e->Top) && !e->Next->NextInLML)
|
||||
result = e->Next;
|
||||
return e->Next;
|
||||
else if ((e->Prev->Top == e->Top) && !e->Prev->NextInLML)
|
||||
result = e->Prev;
|
||||
return e->Prev;
|
||||
else return 0;
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
inline TEdge *GetMaximaPairEx(TEdge *e)
|
||||
{
|
||||
//as GetMaximaPair() but returns 0 if MaxPair isn't in AEL (unless it's horizontal)
|
||||
TEdge* result = GetMaximaPair(e);
|
||||
if (result && (result->OutIdx == Skip ||
|
||||
//result is false if both NextInAEL & PrevInAEL are nil & not horizontal ...
|
||||
(result->NextInAEL == result->PrevInAEL && !IsHorizontal(*result))))
|
||||
return 0;
|
||||
(result->NextInAEL == result->PrevInAEL && !IsHorizontal(*result)))) return 0;
|
||||
return result;
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
@ -2196,7 +2215,7 @@ void Clipper::ProcessHorizontal(TEdge *horzEdge)
|
||||
{
|
||||
Direction dir;
|
||||
cInt horzLeft, horzRight;
|
||||
bool IsOpen = (horzEdge->OutIdx >= 0 && m_PolyOuts[horzEdge->OutIdx].IsOpen);
|
||||
bool IsOpen = (horzEdge->WindDelta == 0);
|
||||
|
||||
GetHorzDirection(*horzEdge, dir, horzLeft, horzRight);
|
||||
|
||||
@ -2464,6 +2483,8 @@ void Clipper::BuildIntersectList(const cInt topY)
|
||||
if(e->Curr.x() > eNext->Curr.x())
|
||||
{
|
||||
IntersectPoint(*e, *eNext, Pt);
|
||||
|
||||
if (Pt.y() < topY) Pt = IntPoint2d(TopX(*e, topY), topY);
|
||||
m_IntersectList.emplace_back(IntersectNode(e, eNext, Pt));
|
||||
SwapPositionsInSEL(e, eNext);
|
||||
isModified = true;
|
||||
@ -2513,7 +2534,7 @@ bool Clipper::FixupIntersectionOrder()
|
||||
|
||||
void Clipper::DoMaxima(TEdge *e)
|
||||
{
|
||||
TEdge* eMaxPair = GetMaximaPair(e);
|
||||
TEdge* eMaxPair = GetMaximaPairEx(e);
|
||||
if (!eMaxPair)
|
||||
{
|
||||
if (e->OutIdx >= 0)
|
||||
@ -2574,7 +2595,7 @@ void Clipper::ProcessEdgesAtTopOfScanbeam(const cInt topY)
|
||||
|
||||
if(IsMaximaEdge)
|
||||
{
|
||||
TEdge* eMaxPair = GetMaximaPair(e);
|
||||
TEdge* eMaxPair = GetMaximaPairEx(e);
|
||||
IsMaximaEdge = (!eMaxPair || !IsHorizontal(*eMaxPair));
|
||||
}
|
||||
|
||||
@ -2649,7 +2670,7 @@ void Clipper::ProcessEdgesAtTopOfScanbeam(const cInt topY)
|
||||
if (ePrev && ePrev->Curr.x() == e->Bot.x() &&
|
||||
ePrev->Curr.y() == e->Bot.y() && op &&
|
||||
ePrev->OutIdx >= 0 && ePrev->Curr.y() > ePrev->Top.y() &&
|
||||
SlopesEqual(*e, *ePrev, m_UseFullRange) &&
|
||||
SlopesEqual(e->Curr, e->Top, ePrev->Curr, ePrev->Top, m_UseFullRange) &&
|
||||
(e->WindDelta != 0) && (ePrev->WindDelta != 0))
|
||||
{
|
||||
OutPt* op2 = AddOutPt(ePrev, e->Bot);
|
||||
@ -2658,7 +2679,7 @@ void Clipper::ProcessEdgesAtTopOfScanbeam(const cInt topY)
|
||||
else if (eNext && eNext->Curr.x() == e->Bot.x() &&
|
||||
eNext->Curr.y() == e->Bot.y() && op &&
|
||||
eNext->OutIdx >= 0 && eNext->Curr.y() > eNext->Top.y() &&
|
||||
SlopesEqual(*e, *eNext, m_UseFullRange) &&
|
||||
SlopesEqual(e->Curr, e->Top, eNext->Curr, eNext->Top, m_UseFullRange) &&
|
||||
(e->WindDelta != 0) && (eNext->WindDelta != 0))
|
||||
{
|
||||
OutPt* op2 = AddOutPt(eNext, e->Bot);
|
||||
@ -3172,27 +3193,59 @@ bool Clipper::JoinPoints(Join *j, OutRec* outRec1, OutRec* outRec2)
|
||||
}
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
inline OutRec* ParseFirstLeft(OutRec* FirstLeft)
|
||||
{
|
||||
while (FirstLeft && !FirstLeft->Pts)
|
||||
FirstLeft = FirstLeft->FirstLeft;
|
||||
return FirstLeft;
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// This is potentially very expensive! O(n^3)!
|
||||
void Clipper::FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec)
|
||||
{
|
||||
//tests if NewOutRec contains the polygon before reassigning FirstLeft
|
||||
for (OutRec &outRec : m_PolyOuts)
|
||||
{
|
||||
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 = ParseFirstLeft(outRec.FirstLeft);
|
||||
if (outRec.Pts && firstLeft == OldOutRec && Poly2ContainsPoly1(outRec.Pts, NewOutRec->Pts))
|
||||
outRec.FirstLeft = NewOutRec;
|
||||
}
|
||||
}
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
void Clipper::FixupFirstLefts2(OutRec* OldOutRec, OutRec* NewOutRec)
|
||||
void Clipper::FixupFirstLefts2(OutRec* InnerOutRec, OutRec* OuterOutRec)
|
||||
{
|
||||
//A polygon has split into two such that one is now the inner of the other.
|
||||
//It's possible that these polygons now wrap around other polygons, so check
|
||||
//every polygon that's also contained by OuterOutRec's FirstLeft container
|
||||
//(including 0) to see if they've become inner to the new inner polygon ...
|
||||
OutRec* orfl = OuterOutRec->FirstLeft;
|
||||
for (OutRec &outRec : m_PolyOuts)
|
||||
{
|
||||
if (!outRec.Pts || &outRec == OuterOutRec || &outRec == InnerOutRec)
|
||||
continue;
|
||||
OutRec* firstLeft = ParseFirstLeft(outRec.FirstLeft);
|
||||
if (firstLeft != orfl && firstLeft != InnerOutRec && firstLeft != OuterOutRec)
|
||||
continue;
|
||||
if (Poly2ContainsPoly1(outRec.Pts, InnerOutRec->Pts))
|
||||
outRec.FirstLeft = InnerOutRec;
|
||||
else if (Poly2ContainsPoly1(outRec.Pts, OuterOutRec->Pts))
|
||||
outRec.FirstLeft = OuterOutRec;
|
||||
else if (outRec.FirstLeft == InnerOutRec || outRec.FirstLeft == OuterOutRec)
|
||||
outRec.FirstLeft = orfl;
|
||||
}
|
||||
}
|
||||
//----------------------------------------------------------------------
|
||||
void Clipper::FixupFirstLefts3(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;
|
||||
{
|
||||
OutRec* firstLeft = ParseFirstLeft(outRec.FirstLeft);
|
||||
if (outRec.Pts && firstLeft == OldOutRec)
|
||||
outRec.FirstLeft = NewOutRec;
|
||||
}
|
||||
}
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
@ -3210,8 +3263,8 @@ void Clipper::JoinCommonEdges()
|
||||
//before calling JoinPoints() ...
|
||||
OutRec *holeStateRec;
|
||||
if (outRec1 == outRec2) holeStateRec = outRec1;
|
||||
else if (Param1RightOfParam2(outRec1, outRec2)) holeStateRec = outRec2;
|
||||
else if (Param1RightOfParam2(outRec2, outRec1)) holeStateRec = outRec1;
|
||||
else if (OutRec1RightOfOutRec2(outRec1, outRec2)) holeStateRec = outRec2;
|
||||
else if (OutRec1RightOfOutRec2(outRec2, outRec1)) holeStateRec = outRec1;
|
||||
else holeStateRec = GetLowermostRec(outRec1, outRec2);
|
||||
|
||||
if (!JoinPoints(&join, outRec1, outRec2)) continue;
|
||||
@ -3228,23 +3281,9 @@ void Clipper::JoinCommonEdges()
|
||||
//update all OutRec2.Pts Idx's ...
|
||||
UpdateOutPtIdxs(*outRec2);
|
||||
|
||||
//We now need to check every OutRec.FirstLeft pointer. If it points
|
||||
//to OutRec1 it may need to point to OutRec2 instead ...
|
||||
if (m_UsingPolyTree)
|
||||
for (size_t j = 0; j < m_PolyOuts.size() - 1; j++)
|
||||
{
|
||||
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 (Poly2ContainsPoly1(outRec2->Pts, outRec1->Pts))
|
||||
{
|
||||
//outRec2 is contained by outRec1 ...
|
||||
//outRec1 contains outRec2 ...
|
||||
outRec2->IsHole = !outRec1->IsHole;
|
||||
outRec2->FirstLeft = outRec1;
|
||||
|
||||
@ -3256,7 +3295,7 @@ void Clipper::JoinCommonEdges()
|
||||
|
||||
} else if (Poly2ContainsPoly1(outRec1->Pts, outRec2->Pts))
|
||||
{
|
||||
//outRec1 is contained by outRec2 ...
|
||||
//outRec2 contains outRec1 ...
|
||||
outRec2->IsHole = outRec1->IsHole;
|
||||
outRec1->IsHole = !outRec2->IsHole;
|
||||
outRec2->FirstLeft = outRec1->FirstLeft;
|
||||
@ -3294,7 +3333,7 @@ void Clipper::JoinCommonEdges()
|
||||
outRec2->FirstLeft = outRec1;
|
||||
|
||||
// For each m_PolyOuts, replace FirstLeft from outRec2 to outRec1.
|
||||
if (m_UsingPolyTree) FixupFirstLefts2(outRec2, outRec1);
|
||||
if (m_UsingPolyTree) FixupFirstLefts3(outRec2, outRec1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3975,6 +4014,7 @@ void CleanPolygon(Path& poly, double distance)
|
||||
|
||||
void CleanPolygons(const Paths& in_polys, Paths& out_polys, double distance)
|
||||
{
|
||||
out_polys.resize(in_polys.size());
|
||||
for (Paths::size_type i = 0; i < in_polys.size(); ++i)
|
||||
CleanPolygon(in_polys[i], out_polys[i], distance);
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Author : Angus Johnson *
|
||||
* Version : 6.2.9 *
|
||||
* Date : 16 February 2015 *
|
||||
* Version : 6.4.2 *
|
||||
* Date : 27 February 2017 *
|
||||
* Website : http://www.angusj.com *
|
||||
* Copyright : Angus Johnson 2010-2015 *
|
||||
* Copyright : Angus Johnson 2010-2017 *
|
||||
* *
|
||||
* License: *
|
||||
* Use, modification & distribution is subject to Boost Software License Ver 1. *
|
||||
@ -144,7 +144,7 @@ typedef std::vector<PolyNode*, Allocator<PolyNode*>> PolyNodes;
|
||||
class PolyNode
|
||||
{
|
||||
public:
|
||||
PolyNode() : Childs(), Parent(0), Index(0), m_IsOpen(false) {}
|
||||
PolyNode() : Parent(0), Index(0), m_IsOpen(false) {}
|
||||
virtual ~PolyNode(){};
|
||||
Path Contour;
|
||||
PolyNodes Childs;
|
||||
@ -234,8 +234,6 @@ enum EdgeSide { esLeft = 1, esRight = 2};
|
||||
IntPoint Curr;
|
||||
// Top point of this edge (with maximum Y).
|
||||
IntPoint Top;
|
||||
// Vector from Bot to Top.
|
||||
IntPoint Delta;
|
||||
// Slope (dx/dy). For horiontal edges, the slope is set to HORIZONTAL (-1.0E+40).
|
||||
double Dx;
|
||||
PolyType PolyTyp;
|
||||
@ -529,7 +527,8 @@ private:
|
||||
void JoinCommonEdges();
|
||||
void DoSimplePolygons();
|
||||
void FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec);
|
||||
void FixupFirstLefts2(OutRec* OldOutRec, OutRec* NewOutRec);
|
||||
void FixupFirstLefts2(OutRec* InnerOutRec, OutRec* OuterOutRec);
|
||||
void FixupFirstLefts3(OutRec* OldOutRec, OutRec* NewOutRec);
|
||||
#ifdef CLIPPERLIB_USE_XYZ
|
||||
void SetZ(IntPoint& pt, TEdge& e1, TEdge& e2);
|
||||
#endif
|
||||
|
276
src/libslic3r/Arachne/PerimeterOrder.cpp
Normal file
276
src/libslic3r/Arachne/PerimeterOrder.cpp
Normal file
@ -0,0 +1,276 @@
|
||||
#include <stack>
|
||||
#include "PerimeterOrder.hpp"
|
||||
|
||||
namespace Slic3r::Arachne::PerimeterOrder {
|
||||
|
||||
using namespace Arachne;
|
||||
|
||||
static size_t get_extrusion_lines_count(const Perimeters &perimeters) {
|
||||
size_t extrusion_lines_count = 0;
|
||||
for (const Perimeter &perimeter : perimeters)
|
||||
extrusion_lines_count += perimeter.size();
|
||||
|
||||
return extrusion_lines_count;
|
||||
}
|
||||
|
||||
static PerimeterExtrusions get_sorted_perimeter_extrusions_by_area(const Perimeters &perimeters) {
|
||||
PerimeterExtrusions sorted_perimeter_extrusions;
|
||||
sorted_perimeter_extrusions.reserve(get_extrusion_lines_count(perimeters));
|
||||
|
||||
for (const Perimeter &perimeter : perimeters) {
|
||||
for (const ExtrusionLine &extrusion_line : perimeter) {
|
||||
if (extrusion_line.empty())
|
||||
continue; // This shouldn't ever happen.
|
||||
|
||||
const BoundingBox bbox = get_extents(extrusion_line);
|
||||
// Be aware that Arachne produces contours with clockwise orientation and holes with counterclockwise orientation.
|
||||
const double area = std::abs(extrusion_line.area());
|
||||
const Polygon polygon = extrusion_line.is_closed ? to_polygon(extrusion_line) : Polygon{};
|
||||
|
||||
sorted_perimeter_extrusions.emplace_back(extrusion_line, area, polygon, bbox);
|
||||
}
|
||||
}
|
||||
|
||||
// Open extrusions have an area equal to zero, so sorting based on the area ensures that open extrusions will always be before closed ones.
|
||||
std::sort(sorted_perimeter_extrusions.begin(), sorted_perimeter_extrusions.end(),
|
||||
[](const PerimeterExtrusion &l, const PerimeterExtrusion &r) { return l.area < r.area; });
|
||||
|
||||
return sorted_perimeter_extrusions;
|
||||
}
|
||||
|
||||
// Functions fill adjacent_perimeter_extrusions field for every PerimeterExtrusion by pointers to PerimeterExtrusions that contain or are inside this PerimeterExtrusion.
|
||||
static void construct_perimeter_extrusions_adjacency_graph(PerimeterExtrusions &sorted_perimeter_extrusions) {
|
||||
// Construct a graph (defined using adjacent_perimeter_extrusions field) where two PerimeterExtrusion are adjacent when one is inside the other.
|
||||
std::vector<bool> root_candidates(sorted_perimeter_extrusions.size(), false);
|
||||
for (PerimeterExtrusion &perimeter_extrusion : sorted_perimeter_extrusions) {
|
||||
const size_t perimeter_extrusion_idx = &perimeter_extrusion - sorted_perimeter_extrusions.data();
|
||||
|
||||
if (!perimeter_extrusion.is_closed()) {
|
||||
root_candidates[perimeter_extrusion_idx] = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
for (PerimeterExtrusion &root_candidate : sorted_perimeter_extrusions) {
|
||||
const size_t root_candidate_idx = &root_candidate - sorted_perimeter_extrusions.data();
|
||||
|
||||
if (!root_candidates[root_candidate_idx])
|
||||
continue;
|
||||
|
||||
if (perimeter_extrusion.bbox.contains(root_candidate.bbox) && perimeter_extrusion.polygon.contains(root_candidate.extrusion.junctions.front().p)) {
|
||||
perimeter_extrusion.adjacent_perimeter_extrusions.emplace_back(&root_candidate);
|
||||
root_candidate.adjacent_perimeter_extrusions.emplace_back(&perimeter_extrusion);
|
||||
root_candidates[root_candidate_idx] = false;
|
||||
}
|
||||
}
|
||||
|
||||
root_candidates[perimeter_extrusion_idx] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Perform the depth-first search to assign the nearest external perimeter for every PerimeterExtrusion.
|
||||
// When some PerimeterExtrusion is achievable from more than one external perimeter, then we choose the
|
||||
// one that comes from a contour.
|
||||
static void assign_nearest_external_perimeter(PerimeterExtrusions &sorted_perimeter_extrusions) {
|
||||
std::stack<PerimeterExtrusion *> stack;
|
||||
for (PerimeterExtrusion &perimeter_extrusion : sorted_perimeter_extrusions) {
|
||||
if (perimeter_extrusion.is_external_perimeter()) {
|
||||
perimeter_extrusion.depth = 0;
|
||||
perimeter_extrusion.nearest_external_perimeter = &perimeter_extrusion;
|
||||
stack.push(&perimeter_extrusion);
|
||||
}
|
||||
}
|
||||
|
||||
while (!stack.empty()) {
|
||||
PerimeterExtrusion *current_extrusion = stack.top();
|
||||
stack.pop();
|
||||
|
||||
for (PerimeterExtrusion *adjacent_extrusion : current_extrusion->adjacent_perimeter_extrusions) {
|
||||
const size_t adjacent_extrusion_depth = current_extrusion->depth + 1;
|
||||
// Update depth when the new depth is smaller or when we can achieve the same depth from a contour.
|
||||
// This will ensure that the internal perimeter will be extruded before the outer external perimeter
|
||||
// when there are two external perimeters and one internal.
|
||||
if (adjacent_extrusion_depth < adjacent_extrusion->depth) {
|
||||
adjacent_extrusion->nearest_external_perimeter = current_extrusion->nearest_external_perimeter;
|
||||
adjacent_extrusion->depth = adjacent_extrusion_depth;
|
||||
stack.push(adjacent_extrusion);
|
||||
} else if (adjacent_extrusion_depth == adjacent_extrusion->depth && !adjacent_extrusion->nearest_external_perimeter->is_contour() && current_extrusion->is_contour()) {
|
||||
adjacent_extrusion->nearest_external_perimeter = current_extrusion->nearest_external_perimeter;
|
||||
stack.push(adjacent_extrusion);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline Point get_end_position(const ExtrusionLine &extrusion) {
|
||||
if (extrusion.is_closed)
|
||||
return extrusion.junctions[0].p; // We ended where we started.
|
||||
else
|
||||
return extrusion.junctions.back().p; // Pick the other end from where we started.
|
||||
}
|
||||
|
||||
// Returns ordered extrusions.
|
||||
static std::vector<const PerimeterExtrusion *> ordered_perimeter_extrusions_to_minimize_distances(Point current_position, std::vector<const PerimeterExtrusion *> extrusions) {
|
||||
// Ensure that open extrusions will be placed before the closed one.
|
||||
std::sort(extrusions.begin(), extrusions.end(),
|
||||
[](const PerimeterExtrusion *l, const PerimeterExtrusion *r) -> bool { return l->is_closed() < r->is_closed(); });
|
||||
|
||||
std::vector<const PerimeterExtrusion *> ordered_extrusions;
|
||||
std::vector<bool> already_selected(extrusions.size(), false);
|
||||
while (ordered_extrusions.size() < extrusions.size()) {
|
||||
double nearest_distance_sqr = std::numeric_limits<double>::max();
|
||||
size_t nearest_extrusion_idx = 0;
|
||||
bool is_nearest_closed = false;
|
||||
|
||||
for (size_t extrusion_idx = 0; extrusion_idx < extrusions.size(); ++extrusion_idx) {
|
||||
if (already_selected[extrusion_idx])
|
||||
continue;
|
||||
|
||||
const ExtrusionLine &extrusion_line = extrusions[extrusion_idx]->extrusion;
|
||||
const Point &extrusion_start_position = extrusion_line.junctions.front().p;
|
||||
const double distance_sqr = (current_position - extrusion_start_position).cast<double>().squaredNorm();
|
||||
if (distance_sqr < nearest_distance_sqr) {
|
||||
if (extrusion_line.is_closed || (!extrusion_line.is_closed && nearest_distance_sqr != std::numeric_limits<double>::max()) || (!extrusion_line.is_closed && !is_nearest_closed)) {
|
||||
nearest_extrusion_idx = extrusion_idx;
|
||||
nearest_distance_sqr = distance_sqr;
|
||||
is_nearest_closed = extrusion_line.is_closed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
already_selected[nearest_extrusion_idx] = true;
|
||||
const PerimeterExtrusion *nearest_extrusion = extrusions[nearest_extrusion_idx];
|
||||
current_position = get_end_position(nearest_extrusion->extrusion);
|
||||
ordered_extrusions.emplace_back(nearest_extrusion);
|
||||
}
|
||||
|
||||
return ordered_extrusions;
|
||||
}
|
||||
|
||||
struct GroupedPerimeterExtrusions
|
||||
{
|
||||
GroupedPerimeterExtrusions() = delete;
|
||||
explicit GroupedPerimeterExtrusions(const PerimeterExtrusion *external_perimeter_extrusion)
|
||||
: external_perimeter_extrusion(external_perimeter_extrusion) {}
|
||||
|
||||
std::vector<const PerimeterExtrusion *> extrusions;
|
||||
const PerimeterExtrusion *external_perimeter_extrusion = nullptr;
|
||||
};
|
||||
|
||||
// Returns vector of indexes that represent the order of grouped extrusions in grouped_extrusions.
|
||||
static std::vector<size_t> order_of_grouped_perimeter_extrusions_to_minimize_distances(Point current_position, std::vector<GroupedPerimeterExtrusions> grouped_extrusions) {
|
||||
// Ensure that holes will be placed before contour and open extrusions before the closed one.
|
||||
std::sort(grouped_extrusions.begin(), grouped_extrusions.end(), [](const GroupedPerimeterExtrusions &l, const GroupedPerimeterExtrusions &r) -> bool {
|
||||
return (l.external_perimeter_extrusion->is_contour() < r.external_perimeter_extrusion->is_contour()) ||
|
||||
(l.external_perimeter_extrusion->is_contour() == r.external_perimeter_extrusion->is_contour() && l.external_perimeter_extrusion->is_closed() < r.external_perimeter_extrusion->is_closed());
|
||||
});
|
||||
|
||||
const size_t holes_cnt = std::count_if(grouped_extrusions.begin(), grouped_extrusions.end(), [](const GroupedPerimeterExtrusions &grouped_extrusions) {
|
||||
return !grouped_extrusions.external_perimeter_extrusion->is_contour();
|
||||
});
|
||||
|
||||
std::vector<size_t> grouped_extrusions_order;
|
||||
std::vector<bool> already_selected(grouped_extrusions.size(), false);
|
||||
while (grouped_extrusions_order.size() < grouped_extrusions.size()) {
|
||||
double nearest_distance_sqr = std::numeric_limits<double>::max();
|
||||
size_t nearest_grouped_extrusions_idx = 0;
|
||||
bool is_nearest_closed = false;
|
||||
|
||||
// First we order all holes and then we start ordering contours.
|
||||
const size_t grouped_extrusion_end = grouped_extrusions_order.size() < holes_cnt ? holes_cnt: grouped_extrusions.size();
|
||||
for (size_t grouped_extrusion_idx = 0; grouped_extrusion_idx < grouped_extrusion_end; ++grouped_extrusion_idx) {
|
||||
if (already_selected[grouped_extrusion_idx])
|
||||
continue;
|
||||
|
||||
const ExtrusionLine &external_perimeter_extrusion_line = grouped_extrusions[grouped_extrusion_idx].external_perimeter_extrusion->extrusion;
|
||||
const Point &extrusion_start_position = external_perimeter_extrusion_line.junctions.front().p;
|
||||
const double distance_sqr = (current_position - extrusion_start_position).cast<double>().squaredNorm();
|
||||
if (distance_sqr < nearest_distance_sqr) {
|
||||
if (external_perimeter_extrusion_line.is_closed || (!external_perimeter_extrusion_line.is_closed && nearest_distance_sqr != std::numeric_limits<double>::max()) || (!external_perimeter_extrusion_line.is_closed && !is_nearest_closed)) {
|
||||
nearest_grouped_extrusions_idx = grouped_extrusion_idx;
|
||||
nearest_distance_sqr = distance_sqr;
|
||||
is_nearest_closed = external_perimeter_extrusion_line.is_closed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
grouped_extrusions_order.emplace_back(nearest_grouped_extrusions_idx);
|
||||
already_selected[nearest_grouped_extrusions_idx] = true;
|
||||
const GroupedPerimeterExtrusions &nearest_grouped_extrusions = grouped_extrusions[nearest_grouped_extrusions_idx];
|
||||
const ExtrusionLine &last_extrusion_line = nearest_grouped_extrusions.extrusions.back()->extrusion;
|
||||
current_position = get_end_position(last_extrusion_line);
|
||||
}
|
||||
|
||||
return grouped_extrusions_order;
|
||||
}
|
||||
|
||||
static PerimeterExtrusions extract_ordered_perimeter_extrusions(const PerimeterExtrusions &sorted_perimeter_extrusions, const bool external_perimeters_first) {
|
||||
// Extrusions are ordered inside each group.
|
||||
std::vector<GroupedPerimeterExtrusions> grouped_extrusions;
|
||||
|
||||
std::stack<const PerimeterExtrusion *> stack;
|
||||
std::vector<bool> visited(sorted_perimeter_extrusions.size(), false);
|
||||
for (const PerimeterExtrusion &perimeter_extrusion : sorted_perimeter_extrusions) {
|
||||
if (!perimeter_extrusion.is_external_perimeter())
|
||||
continue;
|
||||
|
||||
stack.push(&perimeter_extrusion);
|
||||
visited.assign(sorted_perimeter_extrusions.size(), false);
|
||||
|
||||
grouped_extrusions.emplace_back(&perimeter_extrusion);
|
||||
while (!stack.empty()) {
|
||||
const PerimeterExtrusion *current_extrusion = stack.top();
|
||||
const size_t current_extrusion_idx = current_extrusion - sorted_perimeter_extrusions.data();
|
||||
stack.pop();
|
||||
|
||||
if (visited[current_extrusion_idx])
|
||||
continue;
|
||||
|
||||
if (current_extrusion->nearest_external_perimeter == &perimeter_extrusion)
|
||||
grouped_extrusions.back().extrusions.emplace_back(current_extrusion);
|
||||
|
||||
if (current_extrusion->adjacent_perimeter_extrusions.size() == 1) {
|
||||
const PerimeterExtrusion *adjacent_extrusion = current_extrusion->adjacent_perimeter_extrusions.front();
|
||||
stack.push(adjacent_extrusion);
|
||||
} else if (current_extrusion->adjacent_perimeter_extrusions.size() > 1) {
|
||||
// When there is more than one available candidate, then order candidates to minimize distances between
|
||||
// candidates and also to minimize the distance from the current_position.
|
||||
std::vector<const PerimeterExtrusion *> available_candidates;
|
||||
for (const PerimeterExtrusion *adjacent_extrusion : current_extrusion->adjacent_perimeter_extrusions) {
|
||||
if (const size_t adjacent_extrusion_idx = adjacent_extrusion - sorted_perimeter_extrusions.data(); !visited[adjacent_extrusion_idx])
|
||||
available_candidates.emplace_back(adjacent_extrusion);
|
||||
}
|
||||
|
||||
std::vector<const PerimeterExtrusion *> adjacent_extrusions = ordered_perimeter_extrusions_to_minimize_distances(Point::Zero(), available_candidates);
|
||||
std::reverse(adjacent_extrusions.begin(), adjacent_extrusions.end());
|
||||
for (const PerimeterExtrusion *adjacent_extrusion : adjacent_extrusions)
|
||||
stack.push(adjacent_extrusion);
|
||||
}
|
||||
|
||||
visited[current_extrusion_idx] = true;
|
||||
}
|
||||
|
||||
if (!external_perimeters_first)
|
||||
std::reverse(grouped_extrusions.back().extrusions.begin(), grouped_extrusions.back().extrusions.end());
|
||||
}
|
||||
|
||||
const std::vector<size_t> grouped_extrusion_order = order_of_grouped_perimeter_extrusions_to_minimize_distances(Point::Zero(), grouped_extrusions);
|
||||
|
||||
PerimeterExtrusions ordered_extrusions;
|
||||
for (size_t order_idx : grouped_extrusion_order) {
|
||||
for (const PerimeterExtrusion *perimeter_extrusion : grouped_extrusions[order_idx].extrusions)
|
||||
ordered_extrusions.emplace_back(*perimeter_extrusion);
|
||||
}
|
||||
|
||||
return ordered_extrusions;
|
||||
}
|
||||
|
||||
// FIXME: From the point of better patch planning, it should be better to do ordering when we have generated all extrusions (for now, when G-Code is exported).
|
||||
// FIXME: It would be better to extract the adjacency graph of extrusions from the SkeletalTrapezoidation graph.
|
||||
PerimeterExtrusions ordered_perimeter_extrusions(const Perimeters &perimeters, const bool external_perimeters_first) {
|
||||
PerimeterExtrusions sorted_perimeter_extrusions = get_sorted_perimeter_extrusions_by_area(perimeters);
|
||||
construct_perimeter_extrusions_adjacency_graph(sorted_perimeter_extrusions);
|
||||
assign_nearest_external_perimeter(sorted_perimeter_extrusions);
|
||||
return extract_ordered_perimeter_extrusions(sorted_perimeter_extrusions, external_perimeters_first);
|
||||
}
|
||||
|
||||
} // namespace Slic3r::Arachne::PerimeterOrder
|
47
src/libslic3r/Arachne/PerimeterOrder.hpp
Normal file
47
src/libslic3r/Arachne/PerimeterOrder.hpp
Normal file
@ -0,0 +1,47 @@
|
||||
#ifndef slic3r_GCode_PerimeterOrder_hpp_
|
||||
#define slic3r_GCode_PerimeterOrder_hpp_
|
||||
|
||||
#include <Arachne/utils/ExtrusionLine.hpp>
|
||||
|
||||
namespace Slic3r::Arachne::PerimeterOrder {
|
||||
|
||||
// Data structure stores ExtrusionLine (closed and open) together with additional data.
|
||||
struct PerimeterExtrusion
|
||||
{
|
||||
explicit PerimeterExtrusion(const Arachne::ExtrusionLine &extrusion, const double area, const Polygon &polygon, const BoundingBox &bbox)
|
||||
: extrusion(extrusion), area(area), polygon(polygon), bbox(bbox) {}
|
||||
|
||||
Arachne::ExtrusionLine extrusion;
|
||||
// Absolute value of the area of the polygon. The value is always non-negative, even for holes.
|
||||
double area = 0;
|
||||
|
||||
// Polygon is non-empty only for closed extrusions.
|
||||
Polygon polygon;
|
||||
BoundingBox bbox;
|
||||
|
||||
std::vector<PerimeterExtrusion *> adjacent_perimeter_extrusions;
|
||||
|
||||
// How far is this perimeter from the nearest external perimeter. Contour is always preferred over holes.
|
||||
size_t depth = std::numeric_limits<size_t>::max();
|
||||
PerimeterExtrusion *nearest_external_perimeter = nullptr;
|
||||
|
||||
// Should this extrusion be fuzzyfied during path generation?
|
||||
bool fuzzify = false;
|
||||
|
||||
// Returns if ExtrusionLine is a contour or a hole.
|
||||
bool is_contour() const { return extrusion.is_contour(); }
|
||||
|
||||
// Returns if ExtrusionLine is closed or opened.
|
||||
bool is_closed() const { return extrusion.is_closed; }
|
||||
|
||||
// Returns if ExtrusionLine is an external or an internal perimeter.
|
||||
bool is_external_perimeter() const { return extrusion.is_external_perimeter(); }
|
||||
};
|
||||
|
||||
using PerimeterExtrusions = std::vector<PerimeterExtrusion>;
|
||||
|
||||
PerimeterExtrusions ordered_perimeter_extrusions(const Perimeters &perimeters, bool external_perimeters_first);
|
||||
|
||||
} // namespace Slic3r::Arachne::PerimeterOrder
|
||||
|
||||
#endif // slic3r_GCode_Travels_hpp_
|
@ -152,8 +152,7 @@ void SkeletalTrapezoidation::transferEdge(Point from, Point to, const VD::edge_t
|
||||
assert(twin->prev->twin); // Back rib
|
||||
assert(twin->prev->twin->prev); // Prev segment along parabola
|
||||
|
||||
constexpr bool is_not_next_to_start_or_end = false; // Only ribs at the end of a cell should be skipped
|
||||
graph.makeRib(prev_edge, start_source_point, end_source_point, is_not_next_to_start_or_end);
|
||||
graph.makeRib(prev_edge, start_source_point, end_source_point);
|
||||
}
|
||||
assert(prev_edge);
|
||||
}
|
||||
@ -203,10 +202,8 @@ void SkeletalTrapezoidation::transferEdge(Point from, Point to, const VD::edge_t
|
||||
p0 = p1;
|
||||
v0 = v1;
|
||||
|
||||
if (p1_idx < discretized.size() - 1)
|
||||
{ // Rib for last segment gets introduced outside this function!
|
||||
constexpr bool is_not_next_to_start_or_end = false; // Only ribs at the end of a cell should be skipped
|
||||
graph.makeRib(prev_edge, start_source_point, end_source_point, is_not_next_to_start_or_end);
|
||||
if (p1_idx < discretized.size() - 1) { // Rib for last segment gets introduced outside this function!
|
||||
graph.makeRib(prev_edge, start_source_point, end_source_point);
|
||||
}
|
||||
}
|
||||
assert(prev_edge);
|
||||
@ -462,8 +459,7 @@ void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys)
|
||||
node_t *starting_node = vd_node_to_he_node[starting_voronoi_edge->vertex0()];
|
||||
starting_node->data.distance_to_boundary = 0;
|
||||
|
||||
constexpr bool is_next_to_start_or_end = true;
|
||||
graph.makeRib(prev_edge, start_source_point, end_source_point, is_next_to_start_or_end);
|
||||
graph.makeRib(prev_edge, start_source_point, end_source_point);
|
||||
for (const VD::edge_type* vd_edge = starting_voronoi_edge->next(); vd_edge != ending_voronoi_edge; vd_edge = vd_edge->next()) {
|
||||
assert(vd_edge->is_finite());
|
||||
assert(Geometry::VoronoiUtils::is_in_range<coord_t>(*vd_edge));
|
||||
@ -471,7 +467,7 @@ void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys)
|
||||
Point v1 = Geometry::VoronoiUtils::to_point(vd_edge->vertex0()).cast<coord_t>();
|
||||
Point v2 = Geometry::VoronoiUtils::to_point(vd_edge->vertex1()).cast<coord_t>();
|
||||
transferEdge(v1, v2, *vd_edge, prev_edge, start_source_point, end_source_point, segments);
|
||||
graph.makeRib(prev_edge, start_source_point, end_source_point, vd_edge->next() == ending_voronoi_edge);
|
||||
graph.makeRib(prev_edge, start_source_point, end_source_point);
|
||||
}
|
||||
|
||||
transferEdge(Geometry::VoronoiUtils::to_point(ending_voronoi_edge->vertex0()).cast<coord_t>(), end_source_point, *ending_voronoi_edge, prev_edge, start_source_point, end_source_point, segments);
|
||||
|
@ -572,8 +572,6 @@ protected:
|
||||
* Genrate small segments for local maxima where the beading would only result in a single bead
|
||||
*/
|
||||
void generateLocalMaximaSingleBeads();
|
||||
|
||||
friend bool detect_voronoi_edge_intersecting_input_segment(const VD &voronoi_diagram, const std::vector<Segment> &segments);
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
|
@ -314,7 +314,7 @@ void SkeletalTrapezoidationGraph::collapseSmallEdges(coord_t snap_dist)
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletalTrapezoidationGraph::makeRib(edge_t*& prev_edge, Point start_source_point, Point end_source_point, bool is_next_to_start_or_end)
|
||||
void SkeletalTrapezoidationGraph::makeRib(edge_t*& prev_edge, Point start_source_point, Point end_source_point)
|
||||
{
|
||||
Point p;
|
||||
Line(start_source_point, end_source_point).distance_to_infinite_squared(prev_edge->to->p, &p);
|
||||
|
@ -88,7 +88,7 @@ public:
|
||||
*/
|
||||
void collapseSmallEdges(coord_t snap_dist = 5);
|
||||
|
||||
void makeRib(edge_t*& prev_edge, Point start_source_point, Point end_source_point, bool is_next_to_start_or_end);
|
||||
void makeRib(edge_t*& prev_edge, Point start_source_point, Point end_source_point);
|
||||
|
||||
/*!
|
||||
* Insert a node into the graph and connect it to the input polygon using ribs
|
||||
|
@ -6,7 +6,6 @@
|
||||
#include "WallToolPaths.hpp"
|
||||
|
||||
#include "SkeletalTrapezoidation.hpp"
|
||||
#include "../ClipperUtils.hpp"
|
||||
#include "utils/linearAlg2D.hpp"
|
||||
#include "EdgeGrid.hpp"
|
||||
#include "utils/SparseLineGrid.hpp"
|
||||
@ -14,6 +13,7 @@
|
||||
#include "utils/PolylineStitcher.hpp"
|
||||
#include "SVG.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "ClipperUtils.hpp"
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
@ -758,99 +758,4 @@ bool WallToolPaths::removeEmptyToolPaths(std::vector<VariableWidthLines> &toolpa
|
||||
return toolpaths.empty();
|
||||
}
|
||||
|
||||
/*!
|
||||
* Get the order constraints of the insets when printing walls per region / hole.
|
||||
* Each returned pair consists of adjacent wall lines where the left has an inset_idx one lower than the right.
|
||||
*
|
||||
* Odd walls should always go after their enclosing wall polygons.
|
||||
*
|
||||
* \param outer_to_inner Whether the wall polygons with a lower inset_idx should go before those with a higher one.
|
||||
*/
|
||||
WallToolPaths::ExtrusionLineSet WallToolPaths::getRegionOrder(const std::vector<ExtrusionLine *> &input, const bool outer_to_inner)
|
||||
{
|
||||
ExtrusionLineSet order_requirements;
|
||||
|
||||
// We build a grid where we map toolpath vertex locations to toolpaths,
|
||||
// so that we can easily find which two toolpaths are next to each other,
|
||||
// which is the requirement for there to be an order constraint.
|
||||
//
|
||||
// We use a PointGrid rather than a LineGrid to save on computation time.
|
||||
// In very rare cases two insets might lie next to each other without having neighboring vertices, e.g.
|
||||
// \ .
|
||||
// | / .
|
||||
// | / .
|
||||
// || .
|
||||
// | \ .
|
||||
// | \ .
|
||||
// / .
|
||||
// However, because of how Arachne works this will likely never be the case for two consecutive insets.
|
||||
// On the other hand one could imagine that two consecutive insets of a very large circle
|
||||
// could be simplify()ed such that the remaining vertices of the two insets don't align.
|
||||
// In those cases the order requirement is not captured,
|
||||
// which means that the PathOrderOptimizer *might* result in a violation of the user set path order.
|
||||
// This problem is expected to be not so severe and happen very sparsely.
|
||||
|
||||
coord_t max_line_w = 0u;
|
||||
for (const ExtrusionLine *line : input) // compute max_line_w
|
||||
for (const ExtrusionJunction &junction : *line)
|
||||
max_line_w = std::max(max_line_w, junction.w);
|
||||
if (max_line_w == 0u)
|
||||
return order_requirements;
|
||||
|
||||
struct LineLoc
|
||||
{
|
||||
ExtrusionJunction j;
|
||||
const ExtrusionLine *line;
|
||||
};
|
||||
struct Locator
|
||||
{
|
||||
Point operator()(const LineLoc &elem) { return elem.j.p; }
|
||||
};
|
||||
|
||||
// How much farther two verts may be apart due to corners.
|
||||
// This distance must be smaller than 2, because otherwise
|
||||
// we could create an order requirement between e.g.
|
||||
// wall 2 of one region and wall 3 of another region,
|
||||
// while another wall 3 of the first region would lie in between those two walls.
|
||||
// However, higher values are better against the limitations of using a PointGrid rather than a LineGrid.
|
||||
constexpr float diagonal_extension = 1.9f;
|
||||
const auto searching_radius = coord_t(max_line_w * diagonal_extension);
|
||||
using GridT = SparsePointGrid<LineLoc, Locator>;
|
||||
GridT grid(searching_radius);
|
||||
|
||||
for (const ExtrusionLine *line : input)
|
||||
for (const ExtrusionJunction &junction : *line) grid.insert(LineLoc{junction, line});
|
||||
for (const std::pair<const SquareGrid::GridPoint, LineLoc> &pair : grid) {
|
||||
const LineLoc &lineloc_here = pair.second;
|
||||
const ExtrusionLine *here = lineloc_here.line;
|
||||
Point loc_here = pair.second.j.p;
|
||||
std::vector<LineLoc> nearby_verts = grid.getNearby(loc_here, searching_radius);
|
||||
for (const LineLoc &lineloc_nearby : nearby_verts) {
|
||||
const ExtrusionLine *nearby = lineloc_nearby.line;
|
||||
if (nearby == here)
|
||||
continue;
|
||||
if (nearby->inset_idx == here->inset_idx)
|
||||
continue;
|
||||
if (nearby->inset_idx > here->inset_idx + 1)
|
||||
continue; // not directly adjacent
|
||||
if (here->inset_idx > nearby->inset_idx + 1)
|
||||
continue; // not directly adjacent
|
||||
if (!shorter_then(loc_here - lineloc_nearby.j.p, (lineloc_here.j.w + lineloc_nearby.j.w) / 2 * diagonal_extension))
|
||||
continue; // points are too far away from each other
|
||||
if (here->is_odd || nearby->is_odd) {
|
||||
if (here->is_odd && !nearby->is_odd && nearby->inset_idx < here->inset_idx)
|
||||
order_requirements.emplace(std::make_pair(nearby, here));
|
||||
if (nearby->is_odd && !here->is_odd && here->inset_idx < nearby->inset_idx)
|
||||
order_requirements.emplace(std::make_pair(here, nearby));
|
||||
} else if ((nearby->inset_idx < here->inset_idx) == outer_to_inner) {
|
||||
order_requirements.emplace(std::make_pair(nearby, here));
|
||||
} else {
|
||||
assert((nearby->inset_idx > here->inset_idx) == outer_to_inner);
|
||||
order_requirements.emplace(std::make_pair(here, nearby));
|
||||
}
|
||||
}
|
||||
}
|
||||
return order_requirements;
|
||||
}
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
|
@ -75,15 +75,6 @@ public:
|
||||
static bool removeEmptyToolPaths(std::vector<VariableWidthLines> &toolpaths);
|
||||
|
||||
using ExtrusionLineSet = ankerl::unordered_dense::set<std::pair<const ExtrusionLine *, const ExtrusionLine *>, boost::hash<std::pair<const ExtrusionLine *, const ExtrusionLine *>>>;
|
||||
/*!
|
||||
* Get the order constraints of the insets when printing walls per region / hole.
|
||||
* Each returned pair consists of adjacent wall lines where the left has an inset_idx one lower than the right.
|
||||
*
|
||||
* Odd walls should always go after their enclosing wall polygons.
|
||||
*
|
||||
* \param outer_to_inner Whether the wall polygons with a lower inset_idx should go before those with a higher one.
|
||||
*/
|
||||
static ExtrusionLineSet getRegionOrder(const std::vector<ExtrusionLine *> &input, bool outer_to_inner);
|
||||
|
||||
protected:
|
||||
/*!
|
||||
|
@ -1,18 +0,0 @@
|
||||
//Copyright (c) 2020 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include "ExtrusionJunction.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
bool ExtrusionJunction::operator ==(const ExtrusionJunction& other) const
|
||||
{
|
||||
return p == other.p
|
||||
&& w == other.w
|
||||
&& perimeter_index == other.perimeter_index;
|
||||
}
|
||||
|
||||
ExtrusionJunction::ExtrusionJunction(const Point p, const coord_t w, const coord_t perimeter_index) : p(p), w(w), perimeter_index(perimeter_index) {}
|
||||
|
||||
}
|
@ -37,9 +37,11 @@ struct ExtrusionJunction
|
||||
*/
|
||||
size_t perimeter_index;
|
||||
|
||||
ExtrusionJunction(const Point p, const coord_t w, const coord_t perimeter_index);
|
||||
ExtrusionJunction(const Point p, const coord_t w, const coord_t perimeter_index) : p(p), w(w), perimeter_index(perimeter_index) {}
|
||||
|
||||
bool operator==(const ExtrusionJunction& other) const;
|
||||
bool operator==(const ExtrusionJunction &other) const {
|
||||
return p == other.p && w == other.w && perimeter_index == other.perimeter_index;
|
||||
}
|
||||
};
|
||||
|
||||
inline Point operator-(const ExtrusionJunction& a, const ExtrusionJunction& b)
|
||||
|
@ -29,15 +29,6 @@ int64_t ExtrusionLine::getLength() const
|
||||
return len;
|
||||
}
|
||||
|
||||
coord_t ExtrusionLine::getMinimalWidth() const
|
||||
{
|
||||
return std::min_element(junctions.cbegin(), junctions.cend(),
|
||||
[](const ExtrusionJunction& l, const ExtrusionJunction& r)
|
||||
{
|
||||
return l.w < r.w;
|
||||
})->w;
|
||||
}
|
||||
|
||||
void ExtrusionLine::simplify(const int64_t smallest_line_segment_squared, const int64_t allowed_error_distance_squared, const int64_t maximum_extrusion_area_deviation)
|
||||
{
|
||||
const size_t min_path_size = is_closed ? 3 : 2;
|
||||
@ -236,9 +227,10 @@ bool ExtrusionLine::is_contour() const
|
||||
return poly.is_clockwise();
|
||||
}
|
||||
|
||||
double ExtrusionLine::area() const
|
||||
{
|
||||
assert(this->is_closed);
|
||||
double ExtrusionLine::area() const {
|
||||
if (!this->is_closed)
|
||||
return 0.;
|
||||
|
||||
double a = 0.;
|
||||
if (this->junctions.size() >= 3) {
|
||||
Vec2d p1 = this->junctions.back().p.cast<double>();
|
||||
@ -248,9 +240,25 @@ double ExtrusionLine::area() const
|
||||
p1 = p2;
|
||||
}
|
||||
}
|
||||
|
||||
return 0.5 * a;
|
||||
}
|
||||
|
||||
Points to_points(const ExtrusionLine &extrusion_line) {
|
||||
Points points;
|
||||
points.reserve(extrusion_line.junctions.size());
|
||||
for (const ExtrusionJunction &junction : extrusion_line.junctions)
|
||||
points.emplace_back(junction.p);
|
||||
return points;
|
||||
}
|
||||
|
||||
BoundingBox get_extents(const ExtrusionLine &extrusion_line) {
|
||||
BoundingBox bbox;
|
||||
for (const ExtrusionJunction &junction : extrusion_line.junctions)
|
||||
bbox.merge(junction.p);
|
||||
return bbox;
|
||||
}
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
|
||||
namespace Slic3r {
|
||||
|
@ -136,11 +136,6 @@ struct ExtrusionLine
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Get the minimal width of this path
|
||||
*/
|
||||
coord_t getMinimalWidth() const;
|
||||
|
||||
/*!
|
||||
* Removes vertices of the ExtrusionLines to make sure that they are not too high
|
||||
* resolution.
|
||||
@ -192,6 +187,8 @@ struct ExtrusionLine
|
||||
bool is_contour() const;
|
||||
|
||||
double area() const;
|
||||
|
||||
bool is_external_perimeter() const { return this->inset_idx == 0; }
|
||||
};
|
||||
|
||||
static inline Slic3r::ThickPolyline to_thick_polyline(const Arachne::ExtrusionLine &line_junctions)
|
||||
@ -237,6 +234,7 @@ static inline Slic3r::ThickPolyline to_thick_polyline(const ClipperLib_Z::Path &
|
||||
static inline Polygon to_polygon(const ExtrusionLine &line)
|
||||
{
|
||||
Polygon out;
|
||||
assert(line.is_closed);
|
||||
assert(line.junctions.size() >= 3);
|
||||
assert(line.junctions.front().p == line.junctions.back().p);
|
||||
out.points.reserve(line.junctions.size() - 1);
|
||||
@ -245,15 +243,11 @@ static inline Polygon to_polygon(const ExtrusionLine &line)
|
||||
return out;
|
||||
}
|
||||
|
||||
#if 0
|
||||
static BoundingBox get_extents(const ExtrusionLine &extrusion_line)
|
||||
{
|
||||
BoundingBox bbox;
|
||||
for (const ExtrusionJunction &junction : extrusion_line.junctions)
|
||||
bbox.merge(junction.p);
|
||||
return bbox;
|
||||
}
|
||||
Points to_points(const ExtrusionLine &extrusion_line);
|
||||
|
||||
BoundingBox get_extents(const ExtrusionLine &extrusion_line);
|
||||
|
||||
#if 0
|
||||
static BoundingBox get_extents(const std::vector<ExtrusionLine> &extrusion_lines)
|
||||
{
|
||||
BoundingBox bbox;
|
||||
@ -272,15 +266,6 @@ static BoundingBox get_extents(const std::vector<const ExtrusionLine *> &extrusi
|
||||
return bbox;
|
||||
}
|
||||
|
||||
static Points to_points(const ExtrusionLine &extrusion_line)
|
||||
{
|
||||
Points points;
|
||||
points.reserve(extrusion_line.junctions.size());
|
||||
for (const ExtrusionJunction &junction : extrusion_line.junctions)
|
||||
points.emplace_back(junction.p);
|
||||
return points;
|
||||
}
|
||||
|
||||
static std::vector<Points> to_points(const std::vector<const ExtrusionLine *> &extrusion_lines)
|
||||
{
|
||||
std::vector<Points> points;
|
||||
@ -293,6 +278,8 @@ static std::vector<Points> to_points(const std::vector<const ExtrusionLine *> &e
|
||||
#endif
|
||||
|
||||
using VariableWidthLines = std::vector<ExtrusionLine>; //<! The ExtrusionLines generated by libArachne
|
||||
using Perimeter = VariableWidthLines;
|
||||
using Perimeters = std::vector<Perimeter>;
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
|
||||
|
@ -39,16 +39,6 @@ public:
|
||||
*/
|
||||
void insert(const Elem &elem);
|
||||
|
||||
/*!
|
||||
* Get just any element that's within a certain radius of a point.
|
||||
*
|
||||
* Rather than giving a vector of nearby elements, this function just gives
|
||||
* a single element, any element, in no particular order.
|
||||
* \param query_pt The point to query for an object nearby.
|
||||
* \param radius The radius of what is considered "nearby".
|
||||
*/
|
||||
const ElemT *getAnyNearby(const Point &query_pt, coord_t radius);
|
||||
|
||||
protected:
|
||||
using GridPoint = typename SparseGrid<ElemT>::GridPoint;
|
||||
|
||||
@ -68,22 +58,6 @@ void SparsePointGrid<ElemT, Locator>::insert(const Elem &elem)
|
||||
SparseGrid<ElemT>::m_grid.emplace(grid_loc, elem);
|
||||
}
|
||||
|
||||
template<class ElemT, class Locator>
|
||||
const ElemT *SparsePointGrid<ElemT, Locator>::getAnyNearby(const Point &query_pt, coord_t radius)
|
||||
{
|
||||
const ElemT *ret = nullptr;
|
||||
const std::function<bool(const ElemT &)> &process_func = [&ret, query_pt, radius, this](const ElemT &maybe_nearby) {
|
||||
if (shorter_then(m_locator(maybe_nearby) - query_pt, radius)) {
|
||||
ret = &maybe_nearby;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
SparseGrid<ElemT>::processNearby(query_pt, radius, process_func);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
|
||||
#endif // UTILS_SPARSE_POINT_GRID_H
|
||||
|
@ -2,7 +2,6 @@
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include "SquareGrid.hpp"
|
||||
#include "../../Point.hpp"
|
||||
|
||||
using namespace Slic3r::Arachne;
|
||||
|
||||
|
@ -177,6 +177,24 @@ set(SLIC3R_SOURCES
|
||||
GCode/SpiralVase.hpp
|
||||
GCode/SeamPlacer.cpp
|
||||
GCode/SeamPlacer.hpp
|
||||
GCode/SeamChoice.cpp
|
||||
GCode/SeamChoice.hpp
|
||||
GCode/SeamPerimeters.cpp
|
||||
GCode/SeamPerimeters.hpp
|
||||
GCode/SeamShells.cpp
|
||||
GCode/SeamShells.hpp
|
||||
GCode/SeamGeometry.cpp
|
||||
GCode/SeamGeometry.hpp
|
||||
GCode/SeamAligned.cpp
|
||||
GCode/SeamAligned.hpp
|
||||
GCode/SeamRear.cpp
|
||||
GCode/SeamRear.hpp
|
||||
GCode/SeamRandom.cpp
|
||||
GCode/SeamRandom.hpp
|
||||
GCode/SeamPainting.cpp
|
||||
GCode/SeamPainting.hpp
|
||||
GCode/ModelVisibility.cpp
|
||||
GCode/ModelVisibility.hpp
|
||||
GCode/SmoothPath.cpp
|
||||
GCode/SmoothPath.hpp
|
||||
GCode/ToolOrdering.cpp
|
||||
@ -482,7 +500,6 @@ set(SLIC3R_SOURCES
|
||||
Arachne/BeadingStrategy/WideningBeadingStrategy.hpp
|
||||
Arachne/BeadingStrategy/WideningBeadingStrategy.cpp
|
||||
Arachne/utils/ExtrusionJunction.hpp
|
||||
Arachne/utils/ExtrusionJunction.cpp
|
||||
Arachne/utils/ExtrusionLine.hpp
|
||||
Arachne/utils/ExtrusionLine.cpp
|
||||
Arachne/utils/HalfEdge.hpp
|
||||
@ -500,6 +517,8 @@ set(SLIC3R_SOURCES
|
||||
Geometry/Voronoi.cpp
|
||||
Geometry/VoronoiUtils.hpp
|
||||
Geometry/VoronoiUtils.cpp
|
||||
Arachne/PerimeterOrder.hpp
|
||||
Arachne/PerimeterOrder.cpp
|
||||
Arachne/SkeletalTrapezoidation.hpp
|
||||
Arachne/SkeletalTrapezoidation.cpp
|
||||
Arachne/SkeletalTrapezoidationEdge.hpp
|
||||
|
@ -1234,7 +1234,9 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail
|
||||
|
||||
// Collect custom seam data from all objects.
|
||||
std::function<void(void)> throw_if_canceled_func = [&print]() { print.throw_if_canceled();};
|
||||
m_seam_placer.init(print, throw_if_canceled_func);
|
||||
|
||||
const Seams::Params params{Seams::Placer::get_params(print.full_print_config())};
|
||||
m_seam_placer.init(print.objects(), params, throw_if_canceled_func);
|
||||
|
||||
if (! (has_wipe_tower && print.config().single_extruder_multi_material_priming)) {
|
||||
// Set initial extruder only after custom start G-code.
|
||||
@ -2980,7 +2982,7 @@ std::string GCodeGenerator::extrude_loop(const ExtrusionLoop &loop_src, const GC
|
||||
Point seam_point = this->last_position.has_value() ? *this->last_position : Point::Zero();
|
||||
if (!m_config.spiral_vase && comment_is_perimeter(description)) {
|
||||
assert(m_layer != nullptr);
|
||||
seam_point = m_seam_placer.place_seam(m_layer, loop_src, m_config.external_perimeters_first, seam_point);
|
||||
seam_point = m_seam_placer.place_seam(m_layer, loop_src, seam_point);
|
||||
}
|
||||
// Because the G-code export has 1um resolution, don't generate segments shorter than 1.5 microns,
|
||||
// thus empty path segments will not be produced by G-code export.
|
||||
|
@ -353,8 +353,7 @@ private:
|
||||
std::string set_extruder(unsigned int extruder_id, double print_z);
|
||||
bool line_distancer_is_required(const std::vector<unsigned int>& extruder_ids);
|
||||
|
||||
// Cache for custom seam enforcers/blockers for each layer.
|
||||
SeamPlacer m_seam_placer;
|
||||
Seams::Placer m_seam_placer;
|
||||
|
||||
/* Origin of print coordinates expressed in unscaled G-code coordinates.
|
||||
This affects the input arguments supplied to the extrude*() and travel_to()
|
||||
|
301
src/libslic3r/GCode/ModelVisibility.cpp
Normal file
301
src/libslic3r/GCode/ModelVisibility.cpp
Normal file
@ -0,0 +1,301 @@
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#include "libslic3r/ShortEdgeCollapse.hpp"
|
||||
#include "libslic3r/GCode/ModelVisibility.hpp"
|
||||
#include "libslic3r/AABBTreeIndirect.hpp"
|
||||
|
||||
namespace Slic3r::ModelInfo {
|
||||
namespace Impl {
|
||||
|
||||
CoordinateFunctor::CoordinateFunctor(const std::vector<Vec3f> *coords) : coordinates(coords) {}
|
||||
CoordinateFunctor::CoordinateFunctor() : coordinates(nullptr) {}
|
||||
|
||||
const float &CoordinateFunctor::operator()(size_t idx, size_t dim) const {
|
||||
return coordinates->operator[](idx)[dim];
|
||||
}
|
||||
|
||||
|
||||
template<typename T> int sgn(T val) {
|
||||
return int(T(0) < val) - int(val < T(0));
|
||||
}
|
||||
|
||||
/// Coordinate frame
|
||||
class Frame {
|
||||
public:
|
||||
Frame() {
|
||||
mX = Vec3f(1, 0, 0);
|
||||
mY = Vec3f(0, 1, 0);
|
||||
mZ = Vec3f(0, 0, 1);
|
||||
}
|
||||
|
||||
Frame(const Vec3f &x, const Vec3f &y, const Vec3f &z) :
|
||||
mX(x), mY(y), mZ(z) {
|
||||
}
|
||||
|
||||
void set_from_z(const Vec3f &z) {
|
||||
mZ = z.normalized();
|
||||
Vec3f tmpZ = mZ;
|
||||
Vec3f tmpX = (std::abs(tmpZ.x()) > 0.99f) ? Vec3f(0, 1, 0) : Vec3f(1, 0, 0);
|
||||
mY = (tmpZ.cross(tmpX)).normalized();
|
||||
mX = mY.cross(tmpZ);
|
||||
}
|
||||
|
||||
Vec3f to_world(const Vec3f &a) const {
|
||||
return a.x() * mX + a.y() * mY + a.z() * mZ;
|
||||
}
|
||||
|
||||
Vec3f to_local(const Vec3f &a) const {
|
||||
return Vec3f(mX.dot(a), mY.dot(a), mZ.dot(a));
|
||||
}
|
||||
|
||||
const Vec3f& binormal() const {
|
||||
return mX;
|
||||
}
|
||||
|
||||
const Vec3f& tangent() const {
|
||||
return mY;
|
||||
}
|
||||
|
||||
const Vec3f& normal() const {
|
||||
return mZ;
|
||||
}
|
||||
|
||||
private:
|
||||
Vec3f mX, mY, mZ;
|
||||
};
|
||||
|
||||
Vec3f sample_sphere_uniform(const Vec2f &samples) {
|
||||
float term1 = 2.0f * float(PI) * samples.x();
|
||||
float term2 = 2.0f * sqrt(samples.y() - samples.y() * samples.y());
|
||||
return {cos(term1) * term2, sin(term1) * term2,
|
||||
1.0f - 2.0f * samples.y()};
|
||||
}
|
||||
|
||||
Vec3f sample_hemisphere_uniform(const Vec2f &samples) {
|
||||
float term1 = 2.0f * float(PI) * samples.x();
|
||||
float term2 = 2.0f * sqrt(samples.y() - samples.y() * samples.y());
|
||||
return {cos(term1) * term2, sin(term1) * term2,
|
||||
abs(1.0f - 2.0f * samples.y())};
|
||||
}
|
||||
|
||||
Vec3f sample_power_cosine_hemisphere(const Vec2f &samples, float power) {
|
||||
float term1 = 2.f * float(PI) * samples.x();
|
||||
float term2 = pow(samples.y(), 1.f / (power + 1.f));
|
||||
float term3 = sqrt(1.f - term2 * term2);
|
||||
|
||||
return Vec3f(cos(term1) * term3, sin(term1) * term3, term2);
|
||||
}
|
||||
|
||||
std::vector<float> raycast_visibility(
|
||||
const AABBTreeIndirect::Tree<3, float> &raycasting_tree,
|
||||
const indexed_triangle_set &triangles,
|
||||
const TriangleSetSamples &samples,
|
||||
size_t negative_volumes_start_index,
|
||||
const Visibility::Params ¶ms
|
||||
) {
|
||||
BOOST_LOG_TRIVIAL(debug)
|
||||
<< "SeamPlacer: raycast visibility of " << samples.positions.size() << " samples over " << triangles.indices.size()
|
||||
<< " triangles: end";
|
||||
|
||||
//prepare uniform samples of a hemisphere
|
||||
float step_size = 1.0f / params.sqr_rays_per_sample_point;
|
||||
std::vector<Vec3f> precomputed_sample_directions(
|
||||
params.sqr_rays_per_sample_point * params.sqr_rays_per_sample_point);
|
||||
for (size_t x_idx = 0; x_idx < params.sqr_rays_per_sample_point; ++x_idx) {
|
||||
float sample_x = x_idx * step_size + step_size / 2.0;
|
||||
for (size_t y_idx = 0; y_idx < params.sqr_rays_per_sample_point; ++y_idx) {
|
||||
size_t dir_index = x_idx * params.sqr_rays_per_sample_point + y_idx;
|
||||
float sample_y = y_idx * step_size + step_size / 2.0;
|
||||
precomputed_sample_directions[dir_index] = sample_hemisphere_uniform( { sample_x, sample_y });
|
||||
}
|
||||
}
|
||||
|
||||
bool model_contains_negative_parts = negative_volumes_start_index < triangles.indices.size();
|
||||
|
||||
std::vector<float> result(samples.positions.size());
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, result.size()),
|
||||
[&triangles, &precomputed_sample_directions, model_contains_negative_parts, negative_volumes_start_index,
|
||||
&raycasting_tree, &result, &samples, ¶ms](tbb::blocked_range<size_t> r) {
|
||||
// Maintaining hits memory outside of the loop, so it does not have to be reallocated for each query.
|
||||
std::vector<igl::Hit> hits;
|
||||
for (size_t s_idx = r.begin(); s_idx < r.end(); ++s_idx) {
|
||||
result[s_idx] = 1.0f;
|
||||
const float decrease_step = 1.0f
|
||||
/ (params.sqr_rays_per_sample_point * params.sqr_rays_per_sample_point);
|
||||
|
||||
const Vec3f ¢er = samples.positions[s_idx];
|
||||
const Vec3f &normal = samples.normals[s_idx];
|
||||
// apply the local direction via Frame struct - the local_dir is with respect to +Z being forward
|
||||
Frame f;
|
||||
f.set_from_z(normal);
|
||||
|
||||
for (const auto &dir : precomputed_sample_directions) {
|
||||
Vec3f final_ray_dir = (f.to_world(dir));
|
||||
if (!model_contains_negative_parts) {
|
||||
igl::Hit hitpoint;
|
||||
// FIXME: This AABBTTreeIndirect query will not compile for float ray origin and
|
||||
// direction.
|
||||
Vec3d final_ray_dir_d = final_ray_dir.cast<double>();
|
||||
Vec3d ray_origin_d = (center + normal * 0.01f).cast<double>(); // start above surface.
|
||||
bool hit = AABBTreeIndirect::intersect_ray_first_hit(triangles.vertices,
|
||||
triangles.indices, raycasting_tree, ray_origin_d, final_ray_dir_d, hitpoint);
|
||||
if (hit && its_face_normal(triangles, hitpoint.id).dot(final_ray_dir) <= 0) {
|
||||
result[s_idx] -= decrease_step;
|
||||
}
|
||||
} else { //TODO improve logic for order based boolean operations - consider order of volumes
|
||||
bool casting_from_negative_volume = samples.triangle_indices[s_idx]
|
||||
>= negative_volumes_start_index;
|
||||
|
||||
Vec3d ray_origin_d = (center + normal * 0.01f).cast<double>(); // start above surface.
|
||||
if (casting_from_negative_volume) { // if casting from negative volume face, invert direction, change start pos
|
||||
final_ray_dir = -1.0 * final_ray_dir;
|
||||
ray_origin_d = (center - normal * 0.01f).cast<double>();
|
||||
}
|
||||
Vec3d final_ray_dir_d = final_ray_dir.cast<double>();
|
||||
bool some_hit = AABBTreeIndirect::intersect_ray_all_hits(triangles.vertices,
|
||||
triangles.indices, raycasting_tree,
|
||||
ray_origin_d, final_ray_dir_d, hits);
|
||||
if (some_hit) {
|
||||
int counter = 0;
|
||||
// NOTE: iterating in reverse, from the last hit for one simple reason: We know the state of the ray at that point;
|
||||
// It cannot be inside model, and it cannot be inside negative volume
|
||||
for (int hit_index = int(hits.size()) - 1; hit_index >= 0; --hit_index) {
|
||||
Vec3f face_normal = its_face_normal(triangles, hits[hit_index].id);
|
||||
if (hits[hit_index].id >= int(negative_volumes_start_index)) { //negative volume hit
|
||||
counter -= sgn(face_normal.dot(final_ray_dir)); // if volume face aligns with ray dir, we are leaving negative space
|
||||
// which in reverse hit analysis means, that we are entering negative space :) and vice versa
|
||||
} else {
|
||||
counter += sgn(face_normal.dot(final_ray_dir));
|
||||
}
|
||||
}
|
||||
if (counter == 0) {
|
||||
result[s_idx] -= decrease_step;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug)
|
||||
<< "SeamPlacer: raycast visibility of " << samples.positions.size() << " samples over " << triangles.indices.size()
|
||||
<< " triangles: end";
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
Visibility::Visibility(
|
||||
const Transform3d &obj_transform,
|
||||
const ModelVolumePtrs &volumes,
|
||||
const Params ¶ms,
|
||||
const std::function<void(void)> &throw_if_canceled
|
||||
) {
|
||||
BOOST_LOG_TRIVIAL(debug)
|
||||
<< "SeamPlacer: gather occlusion meshes: start";
|
||||
indexed_triangle_set triangle_set;
|
||||
indexed_triangle_set negative_volumes_set;
|
||||
//add all parts
|
||||
for (const ModelVolume *model_volume : volumes) {
|
||||
if (model_volume->type() == ModelVolumeType::MODEL_PART
|
||||
|| model_volume->type() == ModelVolumeType::NEGATIVE_VOLUME) {
|
||||
auto model_transformation = model_volume->get_matrix();
|
||||
indexed_triangle_set model_its = model_volume->mesh().its;
|
||||
its_transform(model_its, model_transformation);
|
||||
if (model_volume->type() == ModelVolumeType::MODEL_PART) {
|
||||
its_merge(triangle_set, model_its);
|
||||
} else {
|
||||
its_merge(negative_volumes_set, model_its);
|
||||
}
|
||||
}
|
||||
}
|
||||
throw_if_canceled();
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug)
|
||||
<< "SeamPlacer: gather occlusion meshes: end";
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug)
|
||||
<< "SeamPlacer: decimate: start";
|
||||
its_short_edge_collpase(triangle_set, params.fast_decimation_triangle_count_target);
|
||||
its_short_edge_collpase(negative_volumes_set, params.fast_decimation_triangle_count_target);
|
||||
|
||||
size_t negative_volumes_start_index = triangle_set.indices.size();
|
||||
its_merge(triangle_set, negative_volumes_set);
|
||||
its_transform(triangle_set, obj_transform);
|
||||
BOOST_LOG_TRIVIAL(debug)
|
||||
<< "SeamPlacer: decimate: end";
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug)
|
||||
<< "SeamPlacer: Compute visibility sample points: start";
|
||||
|
||||
this->mesh_samples = sample_its_uniform_parallel(params.raycasting_visibility_samples_count,
|
||||
triangle_set);
|
||||
this->mesh_samples_coordinate_functor = Impl::CoordinateFunctor(&this->mesh_samples.positions);
|
||||
this->mesh_samples_tree = KDTreeIndirect<3, float, Impl::CoordinateFunctor>(this->mesh_samples_coordinate_functor,
|
||||
this->mesh_samples.positions.size());
|
||||
|
||||
// The following code determines search area for random visibility samples on the mesh when calculating visibility of each perimeter point
|
||||
// number of random samples in the given radius (area) is approximately poisson distribution
|
||||
// to compute ideal search radius (area), we use exponential distribution (complementary distr to poisson)
|
||||
// parameters of exponential distribution to compute area that will have with probability="probability" more than given number of samples="samples"
|
||||
float probability = 0.9f;
|
||||
float samples = 4;
|
||||
float density = params.raycasting_visibility_samples_count / this->mesh_samples.total_area;
|
||||
// exponential probability distrubtion function is : f(x) = P(X > x) = e^(l*x) where l is the rate parameter (computed as 1/u where u is mean value)
|
||||
// probability that sampled area A with S samples contains more than samples count:
|
||||
// P(S > samples in A) = e^-(samples/(density*A)); express A:
|
||||
float search_area = samples / (-logf(probability) * density);
|
||||
float search_radius = sqrt(search_area / PI);
|
||||
this->mesh_samples_radius = search_radius;
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug)
|
||||
<< "SeamPlacer: Compute visiblity sample points: end";
|
||||
throw_if_canceled();
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug)
|
||||
<< "SeamPlacer: Mesh sample raidus: " << this->mesh_samples_radius;
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug)
|
||||
<< "SeamPlacer: build AABB tree: start";
|
||||
auto raycasting_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(triangle_set.vertices,
|
||||
triangle_set.indices);
|
||||
|
||||
throw_if_canceled();
|
||||
BOOST_LOG_TRIVIAL(debug)
|
||||
<< "SeamPlacer: build AABB tree: end";
|
||||
this->mesh_samples_visibility = Impl::raycast_visibility(raycasting_tree, triangle_set, this->mesh_samples,
|
||||
negative_volumes_start_index, params);
|
||||
throw_if_canceled();
|
||||
}
|
||||
|
||||
float Visibility::calculate_point_visibility(const Vec3f &position) const {
|
||||
std::vector<size_t> points = find_nearby_points(mesh_samples_tree, position, mesh_samples_radius);
|
||||
if (points.empty()) {
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
auto compute_dist_to_plane = [](const Vec3f &position, const Vec3f &plane_origin,
|
||||
const Vec3f &plane_normal) {
|
||||
Vec3f orig_to_point = position - plane_origin;
|
||||
return std::abs(orig_to_point.dot(plane_normal));
|
||||
};
|
||||
|
||||
float total_weight = 0;
|
||||
float total_visibility = 0;
|
||||
for (size_t i = 0; i < points.size(); ++i) {
|
||||
size_t sample_idx = points[i];
|
||||
|
||||
Vec3f sample_point = this->mesh_samples.positions[sample_idx];
|
||||
Vec3f sample_normal = this->mesh_samples.normals[sample_idx];
|
||||
|
||||
float weight = mesh_samples_radius -
|
||||
compute_dist_to_plane(position, sample_point, sample_normal);
|
||||
weight += (mesh_samples_radius - (position - sample_point).norm());
|
||||
total_visibility += weight * mesh_samples_visibility[sample_idx];
|
||||
total_weight += weight;
|
||||
}
|
||||
|
||||
return total_visibility / total_weight;
|
||||
}
|
||||
|
||||
}
|
51
src/libslic3r/GCode/ModelVisibility.hpp
Normal file
51
src/libslic3r/GCode/ModelVisibility.hpp
Normal file
@ -0,0 +1,51 @@
|
||||
#ifndef libslic3r_ModelVisibility_hpp_
|
||||
#define libslic3r_ModelVisibility_hpp_
|
||||
|
||||
#include "libslic3r/KDTreeIndirect.hpp"
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/TriangleSetSampling.hpp"
|
||||
|
||||
namespace Slic3r::ModelInfo {
|
||||
namespace Impl {
|
||||
|
||||
struct CoordinateFunctor
|
||||
{
|
||||
const std::vector<Vec3f> *coordinates;
|
||||
CoordinateFunctor(const std::vector<Vec3f> *coords);
|
||||
CoordinateFunctor();
|
||||
|
||||
const float &operator()(size_t idx, size_t dim) const;
|
||||
};
|
||||
} // namespace Impl
|
||||
|
||||
struct Visibility
|
||||
{
|
||||
struct Params
|
||||
{
|
||||
// Number of samples generated on the mesh. There are
|
||||
// sqr_rays_per_sample_point*sqr_rays_per_sample_point rays casted from each samples
|
||||
size_t raycasting_visibility_samples_count{};
|
||||
size_t fast_decimation_triangle_count_target{};
|
||||
// square of number of rays per sample point
|
||||
size_t sqr_rays_per_sample_point{};
|
||||
};
|
||||
|
||||
Visibility(
|
||||
const Transform3d &obj_transform,
|
||||
const ModelVolumePtrs &volumes,
|
||||
const Params ¶ms,
|
||||
const std::function<void(void)> &throw_if_canceled
|
||||
);
|
||||
|
||||
TriangleSetSamples mesh_samples;
|
||||
std::vector<float> mesh_samples_visibility;
|
||||
Impl::CoordinateFunctor mesh_samples_coordinate_functor;
|
||||
KDTreeIndirect<3, float, Impl::CoordinateFunctor> mesh_samples_tree{Impl::CoordinateFunctor{}};
|
||||
float mesh_samples_radius;
|
||||
|
||||
float calculate_point_visibility(const Vec3f &position) const;
|
||||
};
|
||||
|
||||
} // namespace Slic3r::ModelInfo
|
||||
#endif // libslic3r_ModelVisibility_hpp_
|
533
src/libslic3r/GCode/SeamAligned.cpp
Normal file
533
src/libslic3r/GCode/SeamAligned.cpp
Normal file
@ -0,0 +1,533 @@
|
||||
#include "libslic3r/GCode/SeamAligned.hpp"
|
||||
#include "libslic3r/GCode/SeamGeometry.hpp"
|
||||
#include "libslic3r/GCode/ModelVisibility.hpp"
|
||||
#include <fstream>
|
||||
|
||||
namespace Slic3r::Seams::Aligned {
|
||||
using Perimeters::PointType;
|
||||
using Perimeters::PointClassification;
|
||||
|
||||
namespace Impl {
|
||||
const Perimeters::Perimeter::PointTrees &pick_trees(
|
||||
const Perimeters::Perimeter &perimeter, const PointType point_type
|
||||
) {
|
||||
switch (point_type) {
|
||||
case PointType::enforcer: return perimeter.enforced_points;
|
||||
case PointType::blocker: return perimeter.blocked_points;
|
||||
case PointType::common: return perimeter.common_points;
|
||||
}
|
||||
throw std::runtime_error("Point trees for point type do not exist.");
|
||||
}
|
||||
|
||||
const Perimeters::Perimeter::OptionalPointTree &pick_tree(
|
||||
const Perimeters::Perimeter::PointTrees &point_trees,
|
||||
const PointClassification &point_classification
|
||||
) {
|
||||
switch (point_classification) {
|
||||
case PointClassification::overhang: return point_trees.overhanging_points;
|
||||
case PointClassification::embedded: return point_trees.embedded_points;
|
||||
case PointClassification::common: return point_trees.common_points;
|
||||
}
|
||||
throw std::runtime_error("Point tree for classification does not exist.");
|
||||
}
|
||||
|
||||
unsigned point_value(PointType point_type, PointClassification point_classification) {
|
||||
// Better be explicit than smart.
|
||||
switch (point_type) {
|
||||
case PointType::enforcer:
|
||||
switch (point_classification) {
|
||||
case PointClassification::embedded: return 9;
|
||||
case PointClassification::common: return 8;
|
||||
case PointClassification::overhang: return 7;
|
||||
}
|
||||
case PointType::common:
|
||||
switch (point_classification) {
|
||||
case PointClassification::embedded: return 6;
|
||||
case PointClassification::common: return 5;
|
||||
case PointClassification::overhang: return 4;
|
||||
}
|
||||
case PointType::blocker:
|
||||
switch (point_classification) {
|
||||
case PointClassification::embedded: return 3;
|
||||
case PointClassification::common: return 2;
|
||||
case PointClassification::overhang: return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
SeamChoice pick_seam_option(const Perimeters::Perimeter &perimeter, const SeamOptions &options) {
|
||||
const std::vector<PointType> &types{perimeter.point_types};
|
||||
const std::vector<PointClassification> &classifications{perimeter.point_classifications};
|
||||
const std::vector<Vec2d> &positions{perimeter.positions};
|
||||
|
||||
unsigned closeset_point_value =
|
||||
point_value(types.at(options.closest), classifications[options.closest]);
|
||||
|
||||
if (options.snapped) {
|
||||
unsigned snapped_point_value =
|
||||
point_value(types.at(*options.snapped), classifications[*options.snapped]);
|
||||
if (snapped_point_value >= closeset_point_value) {
|
||||
const Vec2d position{positions.at(*options.snapped)};
|
||||
return {*options.snapped, *options.snapped, position};
|
||||
}
|
||||
}
|
||||
|
||||
unsigned adjacent_point_value =
|
||||
point_value(types.at(options.adjacent), classifications[options.adjacent]);
|
||||
if (adjacent_point_value < closeset_point_value) {
|
||||
const Vec2d position = positions[options.closest];
|
||||
return {options.closest, options.closest, position};
|
||||
}
|
||||
|
||||
const std::size_t next_index{options.adjacent_forward ? options.adjacent : options.closest};
|
||||
const std::size_t previous_index{options.adjacent_forward ? options.closest : options.adjacent};
|
||||
return {previous_index, next_index, options.on_edge};
|
||||
}
|
||||
|
||||
std::optional<std::size_t> snap_to_angle(
|
||||
const Vec2d &point,
|
||||
const std::size_t search_start,
|
||||
const Perimeters::Perimeter &perimeter,
|
||||
const double max_detour
|
||||
) {
|
||||
using Perimeters::AngleType;
|
||||
const std::vector<Vec2d> &positions{perimeter.positions};
|
||||
const std::vector<AngleType> &angle_types{perimeter.angle_types};
|
||||
|
||||
std::optional<std::size_t> match;
|
||||
double min_distance{std::numeric_limits<double>::infinity()};
|
||||
AngleType angle_type{AngleType::convex};
|
||||
|
||||
const auto visitor{[&](const std::size_t index) {
|
||||
const double distance = (positions[index] - point).norm();
|
||||
if (distance > max_detour) {
|
||||
return true;
|
||||
}
|
||||
if (angle_types[index] == angle_type &&
|
||||
distance < min_distance) {
|
||||
match = index;
|
||||
min_distance = distance;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}};
|
||||
Geometry::visit_backward(search_start, positions.size(), visitor);
|
||||
Geometry::visit_forward(search_start, positions.size(), visitor);
|
||||
if (match) {
|
||||
return match;
|
||||
}
|
||||
|
||||
min_distance = std::numeric_limits<double>::infinity();
|
||||
angle_type = AngleType::concave;
|
||||
|
||||
Geometry::visit_backward(search_start, positions.size(), visitor);
|
||||
Geometry::visit_forward(search_start, positions.size(), visitor);
|
||||
|
||||
return match;
|
||||
}
|
||||
|
||||
SeamOptions get_seam_options(
|
||||
const Perimeters::Perimeter &perimeter,
|
||||
const Vec2d &prefered_position,
|
||||
const Perimeters::Perimeter::PointTree &points_tree,
|
||||
const double max_detour
|
||||
) {
|
||||
const std::vector<Vec2d> &positions{perimeter.positions};
|
||||
|
||||
const std::size_t closest{find_closest_point(points_tree, prefered_position.head<2>())};
|
||||
std::size_t previous{closest == 0 ? positions.size() - 1 : closest - 1};
|
||||
std::size_t next{closest == positions.size() - 1 ? 0 : closest + 1};
|
||||
|
||||
const Vec2d previous_adjacent_point{positions[previous]};
|
||||
const Vec2d closest_point{positions[closest]};
|
||||
const Vec2d next_adjacent_point{positions[next]};
|
||||
|
||||
const Linef previous_segment{previous_adjacent_point, closest_point};
|
||||
const auto [previous_point, previous_distance] =
|
||||
Geometry::distance_to_segment_squared(previous_segment, prefered_position);
|
||||
const Linef next_segment{closest_point, next_adjacent_point};
|
||||
const auto [next_point, next_distance] =
|
||||
Geometry::distance_to_segment_squared(next_segment, prefered_position);
|
||||
|
||||
const bool adjacent_forward{next_distance < previous_distance};
|
||||
const Vec2d nearest_point{adjacent_forward ? next_point : previous_point};
|
||||
const std::size_t adjacent{adjacent_forward ? next : previous};
|
||||
|
||||
std::optional<std::size_t> snapped{
|
||||
snap_to_angle(nearest_point.head<2>(), closest, perimeter, max_detour)};
|
||||
|
||||
return {
|
||||
closest, adjacent, adjacent_forward, snapped, nearest_point,
|
||||
};
|
||||
}
|
||||
|
||||
std::optional<SeamChoice> LeastVisible::operator()(
|
||||
const Perimeters::Perimeter &perimeter,
|
||||
const PointType point_type,
|
||||
const PointClassification point_classification
|
||||
) const {
|
||||
std::optional<size_t> chosen_index;
|
||||
double visibility{std::numeric_limits<double>::infinity()};
|
||||
|
||||
for (std::size_t i{0}; i < perimeter.positions.size(); ++i) {
|
||||
if (perimeter.point_types[i] != point_type ||
|
||||
perimeter.point_classifications[i] != point_classification) {
|
||||
continue;
|
||||
}
|
||||
const Vec2d point{perimeter.positions[i]};
|
||||
const double point_visibility{precalculated_visibility[i]};
|
||||
|
||||
if (point_visibility < visibility) {
|
||||
visibility = point_visibility;
|
||||
chosen_index = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (chosen_index) {
|
||||
return {{*chosen_index, *chosen_index, perimeter.positions[*chosen_index]}};
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<SeamChoice> Nearest::operator()(
|
||||
const Perimeters::Perimeter &perimeter,
|
||||
const PointType point_type,
|
||||
const PointClassification point_classification
|
||||
) const {
|
||||
const Perimeters::Perimeter::PointTrees &trees{pick_trees(perimeter, point_type)};
|
||||
const Perimeters::Perimeter::OptionalPointTree &tree = pick_tree(trees, point_classification);
|
||||
if (tree) {
|
||||
const SeamOptions options{get_seam_options(perimeter, prefered_position, *tree, max_detour)};
|
||||
return pick_seam_option(perimeter, options);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
} // namespace Impl
|
||||
|
||||
double VisibilityCalculator::operator()(
|
||||
const SeamChoice &choice, const Perimeters::Perimeter &perimeter
|
||||
) const {
|
||||
double visibility = points_visibility.calculate_point_visibility(
|
||||
to_3d(choice.position, perimeter.slice_z).cast<float>()
|
||||
);
|
||||
|
||||
const double angle{
|
||||
choice.previous_index == choice.next_index ? perimeter.angles[choice.previous_index] : 0.0};
|
||||
visibility +=
|
||||
get_angle_visibility_modifier(angle, convex_visibility_modifier, concave_visibility_modifier);
|
||||
return visibility;
|
||||
}
|
||||
|
||||
double VisibilityCalculator::get_angle_visibility_modifier(
|
||||
double angle,
|
||||
const double convex_visibility_modifier,
|
||||
const double concave_visibility_modifier
|
||||
) {
|
||||
const double weight_max{angle > 0 ? convex_visibility_modifier : concave_visibility_modifier};
|
||||
angle = std::abs(angle);
|
||||
const double right_angle{M_PI / 2.0};
|
||||
if (angle > right_angle) {
|
||||
return -weight_max;
|
||||
}
|
||||
const double angle_linear_weight{angle / right_angle};
|
||||
// It is smooth and at angle 0 slope is equal to `angle linear weight`, at right angle the slope is 0 and value is equal to weight max.
|
||||
const double angle_smooth_weight{angle / right_angle * weight_max + (right_angle - angle) / right_angle * angle_linear_weight};
|
||||
return -angle_smooth_weight;
|
||||
}
|
||||
|
||||
std::vector<Vec2d> extract_points(
|
||||
const Perimeters::Perimeter &perimeter, const Perimeters::PointType point_type
|
||||
) {
|
||||
std::vector<Vec2d> result;
|
||||
for (std::size_t i{0}; i < perimeter.positions.size(); ++i) {
|
||||
if (perimeter.point_types[i] == point_type) {
|
||||
result.push_back(perimeter.positions[i]);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<Vec2d> get_starting_positions(const Shells::Shell<> &shell) {
|
||||
const Perimeters::Perimeter &perimeter{shell.front().boundary};
|
||||
|
||||
std::vector<Vec2d> enforcers{extract_points(perimeter, Perimeters::PointType::enforcer)};
|
||||
if (!enforcers.empty()) {
|
||||
return enforcers;
|
||||
}
|
||||
std::vector<Vec2d> common{extract_points(perimeter, Perimeters::PointType::common)};
|
||||
if (!common.empty()) {
|
||||
return common;
|
||||
}
|
||||
return perimeter.positions;
|
||||
}
|
||||
|
||||
struct LeastVisiblePoint
|
||||
{
|
||||
SeamChoice choice;
|
||||
double visibility;
|
||||
};
|
||||
|
||||
struct SeamCandidate {
|
||||
std::vector<SeamChoice> choices;
|
||||
std::vector<double> visibilities;
|
||||
};
|
||||
|
||||
SeamCandidate get_seam_candidate(
|
||||
const Shells::Shell<> &shell,
|
||||
const Vec2d &starting_position,
|
||||
const SeamChoiceVisibility &visibility_calculator,
|
||||
const Params ¶ms,
|
||||
const std::vector<std::vector<double>> &precalculated_visibility,
|
||||
const std::vector<LeastVisiblePoint> &least_visible_points
|
||||
) {
|
||||
using Perimeters::Perimeter, Perimeters::AngleType;
|
||||
|
||||
std::vector<double> choice_visibilities(shell.size(), 1.0);
|
||||
std::vector<SeamChoice> choices{
|
||||
Seams::get_shell_seam(shell, [&, previous_position{starting_position}](const Perimeter &perimeter, std::size_t slice_index) mutable {
|
||||
SeamChoice candidate{Seams::choose_seam_point(
|
||||
perimeter, Impl::Nearest{previous_position, params.max_detour}
|
||||
)};
|
||||
const bool is_too_far{
|
||||
(candidate.position - previous_position).norm() > params.max_detour};
|
||||
const LeastVisiblePoint &least_visible{least_visible_points[slice_index]};
|
||||
|
||||
const bool is_on_edge{
|
||||
candidate.previous_index == candidate.next_index &&
|
||||
perimeter.angle_types[candidate.next_index] != AngleType::smooth};
|
||||
|
||||
if (is_on_edge) {
|
||||
choice_visibilities[slice_index] = precalculated_visibility[slice_index][candidate.previous_index];
|
||||
} else {
|
||||
choice_visibilities[slice_index] =
|
||||
visibility_calculator(candidate, perimeter);
|
||||
}
|
||||
const bool is_too_visible{
|
||||
choice_visibilities[slice_index] >
|
||||
least_visible.visibility + params.jump_visibility_threshold};
|
||||
const bool can_be_on_edge{
|
||||
!is_on_edge &&
|
||||
perimeter.angle_types[least_visible.choice.next_index] != AngleType::smooth};
|
||||
if (is_too_far || (can_be_on_edge && is_too_visible)) {
|
||||
candidate = least_visible.choice;
|
||||
}
|
||||
previous_position = candidate.position;
|
||||
return candidate;
|
||||
})};
|
||||
return {std::move(choices), std::move(choice_visibilities)};
|
||||
}
|
||||
|
||||
using ShellVertexVisibility = std::vector<std::vector<double>>;
|
||||
|
||||
std::vector<ShellVertexVisibility> get_shells_vertex_visibility(
|
||||
const Shells::Shells<> &shells, const SeamChoiceVisibility &visibility_calculator
|
||||
) {
|
||||
std::vector<ShellVertexVisibility> result;
|
||||
|
||||
result.reserve(shells.size());
|
||||
std::transform(
|
||||
shells.begin(), shells.end(), std::back_inserter(result),
|
||||
[](const Shells::Shell<> &shell) { return ShellVertexVisibility(shell.size()); }
|
||||
);
|
||||
|
||||
Geometry::iterate_nested(shells, [&](const std::size_t shell_index, const std::size_t slice_index) {
|
||||
const Shells::Shell<> &shell{shells[shell_index]};
|
||||
const Shells::Slice<> &slice{shell[slice_index]};
|
||||
const std::vector<Vec2d> &positions{slice.boundary.positions};
|
||||
|
||||
for (std::size_t point_index{0}; point_index < positions.size(); ++point_index) {
|
||||
result[shell_index][slice_index].emplace_back(visibility_calculator(
|
||||
SeamChoice{point_index, point_index, positions[point_index]}, slice.boundary
|
||||
));
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
using ShellLeastVisiblePoints = std::vector<LeastVisiblePoint>;
|
||||
|
||||
std::vector<ShellLeastVisiblePoints> get_shells_least_visible_points(
|
||||
const Shells::Shells<> &shells,
|
||||
const std::vector<ShellVertexVisibility> &precalculated_visibility
|
||||
) {
|
||||
std::vector<ShellLeastVisiblePoints> result;
|
||||
|
||||
result.reserve(shells.size());
|
||||
std::transform(
|
||||
shells.begin(), shells.end(), std::back_inserter(result),
|
||||
[](const Shells::Shell<> &shell) { return ShellLeastVisiblePoints(shell.size()); }
|
||||
);
|
||||
|
||||
Geometry::iterate_nested(shells, [&](const std::size_t shell_index, const std::size_t slice_index) {
|
||||
const Shells::Shell<> &shell{shells[shell_index]};
|
||||
const Shells::Slice<> &slice{shell[slice_index]};
|
||||
const SeamChoice least_visibile{
|
||||
Seams::choose_seam_point(slice.boundary, Impl::LeastVisible{precalculated_visibility[shell_index][slice_index]})};
|
||||
|
||||
const double visibility{precalculated_visibility[shell_index][slice_index][least_visibile.previous_index]};
|
||||
result[shell_index][slice_index] = LeastVisiblePoint{least_visibile, visibility};
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
using ShellStartingPositions = std::vector<Vec2d>;
|
||||
|
||||
std::vector<ShellStartingPositions> get_shells_starting_positions(
|
||||
const Shells::Shells<> &shells
|
||||
) {
|
||||
std::vector<ShellStartingPositions> result;
|
||||
for (const Shells::Shell<> &shell : shells) {
|
||||
std::vector<Vec2d> starting_positions{get_starting_positions(shell)};
|
||||
result.push_back(std::move(starting_positions));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
using ShellSeamCandidates = std::vector<SeamCandidate>;
|
||||
|
||||
std::vector<ShellSeamCandidates> get_shells_seam_candidates(
|
||||
const Shells::Shells<> &shells,
|
||||
const std::vector<ShellStartingPositions> &starting_positions,
|
||||
const SeamChoiceVisibility &visibility_calculator,
|
||||
const std::vector<ShellVertexVisibility> &precalculated_visibility,
|
||||
const std::vector<ShellLeastVisiblePoints> &least_visible_points,
|
||||
const Params ¶ms
|
||||
) {
|
||||
std::vector<ShellSeamCandidates> result;
|
||||
|
||||
result.reserve(starting_positions.size());
|
||||
std::transform(
|
||||
starting_positions.begin(), starting_positions.end(), std::back_inserter(result),
|
||||
[](const ShellStartingPositions &positions) { return ShellSeamCandidates(positions.size()); }
|
||||
);
|
||||
|
||||
Geometry::iterate_nested(starting_positions, [&](const std::size_t shell_index, const std::size_t starting_position_index){
|
||||
const Shells::Shell<> &shell{shells[shell_index]};
|
||||
using Perimeters::Perimeter, Perimeters::AngleType;
|
||||
|
||||
result[shell_index][starting_position_index] = get_seam_candidate(
|
||||
shell,
|
||||
starting_positions[shell_index][starting_position_index],
|
||||
visibility_calculator,
|
||||
params,
|
||||
precalculated_visibility[shell_index],
|
||||
least_visible_points[shell_index]
|
||||
);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<SeamChoice> get_shell_seam(
|
||||
const Shells::Shell<> &shell,
|
||||
std::vector<SeamCandidate> seam_candidates,
|
||||
const Perimeters::Perimeter::OptionalPointTree &previous_points,
|
||||
const Params ¶ms
|
||||
) {
|
||||
std::vector<SeamChoice> seam;
|
||||
double visibility{std::numeric_limits<double>::infinity()};
|
||||
|
||||
for (std::size_t i{0}; i < seam_candidates.size(); ++i) {
|
||||
using Perimeters::Perimeter, Perimeters::AngleType;
|
||||
|
||||
SeamCandidate seam_candidate{seam_candidates[i]};
|
||||
const Vec2d first_point{seam_candidate.choices.front().position};
|
||||
|
||||
std::optional<Vec2d> closest_point;
|
||||
if (previous_points) {
|
||||
std::size_t closest_point_index{find_closest_point(*previous_points, first_point)};
|
||||
Vec2d point;
|
||||
point.x() = previous_points->coordinate(closest_point_index, 0);
|
||||
point.y() = previous_points->coordinate(closest_point_index, 1);
|
||||
closest_point = point;
|
||||
}
|
||||
|
||||
std::optional<double> previous_distance;
|
||||
if (closest_point) {
|
||||
previous_distance = (*closest_point - first_point).norm();
|
||||
}
|
||||
const bool is_near_previous{closest_point && *previous_distance < params.max_detour};
|
||||
|
||||
double seam_candidate_visibility{
|
||||
is_near_previous ? -params.continuity_modifier *
|
||||
(params.max_detour - *previous_distance) / params.max_detour :
|
||||
0.0};
|
||||
for (std::size_t slice_index{}; slice_index < shell.size(); ++slice_index) {
|
||||
seam_candidate_visibility += seam_candidate.visibilities[slice_index];
|
||||
}
|
||||
|
||||
if (seam_candidate_visibility < visibility) {
|
||||
seam = std::move(seam_candidate.choices);
|
||||
visibility = seam_candidate_visibility;
|
||||
}
|
||||
}
|
||||
|
||||
return seam;
|
||||
}
|
||||
|
||||
std::vector<std::vector<SeamPerimeterChoice>> get_object_seams(
|
||||
Shells::Shells<> &&shells,
|
||||
const SeamChoiceVisibility &visibility_calculator,
|
||||
const Params ¶ms
|
||||
) {
|
||||
const std::vector<ShellVertexVisibility> precalculated_visibility{
|
||||
get_shells_vertex_visibility(shells, visibility_calculator)};
|
||||
|
||||
const std::vector<ShellLeastVisiblePoints> least_visible_points{
|
||||
get_shells_least_visible_points(shells, precalculated_visibility)
|
||||
};
|
||||
|
||||
const std::vector<ShellStartingPositions> starting_positions{
|
||||
get_shells_starting_positions(shells)
|
||||
};
|
||||
|
||||
const std::vector<ShellSeamCandidates> seam_candidates{
|
||||
get_shells_seam_candidates(
|
||||
shells,
|
||||
starting_positions,
|
||||
visibility_calculator,
|
||||
precalculated_visibility,
|
||||
least_visible_points,
|
||||
params
|
||||
)
|
||||
};
|
||||
|
||||
std::vector<std::vector<SeamPerimeterChoice>> layer_seams(get_layer_count(shells));
|
||||
|
||||
for (std::size_t shell_index{0}; shell_index < shells.size(); ++shell_index) {
|
||||
Shells::Shell<> &shell{shells[shell_index]};
|
||||
|
||||
if (shell.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::size_t layer_index{shell.front().layer_index};
|
||||
tcb::span<const SeamPerimeterChoice> previous_seams{
|
||||
layer_index == 0 ? tcb::span<const SeamPerimeterChoice>{} : layer_seams[layer_index - 1]};
|
||||
std::vector<Vec2d> previous_seams_positions;
|
||||
std::transform(
|
||||
previous_seams.begin(), previous_seams.end(),
|
||||
std::back_inserter(previous_seams_positions),
|
||||
[](const SeamPerimeterChoice &seam) { return seam.choice.position; }
|
||||
);
|
||||
|
||||
Perimeters::Perimeter::OptionalPointTree previous_seams_positions_tree;
|
||||
const Perimeters::Perimeter::IndexToCoord index_to_coord{previous_seams_positions};
|
||||
if (!previous_seams_positions.empty()) {
|
||||
previous_seams_positions_tree =
|
||||
Perimeters::Perimeter::PointTree{index_to_coord, index_to_coord.positions.size()};
|
||||
}
|
||||
|
||||
std::vector<SeamChoice> seam{
|
||||
Aligned::get_shell_seam(shell, seam_candidates[shell_index], previous_seams_positions_tree, params)};
|
||||
|
||||
for (std::size_t perimeter_id{}; perimeter_id < shell.size(); ++perimeter_id) {
|
||||
const SeamChoice &choice{seam[perimeter_id]};
|
||||
Perimeters::Perimeter &perimeter{shell[perimeter_id].boundary};
|
||||
layer_seams[shell[perimeter_id].layer_index].emplace_back(choice, std::move(perimeter));
|
||||
}
|
||||
}
|
||||
return layer_seams;
|
||||
}
|
||||
|
||||
} // namespace Slic3r::Seams::Aligned
|
97
src/libslic3r/GCode/SeamAligned.hpp
Normal file
97
src/libslic3r/GCode/SeamAligned.hpp
Normal file
@ -0,0 +1,97 @@
|
||||
#ifndef libslic3r_SeamAligned_hpp_
|
||||
#define libslic3r_SeamAligned_hpp_
|
||||
|
||||
#include "libslic3r/GCode/SeamPerimeters.hpp"
|
||||
#include "libslic3r/GCode/SeamChoice.hpp"
|
||||
#include "libslic3r/Point.hpp"
|
||||
|
||||
namespace Slic3r::ModelInfo {
|
||||
struct Visibility;
|
||||
}
|
||||
|
||||
namespace Slic3r::Seams::Aligned {
|
||||
|
||||
using SeamChoiceVisibility = std::function<double(const SeamChoice &, const Perimeters::Perimeter &)>;
|
||||
|
||||
namespace Impl {
|
||||
struct SeamOptions
|
||||
{
|
||||
std::size_t closest;
|
||||
std::size_t adjacent;
|
||||
bool adjacent_forward;
|
||||
std::optional<std::size_t> snapped;
|
||||
Vec2d on_edge;
|
||||
};
|
||||
|
||||
SeamChoice pick_seam_option(const Perimeters::Perimeter &perimeter, const SeamOptions &options);
|
||||
|
||||
std::optional<std::size_t> snap_to_angle(
|
||||
const Vec2d &point,
|
||||
const std::size_t search_start,
|
||||
const Perimeters::Perimeter &perimeter,
|
||||
const double max_detour
|
||||
);
|
||||
|
||||
SeamOptions get_seam_options(
|
||||
const Perimeters::Perimeter &perimeter,
|
||||
const Vec2d &prefered_position,
|
||||
const Perimeters::Perimeter::PointTree &points_tree,
|
||||
const double max_detour
|
||||
);
|
||||
|
||||
struct Nearest
|
||||
{
|
||||
Vec2d prefered_position;
|
||||
double max_detour;
|
||||
|
||||
std::optional<SeamChoice> operator()(
|
||||
const Perimeters::Perimeter &perimeter,
|
||||
const Perimeters::PointType point_type,
|
||||
const Perimeters::PointClassification point_classification
|
||||
) const;
|
||||
};
|
||||
|
||||
struct LeastVisible
|
||||
{
|
||||
std::optional<SeamChoice> operator()(
|
||||
const Perimeters::Perimeter &perimeter,
|
||||
const Perimeters::PointType point_type,
|
||||
const Perimeters::PointClassification point_classification
|
||||
) const;
|
||||
|
||||
const std::vector<double> &precalculated_visibility;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
struct VisibilityCalculator
|
||||
{
|
||||
const Slic3r::ModelInfo::Visibility &points_visibility;
|
||||
double convex_visibility_modifier;
|
||||
double concave_visibility_modifier;
|
||||
|
||||
double operator()(const SeamChoice &choice, const Perimeters::Perimeter &perimeter) const;
|
||||
|
||||
private:
|
||||
static double get_angle_visibility_modifier(
|
||||
const double angle,
|
||||
const double convex_visibility_modifier,
|
||||
const double concave_visibility_modifier
|
||||
);
|
||||
};
|
||||
|
||||
struct Params {
|
||||
double max_detour{};
|
||||
double jump_visibility_threshold{};
|
||||
double continuity_modifier{};
|
||||
};
|
||||
|
||||
std::vector<std::vector<SeamPerimeterChoice>> get_object_seams(
|
||||
Shells::Shells<> &&shells,
|
||||
const SeamChoiceVisibility& visibility_calculator,
|
||||
const Params& params
|
||||
);
|
||||
|
||||
} // namespace Slic3r::Seams::Aligned
|
||||
|
||||
#endif // libslic3r_SeamAligned_hpp_
|
108
src/libslic3r/GCode/SeamChoice.cpp
Normal file
108
src/libslic3r/GCode/SeamChoice.cpp
Normal file
@ -0,0 +1,108 @@
|
||||
#include "libslic3r/GCode/SeamChoice.hpp"
|
||||
|
||||
namespace Slic3r::Seams {
|
||||
std::optional<SeamChoice> maybe_choose_seam_point(
|
||||
const Perimeters::Perimeter &perimeter, const SeamPicker &seam_picker
|
||||
) {
|
||||
using Perimeters::PointType;
|
||||
using Perimeters::PointClassification;
|
||||
|
||||
std::vector<PointType>
|
||||
type_search_order{PointType::enforcer, PointType::common, PointType::blocker};
|
||||
std::vector<PointClassification> classification_search_order{
|
||||
PointClassification::embedded, PointClassification::common, PointClassification::overhang};
|
||||
for (const PointType point_type : type_search_order) {
|
||||
for (const PointClassification point_classification : classification_search_order) {
|
||||
if (std::optional<SeamChoice> seam_choice{
|
||||
seam_picker(perimeter, point_type, point_classification)}) {
|
||||
return seam_choice;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
SeamChoice choose_seam_point(const Perimeters::Perimeter &perimeter, const SeamPicker &seam_picker) {
|
||||
using Perimeters::PointType;
|
||||
using Perimeters::PointClassification;
|
||||
|
||||
std::optional<SeamChoice> seam_choice{maybe_choose_seam_point(perimeter, seam_picker)};
|
||||
|
||||
if (seam_choice) {
|
||||
return *seam_choice;
|
||||
}
|
||||
|
||||
// Failed to choose any reasonable point!
|
||||
return SeamChoice{0, 0, perimeter.positions.front()};
|
||||
}
|
||||
|
||||
std::optional<SeamChoice> choose_degenerate_seam_point(const Perimeters::Perimeter &perimeter) {
|
||||
if (!perimeter.positions.empty()) {
|
||||
return SeamChoice{0, 0, perimeter.positions.front()};
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<std::vector<SeamChoice>> maybe_get_shell_seam(
|
||||
const Shells::Shell<> &shell,
|
||||
const std::function<std::optional<SeamChoice>(const Perimeters::Perimeter &, std::size_t)> &chooser
|
||||
) {
|
||||
std::vector<SeamChoice> result;
|
||||
result.reserve(shell.size());
|
||||
for (std::size_t i{0}; i < shell.size(); ++i) {
|
||||
const Shells::Slice<> &slice{shell[i]};
|
||||
if (slice.boundary.is_degenerate) {
|
||||
if (std::optional<SeamChoice> seam_choice{
|
||||
choose_degenerate_seam_point(slice.boundary)}) {
|
||||
result.push_back(*seam_choice);
|
||||
} else {
|
||||
result.emplace_back();
|
||||
}
|
||||
} else {
|
||||
const std::optional<SeamChoice> choice{chooser(slice.boundary, i)};
|
||||
if (!choice) {
|
||||
return std::nullopt;
|
||||
}
|
||||
result.push_back(*choice);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<SeamChoice> get_shell_seam(
|
||||
const Shells::Shell<> &shell,
|
||||
const std::function<SeamChoice(const Perimeters::Perimeter &, std::size_t)> &chooser
|
||||
) {
|
||||
std::optional<std::vector<SeamChoice>> seam{maybe_get_shell_seam(
|
||||
shell,
|
||||
[&](const Perimeters::Perimeter &perimeter, std::size_t slice_index) {
|
||||
return chooser(perimeter, slice_index);
|
||||
}
|
||||
)};
|
||||
if (!seam) {
|
||||
// Should be unreachable as chooser always returns a SeamChoice!
|
||||
return std::vector<SeamChoice>(shell.size());
|
||||
}
|
||||
return *seam;
|
||||
}
|
||||
|
||||
std::vector<std::vector<SeamPerimeterChoice>> get_object_seams(
|
||||
Shells::Shells<> &&shells,
|
||||
const std::function<std::vector<SeamChoice>(const Shells::Shell<>&)> &get_shell_seam
|
||||
) {
|
||||
std::vector<std::vector<SeamPerimeterChoice>> layer_seams(get_layer_count(shells));
|
||||
|
||||
for (Shells::Shell<> &shell : shells) {
|
||||
std::vector<SeamChoice> seam{get_shell_seam(shell)};
|
||||
|
||||
for (std::size_t perimeter_id{}; perimeter_id < shell.size(); ++perimeter_id) {
|
||||
const SeamChoice &choice{seam[perimeter_id]};
|
||||
Perimeters::Perimeter &perimeter{shell[perimeter_id].boundary};
|
||||
layer_seams[shell[perimeter_id].layer_index].emplace_back(choice, std::move(perimeter));
|
||||
}
|
||||
}
|
||||
|
||||
return layer_seams;
|
||||
}
|
||||
} // namespace Slic3r::Seams
|
72
src/libslic3r/GCode/SeamChoice.hpp
Normal file
72
src/libslic3r/GCode/SeamChoice.hpp
Normal file
@ -0,0 +1,72 @@
|
||||
#ifndef libslic3r_SeamChoice_hpp_
|
||||
#define libslic3r_SeamChoice_hpp_
|
||||
|
||||
#include "libslic3r/Polygon.hpp"
|
||||
#include "libslic3r/GCode/SeamPerimeters.hpp"
|
||||
|
||||
namespace Slic3r::Seams {
|
||||
|
||||
/**
|
||||
* When previous_index == next_index, the point is at the point.
|
||||
* Otherwise the point is at the edge.
|
||||
*/
|
||||
struct SeamChoice
|
||||
{
|
||||
std::size_t previous_index{};
|
||||
std::size_t next_index{};
|
||||
Vec2d position{Vec2d::Zero()};
|
||||
};
|
||||
|
||||
struct SeamPerimeterChoice
|
||||
{
|
||||
SeamPerimeterChoice(const SeamChoice &choice, Perimeters::Perimeter &&perimeter)
|
||||
: choice(choice)
|
||||
, perimeter(std::move(perimeter))
|
||||
, bounding_box(Polygon{Geometry::scaled(this->perimeter.positions)}.bounding_box()) {}
|
||||
|
||||
SeamChoice choice;
|
||||
Perimeters::Perimeter perimeter;
|
||||
BoundingBox bounding_box;
|
||||
};
|
||||
|
||||
using SeamPicker = std::function<std::optional<
|
||||
SeamChoice>(const Perimeters::Perimeter &, const Perimeters::PointType, const Perimeters::PointClassification)>;
|
||||
|
||||
std::optional<SeamChoice> maybe_choose_seam_point(
|
||||
const Perimeters::Perimeter &perimeter, const SeamPicker &seam_picker
|
||||
);
|
||||
|
||||
/**
|
||||
* Go throught points on perimeter and choose the best seam point closest to
|
||||
* the prefered position.
|
||||
*
|
||||
* Points in the perimeter can be diveded into 3x3=9 categories. An example category is
|
||||
* enforced overhanging point. These categories are searched in particualr order.
|
||||
* For example enforced overhang will be always choosen over common embedded point, etc.
|
||||
*
|
||||
* A closest point is choosen from the first non-empty category.
|
||||
*/
|
||||
SeamChoice choose_seam_point(
|
||||
const Perimeters::Perimeter &perimeter, const SeamPicker& seam_picker
|
||||
);
|
||||
|
||||
std::optional<SeamChoice> choose_degenerate_seam_point(const Perimeters::Perimeter &perimeter);
|
||||
|
||||
std::optional<std::vector<SeamChoice>> maybe_get_shell_seam(
|
||||
const Shells::Shell<> &shell,
|
||||
const std::function<std::optional<SeamChoice>(const Perimeters::Perimeter &, std::size_t)> &chooser
|
||||
);
|
||||
|
||||
std::vector<SeamChoice> get_shell_seam(
|
||||
const Shells::Shell<> &shell,
|
||||
const std::function<SeamChoice(const Perimeters::Perimeter &, std::size_t)> &chooser
|
||||
);
|
||||
|
||||
std::vector<std::vector<SeamPerimeterChoice>> get_object_seams(
|
||||
Shells::Shells<> &&shells,
|
||||
const std::function<std::vector<SeamChoice>(const Shells::Shell<> &)> &get_shell_seam
|
||||
);
|
||||
|
||||
} // namespace Slic3r::Seams
|
||||
|
||||
#endif // libslic3r_SeamChoice_hpp_
|
369
src/libslic3r/GCode/SeamGeometry.cpp
Normal file
369
src/libslic3r/GCode/SeamGeometry.cpp
Normal file
@ -0,0 +1,369 @@
|
||||
#include "libslic3r/GCode/SeamGeometry.hpp"
|
||||
#include "KDTreeIndirect.hpp"
|
||||
#include "Layer.hpp"
|
||||
#include <fstream>
|
||||
#include <numeric>
|
||||
#include <oneapi/tbb/blocked_range.h>
|
||||
#include <oneapi/tbb/parallel_for.h>
|
||||
|
||||
namespace Slic3r::Seams::Geometry {
|
||||
|
||||
namespace MappingImpl {
|
||||
|
||||
/**
|
||||
* @brief Return 0, 1, ..., size - 1.
|
||||
*/
|
||||
std::vector<std::size_t> range(std::size_t size) {
|
||||
std::vector<std::size_t> result(size);
|
||||
std::iota(result.begin(), result.end(), 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief A link between lists.
|
||||
*/
|
||||
struct Link
|
||||
{
|
||||
std::size_t bucket_id;
|
||||
double weight;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Get optional values. Replace any nullopt Links with new_bucket_id and increment new_bucket_id.
|
||||
*
|
||||
* @param links A list of optional links.
|
||||
* @param new_bucket_id In-out parameter incremented on each nullopt replacement.
|
||||
*/
|
||||
std::vector<std::size_t> assign_buckets(
|
||||
const std::vector<std::optional<Link>> &links, std::size_t &new_bucket_id
|
||||
) {
|
||||
std::vector<std::size_t> result;
|
||||
std::transform(
|
||||
links.begin(), links.end(), std::back_inserter(result),
|
||||
[&](const std::optional<Link> &link) {
|
||||
if (link) {
|
||||
return link->bucket_id;
|
||||
}
|
||||
return new_bucket_id++;
|
||||
}
|
||||
);
|
||||
return result;
|
||||
}
|
||||
} // namespace MappingImpl
|
||||
|
||||
Vec2d get_normal(const Vec2d &vector) { return Vec2d{vector.y(), -vector.x()}.normalized(); }
|
||||
|
||||
Vec2d get_polygon_normal(
|
||||
const std::vector<Vec2d> &points, const std::size_t index, const double min_arm_length
|
||||
) {
|
||||
std::optional<std::size_t> previous_index;
|
||||
std::optional<std::size_t> next_index;
|
||||
|
||||
visit_forward(index, points.size(), [&](const std::size_t index_candidate) {
|
||||
if (index == index_candidate) {
|
||||
return false;
|
||||
}
|
||||
const double distance{(points[index_candidate] - points[index]).norm()};
|
||||
if (distance > min_arm_length) {
|
||||
next_index = index_candidate;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
visit_backward(index, points.size(), [&](const std::size_t index_candidate) {
|
||||
const double distance{(points[index_candidate] - points[index]).norm()};
|
||||
if (distance > min_arm_length) {
|
||||
previous_index = index_candidate;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (previous_index && next_index) {
|
||||
const Vec2d previous_normal{
|
||||
Geometry::get_normal(points.at(index) - points.at(*previous_index))};
|
||||
const Vec2d next_normal{Geometry::get_normal(points.at(*next_index) - points.at(index))};
|
||||
return (previous_normal + next_normal).normalized();
|
||||
}
|
||||
return Vec2d::Zero();
|
||||
}
|
||||
|
||||
std::pair<Vec2d, double> distance_to_segment_squared(const Linef &segment, const Vec2d &point) {
|
||||
Vec2d segment_point;
|
||||
const double distance{line_alg::distance_to_squared(segment, point, &segment_point)};
|
||||
return {segment_point, distance};
|
||||
}
|
||||
|
||||
std::pair<Mapping, std::size_t> get_mapping(
|
||||
const std::vector<std::size_t> &list_sizes, const MappingOperator &mapping_operator
|
||||
) {
|
||||
using namespace MappingImpl;
|
||||
|
||||
std::vector<std::vector<std::size_t>> result;
|
||||
result.reserve(list_sizes.size());
|
||||
result.push_back(range(list_sizes.front()));
|
||||
|
||||
std::size_t new_bucket_id{result.back().size()};
|
||||
|
||||
for (std::size_t layer_index{0}; layer_index < list_sizes.size() - 1; ++layer_index) {
|
||||
// Current layer is already assigned mapping.
|
||||
|
||||
// Links on the next layer to the current layer.
|
||||
std::vector<std::optional<Link>> links(list_sizes[layer_index + 1]);
|
||||
|
||||
for (std::size_t item_index{0}; item_index < list_sizes[layer_index]; ++item_index) {
|
||||
const MappingOperatorResult next_item{
|
||||
mapping_operator(layer_index, item_index)};
|
||||
if (next_item) {
|
||||
const auto [index, weight] = *next_item;
|
||||
const Link link{result.back()[item_index], weight};
|
||||
if (!links[index] || links[index]->weight < link.weight) {
|
||||
links[index] = link;
|
||||
}
|
||||
}
|
||||
}
|
||||
result.push_back(assign_buckets(links, new_bucket_id));
|
||||
}
|
||||
return {result, new_bucket_id};
|
||||
}
|
||||
|
||||
Extrusion::Extrusion(
|
||||
Polygon &&polygon,
|
||||
BoundingBox bounding_box,
|
||||
const double width,
|
||||
const ExPolygon &island_boundary
|
||||
)
|
||||
: polygon(polygon)
|
||||
, bounding_box(std::move(bounding_box))
|
||||
, width(width)
|
||||
, island_boundary(island_boundary) {
|
||||
this->island_boundary_bounding_boxes.push_back(island_boundary.contour.bounding_box());
|
||||
|
||||
std::transform(
|
||||
this->island_boundary.holes.begin(), this->island_boundary.holes.end(),
|
||||
std::back_inserter(this->island_boundary_bounding_boxes),
|
||||
[](const Polygon &polygon) { return polygon.bounding_box(); }
|
||||
);
|
||||
}
|
||||
|
||||
Geometry::Extrusions get_external_perimeters(const Slic3r::Layer &layer, const LayerSlice &slice) {
|
||||
std::vector<Geometry::Extrusion> result;
|
||||
for (const LayerIsland &island : slice.islands) {
|
||||
const LayerRegion &layer_region = *layer.get_region(island.perimeters.region());
|
||||
for (const uint32_t perimeter_id : island.perimeters) {
|
||||
const auto collection{static_cast<const ExtrusionEntityCollection *>(
|
||||
layer_region.perimeters().entities[perimeter_id]
|
||||
)};
|
||||
for (const ExtrusionEntity *entity : *collection) {
|
||||
if (entity->role().is_external_perimeter()) {
|
||||
Polygon polygon{entity->as_polyline().points};
|
||||
const BoundingBox bounding_box{polygon.bounding_box()};
|
||||
const double width{layer_region.flow(FlowRole::frExternalPerimeter).width()};
|
||||
result.emplace_back(std::move(polygon), bounding_box, width, island.boundary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<Extrusions> get_extrusions(tcb::span<const Slic3r::Layer *const> object_layers) {
|
||||
std::vector<Extrusions> result;
|
||||
result.reserve(object_layers.size());
|
||||
|
||||
for (const Slic3r::Layer *object_layer : object_layers) {
|
||||
Extrusions extrusions;
|
||||
|
||||
for (const LayerSlice &slice : object_layer->lslices_ex) {
|
||||
std::vector<Extrusion> external_perimeters{
|
||||
get_external_perimeters(*object_layer, slice)};
|
||||
for (Geometry::Extrusion &extrusion : external_perimeters) {
|
||||
extrusions.push_back(std::move(extrusion));
|
||||
}
|
||||
}
|
||||
|
||||
result.push_back(std::move(extrusions));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<Vec2d> oversample_edge(const Vec2d &from, const Vec2d &to, const double max_distance) {
|
||||
const double total_distance{(from - to).norm()};
|
||||
const auto points_count{static_cast<std::size_t>(std::ceil(total_distance / max_distance)) + 1};
|
||||
if (points_count < 3) {
|
||||
return {};
|
||||
}
|
||||
const double step_size{total_distance / (points_count - 1)};
|
||||
const Vec2d step_vector{step_size * (to - from).normalized()};
|
||||
std::vector<Vec2d> result;
|
||||
result.reserve(points_count - 2);
|
||||
for (std::size_t i{1}; i < points_count - 1; ++i) {
|
||||
result.push_back(from + i * step_vector);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void visit_forward(
|
||||
const std::size_t start_index,
|
||||
const std::size_t loop_size,
|
||||
const std::function<bool(std::size_t)> &visitor
|
||||
) {
|
||||
std::size_t last_index{loop_size - 1};
|
||||
std::size_t index{start_index};
|
||||
for (unsigned _{0}; _ < 30; ++_) { // Infinite loop prevention
|
||||
if (visitor(index)) {
|
||||
return;
|
||||
}
|
||||
index = index == last_index ? 0 : index + 1;
|
||||
}
|
||||
assert(false);
|
||||
}
|
||||
|
||||
void visit_backward(
|
||||
const std::size_t start_index,
|
||||
const std::size_t loop_size,
|
||||
const std::function<bool(std::size_t)> &visitor
|
||||
) {
|
||||
std::size_t last_index{loop_size - 1};
|
||||
std::size_t index{start_index == 0 ? loop_size - 1 : start_index - 1};
|
||||
for (unsigned _{0}; _ < 30; ++_) { // Infinite loop prevention
|
||||
if (visitor(index)) {
|
||||
return;
|
||||
}
|
||||
index = index == 0 ? last_index : index - 1;
|
||||
}
|
||||
assert(false);
|
||||
}
|
||||
|
||||
std::vector<Vec2d> unscaled(const Points &points) {
|
||||
std::vector<Vec2d> result;
|
||||
result.reserve(points.size());
|
||||
using std::transform, std::begin, std::end, std::back_inserter;
|
||||
transform(begin(points), end(points), back_inserter(result), [](const Point &point) {
|
||||
return unscaled(point);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<Linef> unscaled(const Lines &lines) {
|
||||
std::vector<Linef> result;
|
||||
result.reserve(lines.size());
|
||||
std::transform(lines.begin(), lines.end(), std::back_inserter(result), [](const Line &line) {
|
||||
return Linef{unscaled(line.a), unscaled(line.b)};
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
Points scaled(const std::vector<Vec2d> &points) {
|
||||
Points result;
|
||||
for (const Vec2d &point : points) {
|
||||
result.push_back(Slic3r::scaled(point));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<double> get_embedding_distances(
|
||||
const std::vector<Vec2d> &points, const AABBTreeLines::LinesDistancer<Linef> &perimeter_distancer
|
||||
) {
|
||||
std::vector<double> result;
|
||||
result.reserve(points.size());
|
||||
using std::transform, std::back_inserter;
|
||||
transform(points.begin(), points.end(), back_inserter(result), [&](const Vec2d &point) {
|
||||
const double distance{perimeter_distancer.distance_from_lines<true>(point)};
|
||||
return distance < 0 ? -distance : 0.0;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<double> get_overhangs(
|
||||
const std::vector<Vec2d> &points,
|
||||
const AABBTreeLines::LinesDistancer<Linef> &previous_layer_perimeter_distancer,
|
||||
const double layer_height
|
||||
) {
|
||||
std::vector<double> result;
|
||||
result.reserve(points.size());
|
||||
using std::transform, std::back_inserter;
|
||||
transform(points.begin(), points.end(), back_inserter(result), [&](const Vec2d &point) {
|
||||
const double distance{previous_layer_perimeter_distancer.distance_from_lines<true>(point)};
|
||||
return distance > 0 ? M_PI / 2 - std::atan(layer_height / distance) : 0.0;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
// Measured from outside, convex is positive
|
||||
std::vector<double> get_vertex_angles(const std::vector<Vec2d> &points, const double min_arm_length) {
|
||||
std::vector<double> result;
|
||||
result.reserve(points.size());
|
||||
|
||||
for (std::size_t index{0}; index < points.size(); ++index) {
|
||||
std::optional<std::size_t> previous_index;
|
||||
std::optional<std::size_t> next_index;
|
||||
|
||||
visit_forward(index, points.size(), [&](const std::size_t index_candidate) {
|
||||
if (index == index_candidate) {
|
||||
return false;
|
||||
}
|
||||
const double distance{(points[index_candidate] - points[index]).norm()};
|
||||
if (distance > min_arm_length) {
|
||||
next_index = index_candidate;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
visit_backward(index, points.size(), [&](const std::size_t index_candidate) {
|
||||
const double distance{(points[index_candidate] - points[index]).norm()};
|
||||
if (distance > min_arm_length) {
|
||||
previous_index = index_candidate;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (previous_index && next_index) {
|
||||
const Vec2d &previous_point = points[*previous_index];
|
||||
const Vec2d &point = points[index];
|
||||
const Vec2d &next_point = points[*next_index];
|
||||
result.push_back(-angle((point - previous_point), (next_point - point)));
|
||||
} else {
|
||||
result.push_back(0.0);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
double bounding_box_distance(const BoundingBox &a, const BoundingBox &b) {
|
||||
const double bb_max_distance{unscaled(Point{a.max - b.max}).norm()};
|
||||
const double bb_min_distance{unscaled(Point{a.min - b.min}).norm()};
|
||||
return std::max(bb_max_distance, bb_min_distance);
|
||||
}
|
||||
|
||||
std::pair<std::size_t, double> pick_closest_bounding_box(
|
||||
const BoundingBox &to, const BoundingBoxes &choose_from
|
||||
) {
|
||||
double min_distance{std::numeric_limits<double>::infinity()};
|
||||
std::size_t choosen_index{0};
|
||||
|
||||
for (std::size_t i{0}; i < choose_from.size(); ++i) {
|
||||
const BoundingBox &candidate{choose_from[i]};
|
||||
const double distance{bounding_box_distance(candidate, to)};
|
||||
|
||||
if (distance < min_distance) {
|
||||
choosen_index = i;
|
||||
min_distance = distance;
|
||||
}
|
||||
}
|
||||
return {choosen_index, min_distance};
|
||||
}
|
||||
|
||||
Polygon to_polygon(const ExtrusionLoop &loop) {
|
||||
Points loop_points{};
|
||||
for (const ExtrusionPath &path : loop.paths) {
|
||||
for (const Point &point : path.polyline.points) {
|
||||
loop_points.push_back(point);
|
||||
}
|
||||
}
|
||||
return Polygon{loop_points};
|
||||
}
|
||||
} // namespace Slic3r::Seams::Geometry
|
170
src/libslic3r/GCode/SeamGeometry.hpp
Normal file
170
src/libslic3r/GCode/SeamGeometry.hpp
Normal file
@ -0,0 +1,170 @@
|
||||
#ifndef libslic3r_SeamGeometry_hpp_
|
||||
#define libslic3r_SeamGeometry_hpp_
|
||||
|
||||
#include "libslic3r/ExtrusionEntity.hpp"
|
||||
#include "libslic3r/ExPolygon.hpp"
|
||||
#include "libslic3r/AABBTreeLines.hpp"
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include <oneapi/tbb/blocked_range.h>
|
||||
#include <oneapi/tbb/parallel_for.h>
|
||||
#include <vector>
|
||||
#include "tcbspan/span.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
class Layer;
|
||||
}
|
||||
|
||||
namespace Slic3r::Seams::Geometry {
|
||||
|
||||
struct Extrusion
|
||||
{
|
||||
Extrusion(
|
||||
Polygon &&polygon,
|
||||
BoundingBox bounding_box,
|
||||
const double width,
|
||||
const ExPolygon &island_boundary
|
||||
);
|
||||
|
||||
Extrusion(const Extrusion &) = delete;
|
||||
Extrusion(Extrusion &&) = default;
|
||||
Extrusion &operator=(const Extrusion &) = delete;
|
||||
Extrusion &operator=(Extrusion &&) = delete;
|
||||
|
||||
Polygon polygon;
|
||||
BoundingBox bounding_box;
|
||||
double width;
|
||||
const ExPolygon &island_boundary;
|
||||
|
||||
// At index 0 there is the bounding box of contour. Rest are the bounding boxes of holes in order.
|
||||
BoundingBoxes island_boundary_bounding_boxes;
|
||||
};
|
||||
|
||||
using Extrusions = std::vector<Extrusion>;
|
||||
|
||||
std::vector<Extrusions> get_extrusions(tcb::span<const Slic3r::Layer *const> object_layers);
|
||||
|
||||
Vec2d get_polygon_normal(
|
||||
const std::vector<Vec2d> &points, const std::size_t index, const double min_arm_length
|
||||
);
|
||||
|
||||
Vec2d get_normal(const Vec2d &vector);
|
||||
|
||||
std::pair<Vec2d, double> distance_to_segment_squared(const Linef &segment, const Vec2d &point);
|
||||
|
||||
using Mapping = std::vector<std::vector<std::size_t>>;
|
||||
using MappingOperatorResult = std::optional<std::pair<std::size_t, double>>;
|
||||
using MappingOperator = std::function<MappingOperatorResult(std::size_t, std::size_t)>;
|
||||
|
||||
/**
|
||||
* @brief Indirectly map list of lists into buckets.
|
||||
*
|
||||
* Look for chains of items accross the lists.
|
||||
* It may do this mapping: [[1, 2], [3, 4, 5], [6]] -> [[1, 4, 6], [2, 3], [5]].
|
||||
* It depends on the weights provided by the mapping operator.
|
||||
*
|
||||
* Same bucket cannot be choosen for multiple items in any of the inner lists.
|
||||
* Bucket is choosen **based on the weight** provided by the mapping operator. Multiple items from
|
||||
* the same list may want to claim the same bucket. In that case, the item with the biggest weight
|
||||
* wins the bucket. For example: [[1, 2], [3]] -> [[1, 3], [2]]
|
||||
*
|
||||
* @param list_sizes Vector of sizes of the original lists in a list.
|
||||
* @param mapping_operator Operator that takes layer index and item index on that layer as input
|
||||
* and returns the best fitting item index from the next layer, along with weight, representing how
|
||||
* good the fit is. It may return nullopt if there is no good fit.
|
||||
*
|
||||
* @return Mapping [outter_list_index][inner_list_index] -> bucket id and the number of buckets.
|
||||
*/
|
||||
std::pair<Mapping, std::size_t> get_mapping(
|
||||
const std::vector<std::size_t> &list_sizes, const MappingOperator &mapping_operator
|
||||
);
|
||||
|
||||
std::vector<Vec2d> oversample_edge(const Vec2d &from, const Vec2d &to, const double max_distance);
|
||||
|
||||
template <typename NestedVector>
|
||||
std::size_t get_flat_size(const NestedVector &nested_vector) {
|
||||
return std::accumulate(
|
||||
nested_vector.begin(), nested_vector.end(), std::size_t{0},
|
||||
[](const std::size_t sum, const auto &vector) { return sum + vector.size(); }
|
||||
);
|
||||
}
|
||||
|
||||
template<typename NestedVector>
|
||||
std::vector<std::pair<std::size_t, std::size_t>> get_flat_index2indices_table(
|
||||
const NestedVector &nested_vector
|
||||
) {
|
||||
std::vector<std::pair<std::size_t, std::size_t>> result;
|
||||
for (std::size_t parent_index{0}; parent_index < nested_vector.size(); ++parent_index) {
|
||||
const auto &vector{nested_vector[parent_index]};
|
||||
for (std::size_t nested_index{0}; nested_index < vector.size(); ++nested_index) {
|
||||
result.push_back({parent_index, nested_index});
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename NestedVector>
|
||||
void iterate_nested(const NestedVector &nested_vector, const std::function<void(std::size_t, std::size_t)> &function) {
|
||||
std::size_t flat_size{Geometry::get_flat_size(nested_vector)};
|
||||
using Range = tbb::blocked_range<size_t>;
|
||||
const Range range{0, flat_size};
|
||||
|
||||
std::vector<std::pair<std::size_t, std::size_t>> index_table{
|
||||
get_flat_index2indices_table(nested_vector)};
|
||||
|
||||
// Iterate the shells as if it was flat.
|
||||
tbb::parallel_for(range, [&](Range range) {
|
||||
for (std::size_t index{range.begin()}; index < range.end(); ++index) {
|
||||
const auto[parent_index, nested_index]{index_table[index]};
|
||||
function(parent_index, nested_index);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void visit_forward(
|
||||
const std::size_t start_index,
|
||||
const std::size_t loop_size,
|
||||
const std::function<bool(std::size_t)> &visitor
|
||||
);
|
||||
void visit_backward(
|
||||
const std::size_t start_index,
|
||||
const std::size_t loop_size,
|
||||
const std::function<bool(std::size_t)> &visitor
|
||||
);
|
||||
|
||||
std::vector<Vec2d> unscaled(const Points &points);
|
||||
|
||||
std::vector<Linef> unscaled(const Lines &lines);
|
||||
|
||||
Points scaled(const std::vector<Vec2d> &points);
|
||||
|
||||
std::vector<double> get_embedding_distances(
|
||||
const std::vector<Vec2d> &points, const AABBTreeLines::LinesDistancer<Linef> &perimeter_distancer
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Calculate overhang angle for each of the points over the previous layer perimeters.
|
||||
*
|
||||
* Larger angle <=> larger overhang. E.g. floating box has overhang = PI / 2.
|
||||
*
|
||||
* @returns Angles in radians <0, PI / 2>.
|
||||
*/
|
||||
std::vector<double> get_overhangs(
|
||||
const std::vector<Vec2d> &points,
|
||||
const AABBTreeLines::LinesDistancer<Linef> &previous_layer_perimeter_distancer,
|
||||
const double layer_height
|
||||
);
|
||||
|
||||
// Measured from outside, convex is positive
|
||||
std::vector<double> get_vertex_angles(const std::vector<Vec2d> &points, const double min_arm_length);
|
||||
|
||||
double bounding_box_distance(const BoundingBox &a, const BoundingBox &b);
|
||||
|
||||
std::pair<std::size_t, double> pick_closest_bounding_box(
|
||||
const BoundingBox &to, const BoundingBoxes &choose_from
|
||||
);
|
||||
|
||||
Polygon to_polygon(const ExtrusionLoop &loop);
|
||||
|
||||
} // namespace Slic3r::Seams::Geometry
|
||||
|
||||
#endif // libslic3r_SeamGeometry_hpp_
|
48
src/libslic3r/GCode/SeamPainting.cpp
Normal file
48
src/libslic3r/GCode/SeamPainting.cpp
Normal file
@ -0,0 +1,48 @@
|
||||
#include "libslic3r/GCode/SeamPainting.hpp"
|
||||
|
||||
namespace Slic3r::Seams::ModelInfo {
|
||||
Painting::Painting(const Transform3d &obj_transform, const ModelVolumePtrs &volumes) {
|
||||
for (const ModelVolume *mv : volumes) {
|
||||
if (mv->is_seam_painted()) {
|
||||
auto model_transformation = obj_transform * mv->get_matrix();
|
||||
|
||||
indexed_triangle_set enforcers = mv->seam_facets
|
||||
.get_facets(*mv, EnforcerBlockerType::ENFORCER);
|
||||
its_transform(enforcers, model_transformation);
|
||||
its_merge(this->enforcers, enforcers);
|
||||
|
||||
indexed_triangle_set blockers = mv->seam_facets
|
||||
.get_facets(*mv, EnforcerBlockerType::BLOCKER);
|
||||
its_transform(blockers, model_transformation);
|
||||
its_merge(this->blockers, blockers);
|
||||
}
|
||||
}
|
||||
|
||||
this->enforcers_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(
|
||||
this->enforcers.vertices, this->enforcers.indices
|
||||
);
|
||||
this->blockers_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(
|
||||
this->blockers.vertices, this->blockers.indices
|
||||
);
|
||||
}
|
||||
|
||||
bool Painting::is_enforced(const Vec3f &position, float radius) const {
|
||||
if (enforcers.empty()) {
|
||||
return false;
|
||||
}
|
||||
float radius_sqr = radius * radius;
|
||||
return AABBTreeIndirect::is_any_triangle_in_radius(
|
||||
enforcers.vertices, enforcers.indices, enforcers_tree, position, radius_sqr
|
||||
);
|
||||
}
|
||||
|
||||
bool Painting::is_blocked(const Vec3f &position, float radius) const {
|
||||
if (blockers.empty()) {
|
||||
return false;
|
||||
}
|
||||
float radius_sqr = radius * radius;
|
||||
return AABBTreeIndirect::is_any_triangle_in_radius(
|
||||
blockers.vertices, blockers.indices, blockers_tree, position, radius_sqr
|
||||
);
|
||||
}
|
||||
} // namespace Slic3r::Seams::ModelInfo
|
24
src/libslic3r/GCode/SeamPainting.hpp
Normal file
24
src/libslic3r/GCode/SeamPainting.hpp
Normal file
@ -0,0 +1,24 @@
|
||||
#ifndef libslic3r_GlobalModelInfo_hpp_
|
||||
#define libslic3r_GlobalModelInfo_hpp_
|
||||
|
||||
#include "libslic3r/AABBTreeIndirect.hpp"
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
|
||||
namespace Slic3r::Seams::ModelInfo {
|
||||
class Painting
|
||||
{
|
||||
public:
|
||||
Painting(const Transform3d &obj_transform, const ModelVolumePtrs &volumes);
|
||||
|
||||
bool is_enforced(const Vec3f &position, float radius) const;
|
||||
bool is_blocked(const Vec3f &position, float radius) const;
|
||||
|
||||
private:
|
||||
indexed_triangle_set enforcers;
|
||||
indexed_triangle_set blockers;
|
||||
AABBTreeIndirect::Tree<3, float> enforcers_tree;
|
||||
AABBTreeIndirect::Tree<3, float> blockers_tree;
|
||||
};
|
||||
} // namespace Slic3r::Seams::ModelInfo
|
||||
#endif // libslic3r_GlobalModelInfo_hpp_
|
409
src/libslic3r/GCode/SeamPerimeters.cpp
Normal file
409
src/libslic3r/GCode/SeamPerimeters.cpp
Normal file
@ -0,0 +1,409 @@
|
||||
#include <fstream>
|
||||
|
||||
#include "ClipperUtils.hpp"
|
||||
|
||||
#include "libslic3r/Layer.hpp"
|
||||
|
||||
#include "libslic3r/GCode/SeamGeometry.hpp"
|
||||
#include "libslic3r/GCode/SeamPerimeters.hpp"
|
||||
|
||||
namespace Slic3r::Seams::Perimeters::Impl {
|
||||
|
||||
std::vector<Vec2d> oversample_painted(
|
||||
const std::vector<Vec2d> &points,
|
||||
const std::function<bool(Vec3f, double)> &is_painted,
|
||||
const double slice_z,
|
||||
const double max_distance
|
||||
) {
|
||||
std::vector<Vec2d> result;
|
||||
|
||||
for (std::size_t index{0}; index < points.size(); ++index) {
|
||||
const Vec2d &point{points[index]};
|
||||
|
||||
result.push_back(point);
|
||||
|
||||
const std::size_t next_index{index == points.size() - 1 ? 0 : index + 1};
|
||||
const Vec2d &next_point{points[next_index]};
|
||||
const float next_point_distance{static_cast<float>((point - next_point).norm())};
|
||||
const Vec2d middle_point{(point + next_point) / 2.0};
|
||||
Vec3f point3d{to_3d(middle_point, slice_z).cast<float>()};
|
||||
if (is_painted(point3d, next_point_distance / 2.0)) {
|
||||
for (const Vec2d &edge_point :
|
||||
Geometry::oversample_edge(point, next_point, max_distance)) {
|
||||
result.push_back(edge_point);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::pair<std::vector<Vec2d>, std::vector<PointType>> remove_redundant_points(
|
||||
const std::vector<Vec2d> &points,
|
||||
const std::vector<PointType> &point_types,
|
||||
const double tolerance
|
||||
) {
|
||||
std::vector<Vec2d> points_result;
|
||||
std::vector<PointType> point_types_result;
|
||||
|
||||
auto range_start{points.begin()};
|
||||
|
||||
for (auto iterator{points.begin()}; iterator != points.end(); ++iterator) {
|
||||
const std::int64_t index{std::distance(points.begin(), iterator)};
|
||||
if (next(iterator) == points.end() || point_types[index] != point_types[index + 1]) {
|
||||
std::vector<Vec2d> simplification_result;
|
||||
douglas_peucker<double>(
|
||||
range_start, next(iterator), std::back_inserter(simplification_result), tolerance,
|
||||
[](const Vec2d &point) { return point; }
|
||||
);
|
||||
|
||||
points_result.insert(
|
||||
points_result.end(), simplification_result.begin(), simplification_result.end()
|
||||
);
|
||||
const std::vector<PointType>
|
||||
point_types_to_add(simplification_result.size(), point_types[index]);
|
||||
point_types_result.insert(
|
||||
point_types_result.end(), point_types_to_add.begin(), point_types_to_add.end()
|
||||
);
|
||||
|
||||
range_start = next(iterator);
|
||||
}
|
||||
}
|
||||
|
||||
return {points_result, point_types_result};
|
||||
}
|
||||
|
||||
std::vector<PointType> get_point_types(
|
||||
const std::vector<Vec2d> &positions,
|
||||
const ModelInfo::Painting &painting,
|
||||
const double slice_z,
|
||||
const double painting_radius
|
||||
) {
|
||||
std::vector<PointType> result;
|
||||
result.reserve(positions.size());
|
||||
using std::transform, std::back_inserter;
|
||||
transform(
|
||||
positions.begin(), positions.end(), back_inserter(result),
|
||||
[&](const Vec2d &point) {
|
||||
const Vec3f point3d{to_3d(point.cast<float>(), static_cast<float>(slice_z))};
|
||||
if (painting.is_blocked(point3d, painting_radius)) {
|
||||
return PointType::blocker;
|
||||
}
|
||||
if (painting.is_enforced(point3d, painting_radius)) {
|
||||
return PointType::enforcer;
|
||||
}
|
||||
return PointType::common;
|
||||
}
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<PointClassification> classify_points(
|
||||
const std::vector<double> &embeddings,
|
||||
const std::optional<std::vector<double>> &overhangs,
|
||||
const double overhang_threshold,
|
||||
const double embedding_threshold
|
||||
) {
|
||||
std::vector<PointClassification> result;
|
||||
result.reserve(embeddings.size());
|
||||
using std::transform, std::back_inserter;
|
||||
transform(
|
||||
embeddings.begin(), embeddings.end(), back_inserter(result),
|
||||
[&, i = 0](const double embedding) mutable {
|
||||
const unsigned index = i++;
|
||||
if (overhangs && overhangs->operator[](index) > overhang_threshold) {
|
||||
return PointClassification::overhang;
|
||||
}
|
||||
if (embedding > embedding_threshold) {
|
||||
return PointClassification::embedded;
|
||||
}
|
||||
return PointClassification::common;
|
||||
}
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<AngleType> get_angle_types(
|
||||
const std::vector<double> &angles, const double convex_threshold, const double concave_threshold
|
||||
) {
|
||||
std::vector<AngleType> result;
|
||||
using std::transform, std::back_inserter;
|
||||
transform(angles.begin(), angles.end(), back_inserter(result), [&](const double angle) {
|
||||
if (angle > convex_threshold) {
|
||||
return AngleType::convex;
|
||||
}
|
||||
if (angle < -concave_threshold) {
|
||||
return AngleType::concave;
|
||||
}
|
||||
return AngleType::smooth;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<AngleType> merge_angle_types(
|
||||
const std::vector<AngleType> &angle_types,
|
||||
const std::vector<AngleType> &smooth_angle_types,
|
||||
const std::vector<Vec2d> &points,
|
||||
const double min_arm_length
|
||||
) {
|
||||
std::vector<AngleType> result;
|
||||
result.reserve(angle_types.size());
|
||||
for (std::size_t index{0}; index < angle_types.size(); ++index) {
|
||||
const AngleType &angle_type{angle_types[index]};
|
||||
const AngleType &smooth_angle_type{smooth_angle_types[index]};
|
||||
|
||||
AngleType resulting_type{angle_type};
|
||||
|
||||
if (smooth_angle_type != angle_type && smooth_angle_type != AngleType::smooth) {
|
||||
resulting_type = smooth_angle_type;
|
||||
|
||||
// Check if there is a sharp angle in the vicinity. If so, do not use the smooth angle.
|
||||
Geometry::visit_forward(index, angle_types.size(), [&](const std::size_t forward_index) {
|
||||
const double distance{(points[forward_index] - points[index]).norm()};
|
||||
if (distance > min_arm_length) {
|
||||
return true;
|
||||
}
|
||||
if (angle_types[forward_index] == smooth_angle_type) {
|
||||
resulting_type = angle_type;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
Geometry::visit_backward(index, angle_types.size(), [&](const std::size_t backward_index) {
|
||||
const double distance{(points[backward_index] - points[index]).norm()};
|
||||
if (distance > min_arm_length) {
|
||||
return true;
|
||||
}
|
||||
if (angle_types[backward_index] == smooth_angle_type) {
|
||||
resulting_type = angle_type;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
result.push_back(resulting_type);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace Slic3r::Seams::Perimeters::Impl
|
||||
|
||||
namespace Slic3r::Seams::Perimeters {
|
||||
|
||||
LayerInfos get_layer_infos(
|
||||
tcb::span<const Slic3r::Layer* const> object_layers, const double elephant_foot_compensation
|
||||
) {
|
||||
LayerInfos result(object_layers.size());
|
||||
|
||||
using Range = tbb::blocked_range<size_t>;
|
||||
const Range range{0, object_layers.size()};
|
||||
tbb::parallel_for(range, [&](Range range) {
|
||||
for (std::size_t layer_index{range.begin()}; layer_index < range.end(); ++layer_index) {
|
||||
result[layer_index] = LayerInfo::create(
|
||||
*object_layers[layer_index], layer_index, elephant_foot_compensation
|
||||
);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
LayerInfo LayerInfo::create(
|
||||
const Slic3r::Layer &object_layer,
|
||||
const std::size_t index,
|
||||
const double elephant_foot_compensation
|
||||
) {
|
||||
AABBTreeLines::LinesDistancer<Linef> perimeter_distancer{
|
||||
to_unscaled_linesf({object_layer.lslices})};
|
||||
|
||||
using PreviousLayerDistancer = std::optional<AABBTreeLines::LinesDistancer<Linef>>;
|
||||
PreviousLayerDistancer previous_layer_perimeter_distancer;
|
||||
if (object_layer.lower_layer != nullptr) {
|
||||
previous_layer_perimeter_distancer = PreviousLayerDistancer{
|
||||
to_unscaled_linesf(object_layer.lower_layer->lslices)};
|
||||
}
|
||||
|
||||
return {
|
||||
std::move(perimeter_distancer),
|
||||
std::move(previous_layer_perimeter_distancer),
|
||||
index,
|
||||
object_layer.height,
|
||||
object_layer.slice_z,
|
||||
index == 0 ? elephant_foot_compensation : 0.0};
|
||||
}
|
||||
|
||||
double Perimeter::IndexToCoord::operator()(const size_t index, size_t dim) const {
|
||||
return positions[index][dim];
|
||||
}
|
||||
|
||||
Perimeter::PointTrees get_kd_trees(
|
||||
const PointType point_type,
|
||||
const std::vector<PointType> &all_point_types,
|
||||
const std::vector<PointClassification> &point_classifications,
|
||||
const Perimeter::IndexToCoord &index_to_coord
|
||||
) {
|
||||
std::vector<std::size_t> overhang_indexes;
|
||||
std::vector<std::size_t> embedded_indexes;
|
||||
std::vector<std::size_t> common_indexes;
|
||||
for (std::size_t i{0}; i < all_point_types.size(); ++i) {
|
||||
if (all_point_types[i] == point_type) {
|
||||
switch (point_classifications[i]) {
|
||||
case PointClassification::overhang: overhang_indexes.push_back(i); break;
|
||||
case PointClassification::embedded: embedded_indexes.push_back(i); break;
|
||||
case PointClassification::common: common_indexes.push_back(i); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Perimeter::PointTrees trees;
|
||||
if (!overhang_indexes.empty()) {
|
||||
trees.overhanging_points = Perimeter::PointTree{index_to_coord};
|
||||
trees.overhanging_points->build(overhang_indexes);
|
||||
}
|
||||
if (!embedded_indexes.empty()) {
|
||||
trees.embedded_points = Perimeter::PointTree{index_to_coord};
|
||||
trees.embedded_points->build(embedded_indexes);
|
||||
}
|
||||
if (!common_indexes.empty()) {
|
||||
trees.common_points = Perimeter::PointTree{index_to_coord};
|
||||
trees.common_points->build(common_indexes);
|
||||
}
|
||||
return trees;
|
||||
}
|
||||
|
||||
Perimeter::Perimeter(
|
||||
const double slice_z,
|
||||
const std::size_t layer_index,
|
||||
const bool is_hole,
|
||||
std::vector<Vec2d> &&positions,
|
||||
std::vector<double> &&angles,
|
||||
std::vector<PointType> &&point_types,
|
||||
std::vector<PointClassification> &&point_classifications,
|
||||
std::vector<AngleType> &&angle_types
|
||||
)
|
||||
: slice_z(slice_z)
|
||||
, layer_index(layer_index)
|
||||
, is_hole(is_hole)
|
||||
, positions(std::move(positions))
|
||||
, angles(std::move(angles))
|
||||
, index_to_coord(IndexToCoord{tcb::span{this->positions}})
|
||||
, point_types(std::move(point_types))
|
||||
, point_classifications(std::move(point_classifications))
|
||||
, angle_types(std::move(angle_types))
|
||||
, enforced_points(get_kd_trees(
|
||||
PointType::enforcer, this->point_types, this->point_classifications, this->index_to_coord
|
||||
))
|
||||
, common_points(get_kd_trees(
|
||||
PointType::common, this->point_types, this->point_classifications, this->index_to_coord
|
||||
))
|
||||
, blocked_points(get_kd_trees(
|
||||
PointType::blocker, this->point_types, this->point_classifications, this->index_to_coord
|
||||
)) {}
|
||||
|
||||
Perimeter Perimeter::create_degenerate(
|
||||
std::vector<Vec2d> &&points, const double slice_z, const std::size_t layer_index
|
||||
) {
|
||||
std::vector<PointType> point_types(points.size(), PointType::common);
|
||||
std::vector<PointClassification>
|
||||
point_classifications(points.size(), PointClassification::common);
|
||||
std::vector<double> angles(points.size());
|
||||
std::vector<AngleType> angle_types(points.size(), AngleType::smooth);
|
||||
Perimeter perimeter{
|
||||
slice_z,
|
||||
layer_index,
|
||||
false,
|
||||
std::move(points),
|
||||
std::move(angles),
|
||||
std::move(point_types),
|
||||
std::move(point_classifications),
|
||||
std::move(angle_types)};
|
||||
perimeter.is_degenerate = true;
|
||||
return perimeter;
|
||||
}
|
||||
|
||||
Perimeter Perimeter::create(
|
||||
const Polygon &polygon,
|
||||
const ModelInfo::Painting &painting,
|
||||
const LayerInfo &layer_info,
|
||||
const PerimeterParams ¶ms
|
||||
) {
|
||||
if (polygon.size() < 3) {
|
||||
return Perimeter::create_degenerate(
|
||||
Geometry::unscaled(polygon.points), layer_info.slice_z, layer_info.index
|
||||
);
|
||||
}
|
||||
std::vector<Vec2d> points;
|
||||
if (layer_info.elephant_foot_compensation > 0) {
|
||||
const Polygons expanded{expand(polygon, scaled(layer_info.elephant_foot_compensation))};
|
||||
if (expanded.empty()) {
|
||||
points = Geometry::unscaled(polygon.points);
|
||||
} else {
|
||||
points = Geometry::unscaled(expanded.front().points);
|
||||
}
|
||||
} else {
|
||||
points = Geometry::unscaled(polygon.points);
|
||||
}
|
||||
|
||||
auto is_painted{[&](const Vec3f &point, const double radius) {
|
||||
return painting.is_enforced(point, radius) || painting.is_blocked(point, radius);
|
||||
}};
|
||||
|
||||
std::vector<Vec2d> perimeter_points{
|
||||
Impl::oversample_painted(points, is_painted, layer_info.slice_z, params.oversampling_max_distance)};
|
||||
|
||||
std::vector<PointType> point_types{
|
||||
Impl::get_point_types(perimeter_points, painting, layer_info.slice_z, params.painting_radius)};
|
||||
|
||||
std::tie(perimeter_points, point_types) =
|
||||
Impl::remove_redundant_points(perimeter_points, point_types, params.simplification_epsilon);
|
||||
|
||||
const std::vector<double> embeddings{
|
||||
Geometry::get_embedding_distances(perimeter_points, layer_info.distancer)};
|
||||
std::optional<std::vector<double>> overhangs;
|
||||
if (layer_info.previous_distancer) {
|
||||
overhangs = Geometry::get_overhangs(
|
||||
perimeter_points, *layer_info.previous_distancer, layer_info.height
|
||||
);
|
||||
}
|
||||
std::vector<PointClassification> point_classifications{
|
||||
Impl::classify_points(embeddings, overhangs, params.overhang_threshold, params.embedding_threshold)};
|
||||
|
||||
std::vector<double> smooth_angles{Geometry::get_vertex_angles(perimeter_points, params.smooth_angle_arm_length)};
|
||||
std::vector<double> angles{Geometry::get_vertex_angles(perimeter_points, params.sharp_angle_arm_length)};
|
||||
std::vector<AngleType> angle_types{
|
||||
Impl::get_angle_types(angles, params.convex_threshold, params.concave_threshold)};
|
||||
std::vector<AngleType> smooth_angle_types{
|
||||
Impl::get_angle_types(smooth_angles, params.convex_threshold, params.concave_threshold)};
|
||||
angle_types = Impl::merge_angle_types(angle_types, smooth_angle_types, perimeter_points, params.smooth_angle_arm_length);
|
||||
|
||||
const bool is_hole{polygon.is_clockwise()};
|
||||
|
||||
return Perimeter{
|
||||
layer_info.slice_z,
|
||||
layer_info.index,
|
||||
is_hole,
|
||||
std::move(perimeter_points),
|
||||
std::move(angles),
|
||||
std::move(point_types),
|
||||
std::move(point_classifications),
|
||||
std::move(angle_types)};
|
||||
}
|
||||
|
||||
Shells::Shells<> create_perimeters(
|
||||
const std::vector<Shells::Shell<Polygon>> &shells,
|
||||
const std::vector<LayerInfo> &layer_infos,
|
||||
const ModelInfo::Painting &painting,
|
||||
const PerimeterParams ¶ms
|
||||
) {
|
||||
std::vector<Shells::Shell<>> result;
|
||||
result.reserve(shells.size());
|
||||
std::transform(
|
||||
shells.begin(), shells.end(), std::back_inserter(result),
|
||||
[](const Shells::Shell<Polygon> &shell) { return Shells::Shell<>(shell.size()); }
|
||||
);
|
||||
|
||||
Geometry::iterate_nested(shells, [&](const std::size_t shell_index, const std::size_t polygon_index){
|
||||
const Shells::Shell<Polygon> &shell{shells[shell_index]};
|
||||
const Shells::Slice<Polygon>& slice{shell[polygon_index]};
|
||||
const Polygon &polygon{slice.boundary};
|
||||
const LayerInfo &layer_info{layer_infos[slice.layer_index]};
|
||||
result[shell_index][polygon_index] = {Perimeter::create(polygon, painting, layer_info, params), slice.layer_index};
|
||||
});
|
||||
return result;
|
||||
}
|
||||
} // namespace Slic3r::Seams::Perimeter
|
190
src/libslic3r/GCode/SeamPerimeters.hpp
Normal file
190
src/libslic3r/GCode/SeamPerimeters.hpp
Normal file
@ -0,0 +1,190 @@
|
||||
#ifndef libslic3r_SeamPerimeters_hpp_
|
||||
#define libslic3r_SeamPerimeters_hpp_
|
||||
|
||||
#include <tcbspan/span.hpp>
|
||||
|
||||
#include "libslic3r/GCode/SeamPainting.hpp"
|
||||
#include "libslic3r/KDTreeIndirect.hpp"
|
||||
|
||||
#include "libslic3r/GCode/SeamShells.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
class Layer;
|
||||
}
|
||||
|
||||
namespace Slic3r::Seams::ModelInfo {
|
||||
class Painting;
|
||||
}
|
||||
|
||||
namespace Slic3r::Seams::Perimeters {
|
||||
enum class AngleType;
|
||||
enum class PointType;
|
||||
enum class PointClassification;
|
||||
struct Perimeter;
|
||||
struct PerimeterParams;
|
||||
|
||||
struct LayerInfo
|
||||
{
|
||||
static LayerInfo create(
|
||||
const Slic3r::Layer &object_layer, std::size_t index, const double elephant_foot_compensation
|
||||
);
|
||||
|
||||
AABBTreeLines::LinesDistancer<Linef> distancer;
|
||||
std::optional<AABBTreeLines::LinesDistancer<Linef>> previous_distancer;
|
||||
std::size_t index;
|
||||
double height{};
|
||||
double slice_z{};
|
||||
double elephant_foot_compensation;
|
||||
};
|
||||
|
||||
using LayerInfos = std::vector<LayerInfo>;
|
||||
|
||||
/**
|
||||
* @brief Construct LayerInfo for each of the provided layers.
|
||||
*/
|
||||
LayerInfos get_layer_infos(
|
||||
tcb::span<const Slic3r::Layer* const> object_layers, const double elephant_foot_compensation
|
||||
);
|
||||
} // namespace Slic3r::Seams::Perimeters
|
||||
|
||||
namespace Slic3r::Seams::Perimeters::Impl {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Split edges between points into multiple points if there is a painted point anywhere on
|
||||
* the edge.
|
||||
*
|
||||
* The edge will be split by points no more than max_distance apart.
|
||||
* Smaller max_distance -> more points.
|
||||
*
|
||||
* @return All the points (original and added) in order along the edges.
|
||||
*/
|
||||
std::vector<Vec2d> oversample_painted(
|
||||
const std::vector<Vec2d> &points,
|
||||
const std::function<bool(Vec3f, double)> &is_painted,
|
||||
const double slice_z,
|
||||
const double max_distance
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Call Duglas-Peucker for consecutive points of the same type.
|
||||
*
|
||||
* It never removes the first point and last point.
|
||||
*
|
||||
* @param tolerance Douglas-Peucker epsilon.
|
||||
*/
|
||||
std::pair<std::vector<Vec2d>, std::vector<PointType>> remove_redundant_points(
|
||||
const std::vector<Vec2d> &points,
|
||||
const std::vector<PointType> &point_types,
|
||||
const double tolerance
|
||||
);
|
||||
|
||||
} // namespace Slic3r::Seams::Perimeters::Impl
|
||||
|
||||
namespace Slic3r::Seams::Perimeters {
|
||||
|
||||
enum class AngleType { convex, concave, smooth };
|
||||
|
||||
enum class PointType { enforcer, blocker, common };
|
||||
|
||||
enum class PointClassification { overhang, embedded, common };
|
||||
|
||||
struct PerimeterParams
|
||||
{
|
||||
double elephant_foot_compensation{};
|
||||
double oversampling_max_distance{};
|
||||
double embedding_threshold{};
|
||||
double overhang_threshold{};
|
||||
double convex_threshold{};
|
||||
double concave_threshold{};
|
||||
double painting_radius{};
|
||||
double simplification_epsilon{};
|
||||
double smooth_angle_arm_length{};
|
||||
double sharp_angle_arm_length{};
|
||||
};
|
||||
|
||||
struct Perimeter
|
||||
{
|
||||
struct IndexToCoord
|
||||
{
|
||||
double operator()(const size_t index, size_t dim) const;
|
||||
|
||||
tcb::span<const Vec2d> positions;
|
||||
};
|
||||
|
||||
using PointTree = KDTreeIndirect<2, double, IndexToCoord>;
|
||||
using OptionalPointTree = std::optional<PointTree>;
|
||||
|
||||
struct PointTrees
|
||||
{
|
||||
OptionalPointTree embedded_points;
|
||||
OptionalPointTree common_points;
|
||||
OptionalPointTree overhanging_points;
|
||||
};
|
||||
|
||||
Perimeter() = default;
|
||||
|
||||
Perimeter(
|
||||
const double slice_z,
|
||||
const std::size_t layer_index,
|
||||
const bool is_hole,
|
||||
std::vector<Vec2d> &&positions,
|
||||
std::vector<double> &&angles,
|
||||
std::vector<PointType> &&point_types,
|
||||
std::vector<PointClassification> &&point_classifications,
|
||||
std::vector<AngleType> &&angle_types
|
||||
);
|
||||
|
||||
static Perimeter create(
|
||||
const Polygon &polygon,
|
||||
const ModelInfo::Painting &painting,
|
||||
const LayerInfo &layer_info,
|
||||
const PerimeterParams ¶ms
|
||||
);
|
||||
|
||||
static Perimeter create_degenerate(
|
||||
std::vector<Vec2d> &&points, const double slice_z, const std::size_t layer_index
|
||||
);
|
||||
|
||||
bool is_degenerate{false};
|
||||
double slice_z{};
|
||||
bool is_hole{false};
|
||||
std::size_t layer_index{};
|
||||
std::vector<Vec2d> positions{};
|
||||
std::vector<double> angles{};
|
||||
IndexToCoord index_to_coord{};
|
||||
std::vector<PointType> point_types{};
|
||||
std::vector<PointClassification> point_classifications{};
|
||||
std::vector<AngleType> angle_types{};
|
||||
|
||||
PointTrees enforced_points{};
|
||||
PointTrees common_points{};
|
||||
PointTrees blocked_points{};
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Create a Perimeter for each polygon in each of the shells.
|
||||
*/
|
||||
Shells::Shells<Perimeter> create_perimeters(
|
||||
const std::vector<Shells::Shell<Polygon>> &shells,
|
||||
const std::vector<LayerInfo> &layer_infos,
|
||||
const ModelInfo::Painting &painting,
|
||||
const PerimeterParams ¶ms
|
||||
);
|
||||
|
||||
inline std::size_t get_layer_count(
|
||||
const Shells::Shells<> &shells
|
||||
) {
|
||||
std::size_t layer_count{0};
|
||||
for (const Shells::Shell<> &shell : shells) {
|
||||
for (const Shells::Slice<>& slice : shell) {
|
||||
if (slice.layer_index >= layer_count) {
|
||||
layer_count = slice.layer_index + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return layer_count;
|
||||
}
|
||||
} // namespace Slic3r::Seams::Perimeters
|
||||
|
||||
#endif // libslic3r_SeamPerimeters_hpp_
|
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,5 @@
|
||||
///|/ Copyright (c) Prusa Research 2020 - 2022 Pavel Mikuš @Godrak, Lukáš Matěna @lukasmatena, Vojtěch Bubník @bubnikv
|
||||
///|/ Copyright (c) Prusa Research 2020 - 2022 Pavel Mikuš @Godrak, Lukáš Matěna @lukasmatena,
|
||||
/// Vojtěch Bubník @bubnikv
|
||||
///|/
|
||||
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
|
||||
///|/
|
||||
@ -10,159 +11,62 @@
|
||||
#include <memory>
|
||||
#include <atomic>
|
||||
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "libslic3r/ExtrusionEntity.hpp"
|
||||
#include "libslic3r/GCode/SeamAligned.hpp"
|
||||
#include "libslic3r/Polygon.hpp"
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "libslic3r/BoundingBox.hpp"
|
||||
#include "libslic3r/AABBTreeIndirect.hpp"
|
||||
#include "libslic3r/KDTreeIndirect.hpp"
|
||||
#include "libslic3r/Print.hpp"
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/GCode/SeamPerimeters.hpp"
|
||||
#include "libslic3r/GCode/SeamChoice.hpp"
|
||||
#include "libslic3r/GCode/ModelVisibility.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace Slic3r::Seams {
|
||||
|
||||
class PrintObject;
|
||||
class ExtrusionLoop;
|
||||
class Print;
|
||||
class Layer;
|
||||
|
||||
namespace EdgeGrid {
|
||||
class Grid;
|
||||
}
|
||||
|
||||
namespace SeamPlacerImpl {
|
||||
|
||||
|
||||
struct GlobalModelInfo;
|
||||
struct SeamComparator;
|
||||
|
||||
enum class EnforcedBlockedSeamPoint {
|
||||
Blocked = 0,
|
||||
Neutral = 1,
|
||||
Enforced = 2,
|
||||
struct BoundedPerimeter {
|
||||
Perimeters::Perimeter perimeter;
|
||||
BoundingBox bounding_box;
|
||||
};
|
||||
|
||||
// struct representing single perimeter loop
|
||||
struct Perimeter {
|
||||
size_t start_index{};
|
||||
size_t end_index{}; //inclusive!
|
||||
size_t seam_index{};
|
||||
float flow_width{};
|
||||
using ObjectSeams =
|
||||
std::unordered_map<const PrintObject *, std::vector<std::vector<SeamPerimeterChoice>>>;
|
||||
using LayerPerimeters = std::vector<std::vector<BoundedPerimeter>>;
|
||||
using ObjectLayerPerimeters = std::unordered_map<const PrintObject *, LayerPerimeters>;
|
||||
|
||||
// During alignment, a final position may be stored here. In that case, finalized is set to true.
|
||||
// Note that final seam position is not limited to points of the perimeter loop. In theory it can be any position
|
||||
// Random position also uses this flexibility to set final seam point position
|
||||
bool finalized = false;
|
||||
Vec3f final_seam_position = Vec3f::Zero();
|
||||
};
|
||||
|
||||
//Struct over which all processing of perimeters is done. For each perimeter point, its respective candidate is created,
|
||||
// then all the needed attributes are computed and finally, for each perimeter one point is chosen as seam.
|
||||
// This seam position can be then further aligned
|
||||
struct SeamCandidate {
|
||||
SeamCandidate(const Vec3f &pos, Perimeter &perimeter,
|
||||
float local_ccw_angle,
|
||||
EnforcedBlockedSeamPoint type) :
|
||||
position(pos), perimeter(perimeter), visibility(0.0f), overhang(0.0f), embedded_distance(0.0f), local_ccw_angle(
|
||||
local_ccw_angle), type(type), central_enforcer(false) {
|
||||
}
|
||||
const Vec3f position;
|
||||
// pointer to Perimeter loop of this point. It is shared across all points of the loop
|
||||
Perimeter &perimeter;
|
||||
float visibility;
|
||||
float overhang;
|
||||
// distance inside the merged layer regions, for detecting perimeter points which are hidden indside the print (e.g. multimaterial join)
|
||||
// Negative sign means inside the print, comes from EdgeGrid structure
|
||||
float embedded_distance;
|
||||
float local_ccw_angle;
|
||||
EnforcedBlockedSeamPoint type;
|
||||
bool central_enforcer; //marks this candidate as central point of enforced segment on the perimeter - important for alignment
|
||||
};
|
||||
|
||||
struct SeamCandidateCoordinateFunctor {
|
||||
SeamCandidateCoordinateFunctor(const std::vector<SeamCandidate> &seam_candidates) :
|
||||
seam_candidates(seam_candidates) {
|
||||
}
|
||||
const std::vector<SeamCandidate> &seam_candidates;
|
||||
float operator()(size_t index, size_t dim) const {
|
||||
return seam_candidates[index].position[dim];
|
||||
}
|
||||
};
|
||||
} // namespace SeamPlacerImpl
|
||||
|
||||
struct PrintObjectSeamData
|
||||
struct Params
|
||||
{
|
||||
using SeamCandidatesTree = KDTreeIndirect<3, float, SeamPlacerImpl::SeamCandidateCoordinateFunctor>;
|
||||
|
||||
struct LayerSeams
|
||||
{
|
||||
Slic3r::deque<SeamPlacerImpl::Perimeter> perimeters;
|
||||
std::vector<SeamPlacerImpl::SeamCandidate> points;
|
||||
std::unique_ptr<SeamCandidatesTree> points_tree;
|
||||
};
|
||||
// Map of PrintObjects (PO) -> vector of layers of PO -> vector of perimeter
|
||||
std::vector<LayerSeams> layers;
|
||||
// Map of PrintObjects (PO) -> vector of layers of PO -> unique_ptr to KD
|
||||
// tree of all points of the given layer
|
||||
|
||||
void clear()
|
||||
{
|
||||
layers.clear();
|
||||
}
|
||||
double max_nearest_detour;
|
||||
double rear_project_threshold;
|
||||
Aligned::Params aligned;
|
||||
double max_distance{};
|
||||
unsigned random_seed{};
|
||||
double convex_visibility_modifier{};
|
||||
double concave_visibility_modifier{};
|
||||
Perimeters::PerimeterParams perimeter;
|
||||
Slic3r::ModelInfo::Visibility::Params visibility;
|
||||
SeamPosition seam_preference;
|
||||
bool staggered_inner_seams;
|
||||
};
|
||||
|
||||
class SeamPlacer {
|
||||
std::ostream& operator<<(std::ostream& os, const Params& params);
|
||||
|
||||
class Placer
|
||||
{
|
||||
public:
|
||||
// Number of samples generated on the mesh. There are sqr_rays_per_sample_point*sqr_rays_per_sample_point rays casted from each samples
|
||||
static constexpr size_t raycasting_visibility_samples_count = 30000;
|
||||
static constexpr size_t fast_decimation_triangle_count_target = 16000;
|
||||
//square of number of rays per sample point
|
||||
static constexpr size_t sqr_rays_per_sample_point = 5;
|
||||
static Params get_params(const DynamicPrintConfig &config);
|
||||
|
||||
// snapping angle - angles larger than this value will be snapped to during seam painting
|
||||
static constexpr float sharp_angle_snapping_threshold = 55.0f * float(PI) / 180.0f;
|
||||
// overhang angle for seam placement that still yields good results, in degrees, measured from vertical direction
|
||||
static constexpr float overhang_angle_threshold = 50.0f * float(PI) / 180.0f;
|
||||
void init(
|
||||
SpanOfConstPtrs<PrintObject> objects,
|
||||
const Params ¶ms,
|
||||
const std::function<void(void)> &throw_if_canceled
|
||||
);
|
||||
|
||||
// determines angle importance compared to visibility ( neutral value is 1.0f. )
|
||||
static constexpr float angle_importance_aligned = 0.6f;
|
||||
static constexpr float angle_importance_nearest = 1.0f; // use much higher angle importance for nearest mode, to combat the visibility info noise
|
||||
|
||||
// For long polygon sides, if they are close to the custom seam drawings, they are oversampled with this step size
|
||||
static constexpr float enforcer_oversampling_distance = 0.2f;
|
||||
|
||||
// When searching for seam clusters for alignment:
|
||||
// following value describes, how much worse score can point have and still be picked into seam cluster instead of original seam point on the same layer
|
||||
static constexpr float seam_align_score_tolerance = 0.3f;
|
||||
// seam_align_tolerable_dist_factor - how far to search for seam from current position, final dist is seam_align_tolerable_dist_factor * flow_width
|
||||
static constexpr float seam_align_tolerable_dist_factor = 4.0f;
|
||||
// minimum number of seams needed in cluster to make alignment happen
|
||||
static constexpr size_t seam_align_minimum_string_seams = 6;
|
||||
// millimeters covered by spline; determines number of splines for the given string
|
||||
static constexpr size_t seam_align_mm_per_segment = 4.0f;
|
||||
|
||||
//The following data structures hold all perimeter points for all PrintObject.
|
||||
std::unordered_map<const PrintObject*, PrintObjectSeamData> m_seam_per_object;
|
||||
|
||||
void init(const Print &print, std::function<void(void)> throw_if_canceled_func);
|
||||
|
||||
Point place_seam(const Layer *layer, const ExtrusionLoop &loop, bool external_first, const Point &last_pos) const;
|
||||
Point place_seam(const Layer *layer, const ExtrusionLoop &loop, const Point &last_pos) const;
|
||||
|
||||
private:
|
||||
void gather_seam_candidates(const PrintObject *po, const SeamPlacerImpl::GlobalModelInfo &global_model_info);
|
||||
void calculate_candidates_visibility(const PrintObject *po,
|
||||
const SeamPlacerImpl::GlobalModelInfo &global_model_info);
|
||||
void calculate_overhangs_and_layer_embedding(const PrintObject *po);
|
||||
void align_seam_points(const PrintObject *po, const SeamPlacerImpl::SeamComparator &comparator);
|
||||
std::vector<std::pair<size_t, size_t>> find_seam_string(const PrintObject *po,
|
||||
std::pair<size_t, size_t> start_seam,
|
||||
const SeamPlacerImpl::SeamComparator &comparator) const;
|
||||
std::optional<std::pair<size_t, size_t>> find_next_seam_in_layer(
|
||||
const std::vector<PrintObjectSeamData::LayerSeams> &layers,
|
||||
const Vec3f& projected_position,
|
||||
const size_t layer_idx, const float max_distance,
|
||||
const SeamPlacerImpl::SeamComparator &comparator) const;
|
||||
Params params;
|
||||
ObjectSeams seams_per_object;
|
||||
ObjectLayerPerimeters perimeters_per_layer;
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
} // namespace Slic3r::Seams
|
||||
|
||||
#endif // libslic3r_SeamPlacer_hpp_
|
||||
|
136
src/libslic3r/GCode/SeamRandom.cpp
Normal file
136
src/libslic3r/GCode/SeamRandom.cpp
Normal file
@ -0,0 +1,136 @@
|
||||
#include <random>
|
||||
|
||||
#include "libslic3r/GCode/SeamRandom.hpp"
|
||||
#include "libslic3r/GCode/SeamGeometry.hpp"
|
||||
|
||||
namespace Slic3r::Seams::Random {
|
||||
using Perimeters::PointType;
|
||||
using Perimeters::PointClassification;
|
||||
|
||||
namespace Impl {
|
||||
std::vector<PerimeterSegment> get_segments(
|
||||
const Perimeters::Perimeter &perimeter,
|
||||
const PointType point_type,
|
||||
const PointClassification point_classification
|
||||
) {
|
||||
const std::vector<Vec2d> &positions{perimeter.positions};
|
||||
const std::vector<PointType> &point_types{perimeter.point_types};
|
||||
const std::vector<PointClassification> &point_classifications{perimeter.point_classifications};
|
||||
|
||||
std::optional<double> current_begin;
|
||||
std::optional<std::size_t> current_begin_index;
|
||||
Vec2d previous_position{positions.front()};
|
||||
double distance{0.0};
|
||||
std::vector<PerimeterSegment> result;
|
||||
for (std::size_t index{0}; index < positions.size(); ++index) {
|
||||
distance += (positions[index] - previous_position).norm();
|
||||
previous_position = positions[index];
|
||||
|
||||
if (point_types[index] == point_type &&
|
||||
point_classifications[index] == point_classification) {
|
||||
if (!current_begin) {
|
||||
current_begin = distance;
|
||||
current_begin_index = index;
|
||||
}
|
||||
} else {
|
||||
if (current_begin) {
|
||||
result.push_back(PerimeterSegment{*current_begin, distance, *current_begin_index});
|
||||
}
|
||||
current_begin = std::nullopt;
|
||||
current_begin_index = std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
if (current_begin) {
|
||||
result.push_back(PerimeterSegment{*current_begin, distance, *current_begin_index});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
PerimeterSegment pick_random_segment(
|
||||
const std::vector<PerimeterSegment> &segments, std::mt19937 &random_engine
|
||||
) {
|
||||
double length{0.0};
|
||||
for (const PerimeterSegment &segment : segments) {
|
||||
length += segment.length();
|
||||
}
|
||||
|
||||
std::uniform_real_distribution<double> distribution{0.0, length};
|
||||
double random_distance{distribution(random_engine)};
|
||||
|
||||
double distance{0.0};
|
||||
return *std::find_if(segments.begin(), segments.end(), [&](const PerimeterSegment &segment) {
|
||||
if (random_distance >= distance && random_distance <= distance + segment.length()) {
|
||||
return true;
|
||||
}
|
||||
distance += segment.length();
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
SeamChoice pick_random_point(
|
||||
const PerimeterSegment &segment, const Perimeters::Perimeter &perimeter, std::mt19937 &random_engine
|
||||
) {
|
||||
const std::vector<Vec2d> &positions{perimeter.positions};
|
||||
|
||||
if (segment.length() < std::numeric_limits<double>::epsilon()) {
|
||||
return {segment.begin_index, segment.begin_index, positions[segment.begin_index]};
|
||||
}
|
||||
|
||||
std::uniform_real_distribution<double> distribution{0.0, segment.length()};
|
||||
const double random_distance{distribution(random_engine)};
|
||||
|
||||
double distance{0.0};
|
||||
std::size_t previous_index{segment.begin_index};
|
||||
for (std::size_t index{segment.begin_index + 1}; index < perimeter.positions.size(); ++index) {
|
||||
const Vec2d edge{positions[index] - positions[previous_index]};
|
||||
|
||||
if (distance + edge.norm() >= random_distance) {
|
||||
if (random_distance - distance < std::numeric_limits<double>::epsilon()) {
|
||||
index = previous_index;
|
||||
} else if (distance + edge.norm() - random_distance < std::numeric_limits<double>::epsilon()) {
|
||||
previous_index = index;
|
||||
}
|
||||
|
||||
const double remaining_distance{random_distance - distance};
|
||||
const Vec2d position{positions[previous_index] + remaining_distance * edge.normalized()};
|
||||
return {previous_index, index, position};
|
||||
}
|
||||
|
||||
distance += edge.norm();
|
||||
previous_index = index;
|
||||
}
|
||||
|
||||
// Should be unreachable.
|
||||
return {segment.begin_index, segment.begin_index, positions[segment.begin_index]};
|
||||
}
|
||||
|
||||
std::optional<SeamChoice> Random::operator()(
|
||||
const Perimeters::Perimeter &perimeter,
|
||||
const PointType point_type,
|
||||
const PointClassification point_classification
|
||||
) const {
|
||||
std::vector<PerimeterSegment> segments{
|
||||
get_segments(perimeter, point_type, point_classification)};
|
||||
|
||||
if (!segments.empty()) {
|
||||
const PerimeterSegment segment{pick_random_segment(segments, random_engine)};
|
||||
return pick_random_point(segment, perimeter, random_engine);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
} // namespace Impl
|
||||
|
||||
std::vector<std::vector<SeamPerimeterChoice>> get_object_seams(
|
||||
Shells::Shells<> &&shells, const unsigned fixed_seed
|
||||
) {
|
||||
std::mt19937 random_engine{fixed_seed};
|
||||
const Impl::Random random{random_engine};
|
||||
|
||||
return Seams::get_object_seams(std::move(shells), [&](const Shells::Shell<> &shell) {
|
||||
return Seams::get_shell_seam(shell, [&](const Perimeters::Perimeter &perimeter, std::size_t) {
|
||||
return Seams::choose_seam_point(perimeter, random);
|
||||
});
|
||||
});
|
||||
}
|
||||
} // namespace Slic3r::Seams::Random
|
29
src/libslic3r/GCode/SeamRandom.hpp
Normal file
29
src/libslic3r/GCode/SeamRandom.hpp
Normal file
@ -0,0 +1,29 @@
|
||||
#include "libslic3r/GCode/SeamChoice.hpp"
|
||||
#include <random>
|
||||
|
||||
namespace Slic3r::Seams::Random {
|
||||
namespace Impl {
|
||||
struct PerimeterSegment
|
||||
{
|
||||
double begin{};
|
||||
double end{};
|
||||
std::size_t begin_index{};
|
||||
|
||||
double length() const { return end - begin; }
|
||||
};
|
||||
|
||||
struct Random
|
||||
{
|
||||
std::mt19937 &random_engine;
|
||||
|
||||
std::optional<SeamChoice> operator()(
|
||||
const Perimeters::Perimeter &perimeter,
|
||||
const Perimeters::PointType point_type,
|
||||
const Perimeters::PointClassification point_classification
|
||||
) const;
|
||||
};
|
||||
}
|
||||
std::vector<std::vector<SeamPerimeterChoice>> get_object_seams(
|
||||
Shells::Shells<> &&shells, const unsigned fixed_seed
|
||||
);
|
||||
}
|
125
src/libslic3r/GCode/SeamRear.cpp
Normal file
125
src/libslic3r/GCode/SeamRear.cpp
Normal file
@ -0,0 +1,125 @@
|
||||
#include "libslic3r/GCode/SeamRear.hpp"
|
||||
#include "libslic3r/GCode/SeamGeometry.hpp"
|
||||
|
||||
namespace Slic3r::Seams::Rear {
|
||||
using Perimeters::PointType;
|
||||
using Perimeters::PointClassification;
|
||||
|
||||
namespace Impl {
|
||||
|
||||
BoundingBoxf get_bounding_box(const Shells::Shell<> &shell) {
|
||||
BoundingBoxf result;
|
||||
for (const Shells::Slice<> &slice : shell) {
|
||||
result.merge(BoundingBoxf{slice.boundary.positions});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::optional<SeamChoice> get_rearest_point(
|
||||
const Perimeters::Perimeter &perimeter,
|
||||
const PointType point_type,
|
||||
const PointClassification point_classification
|
||||
) {
|
||||
double max_y{-std::numeric_limits<double>::infinity()};
|
||||
std::optional<std::size_t> choosen_index;
|
||||
for (std::size_t i{0}; i < perimeter.positions.size(); ++i) {
|
||||
const Perimeters::PointType _point_type{perimeter.point_types[i]};
|
||||
const Perimeters::PointClassification _point_classification{perimeter.point_classifications[i]};
|
||||
|
||||
if (point_type == _point_type && point_classification == _point_classification) {
|
||||
const Vec2d &position{perimeter.positions[i]};
|
||||
if (position.y() > max_y) {
|
||||
max_y = position.y();
|
||||
choosen_index = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (choosen_index) {
|
||||
return SeamChoice{*choosen_index, *choosen_index, perimeter.positions[*choosen_index]};
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<SeamChoice> StraightLine::operator()(
|
||||
const Perimeters::Perimeter &perimeter,
|
||||
const PointType point_type,
|
||||
const PointClassification point_classification
|
||||
) const {
|
||||
std::vector<PerimeterLine> possible_lines;
|
||||
for (std::size_t i{0}; i < perimeter.positions.size() - 1; ++i) {
|
||||
if (perimeter.point_types[i] != point_type) {
|
||||
continue;
|
||||
}
|
||||
if (perimeter.point_classifications[i] != point_classification) {
|
||||
continue;
|
||||
}
|
||||
if (perimeter.point_types[i + 1] != point_type) {
|
||||
continue;
|
||||
}
|
||||
if (perimeter.point_classifications[i + 1] != point_classification) {
|
||||
continue;
|
||||
}
|
||||
possible_lines.push_back(PerimeterLine{perimeter.positions[i], perimeter.positions[i+1], i, i + 1});
|
||||
}
|
||||
if (possible_lines.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const AABBTreeLines::LinesDistancer<PerimeterLine> possible_distancer{possible_lines};
|
||||
const BoundingBoxf bounding_box{perimeter.positions};
|
||||
|
||||
const std::vector<std::pair<Vec2d, std::size_t>> intersections{
|
||||
possible_distancer.intersections_with_line<true>(PerimeterLine{
|
||||
this->prefered_position, Vec2d{this->prefered_position.x(), bounding_box.min.y()},
|
||||
0, 0})};
|
||||
if (!intersections.empty()) {
|
||||
const auto[position, line_index]{intersections.front()};
|
||||
if (position.y() < bounding_box.max.y() -
|
||||
this->rear_project_threshold * (bounding_box.max.y() - bounding_box.min.y())) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const PerimeterLine &intersected_line{possible_lines[line_index]};
|
||||
const SeamChoice intersected_choice{intersected_line.previous_index, intersected_line.next_index, position};
|
||||
return intersected_choice;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
} // namespace Impl
|
||||
|
||||
std::vector<std::vector<SeamPerimeterChoice>> get_object_seams(
|
||||
Shells::Shells<> &&shells,
|
||||
const double rear_project_threshold
|
||||
) {
|
||||
double average_x_center{0.0};
|
||||
std::size_t count{0};
|
||||
for (const Shells::Shell<> &shell : shells) {
|
||||
for (const Shells::Slice<> &slice : shell) {
|
||||
if (slice.boundary.positions.empty()) {
|
||||
continue;
|
||||
}
|
||||
const BoundingBoxf slice_bounding_box{slice.boundary.positions};
|
||||
average_x_center += (slice_bounding_box.min.x() + slice_bounding_box.max.x()) / 2.0;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
average_x_center /= count;
|
||||
return Seams::get_object_seams(std::move(shells), [&](const Shells::Shell<> &shell) {
|
||||
BoundingBoxf bounding_box{Impl::get_bounding_box(shell)};
|
||||
const Vec2d back_center{average_x_center, bounding_box.max.y()};
|
||||
std::optional<std::vector<SeamChoice>> straight_seam {
|
||||
Seams::maybe_get_shell_seam(shell, [&](const Perimeters::Perimeter &perimeter, std::size_t) {
|
||||
return Seams::maybe_choose_seam_point(
|
||||
perimeter,
|
||||
Impl::StraightLine{back_center, rear_project_threshold}
|
||||
);
|
||||
})
|
||||
};
|
||||
if (!straight_seam) {
|
||||
return Seams::get_shell_seam(shell, [&](const Perimeters::Perimeter &perimeter, std::size_t) {
|
||||
return Seams::choose_seam_point(perimeter, Impl::get_rearest_point);
|
||||
});
|
||||
}
|
||||
return *straight_seam;
|
||||
});
|
||||
}
|
||||
} // namespace Slic3r::Seams::Rear
|
40
src/libslic3r/GCode/SeamRear.hpp
Normal file
40
src/libslic3r/GCode/SeamRear.hpp
Normal file
@ -0,0 +1,40 @@
|
||||
#ifndef libslic3r_SeamRear_hpp_
|
||||
#define libslic3r_SeamRear_hpp_
|
||||
|
||||
#include "libslic3r/GCode/SeamPerimeters.hpp"
|
||||
#include "libslic3r/GCode/SeamChoice.hpp"
|
||||
|
||||
namespace Slic3r::Seams::Rear {
|
||||
namespace Impl {
|
||||
struct PerimeterLine
|
||||
{
|
||||
Vec2d a;
|
||||
Vec2d b;
|
||||
std::size_t previous_index;
|
||||
std::size_t next_index;
|
||||
|
||||
using Scalar = Vec2d::Scalar;
|
||||
static const constexpr int Dim = 2;
|
||||
};
|
||||
|
||||
struct StraightLine
|
||||
{
|
||||
Vec2d prefered_position;
|
||||
double rear_project_threshold;
|
||||
|
||||
std::optional<SeamChoice> operator()(
|
||||
const Perimeters::Perimeter &perimeter,
|
||||
const Perimeters::PointType point_type,
|
||||
const Perimeters::PointClassification point_classification
|
||||
) const;
|
||||
};
|
||||
|
||||
} // namespace Impl
|
||||
|
||||
std::vector<std::vector<SeamPerimeterChoice>> get_object_seams(
|
||||
Shells::Shells<> &&shells,
|
||||
const double rear_project_threshold
|
||||
);
|
||||
} // namespace Slic3r::Seams::Rear
|
||||
|
||||
#endif // libslic3r_SeamRear_hpp_
|
114
src/libslic3r/GCode/SeamShells.cpp
Normal file
114
src/libslic3r/GCode/SeamShells.cpp
Normal file
@ -0,0 +1,114 @@
|
||||
#include "libslic3r/GCode/SeamShells.hpp"
|
||||
#include "libslic3r/ClipperUtils.hpp"
|
||||
#include <oneapi/tbb/blocked_range.h>
|
||||
#include <oneapi/tbb/parallel_for.h>
|
||||
|
||||
namespace Slic3r::Seams::Shells::Impl {
|
||||
|
||||
BoundedPolygons project_to_geometry(const Geometry::Extrusions &external_perimeters, const double max_bb_distance) {
|
||||
BoundedPolygons result;
|
||||
result.reserve(external_perimeters.size());
|
||||
|
||||
using std::transform, std::back_inserter;
|
||||
|
||||
transform(
|
||||
external_perimeters.begin(), external_perimeters.end(), back_inserter(result),
|
||||
[&](const Geometry::Extrusion &external_perimeter) {
|
||||
const auto [choosen_index, _]{Geometry::pick_closest_bounding_box(
|
||||
external_perimeter.bounding_box,
|
||||
external_perimeter.island_boundary_bounding_boxes
|
||||
)};
|
||||
|
||||
const double distance{Geometry::bounding_box_distance(
|
||||
external_perimeter.island_boundary_bounding_boxes[choosen_index],
|
||||
external_perimeter.bounding_box
|
||||
)};
|
||||
|
||||
if (distance > max_bb_distance) {
|
||||
Polygons expanded_extrusion{expand(external_perimeter.polygon, external_perimeter.width / 2.0)};
|
||||
if (!expanded_extrusion.empty()) {
|
||||
return BoundedPolygon{
|
||||
expanded_extrusion.front(), expanded_extrusion.front().bounding_box(), external_perimeter.polygon.is_clockwise()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const bool is_hole{choosen_index != 0};
|
||||
const Polygon &adjacent_boundary{
|
||||
!is_hole ? external_perimeter.island_boundary.contour :
|
||||
external_perimeter.island_boundary.holes[choosen_index - 1]};
|
||||
return BoundedPolygon{adjacent_boundary, external_perimeter.island_boundary_bounding_boxes[choosen_index], is_hole};
|
||||
}
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<BoundedPolygons> project_to_geometry(const std::vector<Geometry::Extrusions> &extrusions, const double max_bb_distance) {
|
||||
std::vector<BoundedPolygons> result(extrusions.size());
|
||||
|
||||
for (std::size_t layer_index{0}; layer_index < extrusions.size(); ++layer_index) {
|
||||
result[layer_index] = project_to_geometry(extrusions[layer_index], max_bb_distance);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Shells<Polygon> map_to_shells(
|
||||
std::vector<BoundedPolygons> &&layers, const Geometry::Mapping &mapping, const std::size_t shell_count
|
||||
) {
|
||||
Shells<Polygon> result(shell_count);
|
||||
for (std::size_t layer_index{0}; layer_index < layers.size(); ++layer_index) {
|
||||
BoundedPolygons &perimeters{layers[layer_index]};
|
||||
for (std::size_t perimeter_index{0}; perimeter_index < perimeters.size();
|
||||
perimeter_index++) {
|
||||
Polygon &perimeter{perimeters[perimeter_index].polygon};
|
||||
result[mapping[layer_index][perimeter_index]].push_back(
|
||||
Slice<Polygon>{std::move(perimeter), layer_index}
|
||||
);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
} // namespace Slic3r::Seams::Shells::Impl
|
||||
|
||||
namespace Slic3r::Seams::Shells {
|
||||
Shells<Polygon> create_shells(
|
||||
const std::vector<Geometry::Extrusions> &extrusions, const double max_distance
|
||||
) {
|
||||
std::vector<Impl::BoundedPolygons> projected{Impl::project_to_geometry(extrusions, max_distance)};
|
||||
|
||||
std::vector<std::size_t> layer_sizes;
|
||||
layer_sizes.reserve(projected.size());
|
||||
for (const Impl::BoundedPolygons &perimeters : projected) {
|
||||
layer_sizes.push_back(perimeters.size());
|
||||
}
|
||||
|
||||
const auto &[shell_mapping, shell_count]{Geometry::get_mapping(
|
||||
layer_sizes,
|
||||
[&](const std::size_t layer_index,
|
||||
const std::size_t item_index) -> Geometry::MappingOperatorResult {
|
||||
const Impl::BoundedPolygons &layer{projected[layer_index]};
|
||||
const Impl::BoundedPolygons &next_layer{projected[layer_index + 1]};
|
||||
if (next_layer.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
BoundingBoxes next_layer_bounding_boxes;
|
||||
for (const Impl::BoundedPolygon &bounded_polygon : next_layer) {
|
||||
next_layer_bounding_boxes.emplace_back(bounded_polygon.bounding_box);
|
||||
}
|
||||
|
||||
const auto [perimeter_index, distance] = Geometry::pick_closest_bounding_box(
|
||||
layer[item_index].bounding_box, next_layer_bounding_boxes
|
||||
);
|
||||
|
||||
if (distance > max_distance) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return std::pair{perimeter_index, 1.0 / distance};
|
||||
}
|
||||
)};
|
||||
|
||||
return Impl::map_to_shells(std::move(projected), shell_mapping, shell_count);
|
||||
}
|
||||
} // namespace Slic3r::Seams::Shells
|
47
src/libslic3r/GCode/SeamShells.hpp
Normal file
47
src/libslic3r/GCode/SeamShells.hpp
Normal file
@ -0,0 +1,47 @@
|
||||
#ifndef libslic3r_SeamShells_hpp_
|
||||
#define libslic3r_SeamShells_hpp_
|
||||
|
||||
#include <vector>
|
||||
#include <tcbspan/span.hpp>
|
||||
|
||||
#include "libslic3r/Polygon.hpp"
|
||||
#include "libslic3r/GCode/SeamGeometry.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
class Layer;
|
||||
}
|
||||
|
||||
namespace Slic3r::Seams::Perimeters {
|
||||
struct Perimeter;
|
||||
}
|
||||
|
||||
namespace Slic3r::Seams::Shells::Impl {
|
||||
|
||||
struct BoundedPolygon {
|
||||
Polygon polygon;
|
||||
BoundingBox bounding_box;
|
||||
bool is_hole{false};
|
||||
};
|
||||
|
||||
using BoundedPolygons = std::vector<BoundedPolygon>;
|
||||
|
||||
BoundedPolygons project_to_geometry(const Geometry::Extrusions &extrusions, const double max_bb_distance);
|
||||
}
|
||||
|
||||
namespace Slic3r::Seams::Shells {
|
||||
template<typename T = Perimeters::Perimeter> struct Slice
|
||||
{
|
||||
T boundary;
|
||||
std::size_t layer_index;
|
||||
};
|
||||
|
||||
template<typename T = Perimeters::Perimeter> using Shell = std::vector<Slice<T>>;
|
||||
|
||||
template<typename T = Perimeters::Perimeter> using Shells = std::vector<Shell<T>>;
|
||||
|
||||
Shells<Polygon> create_shells(
|
||||
const std::vector<Geometry::Extrusions> &extrusions, const double max_distance
|
||||
);
|
||||
} // namespace Slic3r::Seams::Shells
|
||||
|
||||
#endif // libslic3r_SeamShells_hpp_
|
@ -31,75 +31,46 @@ std::optional<Point> sample_path_point_at_distance_from_start(const SmoothPath &
|
||||
{
|
||||
if (distance >= 0) {
|
||||
for (const SmoothPathElement &el : path) {
|
||||
auto it = el.path.begin();
|
||||
auto end = el.path.end();
|
||||
Point prev_point = it->point;
|
||||
for (++ it; it != end; ++ it) {
|
||||
Point point = it->point;
|
||||
if (it->linear()) {
|
||||
Point prev_point = el.path.front().point;
|
||||
for (auto segment_it = el.path.begin() + 1; segment_it != el.path.end(); ++segment_it) {
|
||||
Point point = segment_it->point;
|
||||
if (segment_it->linear()) {
|
||||
// Linear segment
|
||||
Vec2d v = (point - prev_point).cast<double>();
|
||||
double lsqr = v.squaredNorm();
|
||||
const Vec2d v = (point - prev_point).cast<double>();
|
||||
const double lsqr = v.squaredNorm();
|
||||
if (lsqr > sqr(distance))
|
||||
return std::make_optional<Point>(prev_point + (v * (distance / sqrt(lsqr))).cast<coord_t>());
|
||||
return prev_point + (v * (distance / sqrt(lsqr))).cast<coord_t>();
|
||||
distance -= sqrt(lsqr);
|
||||
} else {
|
||||
// Circular segment
|
||||
float angle = Geometry::ArcWelder::arc_angle(prev_point.cast<float>(), point.cast<float>(), it->radius);
|
||||
double len = std::abs(it->radius) * angle;
|
||||
const float angle = Geometry::ArcWelder::arc_angle(prev_point.cast<float>(), point.cast<float>(), segment_it->radius);
|
||||
const double len = std::abs(segment_it->radius) * angle;
|
||||
if (len > distance) {
|
||||
// Rotate the segment end point in reverse towards the start point.
|
||||
return std::make_optional<Point>(prev_point.rotated(- angle * (distance / len),
|
||||
Geometry::ArcWelder::arc_center(prev_point.cast<float>(), point.cast<float>(), it->radius, it->ccw()).cast<coord_t>()));
|
||||
const Point center_pt = Geometry::ArcWelder::arc_center(prev_point.cast<float>(), point.cast<float>(), segment_it->radius, segment_it->ccw()).cast<coord_t>();
|
||||
const float rotation_dir = (segment_it->ccw() ? 1.f : -1.f);
|
||||
// Rotate the segment start point based on the arc orientation.
|
||||
return prev_point.rotated(rotation_dir * angle * (distance / len), center_pt);
|
||||
}
|
||||
|
||||
distance -= len;
|
||||
}
|
||||
|
||||
if (distance < 0)
|
||||
return std::make_optional<Point>(point);
|
||||
return point;
|
||||
|
||||
prev_point = point;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Failed.
|
||||
return {};
|
||||
}
|
||||
|
||||
std::optional<Point> sample_path_point_at_distance_from_end(const SmoothPath &path, double distance)
|
||||
{
|
||||
if (distance >= 0) {
|
||||
for (const SmoothPathElement& el : path) {
|
||||
auto it = el.path.begin();
|
||||
auto end = el.path.end();
|
||||
Point prev_point = it->point;
|
||||
for (++it; it != end; ++it) {
|
||||
Point point = it->point;
|
||||
if (it->linear()) {
|
||||
// Linear segment
|
||||
Vec2d v = (point - prev_point).cast<double>();
|
||||
double lsqr = v.squaredNorm();
|
||||
if (lsqr > sqr(distance))
|
||||
return std::make_optional<Point>(prev_point + (v * (distance / sqrt(lsqr))).cast<coord_t>());
|
||||
distance -= sqrt(lsqr);
|
||||
}
|
||||
else {
|
||||
// Circular segment
|
||||
float angle = Geometry::ArcWelder::arc_angle(prev_point.cast<float>(), point.cast<float>(), it->radius);
|
||||
double len = std::abs(it->radius) * angle;
|
||||
if (len > distance) {
|
||||
// Rotate the segment end point in reverse towards the start point.
|
||||
return std::make_optional<Point>(prev_point.rotated(-angle * (distance / len),
|
||||
Geometry::ArcWelder::arc_center(prev_point.cast<float>(), point.cast<float>(), it->radius, it->ccw()).cast<coord_t>()));
|
||||
}
|
||||
distance -= len;
|
||||
}
|
||||
if (distance < 0)
|
||||
return std::make_optional<Point>(point);
|
||||
prev_point = point;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Failed.
|
||||
return {};
|
||||
std::optional<Point> sample_path_point_at_distance_from_end(const SmoothPath &path, double distance) {
|
||||
SmoothPath path_reversed = path;
|
||||
reverse(path_reversed);
|
||||
return sample_path_point_at_distance_from_start(path_reversed, distance);
|
||||
}
|
||||
|
||||
// Clip length of a smooth path, for seam hiding.
|
||||
|
@ -211,7 +211,7 @@ std::optional<Point> wipe_hide_seam(const SmoothPath &path, bool is_hole, double
|
||||
// Wipe move cannot be calculated, the loop is not long enough. This should not happen due to the longer_than() test above.
|
||||
return {};
|
||||
}
|
||||
if (std::optional<Point> p = sample_path_point_at_distance_from_end(path, wipe_length); p)
|
||||
if (std::optional<Point> p = sample_path_point_at_distance_from_start(path, wipe_length); p)
|
||||
p_prev = *p;
|
||||
else
|
||||
// Wipe move cannot be calculated, the loop is not long enough. This should not happen due to the longer_than() test above.
|
||||
@ -231,7 +231,7 @@ std::optional<Point> wipe_hide_seam(const SmoothPath &path, bool is_hole, double
|
||||
}
|
||||
// Rotate the forward segment inside by 1/3 of the wedge angle.
|
||||
auto v_rotated = Eigen::Rotation2D(angle_inside) * (p_next - p_current).cast<double>().normalized();
|
||||
return std::make_optional<Point>(p_current + (v_rotated * wipe_length).cast<coord_t>());
|
||||
return p_current + (v_rotated * wipe_length).cast<coord_t>();
|
||||
}
|
||||
|
||||
return {};
|
||||
|
@ -947,6 +947,8 @@ void Layer::sort_perimeters_into_islands(
|
||||
region_id,
|
||||
// Whether there are infills with different regions generated for this LayerSlice.
|
||||
has_multiple_regions,
|
||||
// Layer split into surfaces
|
||||
&slices,
|
||||
// Perimeters and gap fills to be sorted into islands.
|
||||
&perimeter_and_gapfill_ranges,
|
||||
// Infill regions to be sorted into islands.
|
||||
@ -959,6 +961,7 @@ void Layer::sort_perimeters_into_islands(
|
||||
lslices_ex[lslice_idx].islands.push_back({});
|
||||
LayerIsland &island = lslices_ex[lslice_idx].islands.back();
|
||||
island.perimeters = LayerExtrusionRange(region_id, perimeter_and_gapfill_ranges[source_slice_idx].first);
|
||||
island.boundary = slices.surfaces[source_slice_idx].expolygon;
|
||||
island.thin_fills = perimeter_and_gapfill_ranges[source_slice_idx].second;
|
||||
if (ExPolygonRange fill_range = fill_expolygons_ranges[source_slice_idx]; ! fill_range.empty()) {
|
||||
if (has_multiple_regions) {
|
||||
|
@ -77,6 +77,8 @@ private:
|
||||
static constexpr const uint32_t fill_region_composite_id = std::numeric_limits<uint32_t>::max();
|
||||
|
||||
public:
|
||||
// Boundary of the LayerIsland before perimeter generation.
|
||||
ExPolygon boundary;
|
||||
// Perimeter extrusions in LayerRegion belonging to this island.
|
||||
LayerExtrusionRange perimeters;
|
||||
// Thin fills of the same region as perimeters. Generated by classic perimeter generator, while Arachne puts them into perimeters.
|
||||
|
@ -6,7 +6,6 @@
|
||||
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
|
||||
///|/
|
||||
#include "PerimeterGenerator.hpp"
|
||||
#include "AABBTreeIndirect.hpp"
|
||||
#include "AABBTreeLines.hpp"
|
||||
#include "BoundingBox.hpp"
|
||||
#include "BridgeDetector.hpp"
|
||||
@ -14,8 +13,6 @@
|
||||
#include "ExPolygon.hpp"
|
||||
#include "ExtrusionEntity.hpp"
|
||||
#include "ExtrusionEntityCollection.hpp"
|
||||
#include "Geometry/MedialAxis.hpp"
|
||||
#include "KDTreeIndirect.hpp"
|
||||
#include "MultiPoint.hpp"
|
||||
#include "Point.hpp"
|
||||
#include "Polygon.hpp"
|
||||
@ -25,9 +22,9 @@
|
||||
#include "Surface.hpp"
|
||||
|
||||
#include "Geometry/ConvexHull.hpp"
|
||||
#include "SurfaceCollection.hpp"
|
||||
#include "clipper/clipper_z.hpp"
|
||||
|
||||
#include "Arachne/PerimeterOrder.hpp"
|
||||
#include "Arachne/WallToolPaths.hpp"
|
||||
#include "Arachne/utils/ExtrusionLine.hpp"
|
||||
#include "Arachne/utils/ExtrusionJunction.hpp"
|
||||
@ -36,16 +33,10 @@
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <cstdlib>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <list>
|
||||
#include <math.h>
|
||||
#include <ostream>
|
||||
#include <stack>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
@ -53,15 +44,13 @@
|
||||
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
// #define ARACHNE_DEBUG
|
||||
//#define ARACHNE_DEBUG
|
||||
|
||||
#ifdef ARACHNE_DEBUG
|
||||
#include "SVG.hpp"
|
||||
#include "Utils.hpp"
|
||||
#endif
|
||||
|
||||
#include "SVG.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
ExtrusionMultiPath PerimeterGenerator::thick_polyline_to_multi_path(const ThickPolyline &thick_polyline, ExtrusionRole role, const Flow &flow, const float tolerance, const float merge_tolerance)
|
||||
@ -503,29 +492,20 @@ static ClipperLib_Z::Paths clip_extrusion(const ClipperLib_Z::Path &subject, con
|
||||
return clipped_paths;
|
||||
}
|
||||
|
||||
struct PerimeterGeneratorArachneExtrusion
|
||||
{
|
||||
Arachne::ExtrusionLine *extrusion = nullptr;
|
||||
// Indicates if closed ExtrusionLine is a contour or a hole. Used it only when ExtrusionLine is a closed loop.
|
||||
bool is_contour = false;
|
||||
// Should this extrusion be fuzzyfied on path generation?
|
||||
bool fuzzify = false;
|
||||
};
|
||||
|
||||
static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator::Parameters ¶ms, const Polygons &lower_slices_polygons_cache, std::vector<PerimeterGeneratorArachneExtrusion> &pg_extrusions)
|
||||
static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator::Parameters ¶ms, const Polygons &lower_slices_polygons_cache, Arachne::PerimeterOrder::PerimeterExtrusions &pg_extrusions)
|
||||
{
|
||||
ExtrusionEntityCollection extrusion_coll;
|
||||
for (PerimeterGeneratorArachneExtrusion &pg_extrusion : pg_extrusions) {
|
||||
Arachne::ExtrusionLine *extrusion = pg_extrusion.extrusion;
|
||||
if (extrusion->empty())
|
||||
for (Arachne::PerimeterOrder::PerimeterExtrusion &pg_extrusion : pg_extrusions) {
|
||||
Arachne::ExtrusionLine &extrusion = pg_extrusion.extrusion;
|
||||
if (extrusion.empty())
|
||||
continue;
|
||||
|
||||
const bool is_external = extrusion->inset_idx == 0;
|
||||
const bool is_external = extrusion.inset_idx == 0;
|
||||
ExtrusionRole role_normal = is_external ? ExtrusionRole::ExternalPerimeter : ExtrusionRole::Perimeter;
|
||||
ExtrusionRole role_overhang = role_normal | ExtrusionRoleModifier::Bridge;
|
||||
|
||||
if (pg_extrusion.fuzzify)
|
||||
fuzzy_extrusion_line(*extrusion, scaled<float>(params.config.fuzzy_skin_thickness.value), scaled<float>(params.config.fuzzy_skin_point_dist.value));
|
||||
fuzzy_extrusion_line(extrusion, scaled<float>(params.config.fuzzy_skin_thickness.value), scaled<float>(params.config.fuzzy_skin_point_dist.value));
|
||||
|
||||
ExtrusionPaths paths;
|
||||
// detect overhanging/bridging perimeters
|
||||
@ -534,9 +514,9 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator::P
|
||||
params.object_config.support_material_contact_distance.value == 0)) {
|
||||
|
||||
ClipperLib_Z::Path extrusion_path;
|
||||
extrusion_path.reserve(extrusion->size());
|
||||
extrusion_path.reserve(extrusion.size());
|
||||
BoundingBox extrusion_path_bbox;
|
||||
for (const Arachne::ExtrusionJunction &ej : extrusion->junctions) {
|
||||
for (const Arachne::ExtrusionJunction &ej : extrusion.junctions) {
|
||||
extrusion_path.emplace_back(ej.p.x(), ej.p.y(), ej.w);
|
||||
extrusion_path_bbox.merge(Point{ej.p.x(), ej.p.y()});
|
||||
}
|
||||
@ -574,7 +554,7 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator::P
|
||||
// Arachne sometimes creates extrusion with zero-length (just two same endpoints);
|
||||
if (!paths.empty()) {
|
||||
Point start_point = paths.front().first_point();
|
||||
if (!extrusion->is_closed) {
|
||||
if (!extrusion.is_closed) {
|
||||
// Especially for open extrusion, we need to select a starting point that is at the start
|
||||
// or the end of the extrusions to make one continuous line. Also, we prefer a non-overhang
|
||||
// starting point.
|
||||
@ -607,15 +587,15 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator::P
|
||||
chain_and_reorder_extrusion_paths(paths, &start_point);
|
||||
}
|
||||
} else {
|
||||
extrusion_paths_append(paths, *extrusion, role_normal, is_external ? params.ext_perimeter_flow : params.perimeter_flow);
|
||||
extrusion_paths_append(paths, extrusion, role_normal, is_external ? params.ext_perimeter_flow : params.perimeter_flow);
|
||||
}
|
||||
|
||||
// Append paths to collection.
|
||||
if (!paths.empty()) {
|
||||
if (extrusion->is_closed) {
|
||||
if (extrusion.is_closed) {
|
||||
ExtrusionLoop extrusion_loop(std::move(paths));
|
||||
// Restore the orientation of the extrusion loop.
|
||||
if (pg_extrusion.is_contour == extrusion_loop.is_clockwise())
|
||||
if (pg_extrusion.is_contour() == extrusion_loop.is_clockwise())
|
||||
extrusion_loop.reverse_loop();
|
||||
|
||||
for (auto it = std::next(extrusion_loop.paths.begin()); it != extrusion_loop.paths.end(); ++it) {
|
||||
@ -654,7 +634,7 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator::P
|
||||
}
|
||||
|
||||
#ifdef ARACHNE_DEBUG
|
||||
static void export_perimeters_to_svg(const std::string &path, const Polygons &contours, const std::vector<Arachne::VariableWidthLines> &perimeters, const ExPolygons &infill_area)
|
||||
static void export_perimeters_to_svg(const std::string &path, const Polygons &contours, const Arachne::Perimeters &perimeters, const ExPolygons &infill_area)
|
||||
{
|
||||
coordf_t stroke_width = scale_(0.03);
|
||||
BoundingBox bbox = get_extents(contours);
|
||||
@ -663,7 +643,7 @@ static void export_perimeters_to_svg(const std::string &path, const Polygons &co
|
||||
|
||||
svg.draw(infill_area, "cyan");
|
||||
|
||||
for (const Arachne::VariableWidthLines &perimeter : perimeters)
|
||||
for (const Arachne::Perimeter &perimeter : perimeters)
|
||||
for (const Arachne::ExtrusionLine &extrusion_line : perimeter) {
|
||||
ThickPolyline thick_polyline = to_thick_polyline(extrusion_line);
|
||||
svg.draw({thick_polyline}, "green", "blue", stroke_width);
|
||||
@ -1130,7 +1110,7 @@ void PerimeterGenerator::process_arachne(
|
||||
ExPolygons last = offset_ex(surface.expolygon.simplify_p(params.scaled_resolution), - float(ext_perimeter_width / 2. - ext_perimeter_spacing / 2.));
|
||||
Polygons last_p = to_polygons(last);
|
||||
Arachne::WallToolPaths wall_tool_paths(last_p, ext_perimeter_spacing, perimeter_spacing, coord_t(loop_number + 1), 0, params.layer_height, params.object_config, params.print_config);
|
||||
std::vector<Arachne::VariableWidthLines> perimeters = wall_tool_paths.getToolPaths();
|
||||
Arachne::Perimeters perimeters = wall_tool_paths.getToolPaths();
|
||||
ExPolygons infill_contour = union_ex(wall_tool_paths.getInnerContour());
|
||||
|
||||
// Check if there are some remaining perimeters to generate (the number of perimeters
|
||||
@ -1168,7 +1148,7 @@ void PerimeterGenerator::process_arachne(
|
||||
|
||||
const Polygons not_top_polygons = to_polygons(not_top_expolygons);
|
||||
Arachne::WallToolPaths inner_wall_tool_paths(not_top_polygons, perimeter_spacing, perimeter_spacing, coord_t(inner_loop_number + 1), 0, params.layer_height, params.object_config, params.print_config);
|
||||
std::vector<Arachne::VariableWidthLines> inner_perimeters = inner_wall_tool_paths.getToolPaths();
|
||||
Arachne::Perimeters inner_perimeters = inner_wall_tool_paths.getToolPaths();
|
||||
|
||||
// Recalculate indexes of inner perimeters before merging them.
|
||||
if (!perimeters.empty()) {
|
||||
@ -1197,7 +1177,7 @@ void PerimeterGenerator::process_arachne(
|
||||
#ifdef ARACHNE_DEBUG
|
||||
{
|
||||
static int iRun = 0;
|
||||
export_perimeters_to_svg(debug_out_path("arachne-perimeters-%d-%d.svg", layer_id, iRun++), to_polygons(last), perimeters, union_ex(wallToolPaths.getInnerContour()));
|
||||
export_perimeters_to_svg(debug_out_path("arachne-perimeters-%d-%d.svg", params.layer_id, iRun++), to_polygons(last), perimeters, union_ex(wallToolPaths.getInnerContour()));
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -1205,107 +1185,20 @@ void PerimeterGenerator::process_arachne(
|
||||
// But in rare cases, Arachne produce ExtrusionLine marked as closed but without
|
||||
// equal the first and the last point.
|
||||
assert([&perimeters = std::as_const(perimeters)]() -> bool {
|
||||
for (const Arachne::VariableWidthLines &perimeter : perimeters)
|
||||
for (const Arachne::Perimeter &perimeter : perimeters)
|
||||
for (const Arachne::ExtrusionLine &el : perimeter)
|
||||
if (el.is_closed && el.junctions.front().p != el.junctions.back().p)
|
||||
return false;
|
||||
return true;
|
||||
}());
|
||||
|
||||
int start_perimeter = int(perimeters.size()) - 1;
|
||||
int end_perimeter = -1;
|
||||
int direction = -1;
|
||||
|
||||
if (params.config.external_perimeters_first) {
|
||||
start_perimeter = 0;
|
||||
end_perimeter = int(perimeters.size());
|
||||
direction = 1;
|
||||
}
|
||||
|
||||
std::vector<Arachne::ExtrusionLine *> all_extrusions;
|
||||
for (int perimeter_idx = start_perimeter; perimeter_idx != end_perimeter; perimeter_idx += direction) {
|
||||
if (perimeters[perimeter_idx].empty())
|
||||
continue;
|
||||
for (Arachne::ExtrusionLine &wall : perimeters[perimeter_idx])
|
||||
all_extrusions.emplace_back(&wall);
|
||||
}
|
||||
|
||||
// Find topological order with constraints from extrusions_constrains.
|
||||
std::vector<size_t> blocked(all_extrusions.size(), 0); // Value indicating how many extrusions it is blocking (preceding extrusions) an extrusion.
|
||||
std::vector<std::vector<size_t>> blocking(all_extrusions.size()); // Each extrusion contains a vector of extrusions that are blocked by this extrusion.
|
||||
ankerl::unordered_dense::map<const Arachne::ExtrusionLine *, size_t> map_extrusion_to_idx;
|
||||
for (size_t idx = 0; idx < all_extrusions.size(); idx++)
|
||||
map_extrusion_to_idx.emplace(all_extrusions[idx], idx);
|
||||
|
||||
Arachne::WallToolPaths::ExtrusionLineSet extrusions_constrains = Arachne::WallToolPaths::getRegionOrder(all_extrusions, params.config.external_perimeters_first);
|
||||
for (auto [before, after] : extrusions_constrains) {
|
||||
auto after_it = map_extrusion_to_idx.find(after);
|
||||
++blocked[after_it->second];
|
||||
blocking[map_extrusion_to_idx.find(before)->second].emplace_back(after_it->second);
|
||||
}
|
||||
|
||||
std::vector<bool> processed(all_extrusions.size(), false); // Indicate that the extrusion was already processed.
|
||||
Point current_position = all_extrusions.empty() ? Point::Zero() : all_extrusions.front()->junctions.front().p; // Some starting position.
|
||||
std::vector<PerimeterGeneratorArachneExtrusion> ordered_extrusions; // To store our result in. At the end we'll std::swap.
|
||||
ordered_extrusions.reserve(all_extrusions.size());
|
||||
|
||||
while (ordered_extrusions.size() < all_extrusions.size()) {
|
||||
size_t best_candidate = 0;
|
||||
double best_distance_sqr = std::numeric_limits<double>::max();
|
||||
bool is_best_closed = false;
|
||||
|
||||
std::vector<size_t> available_candidates;
|
||||
for (size_t candidate = 0; candidate < all_extrusions.size(); ++candidate) {
|
||||
if (processed[candidate] || blocked[candidate])
|
||||
continue; // Not a valid candidate.
|
||||
available_candidates.push_back(candidate);
|
||||
}
|
||||
|
||||
std::sort(available_candidates.begin(), available_candidates.end(), [&all_extrusions](const size_t a_idx, const size_t b_idx) -> bool {
|
||||
return all_extrusions[a_idx]->is_closed < all_extrusions[b_idx]->is_closed;
|
||||
});
|
||||
|
||||
for (const size_t candidate_path_idx : available_candidates) {
|
||||
auto& path = all_extrusions[candidate_path_idx];
|
||||
|
||||
if (path->junctions.empty()) { // No vertices in the path. Can't find the start position then or really plan it in. Put that at the end.
|
||||
if (best_distance_sqr == std::numeric_limits<double>::max()) {
|
||||
best_candidate = candidate_path_idx;
|
||||
is_best_closed = path->is_closed;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const Point candidate_position = path->junctions.front().p;
|
||||
double distance_sqr = (current_position - candidate_position).cast<double>().norm();
|
||||
if (distance_sqr < best_distance_sqr) { // Closer than the best candidate so far.
|
||||
if (path->is_closed || (!path->is_closed && best_distance_sqr != std::numeric_limits<double>::max()) || (!path->is_closed && !is_best_closed)) {
|
||||
best_candidate = candidate_path_idx;
|
||||
best_distance_sqr = distance_sqr;
|
||||
is_best_closed = path->is_closed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto &best_path = all_extrusions[best_candidate];
|
||||
ordered_extrusions.push_back({best_path, best_path->is_contour(), false});
|
||||
processed[best_candidate] = true;
|
||||
for (size_t unlocked_idx : blocking[best_candidate])
|
||||
blocked[unlocked_idx]--;
|
||||
|
||||
if (!best_path->junctions.empty()) { //If all paths were empty, the best path is still empty. We don't upate the current position then.
|
||||
if(best_path->is_closed)
|
||||
current_position = best_path->junctions[0].p; //We end where we started.
|
||||
else
|
||||
current_position = best_path->junctions.back().p; //Pick the other end from where we started.
|
||||
}
|
||||
}
|
||||
Arachne::PerimeterOrder::PerimeterExtrusions ordered_extrusions = Arachne::PerimeterOrder::ordered_perimeter_extrusions(perimeters, params.config.external_perimeters_first);
|
||||
|
||||
if (params.layer_id > 0 && params.config.fuzzy_skin != FuzzySkinType::None) {
|
||||
std::vector<PerimeterGeneratorArachneExtrusion *> closed_loop_extrusions;
|
||||
for (PerimeterGeneratorArachneExtrusion &extrusion : ordered_extrusions)
|
||||
if (extrusion.extrusion->inset_idx == 0) {
|
||||
if (extrusion.extrusion->is_closed && params.config.fuzzy_skin == FuzzySkinType::External) {
|
||||
std::vector<Arachne::PerimeterOrder::PerimeterExtrusion *> closed_loop_extrusions;
|
||||
for (Arachne::PerimeterOrder::PerimeterExtrusion &extrusion : ordered_extrusions)
|
||||
if (extrusion.extrusion.inset_idx == 0) {
|
||||
if (extrusion.extrusion.is_closed && params.config.fuzzy_skin == FuzzySkinType::External) {
|
||||
closed_loop_extrusions.emplace_back(&extrusion);
|
||||
} else {
|
||||
extrusion.fuzzify = true;
|
||||
@ -1316,11 +1209,11 @@ void PerimeterGenerator::process_arachne(
|
||||
ClipperLib_Z::Paths loops_paths;
|
||||
loops_paths.reserve(closed_loop_extrusions.size());
|
||||
for (const auto &cl_extrusion : closed_loop_extrusions) {
|
||||
assert(cl_extrusion->extrusion->junctions.front() == cl_extrusion->extrusion->junctions.back());
|
||||
assert(cl_extrusion->extrusion.junctions.front() == cl_extrusion->extrusion.junctions.back());
|
||||
size_t loop_idx = &cl_extrusion - &closed_loop_extrusions.front();
|
||||
ClipperLib_Z::Path loop_path;
|
||||
loop_path.reserve(cl_extrusion->extrusion->junctions.size() - 1);
|
||||
for (auto junction_it = cl_extrusion->extrusion->junctions.begin(); junction_it != std::prev(cl_extrusion->extrusion->junctions.end()); ++junction_it)
|
||||
loop_path.reserve(cl_extrusion->extrusion.junctions.size() - 1);
|
||||
for (auto junction_it = cl_extrusion->extrusion.junctions.begin(); junction_it != std::prev(cl_extrusion->extrusion.junctions.end()); ++junction_it)
|
||||
loop_path.emplace_back(junction_it->p.x(), junction_it->p.y(), loop_idx);
|
||||
loops_paths.emplace_back(loop_path);
|
||||
}
|
||||
|
@ -9,11 +9,29 @@
|
||||
#include <unordered_set>
|
||||
#include <random>
|
||||
#include <algorithm>
|
||||
#include <boost/random/uniform_int_distribution.hpp>
|
||||
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
/**
|
||||
* Simple implementation of Fisher-Yates algorithm using uniform int
|
||||
* distribution from boost, ensurinng the result is the same
|
||||
* accross platforms.
|
||||
*
|
||||
* DO NOT EXPECT IT TO BE PERFORMANT! Use it only when std::shuffle is
|
||||
* not applicable.
|
||||
*/
|
||||
template<typename Range, typename UniformRandomNumberGenerator>
|
||||
void stable_shuffle(Range &range, UniformRandomNumberGenerator &generator) {
|
||||
const int n{static_cast<int>(range.size())};
|
||||
for (int i{0}; i < n - 2; ++i) {
|
||||
int j{boost::random::uniform_int_distribution<int>{i, n-1}(generator)};
|
||||
std::swap(range[i], range[j]);
|
||||
}
|
||||
}
|
||||
|
||||
void its_short_edge_collpase(indexed_triangle_set &mesh, size_t target_triangle_count) {
|
||||
// whenever vertex is removed, its mapping is update to the index of vertex with wich it merged
|
||||
std::vector<size_t> vertices_index_mapping(mesh.vertices.size());
|
||||
@ -102,7 +120,7 @@ void its_short_edge_collpase(indexed_triangle_set &mesh, size_t target_triangle_
|
||||
float max_edge_len_squared = edge_len * edge_len;
|
||||
|
||||
//shuffle the faces and traverse in random order, this MASSIVELY improves the quality of the result
|
||||
std::shuffle(face_indices.begin(), face_indices.end(), generator);
|
||||
stable_shuffle(face_indices, generator);
|
||||
|
||||
int allowed_face_removals = int(face_indices.size()) - int(target_triangle_count);
|
||||
for (const size_t &face_idx : face_indices) {
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include <random>
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/blocked_range.h>
|
||||
#include <boost/random/uniform_real_distribution.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
@ -33,8 +34,9 @@ TriangleSetSamples sample_its_uniform_parallel(size_t samples_count, const index
|
||||
}
|
||||
|
||||
std::mt19937_64 mersenne_engine { 27644437 };
|
||||
// Use boost instead of std to ensure stability accross platforms!
|
||||
// random numbers on interval [0, 1)
|
||||
std::uniform_real_distribution<double> fdistribution;
|
||||
boost::random::uniform_real_distribution<double> fdistribution;
|
||||
|
||||
auto get_random = [&fdistribution, &mersenne_engine]() {
|
||||
return Vec3d { fdistribution(mersenne_engine), fdistribution(mersenne_engine), fdistribution(mersenne_engine) };
|
||||
|
@ -8,6 +8,9 @@
|
||||
#include "libslic3r/format.hpp" // -> format()
|
||||
#include "I18N.hpp"
|
||||
|
||||
#include <boost/algorithm/string/classification.hpp>
|
||||
#include <boost/algorithm/string/replace.hpp>
|
||||
#include <boost/algorithm/string/split.hpp>
|
||||
#include <cmath>
|
||||
|
||||
#include "ImGuiWrapper.hpp"
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include <GL/glew.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <boost/nowide/convert.hpp>
|
||||
#include <wx/progdlg.h>
|
||||
#include <boost/nowide/convert.hpp>
|
||||
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
#include <functional>
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
|
@ -7,6 +7,8 @@
|
||||
|
||||
#include "libslic3r/CustomGCode.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
|
||||
|
BIN
tests/data/seam_test_object.3mf
Normal file
BIN
tests/data/seam_test_object.3mf
Normal file
Binary file not shown.
@ -14,6 +14,13 @@ add_executable(${_TEST_NAME}_tests
|
||||
test_gaps.cpp
|
||||
test_gcode.cpp
|
||||
test_gcode_travels.cpp
|
||||
test_seam_perimeters.cpp
|
||||
test_seam_shells.cpp
|
||||
test_seam_geometry.cpp
|
||||
test_seam_aligned.cpp
|
||||
test_seam_rear.cpp
|
||||
test_seam_random.cpp
|
||||
benchmark_seams.cpp
|
||||
test_gcodefindreplace.cpp
|
||||
test_gcodewriter.cpp
|
||||
test_cancel_object.cpp
|
||||
@ -33,6 +40,7 @@ add_executable(${_TEST_NAME}_tests
|
||||
)
|
||||
target_link_libraries(${_TEST_NAME}_tests test_common libslic3r)
|
||||
set_property(TARGET ${_TEST_NAME}_tests PROPERTY FOLDER "tests")
|
||||
target_compile_definitions(${_TEST_NAME}_tests PUBLIC CATCH_CONFIG_ENABLE_BENCHMARKING)
|
||||
|
||||
if (WIN32)
|
||||
prusaslicer_copy_dlls(${_TEST_NAME}_tests)
|
||||
|
124
tests/fff_print/benchmark_seams.cpp
Normal file
124
tests/fff_print/benchmark_seams.cpp
Normal file
@ -0,0 +1,124 @@
|
||||
#include <catch2/catch.hpp>
|
||||
#include "test_data.hpp"
|
||||
|
||||
#include "libslic3r/GCode/SeamGeometry.hpp"
|
||||
#include "libslic3r/GCode/SeamAligned.hpp"
|
||||
#include "libslic3r/GCode/SeamRear.hpp"
|
||||
#include "libslic3r/GCode/SeamRandom.hpp"
|
||||
|
||||
TEST_CASE_METHOD(Slic3r::Test::SeamsFixture, "Seam benchmarks", "[Seams][.Benchmarks]") {
|
||||
BENCHMARK_ADVANCED("Create extrusions benchy")(Catch::Benchmark::Chronometer meter) {
|
||||
meter.measure([&] { return Slic3r::Seams::Geometry::get_extrusions(print_object->layers()); });
|
||||
};
|
||||
|
||||
using namespace Slic3r::Seams;
|
||||
BENCHMARK_ADVANCED("Create shells benchy")(Catch::Benchmark::Chronometer meter) {
|
||||
meter.measure([&] { return Shells::create_shells(extrusions, params.max_distance); });
|
||||
};
|
||||
|
||||
|
||||
BENCHMARK_ADVANCED("Get layer infos benchy")(Catch::Benchmark::Chronometer meter) {
|
||||
meter.measure([&] {
|
||||
return Perimeters::get_layer_infos(
|
||||
print_object->layers(), params.perimeter.elephant_foot_compensation
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
BENCHMARK_ADVANCED("Create perimeters benchy")(Catch::Benchmark::Chronometer meter) {
|
||||
meter.measure([&] {
|
||||
return Perimeters::create_perimeters(shell_polygons, layer_infos, painting, params.perimeter);
|
||||
});
|
||||
};
|
||||
|
||||
BENCHMARK_ADVANCED("Generate aligned seam benchy")(Catch::Benchmark::Chronometer meter) {
|
||||
std::vector<Shells::Shells<>> inputs;
|
||||
inputs.reserve(meter.runs());
|
||||
std::generate_n(std::back_inserter(inputs), meter.runs(), [&]() {
|
||||
return Perimeters::create_perimeters(
|
||||
shell_polygons, layer_infos, painting, params.perimeter
|
||||
);
|
||||
});
|
||||
meter.measure([&](const int i) {
|
||||
return Aligned::get_object_seams(
|
||||
std::move(inputs[i]), visibility_calculator, params.aligned
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
BENCHMARK_ADVANCED("Visibility constructor")(Catch::Benchmark::Chronometer meter) {
|
||||
using Visibility = Slic3r::ModelInfo::Visibility;
|
||||
std::vector<Catch::Benchmark::storage_for<Visibility>> storage(meter.runs());
|
||||
meter.measure([&](const int i) {
|
||||
storage[i].construct(transformation, volumes, params.visibility, []() {});
|
||||
});
|
||||
};
|
||||
|
||||
BENCHMARK_ADVANCED("Generate rear seam benchy")(Catch::Benchmark::Chronometer meter) {
|
||||
std::vector<Shells::Shells<>> inputs;
|
||||
inputs.reserve(meter.runs());
|
||||
std::generate_n(std::back_inserter(inputs), meter.runs(), [&]() {
|
||||
return create_perimeters(
|
||||
shell_polygons, layer_infos, painting, params.perimeter
|
||||
);
|
||||
});
|
||||
meter.measure([&](const int i) {
|
||||
return Rear::get_object_seams(std::move(inputs[i]), params.rear_project_threshold);
|
||||
});
|
||||
};
|
||||
|
||||
BENCHMARK_ADVANCED("Generate random seam benchy")(Catch::Benchmark::Chronometer meter) {
|
||||
std::vector<Shells::Shells<>> inputs;
|
||||
inputs.reserve(meter.runs());
|
||||
std::generate_n(std::back_inserter(inputs), meter.runs(), [&]() {
|
||||
return Perimeters::create_perimeters(
|
||||
shell_polygons, layer_infos, painting, params.perimeter
|
||||
);
|
||||
});
|
||||
meter.measure([&](const int i) {
|
||||
return Random::get_object_seams(std::move(inputs[i]), params.random_seed);
|
||||
});
|
||||
};
|
||||
|
||||
Placer placer;
|
||||
BENCHMARK_ADVANCED("Init seam placer aligned")(Catch::Benchmark::Chronometer meter) {
|
||||
meter.measure([&] {
|
||||
return placer.init(print->objects(), params, [](){});
|
||||
});
|
||||
};
|
||||
|
||||
SECTION("Place seam"){
|
||||
using namespace Slic3r;
|
||||
Placer placer;
|
||||
placer.init(print->objects(), params, [](){});
|
||||
std::vector<std::pair<const Layer*, const ExtrusionLoop*>> loops;
|
||||
|
||||
const PrintObject* object{print->objects().front()};
|
||||
for (const Layer* layer :object->layers()) {
|
||||
for (const LayerSlice& lslice : layer->lslices_ex) {
|
||||
for (const LayerIsland &island : lslice.islands) {
|
||||
const LayerRegion &layer_region = *layer->get_region(island.perimeters.region());
|
||||
for (uint32_t perimeter_id : island.perimeters) {
|
||||
const auto *entity_collection{static_cast<const ExtrusionEntityCollection*>(layer_region.perimeters().entities[perimeter_id])};
|
||||
if (entity_collection != nullptr) {
|
||||
for (const ExtrusionEntity *entity : *entity_collection) {
|
||||
const auto loop{static_cast<const ExtrusionLoop*>(entity)};
|
||||
if (loop == nullptr) {
|
||||
continue;
|
||||
}
|
||||
loops.emplace_back(layer, loop);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BENCHMARK_ADVANCED("Place seam benchy")(Catch::Benchmark::Chronometer meter) {
|
||||
meter.measure([&] {
|
||||
for (const auto &[layer, loop] : loops) {
|
||||
placer.place_seam(layer, *loop, {0, 0});
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
@ -2,12 +2,19 @@
|
||||
#define SLIC3R_TEST_DATA_HPP
|
||||
|
||||
#include "libslic3r/Config.hpp"
|
||||
#include "libslic3r/Format/3mf.hpp"
|
||||
#include "libslic3r/GCode/ModelVisibility.hpp"
|
||||
#include "libslic3r/GCode/SeamGeometry.hpp"
|
||||
#include "libslic3r/GCode/SeamPerimeters.hpp"
|
||||
#include "libslic3r/Geometry.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/Print.hpp"
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
#include "libslic3r/GCode/SeamPlacer.hpp"
|
||||
#include "libslic3r/GCode/SeamAligned.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace Slic3r { namespace Test {
|
||||
@ -39,14 +46,13 @@ enum class TestMesh {
|
||||
};
|
||||
|
||||
// Neccessary for <c++17
|
||||
struct TestMeshHash {
|
||||
std::size_t operator()(TestMesh tm) const {
|
||||
return static_cast<std::size_t>(tm);
|
||||
}
|
||||
struct TestMeshHash
|
||||
{
|
||||
std::size_t operator()(TestMesh tm) const { return static_cast<std::size_t>(tm); }
|
||||
};
|
||||
|
||||
/// Mesh enumeration to name mapping
|
||||
extern const std::unordered_map<TestMesh, const char*, TestMeshHash> mesh_names;
|
||||
extern const std::unordered_map<TestMesh, const char *, TestMeshHash> mesh_names;
|
||||
|
||||
/// Port of Slic3r::Test::mesh
|
||||
/// Basic cubes/boxes should call TriangleMesh::make_cube() directly and rescale/translate it
|
||||
@ -56,35 +62,173 @@ TriangleMesh mesh(TestMesh m, Vec3d translate, Vec3d scale = Vec3d(1.0, 1.0, 1.0
|
||||
TriangleMesh mesh(TestMesh m, Vec3d translate, double scale = 1.0);
|
||||
|
||||
/// Templated function to see if two values are equivalent (+/- epsilon)
|
||||
template <typename T>
|
||||
bool _equiv(const T& a, const T& b) { return std::abs(a - b) < EPSILON; }
|
||||
template<typename T> bool _equiv(const T &a, const T &b) { return std::abs(a - b) < EPSILON; }
|
||||
|
||||
template <typename T>
|
||||
bool _equiv(const T& a, const T& b, double epsilon) { return abs(a - b) < epsilon; }
|
||||
template<typename T> bool _equiv(const T &a, const T &b, double epsilon) {
|
||||
return abs(a - b) < epsilon;
|
||||
}
|
||||
|
||||
Slic3r::Model model(const std::string& model_name, TriangleMesh&& _mesh);
|
||||
void init_print(std::vector<TriangleMesh> &&meshes, Slic3r::Print &print, Slic3r::Model& model, const DynamicPrintConfig &config_in, bool comments = false, unsigned duplicate_count = 1);
|
||||
void init_print(std::initializer_list<TestMesh> meshes, Slic3r::Print &print, Slic3r::Model& model, const Slic3r::DynamicPrintConfig &config_in = Slic3r::DynamicPrintConfig::full_print_config(), bool comments = false, unsigned duplicate_count = 1);
|
||||
void init_print(std::initializer_list<TriangleMesh> meshes, Slic3r::Print &print, Slic3r::Model& model, const Slic3r::DynamicPrintConfig &config_in = Slic3r::DynamicPrintConfig::full_print_config(), bool comments = false, unsigned duplicate = 1);
|
||||
void init_print(std::initializer_list<TestMesh> meshes, Slic3r::Print &print, Slic3r::Model& model, std::initializer_list<Slic3r::ConfigBase::SetDeserializeItem> config_items, bool comments = false, unsigned duplicate = 1);
|
||||
void init_print(std::initializer_list<TriangleMesh> meshes, Slic3r::Print &print, Slic3r::Model& model, std::initializer_list<Slic3r::ConfigBase::SetDeserializeItem> config_items, bool comments = false, unsigned duplicate = 1);
|
||||
Slic3r::Model model(const std::string &model_name, TriangleMesh &&_mesh);
|
||||
void init_print(
|
||||
std::vector<TriangleMesh> &&meshes,
|
||||
Slic3r::Print &print,
|
||||
Slic3r::Model &model,
|
||||
const DynamicPrintConfig &config_in,
|
||||
bool comments = false,
|
||||
unsigned duplicate_count = 1
|
||||
);
|
||||
void init_print(
|
||||
std::initializer_list<TestMesh> meshes,
|
||||
Slic3r::Print &print,
|
||||
Slic3r::Model &model,
|
||||
const Slic3r::DynamicPrintConfig &config_in = Slic3r::DynamicPrintConfig::full_print_config(),
|
||||
bool comments = false,
|
||||
unsigned duplicate_count = 1
|
||||
);
|
||||
void init_print(
|
||||
std::initializer_list<TriangleMesh> meshes,
|
||||
Slic3r::Print &print,
|
||||
Slic3r::Model &model,
|
||||
const Slic3r::DynamicPrintConfig &config_in = Slic3r::DynamicPrintConfig::full_print_config(),
|
||||
bool comments = false,
|
||||
unsigned duplicate = 1
|
||||
);
|
||||
void init_print(
|
||||
std::initializer_list<TestMesh> meshes,
|
||||
Slic3r::Print &print,
|
||||
Slic3r::Model &model,
|
||||
std::initializer_list<Slic3r::ConfigBase::SetDeserializeItem> config_items,
|
||||
bool comments = false,
|
||||
unsigned duplicate = 1
|
||||
);
|
||||
void init_print(
|
||||
std::initializer_list<TriangleMesh> meshes,
|
||||
Slic3r::Print &print,
|
||||
Slic3r::Model &model,
|
||||
std::initializer_list<Slic3r::ConfigBase::SetDeserializeItem> config_items,
|
||||
bool comments = false,
|
||||
unsigned duplicate = 1
|
||||
);
|
||||
|
||||
void init_and_process_print(std::initializer_list<TestMesh> meshes, Slic3r::Print &print, const DynamicPrintConfig& config, bool comments = false);
|
||||
void init_and_process_print(std::initializer_list<TriangleMesh> meshes, Slic3r::Print &print, const DynamicPrintConfig& config, bool comments = false);
|
||||
void init_and_process_print(std::initializer_list<TestMesh> meshes, Slic3r::Print &print, std::initializer_list<Slic3r::ConfigBase::SetDeserializeItem> config_items, bool comments = false);
|
||||
void init_and_process_print(std::initializer_list<TriangleMesh> meshes, Slic3r::Print &print, std::initializer_list<Slic3r::ConfigBase::SetDeserializeItem> config_items, bool comments = false);
|
||||
void init_and_process_print(
|
||||
std::initializer_list<TestMesh> meshes,
|
||||
Slic3r::Print &print,
|
||||
const DynamicPrintConfig &config,
|
||||
bool comments = false
|
||||
);
|
||||
void init_and_process_print(
|
||||
std::initializer_list<TriangleMesh> meshes,
|
||||
Slic3r::Print &print,
|
||||
const DynamicPrintConfig &config,
|
||||
bool comments = false
|
||||
);
|
||||
void init_and_process_print(
|
||||
std::initializer_list<TestMesh> meshes,
|
||||
Slic3r::Print &print,
|
||||
std::initializer_list<Slic3r::ConfigBase::SetDeserializeItem> config_items,
|
||||
bool comments = false
|
||||
);
|
||||
void init_and_process_print(
|
||||
std::initializer_list<TriangleMesh> meshes,
|
||||
Slic3r::Print &print,
|
||||
std::initializer_list<Slic3r::ConfigBase::SetDeserializeItem> config_items,
|
||||
bool comments = false
|
||||
);
|
||||
|
||||
std::string gcode(Print& print);
|
||||
std::string gcode(Print &print);
|
||||
|
||||
std::string slice(std::initializer_list<TestMesh> meshes, const DynamicPrintConfig &config, bool comments = false);
|
||||
std::string slice(std::initializer_list<TriangleMesh> meshes, const DynamicPrintConfig &config, bool comments = false);
|
||||
std::string slice(std::initializer_list<TestMesh> meshes, std::initializer_list<Slic3r::ConfigBase::SetDeserializeItem> config_items, bool comments = false);
|
||||
std::string slice(std::initializer_list<TriangleMesh> meshes, std::initializer_list<Slic3r::ConfigBase::SetDeserializeItem> config_items, bool comments = false);
|
||||
std::string slice(
|
||||
std::initializer_list<TestMesh> meshes, const DynamicPrintConfig &config, bool comments = false
|
||||
);
|
||||
std::string slice(
|
||||
std::initializer_list<TriangleMesh> meshes,
|
||||
const DynamicPrintConfig &config,
|
||||
bool comments = false
|
||||
);
|
||||
std::string slice(
|
||||
std::initializer_list<TestMesh> meshes,
|
||||
std::initializer_list<Slic3r::ConfigBase::SetDeserializeItem> config_items,
|
||||
bool comments = false
|
||||
);
|
||||
std::string slice(
|
||||
std::initializer_list<TriangleMesh> meshes,
|
||||
std::initializer_list<Slic3r::ConfigBase::SetDeserializeItem> config_items,
|
||||
bool comments = false
|
||||
);
|
||||
|
||||
bool contains(const std::string &data, const std::string &pattern);
|
||||
bool contains_regex(const std::string &data, const std::string &pattern);
|
||||
|
||||
} } // namespace Slic3r::Test
|
||||
inline std::unique_ptr<Print> process_3mf(const std::filesystem::path &path) {
|
||||
DynamicPrintConfig config;
|
||||
auto print{std::make_unique<Print>()};
|
||||
Model model;
|
||||
|
||||
ConfigSubstitutionContext context{ForwardCompatibilitySubstitutionRule::Disable};
|
||||
load_3mf(path.string().c_str(), config, context, &model, false);
|
||||
|
||||
Slic3r::Test::init_print(std::vector<TriangleMesh>{}, *print, model, config);
|
||||
print->process();
|
||||
|
||||
return print;
|
||||
}
|
||||
|
||||
static std::map<std::string, std::unique_ptr<Print>> prints_3mfs;
|
||||
// Lazy getter, to avoid processing the 3mf multiple times, it already takes ages.
|
||||
inline Print *get_print(const std::filesystem::path &file_path) {
|
||||
if (!prints_3mfs.count(file_path.string())) {
|
||||
prints_3mfs[file_path.string()] = process_3mf(file_path.string());
|
||||
}
|
||||
return prints_3mfs[file_path.string()].get();
|
||||
}
|
||||
|
||||
inline void serialize_seam(std::ostream &output, const std::vector<std::vector<Seams::SeamPerimeterChoice>> &seam) {
|
||||
output << "x,y,z,layer_index" << std::endl;
|
||||
|
||||
for (const std::vector<Seams::SeamPerimeterChoice> &layer : seam) {
|
||||
if (layer.empty()) {
|
||||
continue;
|
||||
}
|
||||
const Seams::SeamPerimeterChoice &choice{layer.front()};
|
||||
|
||||
// clang-format off
|
||||
output
|
||||
<< choice.choice.position.x() << ","
|
||||
<< choice.choice.position.y() << ","
|
||||
<< choice.perimeter.slice_z << ","
|
||||
<< choice.perimeter.layer_index << std::endl;
|
||||
// clang-format on
|
||||
}
|
||||
}
|
||||
|
||||
struct SeamsFixture
|
||||
{
|
||||
const std::filesystem::path file_3mf{
|
||||
std::filesystem::path{TEST_DATA_DIR} / std::filesystem::path{"seam_test_object.3mf"}};
|
||||
const Print *print{Test::get_print(file_3mf)};
|
||||
const PrintObject *print_object{print->objects()[0]};
|
||||
|
||||
Seams::Params params{Seams::Placer::get_params(print->full_print_config())};
|
||||
|
||||
const Transform3d transformation{print_object->trafo_centered()};
|
||||
const ModelVolumePtrs &volumes{print_object->model_object()->volumes};
|
||||
Seams::ModelInfo::Painting painting{transformation, volumes};
|
||||
|
||||
const std::vector<Seams::Geometry::Extrusions> extrusions{
|
||||
Seams::Geometry::get_extrusions(print_object->layers())};
|
||||
const Seams::Perimeters::LayerInfos layer_infos{Seams::Perimeters::get_layer_infos(
|
||||
print_object->layers(), params.perimeter.elephant_foot_compensation
|
||||
)};
|
||||
Seams::Shells::Shells<Polygon> shell_polygons{
|
||||
Seams::Shells::create_shells(extrusions, params.max_distance)};
|
||||
|
||||
const std::size_t shell_index{15};
|
||||
|
||||
const ModelInfo::Visibility visibility{transformation, volumes, params.visibility, [](){}};
|
||||
Seams::Aligned::VisibilityCalculator
|
||||
visibility_calculator{visibility, params.convex_visibility_modifier, params.concave_visibility_modifier};
|
||||
};
|
||||
|
||||
}} // namespace Slic3r::Test
|
||||
|
||||
#endif // SLIC3R_TEST_DATA_HPP
|
||||
|
160
tests/fff_print/test_seam_aligned.cpp
Normal file
160
tests/fff_print/test_seam_aligned.cpp
Normal file
@ -0,0 +1,160 @@
|
||||
#include <libslic3r/Point.hpp>
|
||||
#include <catch2/catch.hpp>
|
||||
#include <libslic3r/GCode/SeamAligned.hpp>
|
||||
#include "test_data.hpp"
|
||||
#include <fstream>
|
||||
|
||||
using namespace Slic3r;
|
||||
using namespace Slic3r::Seams;
|
||||
|
||||
constexpr bool debug_files{false};
|
||||
|
||||
namespace AlignedTest {
|
||||
Perimeters::Perimeter get_perimeter() {
|
||||
const double slice_z{1.0};
|
||||
const std::size_t layer_index{};
|
||||
std::vector<Vec2d> positions{{0.0, 0.0}, {1.0, 0.0}, {1.0, 1.0}, {0.0, 1.0}, {0.0, 0.5}};
|
||||
std::vector<double> angles(positions.size(), -M_PI / 2.0);
|
||||
angles[4] = 0.0;
|
||||
std::vector<Perimeters::PointType> point_types(positions.size(), Perimeters::PointType::common);
|
||||
std::vector<Perimeters::PointClassification>
|
||||
point_classifications{positions.size(), Perimeters::PointClassification::common};
|
||||
std::vector<Perimeters::AngleType> angle_type(positions.size(), Perimeters::AngleType::concave);
|
||||
angle_type[4] = Perimeters::AngleType::smooth;
|
||||
|
||||
return {
|
||||
slice_z,
|
||||
layer_index,
|
||||
false,
|
||||
std::move(positions),
|
||||
std::move(angles),
|
||||
std::move(point_types),
|
||||
std::move(point_classifications),
|
||||
std::move(angle_type)};
|
||||
}
|
||||
} // namespace AlignedTest
|
||||
|
||||
TEST_CASE("Snap to angle", "[Seams][SeamAligned]") {
|
||||
const Vec2d point{0.0, 0.4};
|
||||
const std::size_t search_start{4};
|
||||
const Perimeters::Perimeter perimeter{AlignedTest::get_perimeter()};
|
||||
|
||||
std::optional<std::size_t> snapped_to{
|
||||
Aligned::Impl::snap_to_angle(point, search_start, perimeter, 0.5)};
|
||||
|
||||
REQUIRE(snapped_to);
|
||||
CHECK(*snapped_to == 0);
|
||||
|
||||
snapped_to = Aligned::Impl::snap_to_angle(point, search_start, perimeter, 0.3);
|
||||
REQUIRE(!snapped_to);
|
||||
}
|
||||
|
||||
TEST_CASE("Get seam options", "[Seams][SeamAligned]") {
|
||||
Perimeters::Perimeter perimeter{AlignedTest::get_perimeter()};
|
||||
const Vec2d prefered_position{0.0, 0.3};
|
||||
|
||||
Aligned::Impl::SeamOptions options{Aligned::Impl::get_seam_options(
|
||||
perimeter, prefered_position, *perimeter.common_points.common_points, 0.4
|
||||
)};
|
||||
|
||||
CHECK(options.closest == 4);
|
||||
CHECK(options.adjacent == 0);
|
||||
CHECK((options.on_edge - Vec2d{0.0, 0.3}).norm() == Approx(0.0));
|
||||
REQUIRE(options.snapped);
|
||||
CHECK(options.snapped == 0);
|
||||
}
|
||||
|
||||
struct PickSeamOptionFixture
|
||||
{
|
||||
Perimeters::Perimeter perimeter{AlignedTest::get_perimeter()};
|
||||
|
||||
Aligned::Impl::SeamOptions options{
|
||||
4, // closest
|
||||
0, // adjacent
|
||||
true, // forward
|
||||
false, // snapped
|
||||
Vec2d{0.0, 0.3}, // on_edge
|
||||
};
|
||||
};
|
||||
|
||||
TEST_CASE_METHOD(PickSeamOptionFixture, "Pick seam option", "[Seams][SeamAligned]") {
|
||||
auto [previous_index, next_index, position]{pick_seam_option(perimeter, options)};
|
||||
CHECK(previous_index == next_index);
|
||||
CHECK((position - Vec2d{0.0, 0.0}).norm() == Approx(0.0));
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(PickSeamOptionFixture, "Pick seam option picks enforcer", "[Seams][SeamAligned]") {
|
||||
perimeter.point_types[4] = Perimeters::PointType::enforcer;
|
||||
|
||||
auto [previous_index, next_index, position]{pick_seam_option(perimeter, options)};
|
||||
CHECK(previous_index == next_index);
|
||||
CHECK((position - Vec2d{0.0, 0.5}).norm() == Approx(0.0));
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(PickSeamOptionFixture, "Nearest point", "[Seams][SeamAligned]") {
|
||||
const std::optional<SeamChoice> result{Aligned::Impl::Nearest{Vec2d{0.4, -0.1}, 0.2}(
|
||||
perimeter, Perimeters::PointType::common, Perimeters::PointClassification::common
|
||||
)};
|
||||
CHECK(result->previous_index == 0);
|
||||
CHECK(result->next_index == 1);
|
||||
CHECK((result->position - Vec2d{0.4, 0.0}).norm() == Approx(0.0));
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(PickSeamOptionFixture, "Least visible point", "[Seams][SeamAligned]") {
|
||||
std::vector<double> precalculated_visibility{};
|
||||
for (std::size_t i{0}; i < perimeter.positions.size(); ++i) {
|
||||
precalculated_visibility.push_back(-static_cast<double>(i));
|
||||
}
|
||||
Aligned::Impl::LeastVisible least_visible{precalculated_visibility};
|
||||
const std::optional<SeamChoice> result{least_visible(
|
||||
perimeter, Perimeters::PointType::common, Perimeters::PointClassification::common
|
||||
)};
|
||||
CHECK(result->previous_index == 4);
|
||||
CHECK(result->next_index == 4);
|
||||
CHECK((result->position - Vec2d{0.0, 0.5}).norm() == Approx(0.0));
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(Test::SeamsFixture, "Generate aligned seam", "[Seams][SeamAligned][Integration]") {
|
||||
Shells::Shells<> perimeters{
|
||||
Perimeters::create_perimeters(shell_polygons, layer_infos, painting, params.perimeter)};
|
||||
Shells::Shells<> shell_perimeters;
|
||||
shell_perimeters.push_back(std::move(perimeters[shell_index]));
|
||||
const std::vector<std::vector<SeamPerimeterChoice>> seam{
|
||||
Aligned::get_object_seams(std::move(shell_perimeters), visibility_calculator, params.aligned)};
|
||||
REQUIRE(seam.size() == 125);
|
||||
|
||||
if constexpr (debug_files) {
|
||||
std::ofstream csv{"aligned_seam.csv"};
|
||||
Test::serialize_seam(csv, seam);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(Test::SeamsFixture, "Calculate visibility", "[Seams][SeamAligned][Integration]") {
|
||||
if constexpr (debug_files) {
|
||||
std::ofstream csv{"visibility.csv"};
|
||||
csv << "x,y,z,visibility,total_visibility" << std::endl;
|
||||
Shells::Shells<> perimeters{
|
||||
Perimeters::create_perimeters(shell_polygons, layer_infos, painting, params.perimeter)};
|
||||
for (const Shells::Shell<> &shell : perimeters) {
|
||||
for (const Shells::Slice<> &slice : shell) {
|
||||
for (std::size_t index{0}; index < slice.boundary.positions.size(); ++index) {
|
||||
const Vec2d &position{slice.boundary.positions[index]};
|
||||
const double point_visibility{visibility.calculate_point_visibility(
|
||||
to_3d(position.cast<float>(), slice.boundary.slice_z)
|
||||
)};
|
||||
const double total_visibility{
|
||||
visibility_calculator(SeamChoice{index, index, position}, slice.boundary)};
|
||||
|
||||
// clang-format off
|
||||
csv <<
|
||||
position.x() << "," <<
|
||||
position.y() << "," <<
|
||||
slice.boundary.slice_z << "," <<
|
||||
point_visibility << "," <<
|
||||
total_visibility << std::endl;
|
||||
// clang-format on
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
145
tests/fff_print/test_seam_geometry.cpp
Normal file
145
tests/fff_print/test_seam_geometry.cpp
Normal file
@ -0,0 +1,145 @@
|
||||
#include <libslic3r/Point.hpp>
|
||||
#include <catch2/catch.hpp>
|
||||
#include <libslic3r/GCode/SeamGeometry.hpp>
|
||||
#include <libslic3r/Geometry.hpp>
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
TEST_CASE("Lists mapping", "[Seams][SeamGeometry]") {
|
||||
// clang-format off
|
||||
std::vector<std::vector<int>> list_of_lists{
|
||||
{},
|
||||
{7, 2, 3},
|
||||
{9, 6, 3, 6, 7},
|
||||
{1, 1, 3},
|
||||
{1},
|
||||
{3},
|
||||
{1},
|
||||
{},
|
||||
{3}
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
std::vector<std::size_t> sizes;
|
||||
sizes.reserve(list_of_lists.size());
|
||||
for (const std::vector<int> &list : list_of_lists) {
|
||||
sizes.push_back(list.size());
|
||||
}
|
||||
|
||||
const auto [mapping, bucket_cout]{Seams::Geometry::get_mapping(
|
||||
sizes,
|
||||
[&](const std::size_t layer_index,
|
||||
const std::size_t item_index) -> Seams::Geometry::MappingOperatorResult {
|
||||
unsigned max_diff{0};
|
||||
std::optional<std::size_t> index;
|
||||
const std::vector<int> &layer{list_of_lists[layer_index]};
|
||||
const std::vector<int> &next_layer{list_of_lists[layer_index + 1]};
|
||||
for (std::size_t i{0}; i < next_layer.size(); ++i) {
|
||||
const long diff{std::abs(next_layer[i] - layer[item_index])};
|
||||
if (diff > max_diff) {
|
||||
max_diff = diff;
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
if (!index) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return std::pair{*index, static_cast<double>(max_diff)};
|
||||
}
|
||||
)};
|
||||
|
||||
// clang-format off
|
||||
CHECK(mapping == std::vector<std::vector<std::size_t>>{
|
||||
{},
|
||||
{0, 1, 2},
|
||||
{1, 3, 0, 4, 5},
|
||||
{1, 6, 7},
|
||||
{7},
|
||||
{7},
|
||||
{7},
|
||||
{},
|
||||
{8}
|
||||
});
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
TEST_CASE("Vertex angle calculation counterclockwise", "[Seams][SeamGeometry]") {
|
||||
std::vector<Vec2d> points{Vec2d{0, 0}, Vec2d{1, 0}, Vec2d{1, 1}, Vec2d{0, 1}};
|
||||
std::vector<double> angles{Seams::Geometry::get_vertex_angles(points, 0.1)};
|
||||
|
||||
CHECK(angles.size() == 4);
|
||||
for (const double angle : angles) {
|
||||
CHECK(angle == Approx(-M_PI / 2));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Vertex angle calculation clockwise", "[Seams][SeamGeometry]") {
|
||||
std::vector<Vec2d> points = {Vec2d{0, 0}, Vec2d{0, 1}, Vec2d{1, 1}, Vec2d{1, 0}};
|
||||
std::vector<double> angles = Seams::Geometry::get_vertex_angles(points, 0.1);
|
||||
|
||||
CHECK(angles.size() == 4);
|
||||
for (const double angle : angles) {
|
||||
CHECK(angle == Approx(M_PI / 2));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Vertex angle calculation small convex", "[Seams][SeamGeometry]") {
|
||||
std::vector<Vec2d> points = {Vec2d{0, 0}, Vec2d{-0.01, 1}, Vec2d{0, 2}, Vec2d{-2, 1}};
|
||||
std::vector<double> angles = Seams::Geometry::get_vertex_angles(points, 0.1);
|
||||
|
||||
CHECK(angles.size() == 4);
|
||||
CHECK(angles[1] > 0);
|
||||
CHECK(angles[1] < 0.02);
|
||||
}
|
||||
|
||||
TEST_CASE("Vertex angle calculation small concave", "[Seams][SeamGeometry]") {
|
||||
std::vector<Vec2d> points = {Vec2d{0, 0}, Vec2d{0.01, 1}, Vec2d{0, 2}, Vec2d{-2, 1}};
|
||||
std::vector<double> angles = Seams::Geometry::get_vertex_angles(points, 0.1);
|
||||
|
||||
CHECK(angles.size() == 4);
|
||||
CHECK(angles[1] < 0);
|
||||
CHECK(angles[1] > -0.02);
|
||||
}
|
||||
|
||||
TEST_CASE("Vertex angle is rotation agnostic", "[Seams][SeamGeometry]") {
|
||||
std::vector<Vec2d> points = {Vec2d{0, 0}, Vec2d{0.01, 1}, Vec2d{0, 2}, Vec2d{-2, 1}};
|
||||
std::vector<double> angles = Seams::Geometry::get_vertex_angles(points, 0.1);
|
||||
|
||||
Points polygon_points;
|
||||
using std::transform, std::back_inserter;
|
||||
transform(points.begin(), points.end(), back_inserter(polygon_points), [](const Vec2d &point) {
|
||||
return scaled(point);
|
||||
});
|
||||
Polygon polygon{polygon_points};
|
||||
polygon.rotate(M_PI - Slic3r::Geometry::deg2rad(10.0));
|
||||
|
||||
std::vector<Vec2d> rotated_points;
|
||||
using std::transform, std::back_inserter;
|
||||
transform(
|
||||
polygon.points.begin(), polygon.points.end(), back_inserter(rotated_points),
|
||||
[](const Point &point) { return unscaled(point); }
|
||||
);
|
||||
|
||||
std::vector<double> rotated_angles = Seams::Geometry::get_vertex_angles(points, 0.1);
|
||||
CHECK(rotated_angles[1] == Approx(angles[1]));
|
||||
}
|
||||
|
||||
TEST_CASE("Calculate overhangs", "[Seams][SeamGeometry]") {
|
||||
const ExPolygon square{
|
||||
scaled(Vec2d{0.0, 0.0}),
|
||||
scaled(Vec2d{1.0, 0.0}),
|
||||
scaled(Vec2d{1.0, 1.0}),
|
||||
scaled(Vec2d{0.0, 1.0})
|
||||
};
|
||||
const std::vector<Vec2d> points{Seams::Geometry::unscaled(square.contour.points)};
|
||||
ExPolygon previous_layer{square};
|
||||
previous_layer.translate(scaled(Vec2d{-0.5, 0}));
|
||||
AABBTreeLines::LinesDistancer<Linef> previous_layer_distancer{
|
||||
to_unscaled_linesf({previous_layer})};
|
||||
const std::vector<double> overhangs{
|
||||
Seams::Geometry::get_overhangs(points, previous_layer_distancer, 0.5)};
|
||||
REQUIRE(overhangs.size() == points.size());
|
||||
CHECK_THAT(overhangs, Catch::Matchers::Approx(std::vector<double>{
|
||||
0.0, M_PI / 4.0, M_PI / 4.0, 0.0
|
||||
}));
|
||||
}
|
181
tests/fff_print/test_seam_perimeters.cpp
Normal file
181
tests/fff_print/test_seam_perimeters.cpp
Normal file
@ -0,0 +1,181 @@
|
||||
#include "libslic3r/ClipperUtils.hpp"
|
||||
#include "libslic3r/GCode/SeamPerimeters.hpp"
|
||||
#include "libslic3r/Layer.hpp"
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include <catch2/catch.hpp>
|
||||
#include <libslic3r/GCode/SeamGeometry.hpp>
|
||||
#include <libslic3r/Geometry.hpp>
|
||||
#include <fstream>
|
||||
|
||||
#include "test_data.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
using namespace Slic3r::Seams;
|
||||
|
||||
constexpr bool debug_files{false};
|
||||
|
||||
const ExPolygon square{
|
||||
scaled(Vec2d{0.0, 0.0}), scaled(Vec2d{1.0, 0.0}), scaled(Vec2d{1.0, 1.0}),
|
||||
scaled(Vec2d{0.0, 1.0})};
|
||||
|
||||
TEST_CASE("Oversample painted", "[Seams][SeamPerimeters]") {
|
||||
auto is_painted{[](const Vec3f &position, float radius) {
|
||||
return (position - Vec3f{0.5, 0.0, 1.0}).norm() < radius;
|
||||
}};
|
||||
std::vector<Vec2d> points{Perimeters::Impl::oversample_painted(
|
||||
Seams::Geometry::unscaled(square.contour.points), is_painted, 1.0, 0.2
|
||||
)};
|
||||
|
||||
REQUIRE(points.size() == 8);
|
||||
CHECK((points[1] - Vec2d{0.2, 0.0}).norm() == Approx(0.0));
|
||||
|
||||
points = Perimeters::Impl::oversample_painted(
|
||||
Seams::Geometry::unscaled(square.contour.points), is_painted, 1.0, 0.199
|
||||
);
|
||||
CHECK(points.size() == 9);
|
||||
}
|
||||
|
||||
TEST_CASE("Remove redundant points", "[Seams][SeamPerimeters]") {
|
||||
using Perimeters::PointType;
|
||||
using Perimeters::PointClassification;
|
||||
|
||||
std::vector<Vec2d> points{{0.0, 0.0}, {1.0, 0.0}, {2.0, 0.0}, {3.0, 0.0},
|
||||
{3.0, 1.0}, {3.0, 2.0}, {0.0, 2.0}};
|
||||
std::vector<PointType> point_types{PointType::common,
|
||||
PointType::enforcer, // Should keep this.
|
||||
PointType::enforcer, // Should keep this.
|
||||
PointType::blocker,
|
||||
PointType::blocker, // Should remove this.
|
||||
PointType::blocker, PointType::common};
|
||||
|
||||
const auto [resulting_points, resulting_point_types]{
|
||||
Perimeters::Impl::remove_redundant_points(points, point_types, 0.1)};
|
||||
|
||||
REQUIRE(resulting_points.size() == 6);
|
||||
REQUIRE(resulting_point_types.size() == 6);
|
||||
CHECK((resulting_points[3] - Vec2d{3.0, 0.0}).norm() == Approx(0.0));
|
||||
CHECK((resulting_points[4] - Vec2d{3.0, 2.0}).norm() == Approx(0.0));
|
||||
CHECK(resulting_point_types[3] == PointType::blocker);
|
||||
CHECK(resulting_point_types[4] == PointType::blocker);
|
||||
}
|
||||
|
||||
TEST_CASE("Perimeter constructs KD trees", "[Seams][SeamPerimeters]") {
|
||||
using Perimeters::PointType;
|
||||
using Perimeters::PointClassification;
|
||||
using Perimeters::AngleType;
|
||||
|
||||
std::vector<Vec2d> positions{Vec2d{0.0, 0.0}, Vec2d{1.0, 0.0}, Vec2d{1.0, 1.0}, Vec2d{0.0, 1.0}};
|
||||
std::vector<double> angles(4, -M_PI / 2.0);
|
||||
std::vector<PointType>
|
||||
point_types{PointType::enforcer, PointType::blocker, PointType::common, PointType::common};
|
||||
std::vector<PointClassification> point_classifications{
|
||||
PointClassification::overhang, PointClassification::embedded, PointClassification::embedded,
|
||||
PointClassification::common};
|
||||
std::vector<AngleType>
|
||||
angle_types{AngleType::convex, AngleType::concave, AngleType::smooth, AngleType::smooth};
|
||||
Perimeters::Perimeter perimeter{
|
||||
3.0,
|
||||
2,
|
||||
false,
|
||||
std::move(positions),
|
||||
std::move(angles),
|
||||
std::move(point_types),
|
||||
std::move(point_classifications),
|
||||
std::move(angle_types)};
|
||||
|
||||
CHECK(perimeter.enforced_points.overhanging_points);
|
||||
CHECK(perimeter.blocked_points.embedded_points);
|
||||
CHECK(perimeter.common_points.common_points);
|
||||
CHECK(perimeter.common_points.embedded_points);
|
||||
}
|
||||
|
||||
using std::filesystem::path;
|
||||
|
||||
constexpr const char *to_string(Perimeters::PointType point_type) {
|
||||
using Perimeters::PointType;
|
||||
|
||||
switch (point_type) {
|
||||
case PointType::enforcer: return "enforcer";
|
||||
case PointType::blocker: return "blocker";
|
||||
case PointType::common: return "common";
|
||||
}
|
||||
throw std::runtime_error("Unreachable");
|
||||
}
|
||||
|
||||
constexpr const char *to_string(Perimeters::PointClassification point_classification) {
|
||||
using Perimeters::PointClassification;
|
||||
|
||||
switch (point_classification) {
|
||||
case PointClassification::embedded: return "embedded";
|
||||
case PointClassification::overhang: return "overhang";
|
||||
case PointClassification::common: return "common";
|
||||
}
|
||||
throw std::runtime_error("Unreachable");
|
||||
}
|
||||
|
||||
constexpr const char *to_string(Perimeters::AngleType angle_type) {
|
||||
using Perimeters::AngleType;
|
||||
|
||||
switch (angle_type) {
|
||||
case AngleType::convex: return "convex";
|
||||
case AngleType::concave: return "concave";
|
||||
case AngleType::smooth: return "smooth";
|
||||
}
|
||||
throw std::runtime_error("Unreachable");
|
||||
}
|
||||
|
||||
void serialize_shell(std::ostream &output, const Shells::Shell<Perimeters::Perimeter> &shell) {
|
||||
output << "x,y,z,point_type,point_classification,angle_type,layer_index,"
|
||||
"point_index,distance,distance_to_previous,is_degenerate"
|
||||
<< std::endl;
|
||||
|
||||
for (std::size_t perimeter_index{0}; perimeter_index < shell.size(); ++perimeter_index) {
|
||||
const Shells::Slice<> &slice{shell[perimeter_index]};
|
||||
const Perimeters::Perimeter &perimeter{slice.boundary};
|
||||
const std::vector<Vec2d> &points{perimeter.positions};
|
||||
|
||||
double total_distance{0.0};
|
||||
for (std::size_t point_index{0}; point_index < perimeter.point_types.size(); ++point_index) {
|
||||
const Vec3d point{to_3d(points[point_index], perimeter.slice_z)};
|
||||
const Perimeters::PointType point_type{perimeter.point_types[point_index]};
|
||||
const Perimeters::PointClassification point_classification{
|
||||
perimeter.point_classifications[point_index]};
|
||||
const Perimeters::AngleType angle_type{perimeter.angle_types[point_index]};
|
||||
const std::size_t layer_index{slice.layer_index};
|
||||
const std::size_t previous_index{point_index == 0 ? points.size() - 1 : point_index - 1};
|
||||
const double distance_to_previous{(points[point_index] - points[previous_index]).norm()};
|
||||
total_distance += point_index == 0 ? 0.0 : distance_to_previous;
|
||||
const double distance{total_distance};
|
||||
const bool is_degenerate{perimeter.is_degenerate};
|
||||
|
||||
// clang-format off
|
||||
output
|
||||
<< point.x() << ","
|
||||
<< point.y() << ","
|
||||
<< point.z() << ","
|
||||
<< to_string(point_type) << ","
|
||||
<< to_string(point_classification) << ","
|
||||
<< to_string(angle_type) << ","
|
||||
<< layer_index << ","
|
||||
<< point_index << ","
|
||||
<< distance << ","
|
||||
<< distance_to_previous << ","
|
||||
<< is_degenerate << std::endl;
|
||||
// clang-format on
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(Test::SeamsFixture, "Create perimeters", "[Seams][SeamPerimeters][Integration]") {
|
||||
const Shells::Shells<> perimeters{
|
||||
create_perimeters(shell_polygons, layer_infos, painting, params.perimeter)};
|
||||
|
||||
const Shells::Shell<> &shell{perimeters[shell_index]};
|
||||
|
||||
if constexpr (debug_files) {
|
||||
std::ofstream csv{"perimeters.csv"};
|
||||
serialize_shell(csv, shell);
|
||||
}
|
||||
|
||||
REQUIRE(shell.size() == 54);
|
||||
}
|
99
tests/fff_print/test_seam_random.cpp
Normal file
99
tests/fff_print/test_seam_random.cpp
Normal file
@ -0,0 +1,99 @@
|
||||
#include <libslic3r/Point.hpp>
|
||||
#include <catch2/catch.hpp>
|
||||
#include <libslic3r/GCode/SeamRandom.hpp>
|
||||
#include "test_data.hpp"
|
||||
#include <fstream>
|
||||
|
||||
using namespace Slic3r;
|
||||
using namespace Slic3r::Seams;
|
||||
|
||||
constexpr bool debug_files{false};
|
||||
|
||||
namespace RandomTest {
|
||||
Perimeters::Perimeter get_perimeter() {
|
||||
const double slice_z{1.0};
|
||||
const std::size_t layer_index{};
|
||||
std::vector<Vec2d> positions{{0.0, 0.0}, {0.5, 0.0}, {1.0, 0.0}};
|
||||
std::vector<double> angles(positions.size(), -M_PI / 2.0);
|
||||
std::vector<Perimeters::PointType> point_types(positions.size(), Perimeters::PointType::common);
|
||||
std::vector<Perimeters::PointClassification>
|
||||
point_classifications{positions.size(), Perimeters::PointClassification::common};
|
||||
std::vector<Perimeters::AngleType> angle_type(positions.size(), Perimeters::AngleType::concave);
|
||||
|
||||
return {
|
||||
slice_z,
|
||||
layer_index,
|
||||
false,
|
||||
std::move(positions),
|
||||
std::move(angles),
|
||||
std::move(point_types),
|
||||
std::move(point_classifications),
|
||||
std::move(angle_type)};
|
||||
}
|
||||
} // namespace RandomTest
|
||||
|
||||
double get_chi2_uniform(const std::vector<double> &data, double min, double max, const std::size_t bin_count) {
|
||||
std::vector<std::size_t> bins(bin_count);
|
||||
const double bin_size{(max - min) / bin_count};
|
||||
const double expected_frequncy{static_cast<double>(data.size()) / bin_count};
|
||||
|
||||
for (const double value : data) {
|
||||
auto bin{static_cast<int>(std::floor((value - min) / bin_size))};
|
||||
bins[bin]++;
|
||||
}
|
||||
|
||||
return std::accumulate(bins.begin(), bins.end(), 0.0, [&](const double total, const std::size_t count_in_bin){
|
||||
return total + std::pow(static_cast<double>(count_in_bin - expected_frequncy), 2.0) / expected_frequncy;
|
||||
});
|
||||
}
|
||||
|
||||
TEST_CASE("Random is uniform", "[Seams][SeamRandom]") {
|
||||
const int seed{42};
|
||||
std::mt19937 random_engine{seed};
|
||||
const Random::Impl::Random random{random_engine};
|
||||
Perimeters::Perimeter perimeter{RandomTest::get_perimeter()};
|
||||
|
||||
std::vector<double> x_positions;
|
||||
const std::size_t count{1001};
|
||||
x_positions.reserve(count);
|
||||
std::generate_n(std::back_inserter(x_positions), count, [&]() {
|
||||
std::optional<SeamChoice> choice{
|
||||
random(perimeter, Perimeters::PointType::common, Perimeters::PointClassification::common)};
|
||||
return choice->position.x();
|
||||
});
|
||||
const std::size_t degrees_of_freedom{10};
|
||||
const double critical{18.307}; // dof 10, significance 0.05
|
||||
|
||||
CHECK(get_chi2_uniform(x_positions, 0.0, 1.0, degrees_of_freedom + 1) < critical);
|
||||
}
|
||||
|
||||
TEST_CASE("Random respects point type", "[Seams][SeamRandom]") {
|
||||
const int seed{42};
|
||||
std::mt19937 random_engine{seed};
|
||||
const Random::Impl::Random random{random_engine};
|
||||
Perimeters::Perimeter perimeter{RandomTest::get_perimeter()};
|
||||
std::optional<SeamChoice> choice{
|
||||
random(perimeter, Perimeters::PointType::common, Perimeters::PointClassification::common)};
|
||||
|
||||
REQUIRE(choice);
|
||||
const std::size_t picked_index{choice->previous_index};
|
||||
perimeter.point_types[picked_index] = Perimeters::PointType::blocker;
|
||||
choice = random(perimeter, Perimeters::PointType::common, Perimeters::PointClassification::common);
|
||||
REQUIRE(choice);
|
||||
CHECK(choice->previous_index != picked_index);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(Test::SeamsFixture, "Generate random seam", "[Seams][SeamRandom][Integration]") {
|
||||
Shells::Shells<> perimeters{
|
||||
Seams::Perimeters::create_perimeters(shell_polygons, layer_infos, painting, params.perimeter)};
|
||||
Shells::Shells<> shell_perimeters;
|
||||
shell_perimeters.push_back(std::move(perimeters[shell_index]));
|
||||
const std::vector<std::vector<SeamPerimeterChoice>> seam{
|
||||
Random::get_object_seams(std::move(shell_perimeters), params.random_seed)};
|
||||
REQUIRE(seam.size() == 125);
|
||||
|
||||
if constexpr (debug_files) {
|
||||
std::ofstream csv{"random_seam.csv"};
|
||||
Test::serialize_seam(csv, seam);
|
||||
}
|
||||
}
|
60
tests/fff_print/test_seam_rear.cpp
Normal file
60
tests/fff_print/test_seam_rear.cpp
Normal file
@ -0,0 +1,60 @@
|
||||
#include <libslic3r/Point.hpp>
|
||||
#include <catch2/catch.hpp>
|
||||
#include <libslic3r/GCode/SeamRear.hpp>
|
||||
#include "test_data.hpp"
|
||||
#include <fstream>
|
||||
|
||||
using namespace Slic3r;
|
||||
using namespace Slic3r::Seams;
|
||||
|
||||
constexpr bool debug_files{false};
|
||||
|
||||
namespace RearTest {
|
||||
Perimeters::Perimeter get_perimeter() {
|
||||
const double slice_z{1.0};
|
||||
const std::size_t layer_index{};
|
||||
std::vector<Vec2d> positions{{0.0, 0.0}, {1.0, 0.0}, {1.0, 1.0}, {0.5, 1.0}, {0.0, 1.0}};
|
||||
std::vector<double> angles(positions.size(), -M_PI / 2.0);
|
||||
angles[3] = 0.0;
|
||||
std::vector<Perimeters::PointType> point_types(positions.size(), Perimeters::PointType::common);
|
||||
std::vector<Perimeters::PointClassification>
|
||||
point_classifications{positions.size(), Perimeters::PointClassification::common};
|
||||
std::vector<Perimeters::AngleType> angle_type(positions.size(), Perimeters::AngleType::concave);
|
||||
angle_type[3] = Perimeters::AngleType::smooth;
|
||||
|
||||
return {
|
||||
slice_z,
|
||||
layer_index,
|
||||
false,
|
||||
std::move(positions),
|
||||
std::move(angles),
|
||||
std::move(point_types),
|
||||
std::move(point_classifications),
|
||||
std::move(angle_type)};
|
||||
}
|
||||
} // namespace RearTest
|
||||
|
||||
TEST_CASE("StraightLine operator places seam point near the prefered position", "[Seams][SeamRear]") {
|
||||
const Rear::Impl::StraightLine rearest{Vec2d{0.7, 2.0}};
|
||||
std::optional<SeamChoice> choice{rearest(RearTest::get_perimeter(), Perimeters::PointType::common, Perimeters::PointClassification::common)};
|
||||
|
||||
REQUIRE(choice);
|
||||
CHECK(scaled(choice->position) == scaled(Vec2d{0.7, 1.0}));
|
||||
CHECK(choice->previous_index == 2);
|
||||
CHECK(choice->next_index == 3);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(Test::SeamsFixture, "Generate rear seam", "[Seams][SeamRear][Integration]") {
|
||||
Shells::Shells<> perimeters{
|
||||
Perimeters::create_perimeters(shell_polygons, layer_infos, painting, params.perimeter)};
|
||||
Shells::Shells<> shell_perimeters;
|
||||
shell_perimeters.push_back(std::move(perimeters[shell_index]));
|
||||
const std::vector<std::vector<SeamPerimeterChoice>> seam{
|
||||
Rear::get_object_seams(std::move(shell_perimeters), params.rear_project_threshold)};
|
||||
REQUIRE(seam.size() == 125);
|
||||
|
||||
if constexpr (debug_files) {
|
||||
std::ofstream csv{"rear_seam.csv"};
|
||||
Test::serialize_seam(csv, seam);
|
||||
}
|
||||
}
|
91
tests/fff_print/test_seam_shells.cpp
Normal file
91
tests/fff_print/test_seam_shells.cpp
Normal file
@ -0,0 +1,91 @@
|
||||
#include <catch2/catch.hpp>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include "libslic3r/ClipperUtils.hpp"
|
||||
#include "libslic3r/GCode/SeamPainting.hpp"
|
||||
#include "test_data.hpp"
|
||||
|
||||
#include "libslic3r/GCode/SeamShells.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
using namespace Slic3r::Seams;
|
||||
|
||||
constexpr bool debug_files{false};
|
||||
|
||||
struct ProjectionFixture
|
||||
{
|
||||
Polygon extrusion_path{
|
||||
Point{scaled(Vec2d{-1.0, -1.0})}, Point{scaled(Vec2d{1.0, -1.0})},
|
||||
Point{scaled(Vec2d{1.0, 1.0})}, Point{scaled(Vec2d{-1.0, 1.0})}};
|
||||
|
||||
ExPolygon island_boundary;
|
||||
Seams::Geometry::Extrusions extrusions;
|
||||
double extrusion_width{0.2};
|
||||
|
||||
ProjectionFixture() {
|
||||
extrusions.emplace_back(Polygon{extrusion_path}, extrusion_path.bounding_box(), extrusion_width, island_boundary);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_CASE_METHOD(ProjectionFixture, "Project to geometry matches", "[Seams][SeamShells]") {
|
||||
Polygon boundary_polygon{extrusion_path};
|
||||
// Add + 0.1 to check that boundary polygon has been picked.
|
||||
boundary_polygon.scale(1.0 + extrusion_width / 2.0 + 0.1);
|
||||
island_boundary.contour = boundary_polygon;
|
||||
|
||||
Shells::Impl::BoundedPolygons result{Shells::Impl::project_to_geometry(extrusions, 5.0)};
|
||||
REQUIRE(result.size() == 1);
|
||||
REQUIRE(result[0].polygon.size() == 4);
|
||||
// Boundary polygon is picked.
|
||||
CHECK(result[0].polygon[0].x() == Approx(scaled(-(1.0 + extrusion_width / 2.0 + 0.1))));
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(ProjectionFixture, "Project to geometry does not match", "[Seams][SeamShells]") {
|
||||
Polygon boundary_polygon{extrusion_path};
|
||||
|
||||
// Island boundary is far from the extrusion.
|
||||
boundary_polygon.scale(5.0);
|
||||
|
||||
island_boundary.contour = boundary_polygon;
|
||||
|
||||
Shells::Impl::BoundedPolygons result{Shells::Impl::project_to_geometry(extrusions, 1.0)};
|
||||
REQUIRE(result.size() == 1);
|
||||
REQUIRE(result[0].polygon.size() == 4);
|
||||
|
||||
const Polygon expanded{expand(extrusions.front().polygon, extrusion_width / 2.0).front()};
|
||||
|
||||
// The extrusion is expanded and returned.
|
||||
CHECK(result[0].polygon == expanded);
|
||||
}
|
||||
|
||||
void serialize_shells(
|
||||
std::ostream &out, const Shells::Shells<Polygon> &shells, const double layer_height
|
||||
) {
|
||||
out << "x,y,z,layer_index,slice_id,shell_id" << std::endl;
|
||||
for (std::size_t shell_id{}; shell_id < shells.size(); ++shell_id) {
|
||||
const Shells::Shell<Polygon> &shell{shells[shell_id]};
|
||||
for (std::size_t slice_id{}; slice_id < shell.size(); ++slice_id) {
|
||||
const Shells::Slice<Polygon> &slice{shell[slice_id]};
|
||||
for (const Point &point : slice.boundary) {
|
||||
// clang-format off
|
||||
out
|
||||
<< point.x() << ","
|
||||
<< point.y() << ","
|
||||
<< slice.layer_index * 1e6 * layer_height << ","
|
||||
<< slice.layer_index << ","
|
||||
<< slice_id << ","
|
||||
<< shell_id << std::endl;
|
||||
// clang-format on
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(Test::SeamsFixture, "Create shells", "[Seams][SeamShells][Integration]") {
|
||||
if constexpr (debug_files) {
|
||||
std::ofstream csv{"shells.csv"};
|
||||
serialize_shells(csv, shell_polygons, print->full_print_config().opt_float("layer_height"));
|
||||
}
|
||||
|
||||
CHECK(shell_polygons.size() == 36);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user