Merge branch 'master' of https://github.com/Prusa-Development/PrusaSlicerPrivate into et_sla_switch_view
This commit is contained in:
commit
b13a51f400
@ -546,6 +546,7 @@ foreach(po_file ${L10N_PO_FILES})
|
||||
endforeach()
|
||||
|
||||
find_package(NLopt 1.4 REQUIRED)
|
||||
slic3r_remap_configs(NLopt::nlopt RelWithDebInfo Release)
|
||||
|
||||
if(SLIC3R_STATIC)
|
||||
set(OPENVDB_USE_STATIC_LIBS ON)
|
||||
|
@ -73,25 +73,6 @@ static int const Skip = -2; //edge that would otherwise close a path
|
||||
#define TOLERANCE (1.0e-20)
|
||||
#define NEAR_ZERO(val) (((val) > -TOLERANCE) && ((val) < TOLERANCE))
|
||||
|
||||
// Output polygon.
|
||||
struct OutRec {
|
||||
int Idx;
|
||||
bool IsHole;
|
||||
bool IsOpen;
|
||||
//The 'FirstLeft' field points to another OutRec that contains or is the
|
||||
//'parent' of OutRec. It is 'first left' because the ActiveEdgeList (AEL) is
|
||||
//parsed left from the current edge (owning OutRec) until the owner OutRec
|
||||
//is found. This field simplifies sorting the polygons into a tree structure
|
||||
//which reflects the parent/child relationships of all polygons.
|
||||
//This field should be renamed Parent, and will be later.
|
||||
OutRec *FirstLeft;
|
||||
// Used only by void Clipper::BuildResult2(PolyTree& polytree)
|
||||
PolyNode *PolyNd;
|
||||
// Linked list of output points, dynamically allocated.
|
||||
OutPt *Pts;
|
||||
OutPt *BottomPt;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
inline IntPoint IntPoint2d(cInt x, cInt y)
|
||||
@ -131,7 +112,7 @@ int PolyTree::Total() const
|
||||
void PolyNode::AddChild(PolyNode& child)
|
||||
{
|
||||
unsigned cnt = (unsigned)Childs.size();
|
||||
Childs.push_back(&child);
|
||||
Childs.emplace_back(&child);
|
||||
child.Parent = this;
|
||||
child.Index = cnt;
|
||||
}
|
||||
@ -693,7 +674,7 @@ TEdge* ClipperBase::ProcessBound(TEdge* E, bool NextIsForward)
|
||||
locMin.RightBound = E;
|
||||
E->WindDelta = 0;
|
||||
Result = ProcessBound(E, NextIsForward);
|
||||
m_MinimaList.push_back(locMin);
|
||||
m_MinimaList.emplace_back(locMin);
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
@ -915,7 +896,7 @@ bool ClipperBase::AddPathInternal(const Path &pg, int highI, PolyType PolyTyp, b
|
||||
E->NextInLML = E->Next;
|
||||
E = E->Next;
|
||||
}
|
||||
m_MinimaList.push_back(locMin);
|
||||
m_MinimaList.emplace_back(locMin);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -968,7 +949,7 @@ bool ClipperBase::AddPathInternal(const Path &pg, int highI, PolyType PolyTyp, b
|
||||
locMin.LeftBound = 0;
|
||||
else if (locMin.RightBound->OutIdx == Skip)
|
||||
locMin.RightBound = 0;
|
||||
m_MinimaList.push_back(locMin);
|
||||
m_MinimaList.emplace_back(locMin);
|
||||
if (!leftBoundIsForward) E = E2;
|
||||
}
|
||||
return true;
|
||||
@ -1061,8 +1042,7 @@ IntRect ClipperBase::GetBounds()
|
||||
Clipper::Clipper(int initOptions) :
|
||||
ClipperBase(),
|
||||
m_OutPtsFree(nullptr),
|
||||
m_OutPtsChunkSize(32),
|
||||
m_OutPtsChunkLast(32),
|
||||
m_OutPtsChunkLast(m_OutPtsChunkSize),
|
||||
m_ActiveEdges(nullptr),
|
||||
m_SortedEdges(nullptr)
|
||||
{
|
||||
@ -1153,23 +1133,23 @@ bool Clipper::ExecuteInternal()
|
||||
//FIXME Vojtech: Does it not invalidate the loop hierarchy maintained as OutRec::FirstLeft pointers?
|
||||
//FIXME Vojtech: The area is calculated with floats, it may not be numerically stable!
|
||||
{
|
||||
for (OutRec *outRec : m_PolyOuts)
|
||||
if (outRec->Pts && !outRec->IsOpen && (outRec->IsHole ^ m_ReverseOutput) == (Area(*outRec) > 0))
|
||||
ReversePolyPtLinks(outRec->Pts);
|
||||
for (OutRec &outRec : m_PolyOuts)
|
||||
if (outRec.Pts && !outRec.IsOpen && (outRec.IsHole ^ m_ReverseOutput) == (Area(outRec) > 0))
|
||||
ReversePolyPtLinks(outRec.Pts);
|
||||
}
|
||||
|
||||
JoinCommonEdges();
|
||||
|
||||
//unfortunately FixupOutPolygon() must be done after JoinCommonEdges()
|
||||
{
|
||||
for (OutRec *outRec : m_PolyOuts)
|
||||
if (outRec->Pts) {
|
||||
if (outRec->IsOpen)
|
||||
for (OutRec &outRec : m_PolyOuts)
|
||||
if (outRec.Pts) {
|
||||
if (outRec.IsOpen)
|
||||
// Removes duplicate points.
|
||||
FixupOutPolyline(*outRec);
|
||||
FixupOutPolyline(outRec);
|
||||
else
|
||||
// Removes duplicate points and simplifies consecutive parallel edges by removing the middle vertex.
|
||||
FixupOutPolygon(*outRec);
|
||||
FixupOutPolygon(outRec);
|
||||
}
|
||||
}
|
||||
// For each polygon, search for exactly duplicate non-successive points.
|
||||
@ -1194,22 +1174,18 @@ OutPt* Clipper::AllocateOutPt()
|
||||
m_OutPtsFree = pt->Next;
|
||||
} else if (m_OutPtsChunkLast < m_OutPtsChunkSize) {
|
||||
// Get a point from the last chunk.
|
||||
pt = m_OutPts.back() + (m_OutPtsChunkLast ++);
|
||||
pt = &m_OutPts.back()[m_OutPtsChunkLast ++];
|
||||
} else {
|
||||
// The last chunk is full. Allocate a new one.
|
||||
m_OutPts.push_back(new OutPt[m_OutPtsChunkSize]);
|
||||
m_OutPts.emplace_back();
|
||||
m_OutPtsChunkLast = 1;
|
||||
pt = m_OutPts.back();
|
||||
pt = &m_OutPts.back().front();
|
||||
}
|
||||
return pt;
|
||||
}
|
||||
|
||||
void Clipper::DisposeAllOutRecs()
|
||||
{
|
||||
for (OutPt *pts : m_OutPts)
|
||||
delete[] pts;
|
||||
for (OutRec *rec : m_PolyOuts)
|
||||
delete rec;
|
||||
m_OutPts.clear();
|
||||
m_OutPtsFree = nullptr;
|
||||
m_OutPtsChunkLast = m_OutPtsChunkSize;
|
||||
@ -1832,7 +1808,7 @@ void Clipper::IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &Pt)
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
void Clipper::SetHoleState(TEdge *e, OutRec *outrec) const
|
||||
void Clipper::SetHoleState(TEdge *e, OutRec *outrec)
|
||||
{
|
||||
bool IsHole = false;
|
||||
TEdge *e2 = e->PrevInAEL;
|
||||
@ -1842,7 +1818,7 @@ void Clipper::SetHoleState(TEdge *e, OutRec *outrec) const
|
||||
{
|
||||
IsHole = !IsHole;
|
||||
if (! outrec->FirstLeft)
|
||||
outrec->FirstLeft = m_PolyOuts[e2->OutIdx];
|
||||
outrec->FirstLeft = &m_PolyOuts[e2->OutIdx];
|
||||
}
|
||||
e2 = e2->PrevInAEL;
|
||||
}
|
||||
@ -1883,18 +1859,18 @@ bool Param1RightOfParam2(OutRec* outRec1, OutRec* outRec2)
|
||||
|
||||
OutRec* Clipper::GetOutRec(int Idx)
|
||||
{
|
||||
OutRec* outrec = m_PolyOuts[Idx];
|
||||
while (outrec != m_PolyOuts[outrec->Idx])
|
||||
outrec = m_PolyOuts[outrec->Idx];
|
||||
OutRec* outrec = &m_PolyOuts[Idx];
|
||||
while (outrec != &m_PolyOuts[outrec->Idx])
|
||||
outrec = &m_PolyOuts[outrec->Idx];
|
||||
return outrec;
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
void Clipper::AppendPolygon(TEdge *e1, TEdge *e2) const
|
||||
void Clipper::AppendPolygon(TEdge *e1, TEdge *e2)
|
||||
{
|
||||
//get the start and ends of both output polygons ...
|
||||
OutRec *outRec1 = m_PolyOuts[e1->OutIdx];
|
||||
OutRec *outRec2 = m_PolyOuts[e2->OutIdx];
|
||||
OutRec *outRec1 = &m_PolyOuts[e1->OutIdx];
|
||||
OutRec *outRec2 = &m_PolyOuts[e2->OutIdx];
|
||||
|
||||
OutRec *holeStateRec;
|
||||
if (Param1RightOfParam2(outRec1, outRec2))
|
||||
@ -1991,16 +1967,16 @@ void Clipper::AppendPolygon(TEdge *e1, TEdge *e2) const
|
||||
|
||||
OutRec* Clipper::CreateOutRec()
|
||||
{
|
||||
OutRec* result = new OutRec;
|
||||
result->IsHole = false;
|
||||
result->IsOpen = false;
|
||||
result->FirstLeft = 0;
|
||||
result->Pts = 0;
|
||||
result->BottomPt = 0;
|
||||
result->PolyNd = 0;
|
||||
m_PolyOuts.push_back(result);
|
||||
result->Idx = (int)m_PolyOuts.size()-1;
|
||||
return result;
|
||||
m_PolyOuts.emplace_back();
|
||||
OutRec &result = m_PolyOuts.back();
|
||||
result.IsHole = false;
|
||||
result.IsOpen = false;
|
||||
result.FirstLeft = 0;
|
||||
result.Pts = 0;
|
||||
result.BottomPt = 0;
|
||||
result.PolyNd = 0;
|
||||
result.Idx = (int)m_PolyOuts.size()-1;
|
||||
return &result;
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@ -2022,7 +1998,7 @@ OutPt* Clipper::AddOutPt(TEdge *e, const IntPoint &pt)
|
||||
return newOp;
|
||||
} else
|
||||
{
|
||||
OutRec *outRec = m_PolyOuts[e->OutIdx];
|
||||
OutRec *outRec = &m_PolyOuts[e->OutIdx];
|
||||
//OutRec.Pts is the 'Left-most' point & OutRec.Pts.Prev is the 'Right-most'
|
||||
OutPt* op = outRec->Pts;
|
||||
|
||||
@ -2045,7 +2021,7 @@ OutPt* Clipper::AddOutPt(TEdge *e, const IntPoint &pt)
|
||||
|
||||
OutPt* Clipper::GetLastOutPt(TEdge *e)
|
||||
{
|
||||
OutRec *outRec = m_PolyOuts[e->OutIdx];
|
||||
OutRec *outRec = &m_PolyOuts[e->OutIdx];
|
||||
if (e->Side == esLeft)
|
||||
return outRec->Pts;
|
||||
else
|
||||
@ -2216,7 +2192,7 @@ void Clipper::ProcessHorizontal(TEdge *horzEdge)
|
||||
{
|
||||
Direction dir;
|
||||
cInt horzLeft, horzRight;
|
||||
bool IsOpen = (horzEdge->OutIdx >= 0 && m_PolyOuts[horzEdge->OutIdx]->IsOpen);
|
||||
bool IsOpen = (horzEdge->OutIdx >= 0 && m_PolyOuts[horzEdge->OutIdx].IsOpen);
|
||||
|
||||
GetHorzDirection(*horzEdge, dir, horzLeft, horzRight);
|
||||
|
||||
@ -2600,7 +2576,7 @@ void Clipper::ProcessEdgesAtTopOfScanbeam(const cInt topY)
|
||||
|
||||
if(IsMaximaEdge)
|
||||
{
|
||||
if (m_StrictSimple) m_Maxima.push_back(e->Top.x());
|
||||
if (m_StrictSimple) m_Maxima.emplace_back(e->Top.x());
|
||||
TEdge* ePrev = e->PrevInAEL;
|
||||
DoMaxima(e);
|
||||
if( !ePrev ) e = m_ActiveEdges;
|
||||
@ -2778,12 +2754,12 @@ int PointCount(OutPt *Pts)
|
||||
void Clipper::BuildResult(Paths &polys)
|
||||
{
|
||||
polys.reserve(m_PolyOuts.size());
|
||||
for (OutRec* outRec : m_PolyOuts)
|
||||
for (OutRec &outRec : m_PolyOuts)
|
||||
{
|
||||
assert(! outRec->IsOpen);
|
||||
if (!outRec->Pts) continue;
|
||||
assert(! outRec.IsOpen);
|
||||
if (!outRec.Pts) continue;
|
||||
Path pg;
|
||||
OutPt* p = outRec->Pts->Prev;
|
||||
OutPt* p = outRec.Pts->Prev;
|
||||
int cnt = PointCount(p);
|
||||
if (cnt < 2) continue;
|
||||
pg.reserve(cnt);
|
||||
@ -2802,31 +2778,31 @@ void Clipper::BuildResult2(PolyTree& polytree)
|
||||
polytree.Clear();
|
||||
polytree.AllNodes.reserve(m_PolyOuts.size());
|
||||
//add each output polygon/contour to polytree ...
|
||||
for (OutRec* outRec : m_PolyOuts)
|
||||
for (OutRec &outRec : m_PolyOuts)
|
||||
{
|
||||
int cnt = PointCount(outRec->Pts);
|
||||
if ((outRec->IsOpen && cnt < 2) || (!outRec->IsOpen && cnt < 3))
|
||||
int cnt = PointCount(outRec.Pts);
|
||||
if ((outRec.IsOpen && cnt < 2) || (!outRec.IsOpen && cnt < 3))
|
||||
// Ignore an invalid output loop or a polyline.
|
||||
continue;
|
||||
|
||||
//skip OutRecs that (a) contain outermost polygons or
|
||||
//(b) already have the correct owner/child linkage ...
|
||||
if (outRec->FirstLeft &&
|
||||
(outRec->IsHole == outRec->FirstLeft->IsHole || ! outRec->FirstLeft->Pts)) {
|
||||
OutRec* orfl = outRec->FirstLeft;
|
||||
while (orfl && ((orfl->IsHole == outRec->IsHole) || !orfl->Pts))
|
||||
if (outRec.FirstLeft &&
|
||||
(outRec.IsHole == outRec.FirstLeft->IsHole || ! outRec.FirstLeft->Pts)) {
|
||||
OutRec* orfl = outRec.FirstLeft;
|
||||
while (orfl && ((orfl->IsHole == outRec.IsHole) || !orfl->Pts))
|
||||
orfl = orfl->FirstLeft;
|
||||
outRec->FirstLeft = orfl;
|
||||
outRec.FirstLeft = orfl;
|
||||
}
|
||||
|
||||
//nb: polytree takes ownership of all the PolyNodes
|
||||
polytree.AllNodes.emplace_back(PolyNode());
|
||||
PolyNode* pn = &polytree.AllNodes.back();
|
||||
outRec->PolyNd = pn;
|
||||
outRec.PolyNd = pn;
|
||||
pn->Parent = 0;
|
||||
pn->Index = 0;
|
||||
pn->Contour.reserve(cnt);
|
||||
OutPt *op = outRec->Pts->Prev;
|
||||
OutPt *op = outRec.Pts->Prev;
|
||||
for (int j = 0; j < cnt; j++)
|
||||
{
|
||||
pn->Contour.emplace_back(op->Pt);
|
||||
@ -2836,18 +2812,18 @@ void Clipper::BuildResult2(PolyTree& polytree)
|
||||
|
||||
//fixup PolyNode links etc ...
|
||||
polytree.Childs.reserve(m_PolyOuts.size());
|
||||
for (OutRec* outRec : m_PolyOuts)
|
||||
for (OutRec &outRec : m_PolyOuts)
|
||||
{
|
||||
if (!outRec->PolyNd) continue;
|
||||
if (outRec->IsOpen)
|
||||
if (!outRec.PolyNd) continue;
|
||||
if (outRec.IsOpen)
|
||||
{
|
||||
outRec->PolyNd->m_IsOpen = true;
|
||||
polytree.AddChild(*outRec->PolyNd);
|
||||
outRec.PolyNd->m_IsOpen = true;
|
||||
polytree.AddChild(*outRec.PolyNd);
|
||||
}
|
||||
else if (outRec->FirstLeft && outRec->FirstLeft->PolyNd)
|
||||
outRec->FirstLeft->PolyNd->AddChild(*outRec->PolyNd);
|
||||
else if (outRec.FirstLeft && outRec.FirstLeft->PolyNd)
|
||||
outRec.FirstLeft->PolyNd->AddChild(*outRec.PolyNd);
|
||||
else
|
||||
polytree.AddChild(*outRec->PolyNd);
|
||||
polytree.AddChild(*outRec.PolyNd);
|
||||
}
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
@ -3193,26 +3169,26 @@ bool Clipper::JoinPoints(Join *j, OutRec* outRec1, OutRec* outRec2)
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
// This is potentially very expensive! O(n^3)!
|
||||
void Clipper::FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec) const
|
||||
void Clipper::FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec)
|
||||
{
|
||||
//tests if NewOutRec contains the polygon before reassigning FirstLeft
|
||||
for (OutRec *outRec : m_PolyOuts)
|
||||
for (OutRec &outRec : m_PolyOuts)
|
||||
{
|
||||
if (!outRec->Pts || !outRec->FirstLeft) continue;
|
||||
OutRec* firstLeft = outRec->FirstLeft;
|
||||
if (!outRec.Pts || !outRec.FirstLeft) continue;
|
||||
OutRec* firstLeft = outRec.FirstLeft;
|
||||
// Skip empty polygons.
|
||||
while (firstLeft && !firstLeft->Pts) firstLeft = firstLeft->FirstLeft;
|
||||
if (firstLeft == OldOutRec && Poly2ContainsPoly1(outRec->Pts, NewOutRec->Pts))
|
||||
outRec->FirstLeft = NewOutRec;
|
||||
if (firstLeft == OldOutRec && Poly2ContainsPoly1(outRec.Pts, NewOutRec->Pts))
|
||||
outRec.FirstLeft = NewOutRec;
|
||||
}
|
||||
}
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
void Clipper::FixupFirstLefts2(OutRec* OldOutRec, OutRec* NewOutRec) const
|
||||
void Clipper::FixupFirstLefts2(OutRec* OldOutRec, OutRec* NewOutRec)
|
||||
{
|
||||
//reassigns FirstLeft WITHOUT testing if NewOutRec contains the polygon
|
||||
for (OutRec *outRec : m_PolyOuts)
|
||||
if (outRec->FirstLeft == OldOutRec) outRec->FirstLeft = NewOutRec;
|
||||
for (OutRec &outRec : m_PolyOuts)
|
||||
if (outRec.FirstLeft == OldOutRec) outRec.FirstLeft = NewOutRec;
|
||||
}
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
@ -3253,13 +3229,13 @@ void Clipper::JoinCommonEdges()
|
||||
if (m_UsingPolyTree)
|
||||
for (size_t j = 0; j < m_PolyOuts.size() - 1; j++)
|
||||
{
|
||||
OutRec* oRec = m_PolyOuts[j];
|
||||
OutRec* firstLeft = oRec->FirstLeft;
|
||||
OutRec &oRec = m_PolyOuts[j];
|
||||
OutRec* firstLeft = oRec.FirstLeft;
|
||||
while (firstLeft && !firstLeft->Pts) firstLeft = firstLeft->FirstLeft;
|
||||
if (!oRec->Pts || firstLeft != outRec1 ||
|
||||
oRec->IsHole == outRec1->IsHole) continue;
|
||||
if (Poly2ContainsPoly1(oRec->Pts, join.OutPt2))
|
||||
oRec->FirstLeft = outRec2;
|
||||
if (!oRec.Pts || firstLeft != outRec1 ||
|
||||
oRec.IsHole == outRec1->IsHole) continue;
|
||||
if (Poly2ContainsPoly1(oRec.Pts, join.OutPt2))
|
||||
oRec.FirstLeft = outRec2;
|
||||
}
|
||||
|
||||
if (Poly2ContainsPoly1(outRec2->Pts, outRec1->Pts))
|
||||
@ -3373,7 +3349,7 @@ void ClipperOffset::AddPath(const Path& path, JoinType joinType, EndType endType
|
||||
break;
|
||||
}
|
||||
newNode->Contour.reserve(highI + 1);
|
||||
newNode->Contour.push_back(path[0]);
|
||||
newNode->Contour.emplace_back(path[0]);
|
||||
int j = 0, k = 0;
|
||||
for (int i = 1; i <= highI; i++) {
|
||||
bool same = false;
|
||||
@ -3386,7 +3362,7 @@ void ClipperOffset::AddPath(const Path& path, JoinType joinType, EndType endType
|
||||
if (same)
|
||||
continue;
|
||||
j++;
|
||||
newNode->Contour.push_back(path[i]);
|
||||
newNode->Contour.emplace_back(path[i]);
|
||||
if (path[i].y() > newNode->Contour[k].y() ||
|
||||
(path[i].y() == newNode->Contour[k].y() &&
|
||||
path[i].x() < newNode->Contour[k].x())) k = j;
|
||||
@ -3514,7 +3490,7 @@ void ClipperOffset::DoOffset(double delta)
|
||||
{
|
||||
PolyNode& node = *m_polyNodes.Childs[i];
|
||||
if (node.m_endtype == etClosedPolygon)
|
||||
m_destPolys.push_back(node.Contour);
|
||||
m_destPolys.emplace_back(node.Contour);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -3556,7 +3532,7 @@ void ClipperOffset::DoOffset(double delta)
|
||||
double X = 1.0, Y = 0.0;
|
||||
for (cInt j = 1; j <= steps; j++)
|
||||
{
|
||||
m_destPoly.push_back(IntPoint2d(
|
||||
m_destPoly.emplace_back(IntPoint2d(
|
||||
Round(m_srcPoly[0].x() + X * delta),
|
||||
Round(m_srcPoly[0].y() + Y * delta)));
|
||||
double X2 = X;
|
||||
@ -3569,7 +3545,7 @@ void ClipperOffset::DoOffset(double delta)
|
||||
double X = -1.0, Y = -1.0;
|
||||
for (int j = 0; j < 4; ++j)
|
||||
{
|
||||
m_destPoly.push_back(IntPoint2d(
|
||||
m_destPoly.emplace_back(IntPoint2d(
|
||||
Round(m_srcPoly[0].x() + X * delta),
|
||||
Round(m_srcPoly[0].y() + Y * delta)));
|
||||
if (X < 0) X = 1;
|
||||
@ -3577,32 +3553,32 @@ void ClipperOffset::DoOffset(double delta)
|
||||
else X = -1;
|
||||
}
|
||||
}
|
||||
m_destPolys.push_back(m_destPoly);
|
||||
m_destPolys.emplace_back(m_destPoly);
|
||||
continue;
|
||||
}
|
||||
//build m_normals ...
|
||||
m_normals.clear();
|
||||
m_normals.reserve(len);
|
||||
for (int j = 0; j < len - 1; ++j)
|
||||
m_normals.push_back(GetUnitNormal(m_srcPoly[j], m_srcPoly[j + 1]));
|
||||
m_normals.emplace_back(GetUnitNormal(m_srcPoly[j], m_srcPoly[j + 1]));
|
||||
if (node.m_endtype == etClosedLine || node.m_endtype == etClosedPolygon)
|
||||
m_normals.push_back(GetUnitNormal(m_srcPoly[len - 1], m_srcPoly[0]));
|
||||
m_normals.emplace_back(GetUnitNormal(m_srcPoly[len - 1], m_srcPoly[0]));
|
||||
else
|
||||
m_normals.push_back(DoublePoint(m_normals[len - 2]));
|
||||
m_normals.emplace_back(DoublePoint(m_normals[len - 2]));
|
||||
|
||||
if (node.m_endtype == etClosedPolygon)
|
||||
{
|
||||
int k = len - 1;
|
||||
for (int j = 0; j < len; ++j)
|
||||
OffsetPoint(j, k, node.m_jointype);
|
||||
m_destPolys.push_back(m_destPoly);
|
||||
m_destPolys.emplace_back(m_destPoly);
|
||||
}
|
||||
else if (node.m_endtype == etClosedLine)
|
||||
{
|
||||
int k = len - 1;
|
||||
for (int j = 0; j < len; ++j)
|
||||
OffsetPoint(j, k, node.m_jointype);
|
||||
m_destPolys.push_back(m_destPoly);
|
||||
m_destPolys.emplace_back(m_destPoly);
|
||||
m_destPoly.clear();
|
||||
//re-build m_normals ...
|
||||
DoublePoint n = m_normals[len -1];
|
||||
@ -3612,7 +3588,7 @@ void ClipperOffset::DoOffset(double delta)
|
||||
k = 0;
|
||||
for (int j = len - 1; j >= 0; j--)
|
||||
OffsetPoint(j, k, node.m_jointype);
|
||||
m_destPolys.push_back(m_destPoly);
|
||||
m_destPolys.emplace_back(m_destPoly);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -3625,9 +3601,9 @@ void ClipperOffset::DoOffset(double delta)
|
||||
{
|
||||
int j = len - 1;
|
||||
pt1 = IntPoint2d(Round(m_srcPoly[j].x() + m_normals[j].x() * delta), Round(m_srcPoly[j].y() + m_normals[j].y() * delta));
|
||||
m_destPoly.push_back(pt1);
|
||||
m_destPoly.emplace_back(pt1);
|
||||
pt1 = IntPoint2d(Round(m_srcPoly[j].x() - m_normals[j].x() * delta), Round(m_srcPoly[j].y() - m_normals[j].y() * delta));
|
||||
m_destPoly.push_back(pt1);
|
||||
m_destPoly.emplace_back(pt1);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -3652,9 +3628,9 @@ void ClipperOffset::DoOffset(double delta)
|
||||
if (node.m_endtype == etOpenButt)
|
||||
{
|
||||
pt1 = IntPoint2d(Round(m_srcPoly[0].x() - m_normals[0].x() * delta), Round(m_srcPoly[0].y() - m_normals[0].y() * delta));
|
||||
m_destPoly.push_back(pt1);
|
||||
m_destPoly.emplace_back(pt1);
|
||||
pt1 = IntPoint2d(Round(m_srcPoly[0].x() + m_normals[0].x() * delta), Round(m_srcPoly[0].y() + m_normals[0].y() * delta));
|
||||
m_destPoly.push_back(pt1);
|
||||
m_destPoly.emplace_back(pt1);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -3665,7 +3641,7 @@ void ClipperOffset::DoOffset(double delta)
|
||||
else
|
||||
DoRound(0, 1);
|
||||
}
|
||||
m_destPolys.push_back(m_destPoly);
|
||||
m_destPolys.emplace_back(m_destPoly);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3681,7 +3657,7 @@ void ClipperOffset::OffsetPoint(int j, int& k, JoinType jointype)
|
||||
double cosA = (m_normals[k].x() * m_normals[j].x() + m_normals[j].y() * m_normals[k].y() );
|
||||
if (cosA > 0) // angle => 0 degrees
|
||||
{
|
||||
m_destPoly.push_back(IntPoint2d(Round(m_srcPoly[j].x() + m_normals[k].x() * m_delta),
|
||||
m_destPoly.emplace_back(IntPoint2d(Round(m_srcPoly[j].x() + m_normals[k].x() * m_delta),
|
||||
Round(m_srcPoly[j].y() + m_normals[k].y() * m_delta)));
|
||||
return;
|
||||
}
|
||||
@ -3692,10 +3668,10 @@ void ClipperOffset::OffsetPoint(int j, int& k, JoinType jointype)
|
||||
|
||||
if (m_sinA * m_delta < 0)
|
||||
{
|
||||
m_destPoly.push_back(IntPoint2d(Round(m_srcPoly[j].x() + m_normals[k].x() * m_delta),
|
||||
m_destPoly.emplace_back(IntPoint2d(Round(m_srcPoly[j].x() + m_normals[k].x() * m_delta),
|
||||
Round(m_srcPoly[j].y() + m_normals[k].y() * m_delta)));
|
||||
m_destPoly.push_back(m_srcPoly[j]);
|
||||
m_destPoly.push_back(IntPoint2d(Round(m_srcPoly[j].x() + m_normals[j].x() * m_delta),
|
||||
m_destPoly.emplace_back(m_srcPoly[j]);
|
||||
m_destPoly.emplace_back(IntPoint2d(Round(m_srcPoly[j].x() + m_normals[j].x() * m_delta),
|
||||
Round(m_srcPoly[j].y() + m_normals[j].y() * m_delta)));
|
||||
}
|
||||
else
|
||||
@ -3719,10 +3695,10 @@ void ClipperOffset::DoSquare(int j, int k)
|
||||
{
|
||||
double dx = std::tan(std::atan2(m_sinA,
|
||||
m_normals[k].x() * m_normals[j].x() + m_normals[k].y() * m_normals[j].y()) / 4);
|
||||
m_destPoly.push_back(IntPoint2d(
|
||||
m_destPoly.emplace_back(IntPoint2d(
|
||||
Round(m_srcPoly[j].x() + m_delta * (m_normals[k].x() - m_normals[k].y() * dx)),
|
||||
Round(m_srcPoly[j].y() + m_delta * (m_normals[k].y() + m_normals[k].x() * dx))));
|
||||
m_destPoly.push_back(IntPoint2d(
|
||||
m_destPoly.emplace_back(IntPoint2d(
|
||||
Round(m_srcPoly[j].x() + m_delta * (m_normals[j].x() + m_normals[j].y() * dx)),
|
||||
Round(m_srcPoly[j].y() + m_delta * (m_normals[j].y() - m_normals[j].x() * dx))));
|
||||
}
|
||||
@ -3731,7 +3707,7 @@ void ClipperOffset::DoSquare(int j, int k)
|
||||
void ClipperOffset::DoMiter(int j, int k, double r)
|
||||
{
|
||||
double q = m_delta / r;
|
||||
m_destPoly.push_back(IntPoint2d(Round(m_srcPoly[j].x() + (m_normals[k].x() + m_normals[j].x()) * q),
|
||||
m_destPoly.emplace_back(IntPoint2d(Round(m_srcPoly[j].x() + (m_normals[k].x() + m_normals[j].x()) * q),
|
||||
Round(m_srcPoly[j].y() + (m_normals[k].y() + m_normals[j].y()) * q)));
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
@ -3745,14 +3721,14 @@ void ClipperOffset::DoRound(int j, int k)
|
||||
double X = m_normals[k].x(), Y = m_normals[k].y(), X2;
|
||||
for (int i = 0; i < steps; ++i)
|
||||
{
|
||||
m_destPoly.push_back(IntPoint2d(
|
||||
m_destPoly.emplace_back(IntPoint2d(
|
||||
Round(m_srcPoly[j].x() + X * m_delta),
|
||||
Round(m_srcPoly[j].y() + Y * m_delta)));
|
||||
X2 = X;
|
||||
X = X * m_cos - m_sin * Y;
|
||||
Y = X2 * m_sin + Y * m_cos;
|
||||
}
|
||||
m_destPoly.push_back(IntPoint2d(
|
||||
m_destPoly.emplace_back(IntPoint2d(
|
||||
Round(m_srcPoly[j].x() + m_normals[j].x() * m_delta),
|
||||
Round(m_srcPoly[j].y() + m_normals[j].y() * m_delta)));
|
||||
}
|
||||
@ -3771,13 +3747,13 @@ void Clipper::DoSimplePolygons()
|
||||
size_t i = 0;
|
||||
while (i < m_PolyOuts.size())
|
||||
{
|
||||
OutRec* outrec = m_PolyOuts[i++];
|
||||
OutPt* op = outrec->Pts;
|
||||
if (!op || outrec->IsOpen) continue;
|
||||
OutRec &outrec = m_PolyOuts[i++];
|
||||
OutPt* op = outrec.Pts;
|
||||
if (!op || outrec.IsOpen) continue;
|
||||
do //for each Pt in Polygon until duplicate found do ...
|
||||
{
|
||||
OutPt* op2 = op->Next;
|
||||
while (op2 != outrec->Pts)
|
||||
while (op2 != outrec.Pts)
|
||||
{
|
||||
if ((op->Pt == op2->Pt) && op2->Next != op && op2->Prev != op)
|
||||
{
|
||||
@ -3789,37 +3765,37 @@ void Clipper::DoSimplePolygons()
|
||||
op2->Prev = op3;
|
||||
op3->Next = op2;
|
||||
|
||||
outrec->Pts = op;
|
||||
outrec.Pts = op;
|
||||
OutRec* outrec2 = CreateOutRec();
|
||||
outrec2->Pts = op2;
|
||||
UpdateOutPtIdxs(*outrec2);
|
||||
if (Poly2ContainsPoly1(outrec2->Pts, outrec->Pts))
|
||||
if (Poly2ContainsPoly1(outrec2->Pts, outrec.Pts))
|
||||
{
|
||||
//OutRec2 is contained by OutRec1 ...
|
||||
outrec2->IsHole = !outrec->IsHole;
|
||||
outrec2->FirstLeft = outrec;
|
||||
outrec2->IsHole = !outrec.IsHole;
|
||||
outrec2->FirstLeft = &outrec;
|
||||
// For each m_PolyOuts, replace FirstLeft from outRec2 to outrec.
|
||||
if (m_UsingPolyTree) FixupFirstLefts2(outrec2, outrec);
|
||||
if (m_UsingPolyTree) FixupFirstLefts2(outrec2, &outrec);
|
||||
}
|
||||
else
|
||||
if (Poly2ContainsPoly1(outrec->Pts, outrec2->Pts))
|
||||
if (Poly2ContainsPoly1(outrec.Pts, outrec2->Pts))
|
||||
{
|
||||
//OutRec1 is contained by OutRec2 ...
|
||||
outrec2->IsHole = outrec->IsHole;
|
||||
outrec->IsHole = !outrec2->IsHole;
|
||||
outrec2->FirstLeft = outrec->FirstLeft;
|
||||
outrec->FirstLeft = outrec2;
|
||||
outrec2->IsHole = outrec.IsHole;
|
||||
outrec.IsHole = !outrec2->IsHole;
|
||||
outrec2->FirstLeft = outrec.FirstLeft;
|
||||
outrec.FirstLeft = outrec2;
|
||||
// For each m_PolyOuts, replace FirstLeft from outrec to outrec2.
|
||||
if (m_UsingPolyTree) FixupFirstLefts2(outrec, outrec2);
|
||||
if (m_UsingPolyTree) FixupFirstLefts2(&outrec, outrec2);
|
||||
}
|
||||
else
|
||||
{
|
||||
//the 2 polygons are separate ...
|
||||
outrec2->IsHole = outrec->IsHole;
|
||||
outrec2->FirstLeft = outrec->FirstLeft;
|
||||
outrec2->IsHole = outrec.IsHole;
|
||||
outrec2->FirstLeft = outrec.FirstLeft;
|
||||
// For each polygon of m_PolyOuts, replace FirstLeft from outrec to outrec2 if the polygon is inside outRec2.
|
||||
//FIXME This is potentially very expensive! O(n^3)!
|
||||
if (m_UsingPolyTree) FixupFirstLefts1(outrec, outrec2);
|
||||
if (m_UsingPolyTree) FixupFirstLefts1(&outrec, outrec2);
|
||||
}
|
||||
op2 = op; //ie get ready for the Next iteration
|
||||
}
|
||||
@ -3827,7 +3803,7 @@ void Clipper::DoSimplePolygons()
|
||||
}
|
||||
op = op->Next;
|
||||
}
|
||||
while (op != outrec->Pts);
|
||||
while (op != outrec.Pts);
|
||||
}
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
@ -3845,10 +3821,10 @@ void ReversePaths(Paths& p)
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
Paths SimplifyPolygon(const Path &in_poly, PolyFillType fillType)
|
||||
Paths SimplifyPolygon(const Path &in_poly, PolyFillType fillType, bool strictly_simple /* = true */)
|
||||
{
|
||||
Clipper c;
|
||||
c.StrictlySimple(true);
|
||||
c.StrictlySimple(strictly_simple);
|
||||
c.AddPath(in_poly, ptSubject, true);
|
||||
Paths out;
|
||||
c.Execute(ctUnion, out, fillType, fillType);
|
||||
@ -4020,8 +3996,8 @@ void Minkowski(const Path& poly, const Path& path,
|
||||
Path p;
|
||||
p.reserve(polyCnt);
|
||||
for (size_t j = 0; j < poly.size(); ++j)
|
||||
p.push_back(IntPoint2d(path[i].x() + poly[j].x(), path[i].y() + poly[j].y()));
|
||||
pp.push_back(p);
|
||||
p.emplace_back(IntPoint2d(path[i].x() + poly[j].x(), path[i].y() + poly[j].y()));
|
||||
pp.emplace_back(p);
|
||||
}
|
||||
else
|
||||
for (size_t i = 0; i < pathCnt; ++i)
|
||||
@ -4029,8 +4005,8 @@ void Minkowski(const Path& poly, const Path& path,
|
||||
Path p;
|
||||
p.reserve(polyCnt);
|
||||
for (size_t j = 0; j < poly.size(); ++j)
|
||||
p.push_back(IntPoint2d(path[i].x() - poly[j].x(), path[i].y() - poly[j].y()));
|
||||
pp.push_back(p);
|
||||
p.emplace_back(IntPoint2d(path[i].x() - poly[j].x(), path[i].y() - poly[j].y()));
|
||||
pp.emplace_back(p);
|
||||
}
|
||||
|
||||
solution.clear();
|
||||
@ -4040,12 +4016,12 @@ void Minkowski(const Path& poly, const Path& path,
|
||||
{
|
||||
Path quad;
|
||||
quad.reserve(4);
|
||||
quad.push_back(pp[i % pathCnt][j % polyCnt]);
|
||||
quad.push_back(pp[(i + 1) % pathCnt][j % polyCnt]);
|
||||
quad.push_back(pp[(i + 1) % pathCnt][(j + 1) % polyCnt]);
|
||||
quad.push_back(pp[i % pathCnt][(j + 1) % polyCnt]);
|
||||
quad.emplace_back(pp[i % pathCnt][j % polyCnt]);
|
||||
quad.emplace_back(pp[(i + 1) % pathCnt][j % polyCnt]);
|
||||
quad.emplace_back(pp[(i + 1) % pathCnt][(j + 1) % polyCnt]);
|
||||
quad.emplace_back(pp[i % pathCnt][(j + 1) % polyCnt]);
|
||||
if (!Orientation(quad)) ReversePath(quad);
|
||||
solution.push_back(quad);
|
||||
solution.emplace_back(quad);
|
||||
}
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
@ -4105,7 +4081,7 @@ void AddPolyNodeToPaths(const PolyNode& polynode, NodeType nodetype, Paths& path
|
||||
else if (nodetype == ntOpen) return;
|
||||
|
||||
if (!polynode.Contour.empty() && match)
|
||||
paths.push_back(polynode.Contour);
|
||||
paths.emplace_back(polynode.Contour);
|
||||
for (int i = 0; i < polynode.ChildCount(); ++i)
|
||||
AddPolyNodeToPaths(*polynode.Childs[i], nodetype, paths);
|
||||
}
|
||||
@ -4117,7 +4093,7 @@ void AddPolyNodeToPaths(PolyNode&& polynode, NodeType nodetype, Paths& paths)
|
||||
else if (nodetype == ntOpen) return;
|
||||
|
||||
if (!polynode.Contour.empty() && match)
|
||||
paths.push_back(std::move(polynode.Contour));
|
||||
paths.emplace_back(std::move(polynode.Contour));
|
||||
for (int i = 0; i < polynode.ChildCount(); ++i)
|
||||
AddPolyNodeToPaths(std::move(*polynode.Childs[i]), nodetype, paths);
|
||||
}
|
||||
@ -4155,7 +4131,7 @@ void OpenPathsFromPolyTree(PolyTree& polytree, Paths& paths)
|
||||
//Open paths are top level only, so ...
|
||||
for (int i = 0; i < polytree.ChildCount(); ++i)
|
||||
if (polytree.Childs[i]->IsOpen())
|
||||
paths.push_back(polytree.Childs[i]->Contour);
|
||||
paths.emplace_back(polytree.Childs[i]->Contour);
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
|
@ -52,6 +52,7 @@
|
||||
//use_deprecated: Enables temporary support for the obsolete functions
|
||||
//#define use_deprecated
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
#include <stdexcept>
|
||||
@ -199,7 +200,8 @@ double Area(const Path &poly);
|
||||
inline bool Orientation(const Path &poly) { return Area(poly) >= 0; }
|
||||
int PointInPolygon(const IntPoint &pt, const Path &path);
|
||||
|
||||
Paths SimplifyPolygon(const Path &in_poly, PolyFillType fillType = pftEvenOdd);
|
||||
// Union with "strictly simple" fix enabled.
|
||||
Paths SimplifyPolygon(const Path &in_poly, PolyFillType fillType = pftNonZero, bool strictly_simple = true);
|
||||
|
||||
void CleanPolygon(const Path& in_poly, Path& out_poly, double distance = 1.415);
|
||||
void CleanPolygon(Path& poly, double distance = 1.415);
|
||||
@ -284,7 +286,25 @@ enum EdgeSide { esLeft = 1, esRight = 2};
|
||||
|
||||
using OutPts = std::vector<OutPt, Allocator<OutPt>>;
|
||||
|
||||
struct OutRec;
|
||||
// Output polygon.
|
||||
struct OutRec {
|
||||
int Idx;
|
||||
bool IsHole;
|
||||
bool IsOpen;
|
||||
//The 'FirstLeft' field points to another OutRec that contains or is the
|
||||
//'parent' of OutRec. It is 'first left' because the ActiveEdgeList (AEL) is
|
||||
//parsed left from the current edge (owning OutRec) until the owner OutRec
|
||||
//is found. This field simplifies sorting the polygons into a tree structure
|
||||
//which reflects the parent/child relationships of all polygons.
|
||||
//This field should be renamed Parent, and will be later.
|
||||
OutRec* FirstLeft;
|
||||
// Used only by void Clipper::BuildResult2(PolyTree& polytree)
|
||||
PolyNode* PolyNd;
|
||||
// Linked list of output points, dynamically allocated.
|
||||
OutPt* Pts;
|
||||
OutPt* BottomPt;
|
||||
};
|
||||
|
||||
struct Join {
|
||||
Join(OutPt *OutPt1, OutPt *OutPt2, IntPoint OffPt) :
|
||||
OutPt1(OutPt1), OutPt2(OutPt2), OffPt(OffPt) {}
|
||||
@ -432,12 +452,12 @@ protected:
|
||||
private:
|
||||
|
||||
// Output polygons.
|
||||
std::vector<OutRec*, Allocator<OutRec*>> m_PolyOuts;
|
||||
std::deque<OutRec, Allocator<OutRec>> m_PolyOuts;
|
||||
// Output points, allocated by a continuous sets of m_OutPtsChunkSize.
|
||||
std::vector<OutPt*, Allocator<OutPt*>> m_OutPts;
|
||||
static constexpr const size_t m_OutPtsChunkSize = 32;
|
||||
std::deque<std::array<OutPt, m_OutPtsChunkSize>, Allocator<std::array<OutPt, m_OutPtsChunkSize>>> m_OutPts;
|
||||
// List of free output points, to be used before taking a point from m_OutPts or allocating a new chunk.
|
||||
OutPt *m_OutPtsFree;
|
||||
size_t m_OutPtsChunkSize;
|
||||
size_t m_OutPtsChunkLast;
|
||||
|
||||
std::vector<Join, Allocator<Join>> m_Joins;
|
||||
@ -482,7 +502,7 @@ private:
|
||||
void AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &pt);
|
||||
OutPt* AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &pt);
|
||||
OutRec* GetOutRec(int idx);
|
||||
void AppendPolygon(TEdge *e1, TEdge *e2) const;
|
||||
void AppendPolygon(TEdge *e1, TEdge *e2);
|
||||
void IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &pt);
|
||||
OutRec* CreateOutRec();
|
||||
OutPt* AddOutPt(TEdge *e, const IntPoint &pt);
|
||||
@ -498,7 +518,7 @@ private:
|
||||
void ProcessEdgesAtTopOfScanbeam(const cInt topY);
|
||||
void BuildResult(Paths& polys);
|
||||
void BuildResult2(PolyTree& polytree);
|
||||
void SetHoleState(TEdge *e, OutRec *outrec) const;
|
||||
void SetHoleState(TEdge *e, OutRec *outrec);
|
||||
bool FixupIntersectionOrder();
|
||||
void FixupOutPolygon(OutRec &outrec);
|
||||
void FixupOutPolyline(OutRec &outrec);
|
||||
@ -508,8 +528,8 @@ private:
|
||||
bool JoinHorz(OutPt* op1, OutPt* op1b, OutPt* op2, OutPt* op2b, const IntPoint &Pt, bool DiscardLeft);
|
||||
void JoinCommonEdges();
|
||||
void DoSimplePolygons();
|
||||
void FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec) const;
|
||||
void FixupFirstLefts2(OutRec* OldOutRec, OutRec* NewOutRec) const;
|
||||
void FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec);
|
||||
void FixupFirstLefts2(OutRec* OldOutRec, OutRec* NewOutRec);
|
||||
#ifdef CLIPPERLIB_USE_XYZ
|
||||
void SetZ(IntPoint& pt, TEdge& e1, TEdge& e2);
|
||||
#endif
|
||||
@ -567,10 +587,11 @@ class clipperException : public std::exception
|
||||
};
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// Union with "strictly simple" fix enabled.
|
||||
template<typename PathsProvider>
|
||||
inline Paths SimplifyPolygons(PathsProvider &&in_polys, PolyFillType fillType = pftEvenOdd) {
|
||||
inline Paths SimplifyPolygons(PathsProvider &&in_polys, PolyFillType fillType = pftNonZero, bool strictly_simple = true) {
|
||||
Clipper c;
|
||||
c.StrictlySimple(true);
|
||||
c.StrictlySimple(strictly_simple);
|
||||
c.AddPaths(std::forward<PathsProvider>(in_polys), ptSubject, true);
|
||||
Paths out;
|
||||
c.Execute(ctUnion, out, fillType, fillType);
|
||||
|
@ -232,7 +232,7 @@ std::unique_ptr<LocToLineGrid> cre
|
||||
void fixSelfIntersections(const coord_t epsilon, Polygons &thiss)
|
||||
{
|
||||
if (epsilon < 1) {
|
||||
ClipperLib::SimplifyPolygons(ClipperUtils::PolygonsProvider(thiss));
|
||||
ClipperLib::SimplifyPolygons(ClipperUtils::PolygonsProvider(thiss), ClipperLib::pftEvenOdd);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -273,7 +273,7 @@ void fixSelfIntersections(const coord_t epsilon, Polygons &thiss)
|
||||
}
|
||||
}
|
||||
|
||||
ClipperLib::SimplifyPolygons(ClipperUtils::PolygonsProvider(thiss));
|
||||
ClipperLib::SimplifyPolygons(ClipperUtils::PolygonsProvider(thiss), ClipperLib::pftEvenOdd);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -276,10 +276,21 @@ set(SLIC3R_SOURCES
|
||||
SlicingAdaptive.hpp
|
||||
Subdivide.cpp
|
||||
Subdivide.hpp
|
||||
Support/SupportCommon.cpp
|
||||
Support/SupportCommon.hpp
|
||||
Support/SupportDebug.cpp
|
||||
Support/SupportDebug.hpp
|
||||
Support/SupportLayer.hpp
|
||||
Support/SupportMaterial.cpp
|
||||
Support/SupportMaterial.hpp
|
||||
Support/SupportParameters.cpp
|
||||
Support/SupportParameters.hpp
|
||||
Support/TreeSupport.cpp
|
||||
Support/TreeSupport.hpp
|
||||
Support/TreeModelVolumes.cpp
|
||||
Support/TreeModelVolumes.hpp
|
||||
SupportSpotsGenerator.cpp
|
||||
SupportSpotsGenerator.hpp
|
||||
SupportMaterial.cpp
|
||||
SupportMaterial.hpp
|
||||
Surface.cpp
|
||||
Surface.hpp
|
||||
SurfaceCollection.cpp
|
||||
@ -291,10 +302,6 @@ set(SLIC3R_SOURCES
|
||||
Tesselate.cpp
|
||||
Tesselate.hpp
|
||||
TextConfiguration.hpp
|
||||
TreeSupport.cpp
|
||||
TreeSupport.hpp
|
||||
TreeModelVolumes.cpp
|
||||
TreeModelVolumes.hpp
|
||||
TriangleMesh.cpp
|
||||
TriangleMesh.hpp
|
||||
TriangleMeshSlicer.cpp
|
||||
|
@ -685,6 +685,8 @@ Slic3r::Polygons diff(const Slic3r::Surfaces &subject, const Slic3r::Polygons &c
|
||||
{ return _clipper(ClipperLib::ctDifference, ClipperUtils::SurfacesProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); }
|
||||
Slic3r::Polygons intersection(const Slic3r::Polygon &subject, const Slic3r::Polygon &clip, ApplySafetyOffset do_safety_offset)
|
||||
{ return _clipper(ClipperLib::ctIntersection, ClipperUtils::SinglePathProvider(subject.points), ClipperUtils::SinglePathProvider(clip.points), do_safety_offset); }
|
||||
Slic3r::Polygons intersection_clipped(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)
|
||||
{ return intersection(subject, ClipperUtils::clip_clipper_polygons_with_subject_bbox(clip, get_extents(subject).inflated(SCALED_EPSILON)), do_safety_offset); }
|
||||
Slic3r::Polygons intersection(const Slic3r::Polygons &subject, const Slic3r::ExPolygon &clip, ApplySafetyOffset do_safety_offset)
|
||||
{ return _clipper(ClipperLib::ctIntersection, ClipperUtils::PolygonsProvider(subject), ClipperUtils::ExPolygonProvider(clip), do_safety_offset); }
|
||||
Slic3r::Polygons intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)
|
||||
@ -964,21 +966,18 @@ Polygons union_pt_chained_outside_in(const Polygons &subject)
|
||||
return retval;
|
||||
}
|
||||
|
||||
Polygons simplify_polygons(const Polygons &subject, bool preserve_collinear)
|
||||
Polygons simplify_polygons(const Polygons &subject)
|
||||
{
|
||||
CLIPPER_UTILS_TIME_LIMIT_MILLIS(CLIPPER_UTILS_TIME_LIMIT_DEFAULT);
|
||||
|
||||
ClipperLib::Paths output;
|
||||
if (preserve_collinear) {
|
||||
ClipperLib::Clipper c;
|
||||
c.PreserveCollinear(true);
|
||||
c.StrictlySimple(true);
|
||||
c.AddPaths(ClipperUtils::PolygonsProvider(subject), ClipperLib::ptSubject, true);
|
||||
c.Execute(ClipperLib::ctUnion, output, ClipperLib::pftNonZero, ClipperLib::pftNonZero);
|
||||
} else {
|
||||
output = ClipperLib::SimplifyPolygons(ClipperUtils::PolygonsProvider(subject), ClipperLib::pftNonZero);
|
||||
}
|
||||
|
||||
ClipperLib::Clipper c;
|
||||
// c.PreserveCollinear(true);
|
||||
//FIXME StrictlySimple is very expensive! Is it needed?
|
||||
c.StrictlySimple(true);
|
||||
c.AddPaths(ClipperUtils::PolygonsProvider(subject), ClipperLib::ptSubject, true);
|
||||
c.Execute(ClipperLib::ctUnion, output, ClipperLib::pftNonZero, ClipperLib::pftNonZero);
|
||||
|
||||
// convert into Slic3r polygons
|
||||
return to_polygons(std::move(output));
|
||||
}
|
||||
@ -987,12 +986,10 @@ ExPolygons simplify_polygons_ex(const Polygons &subject, bool preserve_collinear
|
||||
{
|
||||
CLIPPER_UTILS_TIME_LIMIT_MILLIS(CLIPPER_UTILS_TIME_LIMIT_DEFAULT);
|
||||
|
||||
if (! preserve_collinear)
|
||||
return union_ex(simplify_polygons(subject, false));
|
||||
|
||||
ClipperLib::PolyTree polytree;
|
||||
ClipperLib::Clipper c;
|
||||
c.PreserveCollinear(true);
|
||||
// c.PreserveCollinear(true);
|
||||
//FIXME StrictlySimple is very expensive! Is it needed?
|
||||
c.StrictlySimple(true);
|
||||
c.AddPaths(ClipperUtils::PolygonsProvider(subject), ClipperLib::ptSubject, true);
|
||||
c.Execute(ClipperLib::ctUnion, polytree, ClipperLib::pftNonZero, ClipperLib::pftNonZero);
|
||||
|
@ -453,6 +453,9 @@ inline Slic3r::Lines diff_ln(const Slic3r::Lines &subject, const Slic3r::Polygon
|
||||
Slic3r::Polygons intersection(const Slic3r::Polygon &subject, const Slic3r::Polygon &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::Polygons intersection(const Slic3r::Polygons &subject, const Slic3r::ExPolygon &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::Polygons intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
// Optimized version clipping the "clipping" polygon using clip_clipper_polygon_with_subject_bbox().
|
||||
// To be used with complex clipping polygons, where majority of the clipping polygons are outside of the source polygon.
|
||||
Slic3r::Polygons intersection_clipped(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::Polygons intersection(const Slic3r::ExPolygon &subject, const Slic3r::ExPolygon &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::Polygons intersection(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::Polygons intersection(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
@ -596,8 +599,8 @@ void traverse_pt(const ClipperLib::PolyNodes &nodes, ExOrJustPolygons *retval)
|
||||
|
||||
|
||||
/* OTHER */
|
||||
Slic3r::Polygons simplify_polygons(const Slic3r::Polygons &subject, bool preserve_collinear = false);
|
||||
Slic3r::ExPolygons simplify_polygons_ex(const Slic3r::Polygons &subject, bool preserve_collinear = false);
|
||||
Slic3r::Polygons simplify_polygons(const Slic3r::Polygons &subject);
|
||||
Slic3r::ExPolygons simplify_polygons_ex(const Slic3r::Polygons &subject);
|
||||
|
||||
Polygons top_level_islands(const Slic3r::Polygons &polygons);
|
||||
|
||||
|
@ -184,14 +184,14 @@ Polygons ExPolygon::simplify_p(double tolerance) const
|
||||
{
|
||||
Polygon p = this->contour;
|
||||
p.points.push_back(p.points.front());
|
||||
p.points = MultiPoint::_douglas_peucker(p.points, tolerance);
|
||||
p.points = MultiPoint::douglas_peucker(p.points, tolerance);
|
||||
p.points.pop_back();
|
||||
pp.emplace_back(std::move(p));
|
||||
}
|
||||
// holes
|
||||
for (Polygon p : this->holes) {
|
||||
p.points.push_back(p.points.front());
|
||||
p.points = MultiPoint::_douglas_peucker(p.points, tolerance);
|
||||
p.points = MultiPoint::douglas_peucker(p.points, tolerance);
|
||||
p.points.pop_back();
|
||||
pp.emplace_back(std::move(p));
|
||||
}
|
||||
|
@ -176,7 +176,7 @@ Flow Flow::with_cross_section(float area_new) const
|
||||
return this->with_width(width_new);
|
||||
} else {
|
||||
// Create a rounded extrusion.
|
||||
auto dmr = float(sqrt(area_new / M_PI));
|
||||
auto dmr = 2.0 * float(sqrt(area_new / M_PI));
|
||||
return Flow(dmr, dmr, m_spacing, m_nozzle_diameter, false);
|
||||
}
|
||||
} else
|
||||
|
@ -52,15 +52,15 @@ template bool contains(const ExPolygons &vector, const Point &point);
|
||||
|
||||
void simplify_polygons(const Polygons &polygons, double tolerance, Polygons* retval)
|
||||
{
|
||||
Polygons pp;
|
||||
for (Polygons::const_iterator it = polygons.begin(); it != polygons.end(); ++it) {
|
||||
Polygon p = *it;
|
||||
p.points.push_back(p.points.front());
|
||||
p.points = MultiPoint::_douglas_peucker(p.points, tolerance);
|
||||
p.points.pop_back();
|
||||
pp.push_back(p);
|
||||
Polygons simplified_raw;
|
||||
for (const Polygon &source_polygon : polygons) {
|
||||
Points simplified = MultiPoint::douglas_peucker(to_polyline(source_polygon).points, tolerance);
|
||||
if (simplified.size() > 3) {
|
||||
simplified.pop_back();
|
||||
simplified_raw.push_back(Polygon{ std::move(simplified) });
|
||||
}
|
||||
}
|
||||
*retval = Slic3r::simplify_polygons(pp);
|
||||
*retval = Slic3r::simplify_polygons(simplified_raw);
|
||||
}
|
||||
|
||||
double linint(double value, double oldmin, double oldmax, double newmin, double newmax)
|
||||
|
@ -230,10 +230,13 @@ Surfaces expand_bridges_detect_orientations(
|
||||
bboxes[it - it_begin].overlap(bboxes[it2 - it_begin]) &&
|
||||
// One may ignore holes, they are irrelevant for intersection test.
|
||||
! intersection(it->expolygon.contour, it2->expolygon.contour).empty()) {
|
||||
// The two bridge regions intersect. Give them the same group id.
|
||||
// The two bridge regions intersect. Give them the same (lower) group id.
|
||||
uint32_t id = group_id(it->src_id);
|
||||
uint32_t id2 = group_id(it2->src_id);
|
||||
bridges[it->src_id].group_id = bridges[it2->src_id].group_id = std::min(id, id2);
|
||||
if (id < id2)
|
||||
bridges[id2].group_id = id;
|
||||
else
|
||||
bridges[id].group_id = id2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,11 +40,12 @@ template<class L> auto get_b(L &&l) { return Traits<remove_cvref_t<L>>::get_b(l)
|
||||
|
||||
// Distance to the closest point of line.
|
||||
template<class L>
|
||||
double distance_to_squared(const L &line, const Vec<Dim<L>, Scalar<L>> &point, Vec<Dim<L>, Scalar<L>> *nearest_point)
|
||||
inline double distance_to_squared(const L &line, const Vec<Dim<L>, Scalar<L>> &point, Vec<Dim<L>, Scalar<L>> *nearest_point)
|
||||
{
|
||||
const Vec<Dim<L>, double> v = (get_b(line) - get_a(line)).template cast<double>();
|
||||
const Vec<Dim<L>, double> va = (point - get_a(line)).template cast<double>();
|
||||
const double l2 = v.squaredNorm(); // avoid a sqrt
|
||||
using VecType = Vec<Dim<L>, double>;
|
||||
const VecType v = (get_b(line) - get_a(line)).template cast<double>();
|
||||
const VecType va = (point - get_a(line)).template cast<double>();
|
||||
const double l2 = v.squaredNorm();
|
||||
if (l2 == 0.0) {
|
||||
// a == b case
|
||||
*nearest_point = get_a(line);
|
||||
@ -53,19 +54,20 @@ double distance_to_squared(const L &line, const Vec<Dim<L>, Scalar<L>> &point, V
|
||||
// Consider the line extending the segment, parameterized as a + t (b - a).
|
||||
// We find projection of this point onto the line.
|
||||
// It falls where t = [(this-a) . (b-a)] / |b-a|^2
|
||||
const double t = va.dot(v) / l2;
|
||||
const double t = va.dot(v);
|
||||
if (t <= 0.0) {
|
||||
// beyond the 'a' end of the segment
|
||||
*nearest_point = get_a(line);
|
||||
return va.squaredNorm();
|
||||
} else if (t >= 1.0) {
|
||||
} else if (t >= l2) {
|
||||
// beyond the 'b' end of the segment
|
||||
*nearest_point = get_b(line);
|
||||
return (point - get_b(line)).template cast<double>().squaredNorm();
|
||||
}
|
||||
|
||||
*nearest_point = (get_a(line).template cast<double>() + t * v).template cast<Scalar<L>>();
|
||||
return (t * v - va).squaredNorm();
|
||||
const VecType w = ((t / l2) * v).eval();
|
||||
*nearest_point = (get_a(line).template cast<double>() + w).template cast<Scalar<L>>();
|
||||
return (w - va).squaredNorm();
|
||||
}
|
||||
|
||||
// Distance to the closest point of line.
|
||||
|
@ -103,10 +103,10 @@ bool MultiPoint::remove_duplicate_points()
|
||||
return false;
|
||||
}
|
||||
|
||||
Points MultiPoint::_douglas_peucker(const Points &pts, const double tolerance)
|
||||
Points MultiPoint::douglas_peucker(const Points &pts, const double tolerance)
|
||||
{
|
||||
Points result_pts;
|
||||
double tolerance_sq = tolerance * tolerance;
|
||||
auto tolerance_sq = int64_t(sqr(tolerance));
|
||||
if (! pts.empty()) {
|
||||
const Point *anchor = &pts.front();
|
||||
size_t anchor_idx = 0;
|
||||
@ -120,14 +120,40 @@ Points MultiPoint::_douglas_peucker(const Points &pts, const double tolerance)
|
||||
dpStack.reserve(pts.size());
|
||||
dpStack.emplace_back(floater_idx);
|
||||
for (;;) {
|
||||
double max_dist_sq = 0.0;
|
||||
size_t furthest_idx = anchor_idx;
|
||||
int64_t max_dist_sq = 0;
|
||||
size_t furthest_idx = anchor_idx;
|
||||
// find point furthest from line seg created by (anchor, floater) and note it
|
||||
for (size_t i = anchor_idx + 1; i < floater_idx; ++ i) {
|
||||
double dist_sq = Line::distance_to_squared(pts[i], *anchor, *floater);
|
||||
if (dist_sq > max_dist_sq) {
|
||||
max_dist_sq = dist_sq;
|
||||
furthest_idx = i;
|
||||
{
|
||||
const Point a = *anchor;
|
||||
const Point f = *floater;
|
||||
const Vec2i64 v = (f - a).cast<int64_t>();
|
||||
if (const int64_t l2 = v.squaredNorm(); l2 == 0) {
|
||||
for (size_t i = anchor_idx + 1; i < floater_idx; ++ i)
|
||||
if (int64_t dist_sq = (pts[i] - a).cast<int64_t>().squaredNorm(); dist_sq > max_dist_sq) {
|
||||
max_dist_sq = dist_sq;
|
||||
furthest_idx = i;
|
||||
}
|
||||
} else {
|
||||
const double dl2 = double(l2);
|
||||
const Vec2d dv = v.cast<double>();
|
||||
for (size_t i = anchor_idx + 1; i < floater_idx; ++ i) {
|
||||
const Point p = pts[i];
|
||||
const Vec2i64 va = (p - a).template cast<int64_t>();
|
||||
const int64_t t = va.dot(v);
|
||||
int64_t dist_sq;
|
||||
if (t <= 0) {
|
||||
dist_sq = va.squaredNorm();
|
||||
} else if (t >= l2) {
|
||||
dist_sq = (p - f).cast<int64_t>().squaredNorm();
|
||||
} else {
|
||||
const Vec2i64 w = ((double(t) / dl2) * dv).cast<int64_t>();
|
||||
dist_sq = (w - va).squaredNorm();
|
||||
}
|
||||
if (dist_sq > max_dist_sq) {
|
||||
max_dist_sq = dist_sq;
|
||||
furthest_idx = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// remove point if less than tolerance
|
||||
|
@ -81,7 +81,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
static Points _douglas_peucker(const Points &points, const double tolerance);
|
||||
static Points douglas_peucker(const Points &points, const double tolerance);
|
||||
static Points visivalingam(const Points& pts, const double& tolerance);
|
||||
|
||||
inline auto begin() { return points.begin(); }
|
||||
|
@ -1644,9 +1644,9 @@ namespace client
|
||||
}
|
||||
if (! evaluated) {
|
||||
// Clamp x into the table range with EPSILON.
|
||||
if (x > table.table.front().x - EPSILON)
|
||||
if (double x0 = table.table.front().x; x > x0 - EPSILON && x < x0)
|
||||
out.set_d(table.table.front().y);
|
||||
else if (x < table.table.back().x + EPSILON)
|
||||
else if (double x1 = table.table.back().x; x > x1 && x < x1 + EPSILON)
|
||||
out.set_d(table.table.back().y);
|
||||
else
|
||||
// The value is really outside the table range.
|
||||
|
@ -96,7 +96,7 @@ bool Polygon::make_clockwise()
|
||||
void Polygon::douglas_peucker(double tolerance)
|
||||
{
|
||||
this->points.push_back(this->points.front());
|
||||
Points p = MultiPoint::_douglas_peucker(this->points, tolerance);
|
||||
Points p = MultiPoint::douglas_peucker(this->points, tolerance);
|
||||
p.pop_back();
|
||||
this->points = std::move(p);
|
||||
}
|
||||
@ -110,7 +110,7 @@ Polygons Polygon::simplify(double tolerance) const
|
||||
// on the whole polygon
|
||||
Points points = this->points;
|
||||
points.push_back(points.front());
|
||||
Polygon p(MultiPoint::_douglas_peucker(points, tolerance));
|
||||
Polygon p(MultiPoint::douglas_peucker(points, tolerance));
|
||||
p.points.pop_back();
|
||||
|
||||
Polygons pp;
|
||||
@ -577,23 +577,40 @@ void remove_collinear(Polygons &polys)
|
||||
remove_collinear(poly);
|
||||
}
|
||||
|
||||
Polygons polygons_simplify(const Polygons &source_polygons, double tolerance)
|
||||
static inline void simplify_polygon_impl(const Points &points, double tolerance, bool strictly_simple, Polygons &out)
|
||||
{
|
||||
Points simplified = MultiPoint::douglas_peucker(points, tolerance);
|
||||
// then remove the last (repeated) point.
|
||||
simplified.pop_back();
|
||||
// Simplify the decimated contour by ClipperLib.
|
||||
bool ccw = ClipperLib::Area(simplified) > 0.;
|
||||
for (Points& path : ClipperLib::SimplifyPolygons(ClipperUtils::SinglePathProvider(simplified), ClipperLib::pftNonZero, strictly_simple)) {
|
||||
if (!ccw)
|
||||
// ClipperLib likely reoriented negative area contours to become positive. Reverse holes back to CW.
|
||||
std::reverse(path.begin(), path.end());
|
||||
out.emplace_back(std::move(path));
|
||||
}
|
||||
}
|
||||
|
||||
Polygons polygons_simplify(Polygons &&source_polygons, double tolerance, bool strictly_simple /* = true */)
|
||||
{
|
||||
Polygons out;
|
||||
out.reserve(source_polygons.size());
|
||||
for (Polygon &source_polygon : source_polygons) {
|
||||
// Run Douglas / Peucker simplification algorithm on an open polyline (by repeating the first point at the end of the polyline),
|
||||
source_polygon.points.emplace_back(source_polygon.points.front());
|
||||
simplify_polygon_impl(source_polygon.points, tolerance, strictly_simple, out);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
Polygons polygons_simplify(const Polygons &source_polygons, double tolerance, bool strictly_simple /* = true */)
|
||||
{
|
||||
Polygons out;
|
||||
out.reserve(source_polygons.size());
|
||||
for (const Polygon &source_polygon : source_polygons) {
|
||||
// Run Douglas / Peucker simplification algorithm on an open polyline (by repeating the first point at the end of the polyline),
|
||||
Points simplified = MultiPoint::_douglas_peucker(to_polyline(source_polygon).points, tolerance);
|
||||
// then remove the last (repeated) point.
|
||||
simplified.pop_back();
|
||||
// Simplify the decimated contour by ClipperLib.
|
||||
bool ccw = ClipperLib::Area(simplified) > 0.;
|
||||
for (Points &path : ClipperLib::SimplifyPolygons(ClipperUtils::SinglePathProvider(simplified), ClipperLib::pftNonZero)) {
|
||||
if (! ccw)
|
||||
// ClipperLib likely reoriented negative area contours to become positive. Reverse holes back to CW.
|
||||
std::reverse(path.begin(), path.end());
|
||||
out.emplace_back(std::move(path));
|
||||
}
|
||||
simplify_polygon_impl(to_polyline(source_polygon).points, tolerance, strictly_simple, out);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
@ -149,7 +149,8 @@ inline void polygons_append(Polygons &dst, Polygons &&src)
|
||||
}
|
||||
}
|
||||
|
||||
Polygons polygons_simplify(const Polygons &polys, double tolerance);
|
||||
Polygons polygons_simplify(Polygons &&polys, double tolerance, bool strictly_simple = true);
|
||||
Polygons polygons_simplify(const Polygons &polys, double tolerance, bool strictly_simple = true);
|
||||
|
||||
inline void polygons_rotate(Polygons &polys, double angle)
|
||||
{
|
||||
|
@ -110,7 +110,7 @@ Points Polyline::equally_spaced_points(double distance) const
|
||||
|
||||
void Polyline::simplify(double tolerance)
|
||||
{
|
||||
this->points = MultiPoint::_douglas_peucker(this->points, tolerance);
|
||||
this->points = MultiPoint::douglas_peucker(this->points, tolerance);
|
||||
}
|
||||
|
||||
#if 0
|
||||
|
@ -444,7 +444,8 @@ static std::vector<std::string> s_Preset_print_options {
|
||||
"support_material_interface_pattern", "support_material_interface_spacing", "support_material_interface_contact_loops",
|
||||
"support_material_contact_distance", "support_material_bottom_contact_distance",
|
||||
"support_material_buildplate_only",
|
||||
"support_tree_angle", "support_tree_angle_slow", "support_tree_branch_diameter", "support_tree_branch_diameter_angle", "support_tree_top_rate", "support_tree_branch_distance", "support_tree_tip_diameter",
|
||||
"support_tree_angle", "support_tree_angle_slow", "support_tree_branch_diameter", "support_tree_branch_diameter_angle", "support_tree_branch_diameter_double_wall",
|
||||
"support_tree_top_rate", "support_tree_branch_distance", "support_tree_tip_diameter",
|
||||
"dont_support_bridges", "thick_bridges", "notes", "complete_objects", "extruder_clearance_radius",
|
||||
"extruder_clearance_height", "gcode_comments", "gcode_label_objects", "output_filename_format", "post_process", "gcode_substitutions", "perimeter_extruder",
|
||||
"infill_extruder", "solid_infill_extruder", "support_material_extruder", "support_material_interface_extruder",
|
||||
|
@ -8,7 +8,6 @@
|
||||
#include "Geometry/ConvexHull.hpp"
|
||||
#include "I18N.hpp"
|
||||
#include "ShortestPath.hpp"
|
||||
#include "SupportMaterial.hpp"
|
||||
#include "Thread.hpp"
|
||||
#include "GCode.hpp"
|
||||
#include "GCode/WipeTower.hpp"
|
||||
|
@ -2939,6 +2939,18 @@ void PrintConfigDef::init_fff_params()
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloat(5));
|
||||
|
||||
def = this->add("support_tree_branch_diameter_double_wall", coFloat);
|
||||
def->label = L("Branch Diameter with double walls");
|
||||
def->category = L("Support material");
|
||||
// TRN PrintSettings: "Organic supports" > "Branch Diameter"
|
||||
def->tooltip = L("Branches with area larger than the area of a circle of this diameter will be printed with double walls for stability. "
|
||||
"Set this value to zero for no double walls.");
|
||||
def->sidetext = L("mm");
|
||||
def->min = 0;
|
||||
def->max = 100.f;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloat(3));
|
||||
|
||||
// Tree Support Branch Distance
|
||||
// How far apart the branches need to be when they touch the model. Making this distance small will cause
|
||||
// the tree support to touch the model at more points, causing better overhang but making support harder to remove.
|
||||
|
@ -554,6 +554,7 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionFloat, support_tree_angle_slow))
|
||||
((ConfigOptionFloat, support_tree_branch_diameter))
|
||||
((ConfigOptionFloat, support_tree_branch_diameter_angle))
|
||||
((ConfigOptionFloat, support_tree_branch_diameter_double_wall))
|
||||
((ConfigOptionPercent, support_tree_top_rate))
|
||||
((ConfigOptionFloat, support_tree_branch_distance))
|
||||
((ConfigOptionFloat, support_tree_tip_diameter))
|
||||
|
@ -17,8 +17,8 @@
|
||||
#include "MutablePolygon.hpp"
|
||||
#include "PrintBase.hpp"
|
||||
#include "PrintConfig.hpp"
|
||||
#include "SupportMaterial.hpp"
|
||||
#include "TreeSupport.hpp"
|
||||
#include "Support/SupportMaterial.hpp"
|
||||
#include "Support/TreeSupport.hpp"
|
||||
#include "Surface.hpp"
|
||||
#include "Slicing.hpp"
|
||||
#include "Tesselate.hpp"
|
||||
@ -27,7 +27,7 @@
|
||||
#include "Fill/FillAdaptive.hpp"
|
||||
#include "Fill/FillLightning.hpp"
|
||||
#include "Format/STL.hpp"
|
||||
#include "SupportMaterial.hpp"
|
||||
#include "Support/SupportMaterial.hpp"
|
||||
#include "SupportSpotsGenerator.hpp"
|
||||
#include "TriangleSelectorWrapper.hpp"
|
||||
#include "format.hpp"
|
||||
@ -712,6 +712,7 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||
|| opt_key == "support_tree_angle_slow"
|
||||
|| opt_key == "support_tree_branch_diameter"
|
||||
|| opt_key == "support_tree_branch_diameter_angle"
|
||||
|| opt_key == "support_tree_branch_diameter_double_wall"
|
||||
|| opt_key == "support_tree_top_rate"
|
||||
|| opt_key == "support_tree_branch_distance"
|
||||
|| opt_key == "support_tree_tip_diameter"
|
||||
|
1999
src/libslic3r/Support/SupportCommon.cpp
Normal file
1999
src/libslic3r/Support/SupportCommon.cpp
Normal file
File diff suppressed because it is too large
Load Diff
155
src/libslic3r/Support/SupportCommon.hpp
Normal file
155
src/libslic3r/Support/SupportCommon.hpp
Normal file
@ -0,0 +1,155 @@
|
||||
#ifndef slic3r_SupportCommon_hpp_
|
||||
#define slic3r_SupportCommon_hpp_
|
||||
|
||||
#include "../Polygon.hpp"
|
||||
#include "SupportLayer.hpp"
|
||||
#include "SupportParameters.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class PrintObject;
|
||||
class SupportLayer;
|
||||
|
||||
namespace FFFSupport {
|
||||
|
||||
// Remove bridges from support contact areas.
|
||||
// To be called if PrintObjectConfig::dont_support_bridges.
|
||||
void remove_bridges_from_contacts(
|
||||
const PrintConfig &print_config,
|
||||
const Layer &lower_layer,
|
||||
const LayerRegion &layerm,
|
||||
float fw,
|
||||
Polygons &contact_polygons);
|
||||
|
||||
// Turn some of the base layers into base interface layers.
|
||||
// For soluble interfaces with non-soluble bases, print maximum two first interface layers with the base
|
||||
// extruder to improve adhesion of the soluble filament to the base.
|
||||
// For Organic supports, merge top_interface_layers & top_base_interface_layers with the interfaces
|
||||
// produced by this function.
|
||||
std::pair<SupportGeneratorLayersPtr, SupportGeneratorLayersPtr> generate_interface_layers(
|
||||
const PrintObjectConfig &config,
|
||||
const SupportParameters &support_params,
|
||||
const SupportGeneratorLayersPtr &bottom_contacts,
|
||||
const SupportGeneratorLayersPtr &top_contacts,
|
||||
// Input / output, will be merged with output
|
||||
SupportGeneratorLayersPtr &top_interface_layers,
|
||||
SupportGeneratorLayersPtr &top_base_interface_layers,
|
||||
// Input, will be trimmed with the newly created interface layers.
|
||||
SupportGeneratorLayersPtr &intermediate_layers,
|
||||
SupportGeneratorLayerStorage &layer_storage);
|
||||
|
||||
// Generate raft layers, also expand the 1st support layer
|
||||
// in case there is no raft layer to improve support adhesion.
|
||||
SupportGeneratorLayersPtr generate_raft_base(
|
||||
const PrintObject &object,
|
||||
const SupportParameters &support_params,
|
||||
const SlicingParameters &slicing_params,
|
||||
const SupportGeneratorLayersPtr &top_contacts,
|
||||
const SupportGeneratorLayersPtr &interface_layers,
|
||||
const SupportGeneratorLayersPtr &base_interface_layers,
|
||||
const SupportGeneratorLayersPtr &base_layers,
|
||||
SupportGeneratorLayerStorage &layer_storage);
|
||||
|
||||
// returns sorted layers
|
||||
SupportGeneratorLayersPtr generate_support_layers(
|
||||
PrintObject &object,
|
||||
const SupportGeneratorLayersPtr &raft_layers,
|
||||
const SupportGeneratorLayersPtr &bottom_contacts,
|
||||
const SupportGeneratorLayersPtr &top_contacts,
|
||||
const SupportGeneratorLayersPtr &intermediate_layers,
|
||||
const SupportGeneratorLayersPtr &interface_layers,
|
||||
const SupportGeneratorLayersPtr &base_interface_layers);
|
||||
|
||||
// Produce the support G-code.
|
||||
// Used by both classic and tree supports.
|
||||
void generate_support_toolpaths(
|
||||
SupportLayerPtrs &support_layers,
|
||||
const PrintObjectConfig &config,
|
||||
const SupportParameters &support_params,
|
||||
const SlicingParameters &slicing_params,
|
||||
const SupportGeneratorLayersPtr &raft_layers,
|
||||
const SupportGeneratorLayersPtr &bottom_contacts,
|
||||
const SupportGeneratorLayersPtr &top_contacts,
|
||||
const SupportGeneratorLayersPtr &intermediate_layers,
|
||||
const SupportGeneratorLayersPtr &interface_layers,
|
||||
const SupportGeneratorLayersPtr &base_interface_layers);
|
||||
|
||||
// FN_HIGHER_EQUAL: the provided object pointer has a Z value >= of an internal threshold.
|
||||
// Find the first item with Z value >= of an internal threshold of fn_higher_equal.
|
||||
// If no vec item with Z value >= of an internal threshold of fn_higher_equal is found, return vec.size()
|
||||
// If the initial idx is size_t(-1), then use binary search.
|
||||
// Otherwise search linearly upwards.
|
||||
template<typename IteratorType, typename IndexType, typename FN_HIGHER_EQUAL>
|
||||
IndexType idx_higher_or_equal(IteratorType begin, IteratorType end, IndexType idx, FN_HIGHER_EQUAL fn_higher_equal)
|
||||
{
|
||||
auto size = int(end - begin);
|
||||
if (size == 0) {
|
||||
idx = 0;
|
||||
} else if (idx == IndexType(-1)) {
|
||||
// First of the batch of layers per thread pool invocation. Use binary search.
|
||||
int idx_low = 0;
|
||||
int idx_high = std::max(0, size - 1);
|
||||
while (idx_low + 1 < idx_high) {
|
||||
int idx_mid = (idx_low + idx_high) / 2;
|
||||
if (fn_higher_equal(begin[idx_mid]))
|
||||
idx_high = idx_mid;
|
||||
else
|
||||
idx_low = idx_mid;
|
||||
}
|
||||
idx = fn_higher_equal(begin[idx_low]) ? idx_low :
|
||||
(fn_higher_equal(begin[idx_high]) ? idx_high : size);
|
||||
} else {
|
||||
// For the other layers of this batch of layers, search incrementally, which is cheaper than the binary search.
|
||||
while (int(idx) < size && ! fn_higher_equal(begin[idx]))
|
||||
++ idx;
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
template<typename T, typename IndexType, typename FN_HIGHER_EQUAL>
|
||||
IndexType idx_higher_or_equal(const std::vector<T>& vec, IndexType idx, FN_HIGHER_EQUAL fn_higher_equal)
|
||||
{
|
||||
return idx_higher_or_equal(vec.begin(), vec.end(), idx, fn_higher_equal);
|
||||
}
|
||||
|
||||
// FN_LOWER_EQUAL: the provided object pointer has a Z value <= of an internal threshold.
|
||||
// Find the first item with Z value <= of an internal threshold of fn_lower_equal.
|
||||
// If no vec item with Z value <= of an internal threshold of fn_lower_equal is found, return -1.
|
||||
// If the initial idx is < -1, then use binary search.
|
||||
// Otherwise search linearly downwards.
|
||||
template<typename IT, typename FN_LOWER_EQUAL>
|
||||
int idx_lower_or_equal(IT begin, IT end, int idx, FN_LOWER_EQUAL fn_lower_equal)
|
||||
{
|
||||
auto size = int(end - begin);
|
||||
if (size == 0) {
|
||||
idx = -1;
|
||||
} else if (idx < -1) {
|
||||
// First of the batch of layers per thread pool invocation. Use binary search.
|
||||
int idx_low = 0;
|
||||
int idx_high = std::max(0, size - 1);
|
||||
while (idx_low + 1 < idx_high) {
|
||||
int idx_mid = (idx_low + idx_high) / 2;
|
||||
if (fn_lower_equal(begin[idx_mid]))
|
||||
idx_low = idx_mid;
|
||||
else
|
||||
idx_high = idx_mid;
|
||||
}
|
||||
idx = fn_lower_equal(begin[idx_high]) ? idx_high :
|
||||
(fn_lower_equal(begin[idx_low ]) ? idx_low : -1);
|
||||
} else {
|
||||
// For the other layers of this batch of layers, search incrementally, which is cheaper than the binary search.
|
||||
while (idx >= 0 && ! fn_lower_equal(begin[idx]))
|
||||
-- idx;
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
template<typename T, typename FN_LOWER_EQUAL>
|
||||
int idx_lower_or_equal(const std::vector<T*> &vec, int idx, FN_LOWER_EQUAL fn_lower_equal)
|
||||
{
|
||||
return idx_lower_or_equal(vec.begin(), vec.end(), idx, fn_lower_equal);
|
||||
}
|
||||
|
||||
} // namespace FFFSupport
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_SupportCommon_hpp_ */
|
108
src/libslic3r/Support/SupportDebug.cpp
Normal file
108
src/libslic3r/Support/SupportDebug.cpp
Normal file
@ -0,0 +1,108 @@
|
||||
#if 1 //#ifdef SLIC3R_DEBUG
|
||||
|
||||
#include "../ClipperUtils.hpp"
|
||||
#include "../SVG.hpp"
|
||||
#include "../Layer.hpp"
|
||||
|
||||
#include "SupportLayer.hpp"
|
||||
|
||||
namespace Slic3r::FFFSupport {
|
||||
|
||||
const char* support_surface_type_to_color_name(const SupporLayerType surface_type)
|
||||
{
|
||||
switch (surface_type) {
|
||||
case SupporLayerType::TopContact: return "rgb(255,0,0)"; // "red";
|
||||
case SupporLayerType::TopInterface: return "rgb(0,255,0)"; // "green";
|
||||
case SupporLayerType::Base: return "rgb(0,0,255)"; // "blue";
|
||||
case SupporLayerType::BottomInterface:return "rgb(255,255,128)"; // yellow
|
||||
case SupporLayerType::BottomContact: return "rgb(255,0,255)"; // magenta
|
||||
case SupporLayerType::RaftInterface: return "rgb(0,255,255)";
|
||||
case SupporLayerType::RaftBase: return "rgb(128,128,128)";
|
||||
case SupporLayerType::Unknown: return "rgb(128,0,0)"; // maroon
|
||||
default: return "rgb(64,64,64)";
|
||||
};
|
||||
}
|
||||
|
||||
Point export_support_surface_type_legend_to_svg_box_size()
|
||||
{
|
||||
return Point(scale_(1.+10.*8.), scale_(3.));
|
||||
}
|
||||
|
||||
void export_support_surface_type_legend_to_svg(SVG &svg, const Point &pos)
|
||||
{
|
||||
// 1st row
|
||||
coord_t pos_x0 = pos(0) + scale_(1.);
|
||||
coord_t pos_x = pos_x0;
|
||||
coord_t pos_y = pos(1) + scale_(1.5);
|
||||
coord_t step_x = scale_(10.);
|
||||
svg.draw_legend(Point(pos_x, pos_y), "top contact" , support_surface_type_to_color_name(SupporLayerType::TopContact));
|
||||
pos_x += step_x;
|
||||
svg.draw_legend(Point(pos_x, pos_y), "top iface" , support_surface_type_to_color_name(SupporLayerType::TopInterface));
|
||||
pos_x += step_x;
|
||||
svg.draw_legend(Point(pos_x, pos_y), "base" , support_surface_type_to_color_name(SupporLayerType::Base));
|
||||
pos_x += step_x;
|
||||
svg.draw_legend(Point(pos_x, pos_y), "bottom iface" , support_surface_type_to_color_name(SupporLayerType::BottomInterface));
|
||||
pos_x += step_x;
|
||||
svg.draw_legend(Point(pos_x, pos_y), "bottom contact" , support_surface_type_to_color_name(SupporLayerType::BottomContact));
|
||||
// 2nd row
|
||||
pos_x = pos_x0;
|
||||
pos_y = pos(1)+scale_(2.8);
|
||||
svg.draw_legend(Point(pos_x, pos_y), "raft interface" , support_surface_type_to_color_name(SupporLayerType::RaftInterface));
|
||||
pos_x += step_x;
|
||||
svg.draw_legend(Point(pos_x, pos_y), "raft base" , support_surface_type_to_color_name(SupporLayerType::RaftBase));
|
||||
pos_x += step_x;
|
||||
svg.draw_legend(Point(pos_x, pos_y), "unknown" , support_surface_type_to_color_name(SupporLayerType::Unknown));
|
||||
pos_x += step_x;
|
||||
svg.draw_legend(Point(pos_x, pos_y), "intermediate" , support_surface_type_to_color_name(SupporLayerType::Intermediate));
|
||||
}
|
||||
|
||||
void export_print_z_polygons_to_svg(const char *path, SupportGeneratorLayer ** const layers, int n_layers)
|
||||
{
|
||||
BoundingBox bbox;
|
||||
for (int i = 0; i < n_layers; ++ i)
|
||||
bbox.merge(get_extents(layers[i]->polygons));
|
||||
Point legend_size = export_support_surface_type_legend_to_svg_box_size();
|
||||
Point legend_pos(bbox.min(0), bbox.max(1));
|
||||
bbox.merge(Point(std::max(bbox.min(0) + legend_size(0), bbox.max(0)), bbox.max(1) + legend_size(1)));
|
||||
SVG svg(path, bbox);
|
||||
const float transparency = 0.5f;
|
||||
for (int i = 0; i < n_layers; ++ i)
|
||||
svg.draw(union_ex(layers[i]->polygons), support_surface_type_to_color_name(layers[i]->layer_type), transparency);
|
||||
for (int i = 0; i < n_layers; ++ i)
|
||||
svg.draw(to_polylines(layers[i]->polygons), support_surface_type_to_color_name(layers[i]->layer_type));
|
||||
export_support_surface_type_legend_to_svg(svg, legend_pos);
|
||||
svg.Close();
|
||||
}
|
||||
|
||||
void export_print_z_polygons_and_extrusions_to_svg(
|
||||
const char *path,
|
||||
SupportGeneratorLayer ** const layers,
|
||||
int n_layers,
|
||||
SupportLayer &support_layer)
|
||||
{
|
||||
BoundingBox bbox;
|
||||
for (int i = 0; i < n_layers; ++ i)
|
||||
bbox.merge(get_extents(layers[i]->polygons));
|
||||
Point legend_size = export_support_surface_type_legend_to_svg_box_size();
|
||||
Point legend_pos(bbox.min(0), bbox.max(1));
|
||||
bbox.merge(Point(std::max(bbox.min(0) + legend_size(0), bbox.max(0)), bbox.max(1) + legend_size(1)));
|
||||
SVG svg(path, bbox);
|
||||
const float transparency = 0.5f;
|
||||
for (int i = 0; i < n_layers; ++ i)
|
||||
svg.draw(union_ex(layers[i]->polygons), support_surface_type_to_color_name(layers[i]->layer_type), transparency);
|
||||
for (int i = 0; i < n_layers; ++ i)
|
||||
svg.draw(to_polylines(layers[i]->polygons), support_surface_type_to_color_name(layers[i]->layer_type));
|
||||
|
||||
Polygons polygons_support, polygons_interface;
|
||||
support_layer.support_fills.polygons_covered_by_width(polygons_support, float(SCALED_EPSILON));
|
||||
// support_layer.support_interface_fills.polygons_covered_by_width(polygons_interface, SCALED_EPSILON);
|
||||
svg.draw(union_ex(polygons_support), "brown");
|
||||
svg.draw(union_ex(polygons_interface), "black");
|
||||
|
||||
export_support_surface_type_legend_to_svg(svg, legend_pos);
|
||||
svg.Close();
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* SLIC3R_DEBUG */
|
18
src/libslic3r/Support/SupportDebug.hpp
Normal file
18
src/libslic3r/Support/SupportDebug.hpp
Normal file
@ -0,0 +1,18 @@
|
||||
#ifndef slic3r_SupportCommon_hpp_
|
||||
#define slic3r_SupportCommon_hpp_
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class SupportGeneratorLayer;
|
||||
class SupportLayer;
|
||||
|
||||
namespace FFFSupport {
|
||||
|
||||
void export_print_z_polygons_to_svg(const char *path, SupportGeneratorLayer ** const layers, size_t n_layers);
|
||||
void export_print_z_polygons_and_extrusions_to_svg(const char *path, SupportGeneratorLayer ** const layers, size_t n_layers, SupportLayer& support_layer);
|
||||
|
||||
} // namespace FFFSupport
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_SupportCommon_hpp_ */
|
146
src/libslic3r/Support/SupportLayer.hpp
Normal file
146
src/libslic3r/Support/SupportLayer.hpp
Normal file
@ -0,0 +1,146 @@
|
||||
#ifndef slic3r_SupportLayer_hpp_
|
||||
#define slic3r_SupportLayer_hpp_
|
||||
|
||||
#include <oneapi/tbb/scalable_allocator.h>
|
||||
#include <oneapi/tbb/spin_mutex.h>
|
||||
// for Slic3r::deque
|
||||
#include "../libslic3r.h"
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "Polygon.hpp"
|
||||
|
||||
namespace Slic3r::FFFSupport {
|
||||
|
||||
// Support layer type to be used by SupportGeneratorLayer. This type carries a much more detailed information
|
||||
// about the support layer type than the final support layers stored in a PrintObject.
|
||||
enum class SupporLayerType {
|
||||
Unknown = 0,
|
||||
// Ratft base layer, to be printed with the support material.
|
||||
RaftBase,
|
||||
// Raft interface layer, to be printed with the support interface material.
|
||||
RaftInterface,
|
||||
// Bottom contact layer placed over a top surface of an object. To be printed with a support interface material.
|
||||
BottomContact,
|
||||
// Dense interface layer, to be printed with the support interface material.
|
||||
// This layer is separated from an object by an BottomContact layer.
|
||||
BottomInterface,
|
||||
// Sparse base support layer, to be printed with a support material.
|
||||
Base,
|
||||
// Dense interface layer, to be printed with the support interface material.
|
||||
// This layer is separated from an object with TopContact layer.
|
||||
TopInterface,
|
||||
// Top contact layer directly supporting an overhang. To be printed with a support interface material.
|
||||
TopContact,
|
||||
// Some undecided type yet. It will turn into Base first, then it may turn into BottomInterface or TopInterface.
|
||||
Intermediate,
|
||||
};
|
||||
|
||||
// A support layer type used internally by the SupportMaterial class. This class carries a much more detailed
|
||||
// information about the support layer than the layers stored in the PrintObject, mainly
|
||||
// the SupportGeneratorLayer is aware of the bridging flow and the interface gaps between the object and the support.
|
||||
class SupportGeneratorLayer
|
||||
{
|
||||
public:
|
||||
void reset() {
|
||||
*this = SupportGeneratorLayer();
|
||||
}
|
||||
|
||||
bool operator==(const SupportGeneratorLayer &layer2) const {
|
||||
return print_z == layer2.print_z && height == layer2.height && bridging == layer2.bridging;
|
||||
}
|
||||
|
||||
// Order the layers by lexicographically by an increasing print_z and a decreasing layer height.
|
||||
bool operator<(const SupportGeneratorLayer &layer2) const {
|
||||
if (print_z < layer2.print_z) {
|
||||
return true;
|
||||
} else if (print_z == layer2.print_z) {
|
||||
if (height > layer2.height)
|
||||
return true;
|
||||
else if (height == layer2.height) {
|
||||
// Bridging layers first.
|
||||
return bridging && ! layer2.bridging;
|
||||
} else
|
||||
return false;
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
|
||||
void merge(SupportGeneratorLayer &&rhs) {
|
||||
// The union_() does not support move semantic yet, but maybe one day it will.
|
||||
this->polygons = union_(this->polygons, std::move(rhs.polygons));
|
||||
auto merge = [](std::unique_ptr<Polygons> &dst, std::unique_ptr<Polygons> &src) {
|
||||
if (! dst || dst->empty())
|
||||
dst = std::move(src);
|
||||
else if (src && ! src->empty())
|
||||
*dst = union_(*dst, std::move(*src));
|
||||
};
|
||||
merge(this->contact_polygons, rhs.contact_polygons);
|
||||
merge(this->overhang_polygons, rhs.overhang_polygons);
|
||||
merge(this->enforcer_polygons, rhs.enforcer_polygons);
|
||||
rhs.reset();
|
||||
}
|
||||
|
||||
// For the bridging flow, bottom_print_z will be above bottom_z to account for the vertical separation.
|
||||
// For the non-bridging flow, bottom_print_z will be equal to bottom_z.
|
||||
coordf_t bottom_print_z() const { return print_z - height; }
|
||||
|
||||
// To sort the extremes of top / bottom interface layers.
|
||||
coordf_t extreme_z() const { return (this->layer_type == SupporLayerType::TopContact) ? this->bottom_z : this->print_z; }
|
||||
|
||||
SupporLayerType layer_type { SupporLayerType::Unknown };
|
||||
// Z used for printing, in unscaled coordinates.
|
||||
coordf_t print_z { 0 };
|
||||
// Bottom Z of this layer. For soluble layers, bottom_z + height = print_z,
|
||||
// otherwise bottom_z + gap + height = print_z.
|
||||
coordf_t bottom_z { 0 };
|
||||
// Layer height in unscaled coordinates.
|
||||
coordf_t height { 0 };
|
||||
// Index of a PrintObject layer_id supported by this layer. This will be set for top contact layers.
|
||||
// If this is not a contact layer, it will be set to size_t(-1).
|
||||
size_t idx_object_layer_above { size_t(-1) };
|
||||
// Index of a PrintObject layer_id, which supports this layer. This will be set for bottom contact layers.
|
||||
// If this is not a contact layer, it will be set to size_t(-1).
|
||||
size_t idx_object_layer_below { size_t(-1) };
|
||||
// Use a bridging flow when printing this support layer.
|
||||
bool bridging { false };
|
||||
|
||||
// Polygons to be filled by the support pattern.
|
||||
Polygons polygons;
|
||||
// Currently for the contact layers only.
|
||||
std::unique_ptr<Polygons> contact_polygons;
|
||||
std::unique_ptr<Polygons> overhang_polygons;
|
||||
// Enforcers need to be propagated independently in case the "support on build plate only" option is enabled.
|
||||
std::unique_ptr<Polygons> enforcer_polygons;
|
||||
};
|
||||
|
||||
// Layers are allocated and owned by a deque. Once a layer is allocated, it is maintained
|
||||
// up to the end of a generate() method. The layer storage may be replaced by an allocator class in the future,
|
||||
// which would allocate layers by multiple chunks.
|
||||
class SupportGeneratorLayerStorage {
|
||||
public:
|
||||
SupportGeneratorLayer& allocate_unguarded(SupporLayerType layer_type) {
|
||||
m_storage.emplace_back();
|
||||
m_storage.back().layer_type = layer_type;
|
||||
return m_storage.back();
|
||||
}
|
||||
|
||||
SupportGeneratorLayer& allocate(SupporLayerType layer_type)
|
||||
{
|
||||
m_mutex.lock();
|
||||
m_storage.emplace_back();
|
||||
SupportGeneratorLayer *layer_new = &m_storage.back();
|
||||
m_mutex.unlock();
|
||||
layer_new->layer_type = layer_type;
|
||||
return *layer_new;
|
||||
}
|
||||
|
||||
private:
|
||||
template<typename BaseType>
|
||||
using Allocator = tbb::scalable_allocator<BaseType>;
|
||||
Slic3r::deque<SupportGeneratorLayer, Allocator<SupportGeneratorLayer>> m_storage;
|
||||
tbb::spin_mutex m_mutex;
|
||||
};
|
||||
using SupportGeneratorLayersPtr = std::vector<SupportGeneratorLayer*>;
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_SupportLayer_hpp_ */
|
File diff suppressed because it is too large
Load Diff
101
src/libslic3r/Support/SupportMaterial.hpp
Normal file
101
src/libslic3r/Support/SupportMaterial.hpp
Normal file
@ -0,0 +1,101 @@
|
||||
#ifndef slic3r_SupportMaterial_hpp_
|
||||
#define slic3r_SupportMaterial_hpp_
|
||||
|
||||
#include "../Flow.hpp"
|
||||
#include "../PrintConfig.hpp"
|
||||
#include "../Slicing.hpp"
|
||||
|
||||
#include "SupportLayer.hpp"
|
||||
#include "SupportParameters.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class PrintObject;
|
||||
|
||||
// This class manages raft and supports for a single PrintObject.
|
||||
// Instantiated by Slic3r::Print::Object->_support_material()
|
||||
// This class is instantiated before the slicing starts as Object.pm will query
|
||||
// the parameters of the raft to determine the 1st layer height and thickness.
|
||||
class PrintObjectSupportMaterial
|
||||
{
|
||||
public:
|
||||
PrintObjectSupportMaterial(const PrintObject *object, const SlicingParameters &slicing_params);
|
||||
|
||||
// Is raft enabled?
|
||||
bool has_raft() const { return m_slicing_params.has_raft(); }
|
||||
// Has any support?
|
||||
bool has_support() const { return m_object_config->support_material.value || m_object_config->support_material_enforce_layers; }
|
||||
bool build_plate_only() const { return this->has_support() && m_object_config->support_material_buildplate_only.value; }
|
||||
|
||||
bool synchronize_layers() const { return m_slicing_params.soluble_interface && m_object_config->support_material_synchronize_layers.value; }
|
||||
bool has_contact_loops() const { return m_object_config->support_material_interface_contact_loops.value; }
|
||||
|
||||
// Generate support material for the object.
|
||||
// New support layers will be added to the object,
|
||||
// with extrusion paths and islands filled in for each support layer.
|
||||
void generate(PrintObject &object);
|
||||
|
||||
private:
|
||||
using SupportGeneratorLayersPtr = FFFSupport::SupportGeneratorLayersPtr;
|
||||
using SupportGeneratorLayerStorage = FFFSupport::SupportGeneratorLayerStorage;
|
||||
using SupportParameters = FFFSupport::SupportParameters;
|
||||
|
||||
std::vector<Polygons> buildplate_covered(const PrintObject &object) const;
|
||||
|
||||
// Generate top contact layers supporting overhangs.
|
||||
// For a soluble interface material synchronize the layer heights with the object, otherwise leave the layer height undefined.
|
||||
// If supports over bed surface only are requested, don't generate contact layers over an object.
|
||||
SupportGeneratorLayersPtr top_contact_layers(const PrintObject &object, const std::vector<Polygons> &buildplate_covered, SupportGeneratorLayerStorage &layer_storage) const;
|
||||
|
||||
// Generate bottom contact layers supporting the top contact layers.
|
||||
// For a soluble interface material synchronize the layer heights with the object,
|
||||
// otherwise set the layer height to a bridging flow of a support interface nozzle.
|
||||
SupportGeneratorLayersPtr bottom_contact_layers_and_layer_support_areas(
|
||||
const PrintObject &object, const SupportGeneratorLayersPtr &top_contacts, std::vector<Polygons> &buildplate_covered,
|
||||
SupportGeneratorLayerStorage &layer_storage, std::vector<Polygons> &layer_support_areas) const;
|
||||
|
||||
// Trim the top_contacts layers with the bottom_contacts layers if they overlap, so there would not be enough vertical space for both of them.
|
||||
void trim_top_contacts_by_bottom_contacts(const PrintObject &object, const SupportGeneratorLayersPtr &bottom_contacts, SupportGeneratorLayersPtr &top_contacts) const;
|
||||
|
||||
// Generate raft layers and the intermediate support layers between the bottom contact and top contact surfaces.
|
||||
SupportGeneratorLayersPtr raft_and_intermediate_support_layers(
|
||||
const PrintObject &object,
|
||||
const SupportGeneratorLayersPtr &bottom_contacts,
|
||||
const SupportGeneratorLayersPtr &top_contacts,
|
||||
SupportGeneratorLayerStorage &layer_storage) const;
|
||||
|
||||
// Fill in the base layers with polygons.
|
||||
void generate_base_layers(
|
||||
const PrintObject &object,
|
||||
const SupportGeneratorLayersPtr &bottom_contacts,
|
||||
const SupportGeneratorLayersPtr &top_contacts,
|
||||
SupportGeneratorLayersPtr &intermediate_layers,
|
||||
const std::vector<Polygons> &layer_support_areas) const;
|
||||
|
||||
// Trim support layers by an object to leave a defined gap between
|
||||
// the support volume and the object.
|
||||
void trim_support_layers_by_object(
|
||||
const PrintObject &object,
|
||||
SupportGeneratorLayersPtr &support_layers,
|
||||
const coordf_t gap_extra_above,
|
||||
const coordf_t gap_extra_below,
|
||||
const coordf_t gap_xy) const;
|
||||
|
||||
/*
|
||||
void generate_pillars_shape();
|
||||
void clip_with_shape();
|
||||
*/
|
||||
|
||||
// Following objects are not owned by SupportMaterial class.
|
||||
const PrintConfig *m_print_config;
|
||||
const PrintObjectConfig *m_object_config;
|
||||
// Pre-calculated parameters shared between the object slicer and the support generator,
|
||||
// carrying information on a raft, 1st layer height, 1st object layer height, gap between the raft and object etc.
|
||||
SlicingParameters m_slicing_params;
|
||||
// Various precomputed support parameters to be shared with external functions.
|
||||
SupportParameters m_support_params;
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_SupportMaterial_hpp_ */
|
144
src/libslic3r/Support/SupportParameters.cpp
Normal file
144
src/libslic3r/Support/SupportParameters.cpp
Normal file
@ -0,0 +1,144 @@
|
||||
#include "../Print.hpp"
|
||||
#include "../PrintConfig.hpp"
|
||||
#include "../Slicing.hpp"
|
||||
#include "SupportParameters.hpp"
|
||||
|
||||
namespace Slic3r::FFFSupport {
|
||||
|
||||
SupportParameters::SupportParameters(const PrintObject &object)
|
||||
{
|
||||
const PrintConfig &print_config = object.print()->config();
|
||||
const PrintObjectConfig &object_config = object.config();
|
||||
const SlicingParameters &slicing_params = object.slicing_parameters();
|
||||
|
||||
this->soluble_interface = slicing_params.soluble_interface;
|
||||
this->soluble_interface_non_soluble_base =
|
||||
// Zero z-gap between the overhangs and the support interface.
|
||||
slicing_params.soluble_interface &&
|
||||
// Interface extruder soluble.
|
||||
object_config.support_material_interface_extruder.value > 0 && print_config.filament_soluble.get_at(object_config.support_material_interface_extruder.value - 1) &&
|
||||
// Base extruder: Either "print with active extruder" not soluble.
|
||||
(object_config.support_material_extruder.value == 0 || ! print_config.filament_soluble.get_at(object_config.support_material_extruder.value - 1));
|
||||
|
||||
{
|
||||
int num_top_interface_layers = std::max(0, object_config.support_material_interface_layers.value);
|
||||
int num_bottom_interface_layers = object_config.support_material_bottom_interface_layers < 0 ?
|
||||
num_top_interface_layers : object_config.support_material_bottom_interface_layers;
|
||||
this->has_top_contacts = num_top_interface_layers > 0;
|
||||
this->has_bottom_contacts = num_bottom_interface_layers > 0;
|
||||
this->num_top_interface_layers = this->has_top_contacts ? size_t(num_top_interface_layers - 1) : 0;
|
||||
this->num_bottom_interface_layers = this->has_bottom_contacts ? size_t(num_bottom_interface_layers - 1) : 0;
|
||||
if (this->soluble_interface_non_soluble_base) {
|
||||
// Try to support soluble dense interfaces with non-soluble dense interfaces.
|
||||
this->num_top_base_interface_layers = size_t(std::min(num_top_interface_layers / 2, 2));
|
||||
this->num_bottom_base_interface_layers = size_t(std::min(num_bottom_interface_layers / 2, 2));
|
||||
} else {
|
||||
this->num_top_base_interface_layers = 0;
|
||||
this->num_bottom_base_interface_layers = 0;
|
||||
}
|
||||
}
|
||||
|
||||
this->first_layer_flow = Slic3r::support_material_1st_layer_flow(&object, float(slicing_params.first_print_layer_height));
|
||||
this->support_material_flow = Slic3r::support_material_flow(&object, float(slicing_params.layer_height));
|
||||
this->support_material_interface_flow = Slic3r::support_material_interface_flow(&object, float(slicing_params.layer_height));
|
||||
this->raft_interface_flow = support_material_interface_flow;
|
||||
|
||||
// Calculate a minimum support layer height as a minimum over all extruders, but not smaller than 10um.
|
||||
this->support_layer_height_min = scaled<coord_t>(0.01);
|
||||
for (auto lh : print_config.min_layer_height.values)
|
||||
this->support_layer_height_min = std::min(this->support_layer_height_min, std::max(0.01, lh));
|
||||
for (auto layer : object.layers())
|
||||
this->support_layer_height_min = std::min(this->support_layer_height_min, std::max(0.01, layer->height));
|
||||
|
||||
if (object_config.support_material_interface_layers.value == 0) {
|
||||
// No interface layers allowed, print everything with the base support pattern.
|
||||
this->support_material_interface_flow = this->support_material_flow;
|
||||
}
|
||||
|
||||
// Evaluate the XY gap between the object outer perimeters and the support structures.
|
||||
// Evaluate the XY gap between the object outer perimeters and the support structures.
|
||||
coordf_t external_perimeter_width = 0.;
|
||||
coordf_t bridge_flow_ratio = 0;
|
||||
for (size_t region_id = 0; region_id < object.num_printing_regions(); ++ region_id) {
|
||||
const PrintRegion ®ion = object.printing_region(region_id);
|
||||
external_perimeter_width = std::max(external_perimeter_width, coordf_t(region.flow(object, frExternalPerimeter, slicing_params.layer_height).width()));
|
||||
bridge_flow_ratio += region.config().bridge_flow_ratio;
|
||||
}
|
||||
this->gap_xy = object_config.support_material_xy_spacing.get_abs_value(external_perimeter_width);
|
||||
bridge_flow_ratio /= object.num_printing_regions();
|
||||
|
||||
this->support_material_bottom_interface_flow = slicing_params.soluble_interface || ! object_config.thick_bridges ?
|
||||
this->support_material_interface_flow.with_flow_ratio(bridge_flow_ratio) :
|
||||
Flow::bridging_flow(bridge_flow_ratio * this->support_material_interface_flow.nozzle_diameter(), this->support_material_interface_flow.nozzle_diameter());
|
||||
|
||||
this->can_merge_support_regions = object_config.support_material_extruder.value == object_config.support_material_interface_extruder.value;
|
||||
if (!this->can_merge_support_regions && (object_config.support_material_extruder.value == 0 || object_config.support_material_interface_extruder.value == 0)) {
|
||||
// One of the support extruders is of "don't care" type.
|
||||
auto object_extruders = object.object_extruders();
|
||||
if (object_extruders.size() == 1 &&
|
||||
*object_extruders.begin() == std::max<unsigned int>(object_config.support_material_extruder.value, object_config.support_material_interface_extruder.value))
|
||||
// Object is printed with the same extruder as the support.
|
||||
this->can_merge_support_regions = true;
|
||||
}
|
||||
|
||||
double interface_spacing = object_config.support_material_interface_spacing.value + this->support_material_interface_flow.spacing();
|
||||
this->interface_density = std::min(1., this->support_material_interface_flow.spacing() / interface_spacing);
|
||||
double raft_interface_spacing = object_config.support_material_interface_spacing.value + this->raft_interface_flow.spacing();
|
||||
this->raft_interface_density = std::min(1., this->raft_interface_flow.spacing() / raft_interface_spacing);
|
||||
double support_spacing = object_config.support_material_spacing.value + this->support_material_flow.spacing();
|
||||
this->support_density = std::min(1., this->support_material_flow.spacing() / support_spacing);
|
||||
if (object_config.support_material_interface_layers.value == 0) {
|
||||
// No interface layers allowed, print everything with the base support pattern.
|
||||
this->interface_density = this->support_density;
|
||||
}
|
||||
|
||||
SupportMaterialPattern support_pattern = object_config.support_material_pattern;
|
||||
this->with_sheath = object_config.support_material_with_sheath;
|
||||
this->base_fill_pattern =
|
||||
support_pattern == smpHoneycomb ? ipHoneycomb :
|
||||
this->support_density > 0.95 || this->with_sheath ? ipRectilinear : ipSupportBase;
|
||||
this->interface_fill_pattern = (this->interface_density > 0.95 ? ipRectilinear : ipSupportBase);
|
||||
this->raft_interface_fill_pattern = this->raft_interface_density > 0.95 ? ipRectilinear : ipSupportBase;
|
||||
this->contact_fill_pattern =
|
||||
(object_config.support_material_interface_pattern == smipAuto && slicing_params.soluble_interface) ||
|
||||
object_config.support_material_interface_pattern == smipConcentric ?
|
||||
ipConcentric :
|
||||
(this->interface_density > 0.95 ? ipRectilinear : ipSupportBase);
|
||||
|
||||
this->base_angle = Geometry::deg2rad(float(object_config.support_material_angle.value));
|
||||
this->interface_angle = Geometry::deg2rad(float(object_config.support_material_angle.value + 90.));
|
||||
this->raft_angle_1st_layer = 0.f;
|
||||
this->raft_angle_base = 0.f;
|
||||
this->raft_angle_interface = 0.f;
|
||||
if (slicing_params.base_raft_layers > 1) {
|
||||
assert(slicing_params.raft_layers() >= 4);
|
||||
// There are all raft layer types (1st layer, base, interface & contact layers) available.
|
||||
this->raft_angle_1st_layer = this->interface_angle;
|
||||
this->raft_angle_base = this->base_angle;
|
||||
this->raft_angle_interface = this->interface_angle;
|
||||
if ((slicing_params.interface_raft_layers & 1) == 0)
|
||||
// Allign the 1st raft interface layer so that the object 1st layer is hatched perpendicularly to the raft contact interface.
|
||||
this->raft_angle_interface += float(0.5 * M_PI);
|
||||
} else if (slicing_params.base_raft_layers == 1 || slicing_params.interface_raft_layers > 1) {
|
||||
assert(slicing_params.raft_layers() == 2 || slicing_params.raft_layers() == 3);
|
||||
// 1st layer, interface & contact layers available.
|
||||
this->raft_angle_1st_layer = this->base_angle;
|
||||
this->raft_angle_interface = this->interface_angle + 0.5 * M_PI;
|
||||
} else if (slicing_params.interface_raft_layers == 1) {
|
||||
// Only the contact raft layer is non-empty, which will be printed as the 1st layer.
|
||||
assert(slicing_params.base_raft_layers == 0);
|
||||
assert(slicing_params.interface_raft_layers == 1);
|
||||
assert(slicing_params.raft_layers() == 1);
|
||||
this->raft_angle_1st_layer = float(0.5 * M_PI);
|
||||
this->raft_angle_interface = this->raft_angle_1st_layer;
|
||||
} else {
|
||||
// No raft.
|
||||
assert(slicing_params.base_raft_layers == 0);
|
||||
assert(slicing_params.interface_raft_layers == 0);
|
||||
assert(slicing_params.raft_layers() == 0);
|
||||
}
|
||||
|
||||
this->tree_branch_diameter_double_wall_area_scaled = 0.25 * sqr(scaled<double>(object_config.support_tree_branch_diameter_double_wall.value)) * M_PI;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
96
src/libslic3r/Support/SupportParameters.hpp
Normal file
96
src/libslic3r/Support/SupportParameters.hpp
Normal file
@ -0,0 +1,96 @@
|
||||
#ifndef slic3r_SupportParameters_hpp_
|
||||
#define slic3r_SupportParameters_hpp_
|
||||
|
||||
#include "../libslic3r.h"
|
||||
#include "../Flow.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class PrintObject;
|
||||
enum InfillPattern : int;
|
||||
|
||||
namespace FFFSupport {
|
||||
|
||||
struct SupportParameters {
|
||||
SupportParameters(const PrintObject &object);
|
||||
|
||||
// Both top / bottom contacts and interfaces are soluble.
|
||||
bool soluble_interface;
|
||||
// Support contact & interface are soluble, but support base is non-soluble.
|
||||
bool soluble_interface_non_soluble_base;
|
||||
|
||||
// Is there at least a top contact layer extruded above support base?
|
||||
bool has_top_contacts;
|
||||
// Is there at least a bottom contact layer extruded below support base?
|
||||
bool has_bottom_contacts;
|
||||
// Number of top interface layers without counting the contact layer.
|
||||
size_t num_top_interface_layers;
|
||||
// Number of bottom interface layers without counting the contact layer.
|
||||
size_t num_bottom_interface_layers;
|
||||
// Number of top base interface layers. Zero if not soluble_interface_non_soluble_base.
|
||||
size_t num_top_base_interface_layers;
|
||||
// Number of bottom base interface layers. Zero if not soluble_interface_non_soluble_base.
|
||||
size_t num_bottom_base_interface_layers;
|
||||
|
||||
bool has_contacts() const { return this->has_top_contacts || this->has_bottom_contacts; }
|
||||
bool has_interfaces() const { return this->num_top_interface_layers + this->num_bottom_interface_layers > 0; }
|
||||
bool has_base_interfaces() const { return this->num_top_base_interface_layers + this->num_bottom_base_interface_layers > 0; }
|
||||
size_t num_top_interface_layers_only() const { return this->num_top_interface_layers - this->num_top_base_interface_layers; }
|
||||
size_t num_bottom_interface_layers_only() const { return this->num_bottom_interface_layers - this->num_bottom_base_interface_layers; }
|
||||
|
||||
// Flow at the 1st print layer.
|
||||
Flow first_layer_flow;
|
||||
// Flow at the support base (neither top, nor bottom interface).
|
||||
// Also flow at the raft base with the exception of raft interface and contact layers.
|
||||
Flow support_material_flow;
|
||||
// Flow at the top interface and contact layers.
|
||||
Flow support_material_interface_flow;
|
||||
// Flow at the bottom interfaces and contacts.
|
||||
Flow support_material_bottom_interface_flow;
|
||||
// Flow at raft inteface & contact layers.
|
||||
Flow raft_interface_flow;
|
||||
// Is merging of regions allowed? Could the interface & base support regions be printed with the same extruder?
|
||||
bool can_merge_support_regions;
|
||||
|
||||
coordf_t support_layer_height_min;
|
||||
// coordf_t support_layer_height_max;
|
||||
|
||||
coordf_t gap_xy;
|
||||
|
||||
float base_angle;
|
||||
float interface_angle;
|
||||
|
||||
// Density of the top / bottom interface and contact layers.
|
||||
coordf_t interface_density;
|
||||
// Density of the raft interface and contact layers.
|
||||
coordf_t raft_interface_density;
|
||||
// Density of the base support layers.
|
||||
coordf_t support_density;
|
||||
|
||||
// Pattern of the sparse infill including sparse raft layers.
|
||||
InfillPattern base_fill_pattern;
|
||||
// Pattern of the top / bottom interface and contact layers.
|
||||
InfillPattern interface_fill_pattern;
|
||||
// Pattern of the raft interface and contact layers.
|
||||
InfillPattern raft_interface_fill_pattern;
|
||||
// Pattern of the contact layers.
|
||||
InfillPattern contact_fill_pattern;
|
||||
// Shall the sparse (base) layers be printed with a single perimeter line (sheath) for robustness?
|
||||
bool with_sheath;
|
||||
// Branches of organic supports with area larger than this threshold will be extruded with double lines.
|
||||
double tree_branch_diameter_double_wall_area_scaled;
|
||||
|
||||
float raft_angle_1st_layer;
|
||||
float raft_angle_base;
|
||||
float raft_angle_interface;
|
||||
|
||||
// Produce a raft interface angle for a given SupportLayer::interface_id()
|
||||
float raft_interface_angle(size_t interface_id) const
|
||||
{ return this->raft_angle_interface + ((interface_id & 1) ? float(- M_PI / 4.) : float(+ M_PI / 4.)); }
|
||||
};
|
||||
|
||||
} // namespace FFFSupport
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_SupportParameters_hpp_ */
|
@ -9,14 +9,15 @@
|
||||
#include "TreeModelVolumes.hpp"
|
||||
#include "TreeSupport.hpp"
|
||||
|
||||
#include "BuildVolume.hpp"
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "Flow.hpp"
|
||||
#include "Layer.hpp"
|
||||
#include "Point.hpp"
|
||||
#include "Print.hpp"
|
||||
#include "PrintConfig.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "../BuildVolume.hpp"
|
||||
#include "../ClipperUtils.hpp"
|
||||
#include "../Flow.hpp"
|
||||
#include "../Layer.hpp"
|
||||
#include "../Point.hpp"
|
||||
#include "../Print.hpp"
|
||||
#include "../PrintConfig.hpp"
|
||||
#include "../Utils.hpp"
|
||||
#include "../format.hpp"
|
||||
|
||||
#include <string_view>
|
||||
|
||||
@ -34,6 +35,8 @@ using namespace std::literals;
|
||||
// had to use a define beacuse the macro processing inside macro BOOST_LOG_TRIVIAL()
|
||||
#define error_level_not_in_cache error
|
||||
|
||||
static constexpr const bool polygons_strictly_simple = false;
|
||||
|
||||
TreeSupportMeshGroupSettings::TreeSupportMeshGroupSettings(const PrintObject &print_object)
|
||||
{
|
||||
const PrintConfig &print_config = print_object.print()->config();
|
||||
@ -76,7 +79,9 @@ TreeSupportMeshGroupSettings::TreeSupportMeshGroupSettings(const PrintObject &pr
|
||||
// this->support_interface_skip_height =
|
||||
// this->support_infill_angles =
|
||||
this->support_roof_enable = config.support_material_interface_layers.value > 0;
|
||||
this->support_roof_height = config.support_material_interface_layers.value * this->layer_height;
|
||||
this->support_roof_layers = this->support_roof_enable ? config.support_material_interface_layers.value : 0;
|
||||
this->support_floor_enable = config.support_material_interface_layers.value > 0 && config.support_material_bottom_interface_layers.value > 0;
|
||||
this->support_floor_layers = this->support_floor_enable ? config.support_material_bottom_interface_layers.value : 0;
|
||||
// this->minimum_roof_area =
|
||||
// this->support_roof_angles =
|
||||
this->support_roof_pattern = config.support_material_interface_pattern;
|
||||
@ -175,7 +180,7 @@ TreeModelVolumes::TreeModelVolumes(
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(num_raft_layers, num_layers, std::min<size_t>(1, std::max<size_t>(16, num_layers / (8 * tbb::this_task_arena::max_concurrency())))),
|
||||
[&](const tbb::blocked_range<size_t> &range) {
|
||||
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx)
|
||||
outlines[layer_idx] = to_polygons(expolygons_simplify(print_object.get_layer(layer_idx - num_raft_layers)->lslices, mesh_settings.resolution));
|
||||
outlines[layer_idx] = polygons_simplify(to_polygons(print_object.get_layer(layer_idx - num_raft_layers)->lslices), mesh_settings.resolution, polygons_strictly_simple);
|
||||
});
|
||||
}
|
||||
#endif
|
||||
@ -424,7 +429,7 @@ const Polygons& TreeModelVolumes::getPlaceableAreas(const coord_t orig_radius, L
|
||||
return (*result).get();
|
||||
if (m_precalculated) {
|
||||
BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate Placeable Areas at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!";
|
||||
tree_supports_show_error("Not precalculated Placeable areas requested."sv, false);
|
||||
tree_supports_show_error(format("Not precalculated Placeable areas requested, radius %1%, layer %2%", radius, layer_idx), false);
|
||||
}
|
||||
if (orig_radius == 0)
|
||||
// Placable areas for radius 0 are calculated in the general collision code.
|
||||
@ -585,7 +590,7 @@ void TreeModelVolumes::calculateCollision(const coord_t radius, const LayerIndex
|
||||
if (processing_last_mesh) {
|
||||
if (! dst.empty())
|
||||
collisions = union_(collisions, dst);
|
||||
dst = polygons_simplify(collisions, min_resolution);
|
||||
dst = polygons_simplify(collisions, min_resolution, polygons_strictly_simple);
|
||||
} else
|
||||
append(dst, std::move(collisions));
|
||||
throw_on_cancel();
|
||||
@ -595,21 +600,24 @@ void TreeModelVolumes::calculateCollision(const coord_t radius, const LayerIndex
|
||||
// 3) Optionally calculate placables.
|
||||
if (calculate_placable) {
|
||||
// Now calculate the placable areas.
|
||||
tbb::parallel_for(tbb::blocked_range<LayerIndex>(std::max(data.idx_begin, 1), data.idx_end),
|
||||
[&collision_areas_offsetted, &anti_overhang = m_anti_overhang, processing_last_mesh,
|
||||
min_resolution = m_min_resolution, &data_placeable, &throw_on_cancel]
|
||||
tbb::parallel_for(tbb::blocked_range<LayerIndex>(std::max(z_distance_bottom_layers + 1, data.idx_begin), data.idx_end),
|
||||
[&collision_areas_offsetted, &outlines, &anti_overhang = m_anti_overhang, processing_last_mesh,
|
||||
min_resolution = m_min_resolution, z_distance_bottom_layers, xy_distance, &data_placeable, &throw_on_cancel]
|
||||
(const tbb::blocked_range<LayerIndex>& range) {
|
||||
for (LayerIndex layer_idx = range.begin(); layer_idx != range.end(); ++ layer_idx) {
|
||||
LayerIndex layer_idx_below = layer_idx - 1;
|
||||
LayerIndex layer_idx_below = layer_idx - z_distance_bottom_layers - 1;
|
||||
assert(layer_idx_below >= 0);
|
||||
const Polygons ¤t = collision_areas_offsetted[layer_idx];
|
||||
const Polygons &below = collision_areas_offsetted[layer_idx_below];
|
||||
Polygons placable = diff(below, layer_idx_below < int(anti_overhang.size()) ? union_(current, anti_overhang[layer_idx_below]) : current);
|
||||
const Polygons &below = outlines[layer_idx_below];
|
||||
Polygons placable = diff(
|
||||
// Inflate the surface to sit on by the separation distance to increase chance of a support being placed on a sloped surface.
|
||||
offset(below, xy_distance),
|
||||
layer_idx_below < int(anti_overhang.size()) ? union_(current, anti_overhang[layer_idx_below]) : current);
|
||||
auto &dst = data_placeable[layer_idx];
|
||||
if (processing_last_mesh) {
|
||||
if (! dst.empty())
|
||||
placable = union_(placable, dst);
|
||||
dst = polygons_simplify(placable, min_resolution);
|
||||
dst = polygons_simplify(placable, min_resolution, polygons_strictly_simple);
|
||||
} else
|
||||
append(dst, placable);
|
||||
throw_on_cancel();
|
||||
@ -657,7 +665,7 @@ void TreeModelVolumes::calculateCollisionHolefree(const std::vector<RadiusLayerP
|
||||
data.emplace_back(RadiusLayerPair(radius, layer_idx), polygons_simplify(
|
||||
offset(union_ex(this->getCollision(m_increase_until_radius, layer_idx, false)),
|
||||
5 - increase_radius_ceil, ClipperLib::jtRound, m_min_resolution),
|
||||
m_min_resolution));
|
||||
m_min_resolution, polygons_strictly_simple));
|
||||
throw_on_cancel();
|
||||
}
|
||||
}
|
||||
@ -744,7 +752,7 @@ void TreeModelVolumes::calculateAvoidance(const std::vector<RadiusLayerPair> &ke
|
||||
ClipperLib::jtRound, m_min_resolution));
|
||||
if (task.to_model)
|
||||
latest_avoidance = diff(latest_avoidance, getPlaceableAreas(task.radius, layer_idx, throw_on_cancel));
|
||||
latest_avoidance = polygons_simplify(latest_avoidance, m_min_resolution);
|
||||
latest_avoidance = polygons_simplify(latest_avoidance, m_min_resolution, polygons_strictly_simple);
|
||||
data.emplace_back(RadiusLayerPair{task.radius, layer_idx}, latest_avoidance);
|
||||
throw_on_cancel();
|
||||
}
|
||||
@ -865,12 +873,12 @@ void TreeModelVolumes::calculateWallRestrictions(const std::vector<RadiusLayerPa
|
||||
data[layer_idx - min_layer_bottom] = polygons_simplify(
|
||||
// radius contains m_current_min_xy_dist_delta already if required
|
||||
intersection(getCollision(0, layer_idx, false), getCollision(radius, layer_idx - 1, true)),
|
||||
m_min_resolution);
|
||||
m_min_resolution, polygons_strictly_simple);
|
||||
if (! data_min.empty())
|
||||
data_min[layer_idx - min_layer_bottom] =
|
||||
polygons_simplify(
|
||||
intersection(getCollision(0, layer_idx, true), getCollision(radius, layer_idx - 1, true)),
|
||||
m_min_resolution);
|
||||
m_min_resolution, polygons_strictly_simple);
|
||||
throw_on_cancel();
|
||||
}
|
||||
});
|
@ -14,9 +14,9 @@
|
||||
|
||||
#include <boost/functional/hash.hpp>
|
||||
|
||||
#include "Point.hpp"
|
||||
#include "Polygon.hpp"
|
||||
#include "PrintConfig.hpp"
|
||||
#include "../Point.hpp"
|
||||
#include "../Polygon.hpp"
|
||||
#include "../PrintConfig.hpp"
|
||||
|
||||
namespace Slic3r
|
||||
{
|
||||
@ -94,7 +94,9 @@ struct TreeSupportMeshGroupSettings {
|
||||
bool support_roof_enable { false };
|
||||
// Support Roof Thickness
|
||||
// The thickness of the support roofs. This controls the amount of dense layers at the top of the support on which the model rests.
|
||||
coord_t support_roof_height { scaled<coord_t>(1.) };
|
||||
coord_t support_roof_layers { 2 };
|
||||
bool support_floor_enable { false };
|
||||
coord_t support_floor_layers { 2 };
|
||||
// Minimum Support Roof Area
|
||||
// Minimum area size for the roofs of the support. Polygons which have an area smaller than this value will be printed as normal support.
|
||||
double minimum_roof_area { scaled<double>(scaled<double>(1.)) };
|
||||
@ -215,6 +217,7 @@ public:
|
||||
void clear() {
|
||||
this->clear_all_but_object_collision();
|
||||
m_collision_cache.clear();
|
||||
m_placeable_areas_cache.clear();
|
||||
}
|
||||
void clear_all_but_object_collision() {
|
||||
//m_collision_cache.clear_all_but_radius0();
|
||||
@ -223,7 +226,7 @@ public:
|
||||
m_avoidance_cache_slow.clear();
|
||||
m_avoidance_cache_to_model.clear();
|
||||
m_avoidance_cache_to_model_slow.clear();
|
||||
m_placeable_areas_cache.clear();
|
||||
m_placeable_areas_cache.clear_all_but_radius0();
|
||||
m_avoidance_cache_holefree.clear();
|
||||
m_avoidance_cache_holefree_to_model.clear();
|
||||
m_wall_restrictions_cache.clear();
|
File diff suppressed because it is too large
Load Diff
@ -11,6 +11,7 @@
|
||||
|
||||
#include "TreeModelVolumes.hpp"
|
||||
#include "Point.hpp"
|
||||
#include "Support/SupportLayer.hpp"
|
||||
|
||||
#include <boost/container/small_vector.hpp>
|
||||
|
||||
@ -39,10 +40,7 @@ namespace Slic3r
|
||||
// Forward declarations
|
||||
class Print;
|
||||
class PrintObject;
|
||||
class SupportGeneratorLayer;
|
||||
struct SlicingParameters;
|
||||
using SupportGeneratorLayerStorage = std::deque<SupportGeneratorLayer>;
|
||||
using SupportGeneratorLayersPtr = std::vector<SupportGeneratorLayer*>;
|
||||
|
||||
namespace FFFTreeSupport
|
||||
{
|
||||
@ -93,6 +91,8 @@ struct AreaIncreaseSettings
|
||||
|
||||
struct TreeSupportSettings;
|
||||
|
||||
// #define TREE_SUPPORTS_TRACK_LOST
|
||||
|
||||
// C++17 does not support in place initializers of bit values, thus a constructor zeroing the bits is provided.
|
||||
struct SupportElementStateBits {
|
||||
SupportElementStateBits() :
|
||||
@ -102,6 +102,10 @@ struct SupportElementStateBits {
|
||||
supports_roof(false),
|
||||
can_use_safe_radius(false),
|
||||
skip_ovalisation(false),
|
||||
#ifdef TREE_SUPPORTS_TRACK_LOST
|
||||
lost(false),
|
||||
verylost(false),
|
||||
#endif // TREE_SUPPORTS_TRACK_LOST
|
||||
deleted(false),
|
||||
marked(false)
|
||||
{}
|
||||
@ -136,6 +140,12 @@ struct SupportElementStateBits {
|
||||
*/
|
||||
bool skip_ovalisation : 1;
|
||||
|
||||
#ifdef TREE_SUPPORTS_TRACK_LOST
|
||||
// Likely a lost branch, debugging information.
|
||||
bool lost : 1;
|
||||
bool verylost : 1;
|
||||
#endif // TREE_SUPPORTS_TRACK_LOST
|
||||
|
||||
// Not valid anymore, to be deleted.
|
||||
bool deleted : 1;
|
||||
|
||||
@ -354,10 +364,6 @@ public:
|
||||
* \brief Amount of layers distance required from the top of the model to the bottom of a support structure.
|
||||
*/
|
||||
size_t z_distance_bottom_layers;
|
||||
/*!
|
||||
* \brief used for performance optimization at the support floor. Should have no impact on the resulting tree.
|
||||
*/
|
||||
size_t performance_interface_skip_layers;
|
||||
/*!
|
||||
* \brief User specified angles for the support infill.
|
||||
*/
|
@ -1,314 +0,0 @@
|
||||
#ifndef slic3r_SupportMaterial_hpp_
|
||||
#define slic3r_SupportMaterial_hpp_
|
||||
|
||||
#include "Flow.hpp"
|
||||
#include "PrintConfig.hpp"
|
||||
#include "Slicing.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class PrintObject;
|
||||
class PrintConfig;
|
||||
class PrintObjectConfig;
|
||||
|
||||
// Support layer type to be used by SupportGeneratorLayer. This type carries a much more detailed information
|
||||
// about the support layer type than the final support layers stored in a PrintObject.
|
||||
enum class SupporLayerType {
|
||||
Unknown = 0,
|
||||
// Ratft base layer, to be printed with the support material.
|
||||
RaftBase,
|
||||
// Raft interface layer, to be printed with the support interface material.
|
||||
RaftInterface,
|
||||
// Bottom contact layer placed over a top surface of an object. To be printed with a support interface material.
|
||||
BottomContact,
|
||||
// Dense interface layer, to be printed with the support interface material.
|
||||
// This layer is separated from an object by an BottomContact layer.
|
||||
BottomInterface,
|
||||
// Sparse base support layer, to be printed with a support material.
|
||||
Base,
|
||||
// Dense interface layer, to be printed with the support interface material.
|
||||
// This layer is separated from an object with TopContact layer.
|
||||
TopInterface,
|
||||
// Top contact layer directly supporting an overhang. To be printed with a support interface material.
|
||||
TopContact,
|
||||
// Some undecided type yet. It will turn into Base first, then it may turn into BottomInterface or TopInterface.
|
||||
Intermediate,
|
||||
};
|
||||
|
||||
// A support layer type used internally by the SupportMaterial class. This class carries a much more detailed
|
||||
// information about the support layer than the layers stored in the PrintObject, mainly
|
||||
// the SupportGeneratorLayer is aware of the bridging flow and the interface gaps between the object and the support.
|
||||
class SupportGeneratorLayer
|
||||
{
|
||||
public:
|
||||
void reset() {
|
||||
*this = SupportGeneratorLayer();
|
||||
}
|
||||
|
||||
bool operator==(const SupportGeneratorLayer &layer2) const {
|
||||
return print_z == layer2.print_z && height == layer2.height && bridging == layer2.bridging;
|
||||
}
|
||||
|
||||
// Order the layers by lexicographically by an increasing print_z and a decreasing layer height.
|
||||
bool operator<(const SupportGeneratorLayer &layer2) const {
|
||||
if (print_z < layer2.print_z) {
|
||||
return true;
|
||||
} else if (print_z == layer2.print_z) {
|
||||
if (height > layer2.height)
|
||||
return true;
|
||||
else if (height == layer2.height) {
|
||||
// Bridging layers first.
|
||||
return bridging && ! layer2.bridging;
|
||||
} else
|
||||
return false;
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
|
||||
void merge(SupportGeneratorLayer &&rhs) {
|
||||
// The union_() does not support move semantic yet, but maybe one day it will.
|
||||
this->polygons = union_(this->polygons, std::move(rhs.polygons));
|
||||
auto merge = [](std::unique_ptr<Polygons> &dst, std::unique_ptr<Polygons> &src) {
|
||||
if (! dst || dst->empty())
|
||||
dst = std::move(src);
|
||||
else if (src && ! src->empty())
|
||||
*dst = union_(*dst, std::move(*src));
|
||||
};
|
||||
merge(this->contact_polygons, rhs.contact_polygons);
|
||||
merge(this->overhang_polygons, rhs.overhang_polygons);
|
||||
merge(this->enforcer_polygons, rhs.enforcer_polygons);
|
||||
rhs.reset();
|
||||
}
|
||||
|
||||
// For the bridging flow, bottom_print_z will be above bottom_z to account for the vertical separation.
|
||||
// For the non-bridging flow, bottom_print_z will be equal to bottom_z.
|
||||
coordf_t bottom_print_z() const { return print_z - height; }
|
||||
|
||||
// To sort the extremes of top / bottom interface layers.
|
||||
coordf_t extreme_z() const { return (this->layer_type == SupporLayerType::TopContact) ? this->bottom_z : this->print_z; }
|
||||
|
||||
SupporLayerType layer_type { SupporLayerType::Unknown };
|
||||
// Z used for printing, in unscaled coordinates.
|
||||
coordf_t print_z { 0 };
|
||||
// Bottom Z of this layer. For soluble layers, bottom_z + height = print_z,
|
||||
// otherwise bottom_z + gap + height = print_z.
|
||||
coordf_t bottom_z { 0 };
|
||||
// Layer height in unscaled coordinates.
|
||||
coordf_t height { 0 };
|
||||
// Index of a PrintObject layer_id supported by this layer. This will be set for top contact layers.
|
||||
// If this is not a contact layer, it will be set to size_t(-1).
|
||||
size_t idx_object_layer_above { size_t(-1) };
|
||||
// Index of a PrintObject layer_id, which supports this layer. This will be set for bottom contact layers.
|
||||
// If this is not a contact layer, it will be set to size_t(-1).
|
||||
size_t idx_object_layer_below { size_t(-1) };
|
||||
// Use a bridging flow when printing this support layer.
|
||||
bool bridging { false };
|
||||
|
||||
// Polygons to be filled by the support pattern.
|
||||
Polygons polygons;
|
||||
// Currently for the contact layers only.
|
||||
std::unique_ptr<Polygons> contact_polygons;
|
||||
std::unique_ptr<Polygons> overhang_polygons;
|
||||
// Enforcers need to be propagated independently in case the "support on build plate only" option is enabled.
|
||||
std::unique_ptr<Polygons> enforcer_polygons;
|
||||
};
|
||||
|
||||
// Layers are allocated and owned by a deque. Once a layer is allocated, it is maintained
|
||||
// up to the end of a generate() method. The layer storage may be replaced by an allocator class in the future,
|
||||
// which would allocate layers by multiple chunks.
|
||||
using SupportGeneratorLayerStorage = std::deque<SupportGeneratorLayer>;
|
||||
using SupportGeneratorLayersPtr = std::vector<SupportGeneratorLayer*>;
|
||||
|
||||
struct SupportParameters {
|
||||
SupportParameters(const PrintObject &object);
|
||||
|
||||
// Flow at the 1st print layer.
|
||||
Flow first_layer_flow;
|
||||
// Flow at the support base (neither top, nor bottom interface).
|
||||
// Also flow at the raft base with the exception of raft interface and contact layers.
|
||||
Flow support_material_flow;
|
||||
// Flow at the top interface and contact layers.
|
||||
Flow support_material_interface_flow;
|
||||
// Flow at the bottom interfaces and contacts.
|
||||
Flow support_material_bottom_interface_flow;
|
||||
// Flow at raft inteface & contact layers.
|
||||
Flow raft_interface_flow;
|
||||
// Is merging of regions allowed? Could the interface & base support regions be printed with the same extruder?
|
||||
bool can_merge_support_regions;
|
||||
|
||||
coordf_t support_layer_height_min;
|
||||
// coordf_t support_layer_height_max;
|
||||
|
||||
coordf_t gap_xy;
|
||||
|
||||
float base_angle;
|
||||
float interface_angle;
|
||||
|
||||
// Density of the top / bottom interface and contact layers.
|
||||
coordf_t interface_density;
|
||||
// Density of the raft interface and contact layers.
|
||||
coordf_t raft_interface_density;
|
||||
// Density of the base support layers.
|
||||
coordf_t support_density;
|
||||
|
||||
// Pattern of the sparse infill including sparse raft layers.
|
||||
InfillPattern base_fill_pattern;
|
||||
// Pattern of the top / bottom interface and contact layers.
|
||||
InfillPattern interface_fill_pattern;
|
||||
// Pattern of the raft interface and contact layers.
|
||||
InfillPattern raft_interface_fill_pattern;
|
||||
// Pattern of the contact layers.
|
||||
InfillPattern contact_fill_pattern;
|
||||
// Shall the sparse (base) layers be printed with a single perimeter line (sheath) for robustness?
|
||||
bool with_sheath;
|
||||
|
||||
float raft_angle_1st_layer;
|
||||
float raft_angle_base;
|
||||
float raft_angle_interface;
|
||||
|
||||
// Produce a raft interface angle for a given SupportLayer::interface_id()
|
||||
float raft_interface_angle(size_t interface_id) const
|
||||
{ return this->raft_angle_interface + ((interface_id & 1) ? float(- M_PI / 4.) : float(+ M_PI / 4.)); }
|
||||
};
|
||||
|
||||
// Remove bridges from support contact areas.
|
||||
// To be called if PrintObjectConfig::dont_support_bridges.
|
||||
void remove_bridges_from_contacts(
|
||||
const PrintConfig &print_config,
|
||||
const Layer &lower_layer,
|
||||
const LayerRegion &layerm,
|
||||
float fw,
|
||||
Polygons &contact_polygons);
|
||||
|
||||
// Generate raft layers, also expand the 1st support layer
|
||||
// in case there is no raft layer to improve support adhesion.
|
||||
SupportGeneratorLayersPtr generate_raft_base(
|
||||
const PrintObject &object,
|
||||
const SupportParameters &support_params,
|
||||
const SlicingParameters &slicing_params,
|
||||
const SupportGeneratorLayersPtr &top_contacts,
|
||||
const SupportGeneratorLayersPtr &interface_layers,
|
||||
const SupportGeneratorLayersPtr &base_interface_layers,
|
||||
const SupportGeneratorLayersPtr &base_layers,
|
||||
SupportGeneratorLayerStorage &layer_storage);
|
||||
|
||||
// returns sorted layers
|
||||
SupportGeneratorLayersPtr generate_support_layers(
|
||||
PrintObject &object,
|
||||
const SupportGeneratorLayersPtr &raft_layers,
|
||||
const SupportGeneratorLayersPtr &bottom_contacts,
|
||||
const SupportGeneratorLayersPtr &top_contacts,
|
||||
const SupportGeneratorLayersPtr &intermediate_layers,
|
||||
const SupportGeneratorLayersPtr &interface_layers,
|
||||
const SupportGeneratorLayersPtr &base_interface_layers);
|
||||
|
||||
// Produce the support G-code.
|
||||
// Used by both classic and tree supports.
|
||||
void generate_support_toolpaths(
|
||||
SupportLayerPtrs &support_layers,
|
||||
const PrintObjectConfig &config,
|
||||
const SupportParameters &support_params,
|
||||
const SlicingParameters &slicing_params,
|
||||
const SupportGeneratorLayersPtr &raft_layers,
|
||||
const SupportGeneratorLayersPtr &bottom_contacts,
|
||||
const SupportGeneratorLayersPtr &top_contacts,
|
||||
const SupportGeneratorLayersPtr &intermediate_layers,
|
||||
const SupportGeneratorLayersPtr &interface_layers,
|
||||
const SupportGeneratorLayersPtr &base_interface_layers);
|
||||
|
||||
void export_print_z_polygons_to_svg(const char *path, SupportGeneratorLayer ** const layers, size_t n_layers);
|
||||
void export_print_z_polygons_and_extrusions_to_svg(const char *path, SupportGeneratorLayer ** const layers, size_t n_layers, SupportLayer& support_layer);
|
||||
|
||||
// This class manages raft and supports for a single PrintObject.
|
||||
// Instantiated by Slic3r::Print::Object->_support_material()
|
||||
// This class is instantiated before the slicing starts as Object.pm will query
|
||||
// the parameters of the raft to determine the 1st layer height and thickness.
|
||||
class PrintObjectSupportMaterial
|
||||
{
|
||||
public:
|
||||
PrintObjectSupportMaterial(const PrintObject *object, const SlicingParameters &slicing_params);
|
||||
|
||||
// Is raft enabled?
|
||||
bool has_raft() const { return m_slicing_params.has_raft(); }
|
||||
// Has any support?
|
||||
bool has_support() const { return m_object_config->support_material.value || m_object_config->support_material_enforce_layers; }
|
||||
bool build_plate_only() const { return this->has_support() && m_object_config->support_material_buildplate_only.value; }
|
||||
|
||||
bool synchronize_layers() const { return m_slicing_params.soluble_interface && m_object_config->support_material_synchronize_layers.value; }
|
||||
bool has_contact_loops() const { return m_object_config->support_material_interface_contact_loops.value; }
|
||||
|
||||
// Generate support material for the object.
|
||||
// New support layers will be added to the object,
|
||||
// with extrusion paths and islands filled in for each support layer.
|
||||
void generate(PrintObject &object);
|
||||
|
||||
private:
|
||||
std::vector<Polygons> buildplate_covered(const PrintObject &object) const;
|
||||
|
||||
// Generate top contact layers supporting overhangs.
|
||||
// For a soluble interface material synchronize the layer heights with the object, otherwise leave the layer height undefined.
|
||||
// If supports over bed surface only are requested, don't generate contact layers over an object.
|
||||
SupportGeneratorLayersPtr top_contact_layers(const PrintObject &object, const std::vector<Polygons> &buildplate_covered, SupportGeneratorLayerStorage &layer_storage) const;
|
||||
|
||||
// Generate bottom contact layers supporting the top contact layers.
|
||||
// For a soluble interface material synchronize the layer heights with the object,
|
||||
// otherwise set the layer height to a bridging flow of a support interface nozzle.
|
||||
SupportGeneratorLayersPtr bottom_contact_layers_and_layer_support_areas(
|
||||
const PrintObject &object, const SupportGeneratorLayersPtr &top_contacts, std::vector<Polygons> &buildplate_covered,
|
||||
SupportGeneratorLayerStorage &layer_storage, std::vector<Polygons> &layer_support_areas) const;
|
||||
|
||||
// Trim the top_contacts layers with the bottom_contacts layers if they overlap, so there would not be enough vertical space for both of them.
|
||||
void trim_top_contacts_by_bottom_contacts(const PrintObject &object, const SupportGeneratorLayersPtr &bottom_contacts, SupportGeneratorLayersPtr &top_contacts) const;
|
||||
|
||||
// Generate raft layers and the intermediate support layers between the bottom contact and top contact surfaces.
|
||||
SupportGeneratorLayersPtr raft_and_intermediate_support_layers(
|
||||
const PrintObject &object,
|
||||
const SupportGeneratorLayersPtr &bottom_contacts,
|
||||
const SupportGeneratorLayersPtr &top_contacts,
|
||||
SupportGeneratorLayerStorage &layer_storage) const;
|
||||
|
||||
// Fill in the base layers with polygons.
|
||||
void generate_base_layers(
|
||||
const PrintObject &object,
|
||||
const SupportGeneratorLayersPtr &bottom_contacts,
|
||||
const SupportGeneratorLayersPtr &top_contacts,
|
||||
SupportGeneratorLayersPtr &intermediate_layers,
|
||||
const std::vector<Polygons> &layer_support_areas) const;
|
||||
|
||||
// Turn some of the base layers into base interface layers.
|
||||
// For soluble interfaces with non-soluble bases, print maximum two first interface layers with the base
|
||||
// extruder to improve adhesion of the soluble filament to the base.
|
||||
std::pair<SupportGeneratorLayersPtr, SupportGeneratorLayersPtr> generate_interface_layers(
|
||||
const SupportGeneratorLayersPtr &bottom_contacts,
|
||||
const SupportGeneratorLayersPtr &top_contacts,
|
||||
SupportGeneratorLayersPtr &intermediate_layers,
|
||||
SupportGeneratorLayerStorage &layer_storage) const;
|
||||
|
||||
|
||||
// Trim support layers by an object to leave a defined gap between
|
||||
// the support volume and the object.
|
||||
void trim_support_layers_by_object(
|
||||
const PrintObject &object,
|
||||
SupportGeneratorLayersPtr &support_layers,
|
||||
const coordf_t gap_extra_above,
|
||||
const coordf_t gap_extra_below,
|
||||
const coordf_t gap_xy) const;
|
||||
|
||||
/*
|
||||
void generate_pillars_shape();
|
||||
void clip_with_shape();
|
||||
*/
|
||||
|
||||
// Following objects are not owned by SupportMaterial class.
|
||||
const PrintConfig *m_print_config;
|
||||
const PrintObjectConfig *m_object_config;
|
||||
// Pre-calculated parameters shared between the object slicer and the support generator,
|
||||
// carrying information on a raft, 1st layer height, 1st object layer height, gap between the raft and object etc.
|
||||
SlicingParameters m_slicing_params;
|
||||
// Various precomputed support parameters to be shared with external functions.
|
||||
SupportParameters m_support_params;
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_SupportMaterial_hpp_ */
|
@ -10,11 +10,15 @@
|
||||
#include <deque>
|
||||
#include <queue>
|
||||
#include <mutex>
|
||||
#include <new>
|
||||
#include <utility>
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/scalable_allocator.h>
|
||||
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#ifndef NDEBUG
|
||||
// #define EXPENSIVE_DEBUG_CHECKS
|
||||
@ -32,6 +36,13 @@
|
||||
#include <boost/thread/mutex.hpp>
|
||||
#include <boost/thread/lock_guard.hpp>
|
||||
|
||||
#if defined(__cpp_lib_hardware_interference_size) && ! defined(__APPLE__)
|
||||
using std::hardware_destructive_interference_size;
|
||||
#else
|
||||
// 64 bytes on x86-64 │ L1_CACHE_BYTES │ L1_CACHE_SHIFT │ __cacheline_aligned │ ...
|
||||
constexpr std::size_t hardware_destructive_interference_size = 64;
|
||||
#endif
|
||||
|
||||
// #define SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
@ -139,7 +150,7 @@ public:
|
||||
#endif
|
||||
};
|
||||
|
||||
using IntersectionLines = std::vector<IntersectionLine>;
|
||||
using IntersectionLines = std::vector<IntersectionLine, tbb::scalable_allocator<IntersectionLine>>;
|
||||
|
||||
enum class FacetSliceType {
|
||||
NoSlice = 0,
|
||||
@ -351,6 +362,21 @@ inline FacetSliceType slice_facet(
|
||||
return FacetSliceType::NoSlice;
|
||||
}
|
||||
|
||||
class LinesMutexes {
|
||||
public:
|
||||
std::mutex& operator()(size_t slice_id) {
|
||||
ankerl::unordered_dense::hash<size_t> hash;
|
||||
return m_mutexes[hash(slice_id) % m_mutexes.size()].mutex;
|
||||
}
|
||||
|
||||
private:
|
||||
struct CacheLineAlignedMutex
|
||||
{
|
||||
alignas(hardware_destructive_interference_size) std::mutex mutex;
|
||||
};
|
||||
std::array<CacheLineAlignedMutex, 64> m_mutexes;
|
||||
};
|
||||
|
||||
template<typename TransformVertex>
|
||||
void slice_facet_at_zs(
|
||||
// Scaled or unscaled vertices. transform_vertex_fn may scale zs.
|
||||
@ -361,7 +387,7 @@ void slice_facet_at_zs(
|
||||
// Scaled or unscaled zs. If vertices have their zs scaled or transform_vertex_fn scales them, then zs have to be scaled as well.
|
||||
const std::vector<float> &zs,
|
||||
std::vector<IntersectionLines> &lines,
|
||||
std::array<std::mutex, 64> &lines_mutex)
|
||||
LinesMutexes &lines_mutex)
|
||||
{
|
||||
stl_vertex vertices[3] { transform_vertex_fn(mesh_vertices[indices(0)]), transform_vertex_fn(mesh_vertices[indices(1)]), transform_vertex_fn(mesh_vertices[indices(2)]) };
|
||||
|
||||
@ -380,7 +406,7 @@ void slice_facet_at_zs(
|
||||
if (min_z != max_z && slice_facet(*it, vertices, indices, edge_ids, idx_vertex_lowest, false, il) == FacetSliceType::Slicing) {
|
||||
assert(il.edge_type != IntersectionLine::FacetEdgeType::Horizontal);
|
||||
size_t slice_id = it - zs.begin();
|
||||
boost::lock_guard<std::mutex> l(lines_mutex[slice_id % lines_mutex.size()]);
|
||||
boost::lock_guard<std::mutex> l(lines_mutex(slice_id));
|
||||
lines[slice_id].emplace_back(il);
|
||||
}
|
||||
}
|
||||
@ -395,8 +421,8 @@ static inline std::vector<IntersectionLines> slice_make_lines(
|
||||
const std::vector<float> &zs,
|
||||
const ThrowOnCancel throw_on_cancel_fn)
|
||||
{
|
||||
std::vector<IntersectionLines> lines(zs.size(), IntersectionLines());
|
||||
std::array<std::mutex, 64> lines_mutex;
|
||||
std::vector<IntersectionLines> lines(zs.size(), IntersectionLines{});
|
||||
LinesMutexes lines_mutex;
|
||||
tbb::parallel_for(
|
||||
tbb::blocked_range<int>(0, int(indices.size())),
|
||||
[&vertices, &transform_vertex_fn, &indices, &face_edge_ids, &zs, &lines, &lines_mutex, throw_on_cancel_fn](const tbb::blocked_range<int> &range) {
|
||||
@ -475,7 +501,7 @@ void slice_facet_with_slabs(
|
||||
const int num_edges,
|
||||
const std::vector<float> &zs,
|
||||
SlabLines &lines,
|
||||
std::array<std::mutex, 64> &lines_mutex)
|
||||
LinesMutexes &lines_mutex)
|
||||
{
|
||||
const stl_triangle_vertex_indices &indices = mesh_triangles[facet_idx];
|
||||
stl_vertex vertices[3] { mesh_vertices[indices(0)], mesh_vertices[indices(1)], mesh_vertices[indices(2)] };
|
||||
@ -494,7 +520,7 @@ void slice_facet_with_slabs(
|
||||
auto emit_slab_edge = [&lines, &lines_mutex](IntersectionLine il, size_t slab_id, bool reverse) {
|
||||
if (reverse)
|
||||
il.reverse();
|
||||
boost::lock_guard<std::mutex> l(lines_mutex[(slab_id + lines_mutex.size() / 2) % lines_mutex.size()]);
|
||||
boost::lock_guard<std::mutex> l(lines_mutex(slab_id));
|
||||
lines.between_slices[slab_id].emplace_back(il);
|
||||
};
|
||||
|
||||
@ -530,7 +556,7 @@ void slice_facet_with_slabs(
|
||||
};
|
||||
// Don't flip the FacetEdgeType::Top edge, it will be flipped when chaining.
|
||||
// if (! ProjectionFromTop) il.reverse();
|
||||
boost::lock_guard<std::mutex> l(lines_mutex[line_id % lines_mutex.size()]);
|
||||
boost::lock_guard<std::mutex> l(lines_mutex(line_id));
|
||||
lines.at_slice[line_id].emplace_back(il);
|
||||
}
|
||||
} else {
|
||||
@ -649,7 +675,7 @@ void slice_facet_with_slabs(
|
||||
if (! ProjectionFromTop)
|
||||
il.reverse();
|
||||
size_t line_id = it - zs.begin();
|
||||
boost::lock_guard<std::mutex> l(lines_mutex[line_id % lines_mutex.size()]);
|
||||
boost::lock_guard<std::mutex> l(lines_mutex(line_id));
|
||||
lines.at_slice[line_id].emplace_back(il);
|
||||
}
|
||||
}
|
||||
@ -804,8 +830,8 @@ inline std::pair<SlabLines, SlabLines> slice_slabs_make_lines(
|
||||
std::pair<SlabLines, SlabLines> out;
|
||||
SlabLines &lines_top = out.first;
|
||||
SlabLines &lines_bottom = out.second;
|
||||
std::array<std::mutex, 64> lines_mutex_top;
|
||||
std::array<std::mutex, 64> lines_mutex_bottom;
|
||||
LinesMutexes lines_mutex_top;
|
||||
LinesMutexes lines_mutex_bottom;
|
||||
|
||||
if (top) {
|
||||
lines_top.at_slice.assign(zs.size(), IntersectionLines());
|
||||
@ -1540,7 +1566,7 @@ static std::vector<Polygons> make_slab_loops(
|
||||
}
|
||||
|
||||
// Used to cut the mesh into two halves.
|
||||
static ExPolygons make_expolygons_simple(std::vector<IntersectionLine> &lines)
|
||||
static ExPolygons make_expolygons_simple(IntersectionLines &lines)
|
||||
{
|
||||
ExPolygons slices;
|
||||
Polygons holes;
|
||||
|
@ -107,6 +107,7 @@
|
||||
#include <boost/version.hpp>
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/scalable_allocator.h>
|
||||
#include <tbb/spin_mutex.h>
|
||||
#include <tbb/task_group.h>
|
||||
|
||||
|
@ -291,7 +291,8 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config)
|
||||
(config->opt_bool("support_material") ||
|
||||
config->opt_int("support_material_enforce_layers") > 0);
|
||||
for (const std::string& key : { "support_tree_angle", "support_tree_angle_slow", "support_tree_branch_diameter",
|
||||
"support_tree_branch_diameter_angle", "support_tree_tip_diameter", "support_tree_branch_distance", "support_tree_top_rate" })
|
||||
"support_tree_branch_diameter_angle", "support_tree_branch_diameter_double_wall",
|
||||
"support_tree_tip_diameter", "support_tree_branch_distance", "support_tree_top_rate" })
|
||||
toggle_field(key, has_organic_supports);
|
||||
|
||||
for (auto el : { "support_material_bottom_interface_layers", "support_material_interface_spacing", "support_material_interface_extruder",
|
||||
|
@ -3916,6 +3916,7 @@ void GLCanvas3D::do_move(const std::string& snapshot_type)
|
||||
}
|
||||
|
||||
// Fixes flying instances
|
||||
std::set<int> obj_idx_for_update_info_items;
|
||||
for (const std::pair<int, int>& i : done) {
|
||||
ModelObject* m = m_model->objects[i.first];
|
||||
const double shift_z = m->get_instance_min_z(i.second);
|
||||
@ -3924,8 +3925,11 @@ void GLCanvas3D::do_move(const std::string& snapshot_type)
|
||||
m_selection.translate(i.first, i.second, shift);
|
||||
m->translate_instance(i.second, shift);
|
||||
}
|
||||
wxGetApp().obj_list()->update_info_items(static_cast<size_t>(i.first));
|
||||
obj_idx_for_update_info_items.emplace(i.first);
|
||||
}
|
||||
//update sinking information in ObjectList
|
||||
for (int id : obj_idx_for_update_info_items)
|
||||
wxGetApp().obj_list()->update_info_items(static_cast<size_t>(id));
|
||||
|
||||
// if the selection is not valid to allow for layer editing after the move, we need to turn off the tool if it is running
|
||||
// similar to void Plater::priv::selection_changed()
|
||||
@ -4003,6 +4007,7 @@ void GLCanvas3D::do_rotate(const std::string& snapshot_type)
|
||||
}
|
||||
|
||||
// Fixes sinking/flying instances
|
||||
std::set<int> obj_idx_for_update_info_items;
|
||||
for (const std::pair<int, int>& i : done) {
|
||||
ModelObject* m = m_model->objects[i.first];
|
||||
const double shift_z = m->get_instance_min_z(i.second);
|
||||
@ -4013,8 +4018,11 @@ void GLCanvas3D::do_rotate(const std::string& snapshot_type)
|
||||
m->translate_instance(i.second, shift);
|
||||
}
|
||||
|
||||
wxGetApp().obj_list()->update_info_items(static_cast<size_t>(i.first));
|
||||
obj_idx_for_update_info_items.emplace(i.first);
|
||||
}
|
||||
//update sinking information in ObjectList
|
||||
for (int id : obj_idx_for_update_info_items)
|
||||
wxGetApp().obj_list()->update_info_items(static_cast<size_t>(id));
|
||||
|
||||
if (!done.empty())
|
||||
post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_ROTATED));
|
||||
@ -4072,6 +4080,7 @@ void GLCanvas3D::do_scale(const std::string& snapshot_type)
|
||||
}
|
||||
|
||||
// Fixes sinking/flying instances
|
||||
std::set<int> obj_idx_for_update_info_items;
|
||||
for (const std::pair<int, int>& i : done) {
|
||||
ModelObject* m = m_model->objects[i.first];
|
||||
const double shift_z = m->get_instance_min_z(i.second);
|
||||
@ -4081,8 +4090,11 @@ void GLCanvas3D::do_scale(const std::string& snapshot_type)
|
||||
m_selection.translate(i.first, i.second, shift);
|
||||
m->translate_instance(i.second, shift);
|
||||
}
|
||||
wxGetApp().obj_list()->update_info_items(static_cast<size_t>(i.first));
|
||||
obj_idx_for_update_info_items.emplace(i.first);
|
||||
}
|
||||
//update sinking information in ObjectList
|
||||
for (int id : obj_idx_for_update_info_items)
|
||||
wxGetApp().obj_list()->update_info_items(static_cast<size_t>(id));
|
||||
|
||||
if (!done.empty())
|
||||
post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_SCALED));
|
||||
@ -4135,6 +4147,7 @@ void GLCanvas3D::do_mirror(const std::string& snapshot_type)
|
||||
}
|
||||
|
||||
// Fixes sinking/flying instances
|
||||
std::set<int> obj_idx_for_update_info_items;
|
||||
for (const std::pair<int, int>& i : done) {
|
||||
ModelObject* m = m_model->objects[i.first];
|
||||
double shift_z = m->get_instance_min_z(i.second);
|
||||
@ -4144,8 +4157,11 @@ void GLCanvas3D::do_mirror(const std::string& snapshot_type)
|
||||
m_selection.translate(i.first, i.second, shift);
|
||||
m->translate_instance(i.second, shift);
|
||||
}
|
||||
wxGetApp().obj_list()->update_info_items(static_cast<size_t>(i.first));
|
||||
obj_idx_for_update_info_items.emplace(i.first);
|
||||
}
|
||||
//update sinking information in ObjectList
|
||||
for (int id : obj_idx_for_update_info_items)
|
||||
wxGetApp().obj_list()->update_info_items(static_cast<size_t>(id));
|
||||
|
||||
post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
|
||||
|
||||
@ -4197,6 +4213,7 @@ void GLCanvas3D::do_reset_skew(const std::string& snapshot_type)
|
||||
}
|
||||
|
||||
// Fixes sinking/flying instances
|
||||
std::set<int> obj_idx_for_update_info_items;
|
||||
for (const std::pair<int, int>& i : done) {
|
||||
ModelObject* m = m_model->objects[i.first];
|
||||
double shift_z = m->get_instance_min_z(i.second);
|
||||
@ -4206,8 +4223,11 @@ void GLCanvas3D::do_reset_skew(const std::string& snapshot_type)
|
||||
m_selection.translate(i.first, i.second, shift);
|
||||
m->translate_instance(i.second, shift);
|
||||
}
|
||||
wxGetApp().obj_list()->update_info_items(static_cast<size_t>(i.first));
|
||||
obj_idx_for_update_info_items.emplace(i.first);
|
||||
}
|
||||
//update sinking information in ObjectList
|
||||
for (int id : obj_idx_for_update_info_items)
|
||||
wxGetApp().obj_list()->update_info_items(static_cast<size_t>(id));
|
||||
|
||||
post_event(SimpleEvent(EVT_GLCANVAS_RESET_SKEW));
|
||||
|
||||
|
@ -2998,21 +2998,19 @@ void ObjectList::update_info_items(size_t obj_idx, wxDataViewItemArray* selectio
|
||||
wxGetApp().notification_manager()->push_updated_item_info_notification(type);
|
||||
}
|
||||
else if (shows && ! should_show) {
|
||||
if (!selections)
|
||||
if (!selections && IsSelected(item)) {
|
||||
Unselect(item);
|
||||
m_objects_model->Delete(item);
|
||||
if (selections) {
|
||||
if (selections->Index(item) != wxNOT_FOUND) {
|
||||
// If info item was deleted from the list,
|
||||
// it's need to be deleted from selection array, if it was there
|
||||
selections->Remove(item);
|
||||
// Select item_obj, if info_item doesn't exist for item anymore, but was selected
|
||||
if (selections->Index(item_obj) == wxNOT_FOUND)
|
||||
selections->Add(item_obj);
|
||||
}
|
||||
}
|
||||
else
|
||||
Select(item_obj);
|
||||
}
|
||||
m_objects_model->Delete(item);
|
||||
if (selections && selections->Index(item) != wxNOT_FOUND) {
|
||||
// If info item was deleted from the list,
|
||||
// it's need to be deleted from selection array, if it was there
|
||||
selections->Remove(item);
|
||||
// Select item_obj, if info_item doesn't exist for item anymore, but was selected
|
||||
if (selections->Index(item_obj) == wxNOT_FOUND)
|
||||
selections->Add(item_obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -434,18 +434,15 @@ void GLGizmoFdmSupports::apply_data_from_backend()
|
||||
mesh_id++;
|
||||
auto selector = selectors.find(mv->id().id);
|
||||
if (selector != selectors.end()) {
|
||||
mv->supported_facets.set(selector->second.selector);
|
||||
m_triangle_selectors[mesh_id]->deserialize(mv->supported_facets.get_data(), true);
|
||||
m_triangle_selectors[mesh_id]->deserialize(selector->second.selector.serialize(), true);
|
||||
m_triangle_selectors[mesh_id]->request_update_render_data();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
|
||||
m_parent.set_as_dirty();
|
||||
}
|
||||
this->waiting_for_autogenerated_supports = false;
|
||||
}
|
||||
this->waiting_for_autogenerated_supports = false;
|
||||
update_model_object();
|
||||
}
|
||||
|
||||
void GLGizmoFdmSupports::update_model_object() const
|
||||
|
@ -1634,6 +1634,8 @@ void ImGuiWrapper::init_font(bool compress)
|
||||
ImFontGlyphRangesBuilder builder;
|
||||
builder.AddRanges(m_glyph_ranges);
|
||||
|
||||
builder.AddChar(ImWchar(0x2026)); // …
|
||||
|
||||
if (m_font_cjk) {
|
||||
// This is a temporary fix of https://github.com/prusa3d/PrusaSlicer/issues/8171. The translation
|
||||
// contains characters not in the ImGui ranges for simplified Chinese. For now, just add them manually.
|
||||
|
@ -1536,6 +1536,7 @@ void TabPrint::build()
|
||||
optgroup->append_single_option_line("support_tree_angle_slow", category_path + "tree_angle_slow");
|
||||
optgroup->append_single_option_line("support_tree_branch_diameter", category_path + "tree_branch_diameter");
|
||||
optgroup->append_single_option_line("support_tree_branch_diameter_angle", category_path + "tree_branch_diameter_angle");
|
||||
optgroup->append_single_option_line("support_tree_branch_diameter_double_wall", category_path + "tree_branch_diameter_double_wall");
|
||||
optgroup->append_single_option_line("support_tree_tip_diameter", category_path + "tree_tip_diameter");
|
||||
optgroup->append_single_option_line("support_tree_branch_distance", category_path + "tree_branch_distance");
|
||||
optgroup->append_single_option_line("support_tree_top_rate", category_path + "tree_top_rate");
|
||||
|
Loading…
Reference in New Issue
Block a user