mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-10-22 00:01:09 -06:00
Merge branch 'master' into sender
This commit is contained in:
commit
d2172b4383
47 changed files with 860 additions and 429 deletions
|
@ -63,7 +63,7 @@ my $build = Module::Build::WithXSpp->new(
|
|||
dist_abstract => 'XS code for Slic3r',
|
||||
build_requires => {qw(
|
||||
ExtUtils::ParseXS 3.18
|
||||
ExtUtils::Typemap 1.00
|
||||
ExtUtils::Typemaps 1.00
|
||||
ExtUtils::Typemaps::Default 1.05
|
||||
ExtUtils::XSpp 0.17
|
||||
Module::Build 0.3601
|
||||
|
|
|
@ -135,7 +135,21 @@ stl_count_facets(stl_file *stl, char *file) {
|
|||
/* Otherwise, if the .STL file is ASCII, then do the following */
|
||||
else {
|
||||
/* Reopen the file in text mode (for getting correct newlines on Windows) */
|
||||
freopen(file, "r", stl->fp);
|
||||
// fix to silence a warning about unused return value.
|
||||
// obviously if it fails we have problems....
|
||||
stl->fp = freopen(file, "r", stl->fp);
|
||||
|
||||
// do another null check to be safe
|
||||
if(stl->fp == NULL) {
|
||||
error_msg = (char*)
|
||||
malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */
|
||||
sprintf(error_msg, "stl_initialize: Couldn't open %s for reading",
|
||||
file);
|
||||
perror(error_msg);
|
||||
free(error_msg);
|
||||
stl->error = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Find the number of facets */
|
||||
j = 0;
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
/*******************************************************************************
|
||||
* *
|
||||
* Author : Angus Johnson *
|
||||
* Version : 6.2.1 *
|
||||
* Date : 31 October 2014 *
|
||||
* Version : 6.2.9 *
|
||||
* Date : 16 February 2015 *
|
||||
* Website : http://www.angusj.com *
|
||||
* Copyright : Angus Johnson 2010-2014 *
|
||||
* Copyright : Angus Johnson 2010-2015 *
|
||||
* *
|
||||
* License: *
|
||||
* Use, modification & distribution is subject to Boost Software License Ver 1. *
|
||||
|
@ -381,13 +381,6 @@ Int128 Int128Mul (long64 lhs, long64 rhs)
|
|||
// Miscellaneous global functions
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
void Swap(cInt& val1, cInt& val2)
|
||||
{
|
||||
cInt tmp = val1;
|
||||
val1 = val2;
|
||||
val2 = tmp;
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
bool Orientation(const Path &poly)
|
||||
{
|
||||
return Area(poly) >= 0;
|
||||
|
@ -435,11 +428,11 @@ bool PointIsVertex(const IntPoint &Pt, OutPt *pp)
|
|||
}
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
int PointInPolygon (const IntPoint &pt, const Path &path)
|
||||
//See "The Point in Polygon Problem for Arbitrary Polygons" by Hormann & Agathos
|
||||
//http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.88.5498&rep=rep1&type=pdf
|
||||
int PointInPolygon(const IntPoint &pt, const Path &path)
|
||||
{
|
||||
//returns 0 if false, +1 if true, -1 if pt ON polygon boundary
|
||||
//See "The Point in Polygon Problem for Arbitrary Polygons" by Hormann & Agathos
|
||||
//http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.88.5498&rep=rep1&type=pdf
|
||||
int result = 0;
|
||||
size_t cnt = path.size();
|
||||
if (cnt < 3) return 0;
|
||||
|
@ -758,9 +751,9 @@ inline void ReverseHorizontal(TEdge &e)
|
|||
//swap horizontal edges' Top and Bottom x's so they follow the natural
|
||||
//progression of the bounds - ie so their xbots will align with the
|
||||
//adjoining lower edge. [Helpful in the ProcessHorizontal() method.]
|
||||
Swap(e.Top.X, e.Bot.X);
|
||||
std::swap(e.Top.X, e.Bot.X);
|
||||
#ifdef use_xyz
|
||||
Swap(e.Top.Z, e.Bot.Z);
|
||||
std::swap(e.Top.Z, e.Bot.Z);
|
||||
#endif
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
|
@ -866,8 +859,8 @@ bool Pt2IsBetweenPt1AndPt3(const IntPoint pt1,
|
|||
|
||||
bool HorzSegmentsOverlap(cInt seg1a, cInt seg1b, cInt seg2a, cInt seg2b)
|
||||
{
|
||||
if (seg1a > seg1b) Swap(seg1a, seg1b);
|
||||
if (seg2a > seg2b) Swap(seg2a, seg2b);
|
||||
if (seg1a > seg1b) std::swap(seg1a, seg1b);
|
||||
if (seg2a > seg2b) std::swap(seg2a, seg2b);
|
||||
return (seg1a < seg2b) && (seg2a < seg1b);
|
||||
}
|
||||
|
||||
|
@ -976,16 +969,13 @@ TEdge* ClipperBase::ProcessBound(TEdge* E, bool NextIsForward)
|
|||
EStart = E->Prev;
|
||||
else
|
||||
EStart = E->Next;
|
||||
if (EStart->OutIdx != Skip)
|
||||
{
|
||||
if (IsHorizontal(*EStart)) //ie an adjoining horizontal skip edge
|
||||
if (IsHorizontal(*EStart)) //ie an adjoining horizontal skip edge
|
||||
{
|
||||
if (EStart->Bot.X != E->Bot.X && EStart->Top.X != E->Bot.X)
|
||||
ReverseHorizontal(*E);
|
||||
}
|
||||
else if (EStart->Bot.X != E->Bot.X)
|
||||
ReverseHorizontal(*E);
|
||||
}
|
||||
}
|
||||
|
||||
EStart = E;
|
||||
|
@ -1000,11 +990,7 @@ TEdge* ClipperBase::ProcessBound(TEdge* E, bool NextIsForward)
|
|||
//unless a Skip edge is encountered when that becomes the top divide
|
||||
Horz = Result;
|
||||
while (IsHorizontal(*Horz->Prev)) Horz = Horz->Prev;
|
||||
if (Horz->Prev->Top.X == Result->Next->Top.X)
|
||||
{
|
||||
if (!NextIsForward) Result = Horz->Prev;
|
||||
}
|
||||
else if (Horz->Prev->Top.X > Result->Next->Top.X) Result = Horz->Prev;
|
||||
if (Horz->Prev->Top.X > Result->Next->Top.X) Result = Horz->Prev;
|
||||
}
|
||||
while (E != Result)
|
||||
{
|
||||
|
@ -1024,11 +1010,8 @@ TEdge* ClipperBase::ProcessBound(TEdge* E, bool NextIsForward)
|
|||
{
|
||||
Horz = Result;
|
||||
while (IsHorizontal(*Horz->Next)) Horz = Horz->Next;
|
||||
if (Horz->Next->Top.X == Result->Prev->Top.X)
|
||||
{
|
||||
if (!NextIsForward) Result = Horz->Next;
|
||||
}
|
||||
else if (Horz->Next->Top.X > Result->Prev->Top.X) Result = Horz->Next;
|
||||
if (Horz->Next->Top.X == Result->Prev->Top.X ||
|
||||
Horz->Next->Top.X > Result->Prev->Top.X) Result = Horz->Next;
|
||||
}
|
||||
|
||||
while (E != Result)
|
||||
|
@ -1155,17 +1138,17 @@ bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed)
|
|||
return false;
|
||||
}
|
||||
E->Prev->OutIdx = Skip;
|
||||
if (E->Prev->Bot.X < E->Prev->Top.X) ReverseHorizontal(*E->Prev);
|
||||
MinimaList::value_type locMin;
|
||||
locMin.Y = E->Bot.Y;
|
||||
locMin.LeftBound = 0;
|
||||
locMin.RightBound = E;
|
||||
locMin.RightBound->Side = esRight;
|
||||
locMin.RightBound->WindDelta = 0;
|
||||
while (E->Next->OutIdx != Skip)
|
||||
for (;;)
|
||||
{
|
||||
E->NextInLML = E->Next;
|
||||
if (E->Bot.X != E->Prev->Top.X) ReverseHorizontal(*E);
|
||||
if (E->Next->OutIdx == Skip) break;
|
||||
E->NextInLML = E->Next;
|
||||
E = E->Next;
|
||||
}
|
||||
m_MinimaList.push_back(locMin);
|
||||
|
@ -1371,6 +1354,7 @@ void Clipper::Reset()
|
|||
{
|
||||
ClipperBase::Reset();
|
||||
m_Scanbeam = ScanbeamList();
|
||||
m_Maxima = MaximaList();
|
||||
m_ActiveEdges = 0;
|
||||
m_SortedEdges = 0;
|
||||
for (MinimaList::iterator lm = m_MinimaList.begin(); lm != m_MinimaList.end(); ++lm)
|
||||
|
@ -1378,12 +1362,24 @@ void Clipper::Reset()
|
|||
}
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
bool Clipper::Execute(ClipType clipType, Paths &solution, PolyFillType fillType)
|
||||
{
|
||||
return Execute(clipType, solution, fillType, fillType);
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
bool Clipper::Execute(ClipType clipType, PolyTree &polytree, PolyFillType fillType)
|
||||
{
|
||||
return Execute(clipType, polytree, fillType, fillType);
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
bool Clipper::Execute(ClipType clipType, Paths &solution,
|
||||
PolyFillType subjFillType, PolyFillType clipFillType)
|
||||
{
|
||||
if( m_ExecuteLocked ) return false;
|
||||
if (m_HasOpenPaths)
|
||||
throw clipperException("Error: PolyTree struct is need for open path clipping.");
|
||||
throw clipperException("Error: PolyTree struct is needed for open path clipping.");
|
||||
m_ExecuteLocked = true;
|
||||
solution.resize(0);
|
||||
m_SubjFillType = subjFillType;
|
||||
|
@ -1439,9 +1435,9 @@ bool Clipper::ExecuteInternal()
|
|||
cInt botY = PopScanbeam();
|
||||
do {
|
||||
InsertLocalMinimaIntoAEL(botY);
|
||||
ClearGhostJoins();
|
||||
ProcessHorizontals(false);
|
||||
if (m_Scanbeam.empty()) break;
|
||||
ProcessHorizontals();
|
||||
ClearGhostJoins();
|
||||
if (m_Scanbeam.empty()) break;
|
||||
cInt topY = PopScanbeam();
|
||||
succeeded = ProcessIntersections(topY);
|
||||
if (!succeeded) break;
|
||||
|
@ -1471,7 +1467,10 @@ bool Clipper::ExecuteInternal()
|
|||
for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i)
|
||||
{
|
||||
OutRec *outRec = m_PolyOuts[i];
|
||||
if (outRec->Pts && !outRec->IsOpen)
|
||||
if (!outRec->Pts) continue;
|
||||
if (outRec->IsOpen)
|
||||
FixupOutPolyline(*outRec);
|
||||
else
|
||||
FixupOutPolygon(*outRec);
|
||||
}
|
||||
|
||||
|
@ -1486,17 +1485,16 @@ bool Clipper::ExecuteInternal()
|
|||
|
||||
void Clipper::InsertScanbeam(const cInt Y)
|
||||
{
|
||||
//if (!m_Scanbeam.empty() && Y == m_Scanbeam.top()) return;// avoid duplicates.
|
||||
m_Scanbeam.push(Y);
|
||||
m_Scanbeam.push(Y);
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
cInt Clipper::PopScanbeam()
|
||||
{
|
||||
const cInt Y = m_Scanbeam.top();
|
||||
m_Scanbeam.pop();
|
||||
while (!m_Scanbeam.empty() && Y == m_Scanbeam.top()) { m_Scanbeam.pop(); } // Pop duplicates.
|
||||
return Y;
|
||||
const cInt Y = m_Scanbeam.top();
|
||||
m_Scanbeam.pop();
|
||||
while (!m_Scanbeam.empty() && Y == m_Scanbeam.top()) { m_Scanbeam.pop(); } // Pop duplicates.
|
||||
return Y;
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
|
@ -2356,7 +2354,6 @@ OutRec* Clipper::CreateOutRec()
|
|||
|
||||
OutPt* Clipper::AddOutPt(TEdge *e, const IntPoint &pt)
|
||||
{
|
||||
bool ToFront = (e->Side == esLeft);
|
||||
if( e->OutIdx < 0 )
|
||||
{
|
||||
OutRec *outRec = CreateOutRec();
|
||||
|
@ -2377,7 +2374,8 @@ OutPt* Clipper::AddOutPt(TEdge *e, const IntPoint &pt)
|
|||
//OutRec.Pts is the 'Left-most' point & OutRec.Pts.Prev is the 'Right-most'
|
||||
OutPt* op = outRec->Pts;
|
||||
|
||||
if (ToFront && (pt == op->Pt)) return op;
|
||||
bool ToFront = (e->Side == esLeft);
|
||||
if (ToFront && (pt == op->Pt)) return op;
|
||||
else if (!ToFront && (pt == op->Prev->Pt)) return op->Prev;
|
||||
|
||||
OutPt* newOp = new OutPt;
|
||||
|
@ -2393,13 +2391,23 @@ OutPt* Clipper::AddOutPt(TEdge *e, const IntPoint &pt)
|
|||
}
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
void Clipper::ProcessHorizontals(bool IsTopOfScanbeam)
|
||||
OutPt* Clipper::GetLastOutPt(TEdge *e)
|
||||
{
|
||||
OutRec *outRec = m_PolyOuts[e->OutIdx];
|
||||
if (e->Side == esLeft)
|
||||
return outRec->Pts;
|
||||
else
|
||||
return outRec->Pts->Prev;
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
void Clipper::ProcessHorizontals()
|
||||
{
|
||||
TEdge* horzEdge = m_SortedEdges;
|
||||
while(horzEdge)
|
||||
{
|
||||
DeleteFromSEL(horzEdge);
|
||||
ProcessHorizontal(horzEdge, IsTopOfScanbeam);
|
||||
ProcessHorizontal(horzEdge);
|
||||
horzEdge = m_SortedEdges;
|
||||
}
|
||||
}
|
||||
|
@ -2564,10 +2572,11 @@ void GetHorzDirection(TEdge& HorzEdge, Direction& Dir, cInt& Left, cInt& Right)
|
|||
* the AEL. These 'promoted' edges may in turn intersect [%] with other HEs. *
|
||||
*******************************************************************************/
|
||||
|
||||
void Clipper::ProcessHorizontal(TEdge *horzEdge, bool isTopOfScanbeam)
|
||||
void Clipper::ProcessHorizontal(TEdge *horzEdge)
|
||||
{
|
||||
Direction dir;
|
||||
cInt horzLeft, horzRight;
|
||||
bool IsOpen = (horzEdge->OutIdx >= 0 && m_PolyOuts[horzEdge->OutIdx]->IsOpen);
|
||||
|
||||
GetHorzDirection(*horzEdge, dir, horzLeft, horzRight);
|
||||
|
||||
|
@ -2577,50 +2586,100 @@ void Clipper::ProcessHorizontal(TEdge *horzEdge, bool isTopOfScanbeam)
|
|||
if (!eLastHorz->NextInLML)
|
||||
eMaxPair = GetMaximaPair(eLastHorz);
|
||||
|
||||
for (;;)
|
||||
MaximaList::const_iterator maxIt;
|
||||
MaximaList::const_reverse_iterator maxRit;
|
||||
if (m_Maxima.size() > 0)
|
||||
{
|
||||
//get the first maxima in range (X) ...
|
||||
if (dir == dLeftToRight)
|
||||
{
|
||||
maxIt = m_Maxima.begin();
|
||||
while (maxIt != m_Maxima.end() && *maxIt <= horzEdge->Bot.X) maxIt++;
|
||||
if (maxIt != m_Maxima.end() && *maxIt >= eLastHorz->Top.X)
|
||||
maxIt = m_Maxima.end();
|
||||
}
|
||||
else
|
||||
{
|
||||
maxRit = m_Maxima.rbegin();
|
||||
while (maxRit != m_Maxima.rend() && *maxRit > horzEdge->Bot.X) maxRit++;
|
||||
if (maxRit != m_Maxima.rend() && *maxRit <= eLastHorz->Top.X)
|
||||
maxRit = m_Maxima.rend();
|
||||
}
|
||||
}
|
||||
|
||||
OutPt* op1 = 0;
|
||||
|
||||
for (;;) //loop through consec. horizontal edges
|
||||
{
|
||||
|
||||
bool IsLastHorz = (horzEdge == eLastHorz);
|
||||
TEdge* e = GetNextInAEL(horzEdge, dir);
|
||||
while(e)
|
||||
{
|
||||
//Break if we've got to the end of an intermediate horizontal edge ...
|
||||
//nb: Smaller Dx's are to the right of larger Dx's ABOVE the horizontal.
|
||||
if (e->Curr.X == horzEdge->Top.X && horzEdge->NextInLML &&
|
||||
e->Dx < horzEdge->NextInLML->Dx) break;
|
||||
|
||||
TEdge* eNext = GetNextInAEL(e, dir); //saves eNext for later
|
||||
//this code block inserts extra coords into horizontal edges (in output
|
||||
//polygons) whereever maxima touch these horizontal edges. This helps
|
||||
//'simplifying' polygons (ie if the Simplify property is set).
|
||||
if (m_Maxima.size() > 0)
|
||||
{
|
||||
if (dir == dLeftToRight)
|
||||
{
|
||||
while (maxIt != m_Maxima.end() && *maxIt < e->Curr.X)
|
||||
{
|
||||
if (horzEdge->OutIdx >= 0 && !IsOpen)
|
||||
AddOutPt(horzEdge, IntPoint(*maxIt, horzEdge->Bot.Y));
|
||||
maxIt++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
while (maxRit != m_Maxima.rend() && *maxRit > e->Curr.X)
|
||||
{
|
||||
if (horzEdge->OutIdx >= 0 && !IsOpen)
|
||||
AddOutPt(horzEdge, IntPoint(*maxRit, horzEdge->Bot.Y));
|
||||
maxRit++;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if ((dir == dLeftToRight && e->Curr.X <= horzRight) ||
|
||||
(dir == dRightToLeft && e->Curr.X >= horzLeft))
|
||||
{
|
||||
//so far we're still in range of the horizontal Edge but make sure
|
||||
if ((dir == dLeftToRight && e->Curr.X > horzRight) ||
|
||||
(dir == dRightToLeft && e->Curr.X < horzLeft)) break;
|
||||
|
||||
//Also break if we've got to the end of an intermediate horizontal edge ...
|
||||
//nb: Smaller Dx's are to the right of larger Dx's ABOVE the horizontal.
|
||||
if (e->Curr.X == horzEdge->Top.X && horzEdge->NextInLML &&
|
||||
e->Dx < horzEdge->NextInLML->Dx) break;
|
||||
|
||||
if (horzEdge->OutIdx >= 0 && !IsOpen) //note: may be done multiple times
|
||||
{
|
||||
op1 = AddOutPt(horzEdge, e->Curr);
|
||||
TEdge* eNextHorz = m_SortedEdges;
|
||||
while (eNextHorz)
|
||||
{
|
||||
if (eNextHorz->OutIdx >= 0 &&
|
||||
HorzSegmentsOverlap(horzEdge->Bot.X,
|
||||
horzEdge->Top.X, eNextHorz->Bot.X, eNextHorz->Top.X))
|
||||
{
|
||||
OutPt* op2 = GetLastOutPt(eNextHorz);
|
||||
AddJoin(op2, op1, eNextHorz->Top);
|
||||
}
|
||||
eNextHorz = eNextHorz->NextInSEL;
|
||||
}
|
||||
AddGhostJoin(op1, horzEdge->Bot);
|
||||
}
|
||||
|
||||
//OK, so far we're still in range of the horizontal Edge but make sure
|
||||
//we're at the last of consec. horizontals when matching with eMaxPair
|
||||
if(e == eMaxPair && IsLastHorz)
|
||||
{
|
||||
|
||||
if (horzEdge->OutIdx >= 0)
|
||||
{
|
||||
OutPt* op1 = AddOutPt(horzEdge, horzEdge->Top);
|
||||
TEdge* eNextHorz = m_SortedEdges;
|
||||
while (eNextHorz)
|
||||
{
|
||||
if (eNextHorz->OutIdx >= 0 &&
|
||||
HorzSegmentsOverlap(horzEdge->Bot.X,
|
||||
horzEdge->Top.X, eNextHorz->Bot.X, eNextHorz->Top.X))
|
||||
{
|
||||
OutPt* op2 = AddOutPt(eNextHorz, eNextHorz->Bot);
|
||||
AddJoin(op2, op1, eNextHorz->Top);
|
||||
}
|
||||
eNextHorz = eNextHorz->NextInSEL;
|
||||
}
|
||||
AddGhostJoin(op1, horzEdge->Bot);
|
||||
AddLocalMaxPoly(horzEdge, eMaxPair, horzEdge->Top);
|
||||
}
|
||||
DeleteFromAEL(horzEdge);
|
||||
DeleteFromAEL(eMaxPair);
|
||||
return;
|
||||
}
|
||||
else if(dir == dLeftToRight)
|
||||
|
||||
if(dir == dLeftToRight)
|
||||
{
|
||||
IntPoint Pt = IntPoint(e->Curr.X, horzEdge->Curr.Y);
|
||||
IntersectEdges(horzEdge, e, Pt);
|
||||
|
@ -2630,28 +2689,43 @@ void Clipper::ProcessHorizontal(TEdge *horzEdge, bool isTopOfScanbeam)
|
|||
IntPoint Pt = IntPoint(e->Curr.X, horzEdge->Curr.Y);
|
||||
IntersectEdges( e, horzEdge, Pt);
|
||||
}
|
||||
TEdge* eNext = GetNextInAEL(e, dir);
|
||||
SwapPositionsInAEL( horzEdge, e );
|
||||
}
|
||||
else if( (dir == dLeftToRight && e->Curr.X >= horzRight) ||
|
||||
(dir == dRightToLeft && e->Curr.X <= horzLeft) ) break;
|
||||
e = eNext;
|
||||
} //end while
|
||||
e = eNext;
|
||||
} //end while(e)
|
||||
|
||||
//Break out of loop if HorzEdge.NextInLML is not also horizontal ...
|
||||
if (!horzEdge->NextInLML || !IsHorizontal(*horzEdge->NextInLML)) break;
|
||||
|
||||
UpdateEdgeIntoAEL(horzEdge);
|
||||
if (horzEdge->OutIdx >= 0) AddOutPt(horzEdge, horzEdge->Bot);
|
||||
GetHorzDirection(*horzEdge, dir, horzLeft, horzRight);
|
||||
|
||||
if (horzEdge->NextInLML && IsHorizontal(*horzEdge->NextInLML))
|
||||
{
|
||||
UpdateEdgeIntoAEL(horzEdge);
|
||||
if (horzEdge->OutIdx >= 0) AddOutPt(horzEdge, horzEdge->Bot);
|
||||
GetHorzDirection(*horzEdge, dir, horzLeft, horzRight);
|
||||
} else
|
||||
break;
|
||||
} //end for (;;)
|
||||
|
||||
if(horzEdge->NextInLML)
|
||||
if (horzEdge->OutIdx >= 0 && !op1)
|
||||
{
|
||||
op1 = GetLastOutPt(horzEdge);
|
||||
TEdge* eNextHorz = m_SortedEdges;
|
||||
while (eNextHorz)
|
||||
{
|
||||
if (eNextHorz->OutIdx >= 0 &&
|
||||
HorzSegmentsOverlap(horzEdge->Bot.X,
|
||||
horzEdge->Top.X, eNextHorz->Bot.X, eNextHorz->Top.X))
|
||||
{
|
||||
OutPt* op2 = GetLastOutPt(eNextHorz);
|
||||
AddJoin(op2, op1, eNextHorz->Top);
|
||||
}
|
||||
eNextHorz = eNextHorz->NextInSEL;
|
||||
}
|
||||
AddGhostJoin(op1, horzEdge->Top);
|
||||
}
|
||||
|
||||
if (horzEdge->NextInLML)
|
||||
{
|
||||
if(horzEdge->OutIdx >= 0)
|
||||
{
|
||||
OutPt* op1 = AddOutPt( horzEdge, horzEdge->Top);
|
||||
if (isTopOfScanbeam) AddGhostJoin(op1, horzEdge->Bot);
|
||||
op1 = AddOutPt( horzEdge, horzEdge->Top);
|
||||
UpdateEdgeIntoAEL(horzEdge);
|
||||
if (horzEdge->WindDelta == 0) return;
|
||||
//nb: HorzEdge is no longer horizontal here
|
||||
|
@ -2906,6 +2980,7 @@ void Clipper::ProcessEdgesAtTopOfScanbeam(const cInt topY)
|
|||
|
||||
if(IsMaximaEdge)
|
||||
{
|
||||
if (m_StrictSimple) m_Maxima.push_back(e->Top.X);
|
||||
TEdge* ePrev = e->PrevInAEL;
|
||||
DoMaxima(e);
|
||||
if( !ePrev ) e = m_ActiveEdges;
|
||||
|
@ -2927,6 +3002,8 @@ void Clipper::ProcessEdgesAtTopOfScanbeam(const cInt topY)
|
|||
e->Curr.Y = topY;
|
||||
}
|
||||
|
||||
//When StrictlySimple and 'e' is being touched by another edge, then
|
||||
//make sure both edges have a vertex here ...
|
||||
if (m_StrictSimple)
|
||||
{
|
||||
TEdge* ePrev = e->PrevInAEL;
|
||||
|
@ -2948,7 +3025,9 @@ void Clipper::ProcessEdgesAtTopOfScanbeam(const cInt topY)
|
|||
}
|
||||
|
||||
//3. Process horizontals at the Top of the scanbeam ...
|
||||
ProcessHorizontals(true);
|
||||
m_Maxima.sort();
|
||||
ProcessHorizontals();
|
||||
m_Maxima.clear();
|
||||
|
||||
//4. Promote intermediate vertices ...
|
||||
e = m_ActiveEdges;
|
||||
|
@ -2988,44 +3067,71 @@ void Clipper::ProcessEdgesAtTopOfScanbeam(const cInt topY)
|
|||
}
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
void Clipper::FixupOutPolygon(OutRec &outrec)
|
||||
void Clipper::FixupOutPolyline(OutRec &outrec)
|
||||
{
|
||||
//FixupOutPolygon() - removes duplicate points and simplifies consecutive
|
||||
//parallel edges by removing the middle vertex.
|
||||
OutPt *lastOK = 0;
|
||||
outrec.BottomPt = 0;
|
||||
OutPt *pp = outrec.Pts;
|
||||
|
||||
for (;;)
|
||||
OutPt *lastPP = pp->Prev;
|
||||
while (pp != lastPP)
|
||||
{
|
||||
if (pp->Prev == pp || pp->Prev == pp->Next )
|
||||
pp = pp->Next;
|
||||
if (pp->Pt == pp->Prev->Pt)
|
||||
{
|
||||
DisposeOutPts(pp);
|
||||
outrec.Pts = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
//test for duplicate points and collinear edges ...
|
||||
if ((pp->Pt == pp->Next->Pt) || (pp->Pt == pp->Prev->Pt) ||
|
||||
(SlopesEqual(pp->Prev->Pt, pp->Pt, pp->Next->Pt, m_UseFullRange) &&
|
||||
(!m_PreserveCollinear ||
|
||||
!Pt2IsBetweenPt1AndPt3(pp->Prev->Pt, pp->Pt, pp->Next->Pt))))
|
||||
{
|
||||
lastOK = 0;
|
||||
OutPt *tmp = pp;
|
||||
pp->Prev->Next = pp->Next;
|
||||
pp->Next->Prev = pp->Prev;
|
||||
pp = pp->Prev;
|
||||
delete tmp;
|
||||
}
|
||||
else if (pp == lastOK) break;
|
||||
else
|
||||
{
|
||||
if (!lastOK) lastOK = pp;
|
||||
pp = pp->Next;
|
||||
if (pp == lastPP) lastPP = pp->Prev;
|
||||
OutPt *tmpPP = pp->Prev;
|
||||
tmpPP->Next = pp->Next;
|
||||
pp->Next->Prev = tmpPP;
|
||||
delete pp;
|
||||
pp = tmpPP;
|
||||
}
|
||||
}
|
||||
outrec.Pts = pp;
|
||||
|
||||
if (pp == pp->Prev)
|
||||
{
|
||||
DisposeOutPts(pp);
|
||||
outrec.Pts = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
void Clipper::FixupOutPolygon(OutRec &outrec)
|
||||
{
|
||||
//FixupOutPolygon() - removes duplicate points and simplifies consecutive
|
||||
//parallel edges by removing the middle vertex.
|
||||
OutPt *lastOK = 0;
|
||||
outrec.BottomPt = 0;
|
||||
OutPt *pp = outrec.Pts;
|
||||
bool preserveCol = m_PreserveCollinear || m_StrictSimple;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (pp->Prev == pp || pp->Prev == pp->Next)
|
||||
{
|
||||
DisposeOutPts(pp);
|
||||
outrec.Pts = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
//test for duplicate points and collinear edges ...
|
||||
if ((pp->Pt == pp->Next->Pt) || (pp->Pt == pp->Prev->Pt) ||
|
||||
(SlopesEqual(pp->Prev->Pt, pp->Pt, pp->Next->Pt, m_UseFullRange) &&
|
||||
(!preserveCol || !Pt2IsBetweenPt1AndPt3(pp->Prev->Pt, pp->Pt, pp->Next->Pt))))
|
||||
{
|
||||
lastOK = 0;
|
||||
OutPt *tmp = pp;
|
||||
pp->Prev->Next = pp->Next;
|
||||
pp->Next->Prev = pp->Prev;
|
||||
pp = pp->Prev;
|
||||
delete tmp;
|
||||
}
|
||||
else if (pp == lastOK) break;
|
||||
else
|
||||
{
|
||||
if (!lastOK) lastOK = pp;
|
||||
pp = pp->Next;
|
||||
}
|
||||
}
|
||||
outrec.Pts = pp;
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
|
@ -3309,7 +3415,7 @@ bool Clipper::JoinPoints(Join *j, OutRec* outRec1, OutRec* outRec2)
|
|||
OutPt *op2 = j->OutPt2, *op2b;
|
||||
|
||||
//There are 3 kinds of joins for output polygons ...
|
||||
//1. Horizontal joins where Join.OutPt1 & Join.OutPt2 are a vertices anywhere
|
||||
//1. Horizontal joins where Join.OutPt1 & Join.OutPt2 are vertices anywhere
|
||||
//along (horizontal) collinear edges (& Join.OffPt is on the same horizontal).
|
||||
//2. Non-horizontal joins where Join.OutPt1 & Join.OutPt2 are at the same
|
||||
//location at the Bottom of the overlapping segment (& Join.OffPt is above).
|
||||
|
@ -3508,6 +3614,7 @@ void Clipper::JoinCommonEdges()
|
|||
OutRec *outRec2 = GetOutRec(join->OutPt2->Idx);
|
||||
|
||||
if (!outRec1->Pts || !outRec2->Pts) continue;
|
||||
if (outRec1->IsOpen || outRec2->IsOpen) continue;
|
||||
|
||||
//get the polygon fragment with the correct hole state (FirstLeft)
|
||||
//before calling JoinPoints() ...
|
||||
|
@ -4355,7 +4462,7 @@ void MinkowskiSum(const Path& pattern, const Path& path, Paths& solution, bool p
|
|||
}
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
void TranslatePath(const Path& input, Path& output, IntPoint delta)
|
||||
void TranslatePath(const Path& input, Path& output, const IntPoint delta)
|
||||
{
|
||||
//precondition: input != output
|
||||
output.resize(input.size());
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
/*******************************************************************************
|
||||
* *
|
||||
* Author : Angus Johnson *
|
||||
* Version : 6.2.1 *
|
||||
* Date : 31 October 2014 *
|
||||
* Version : 6.2.9 *
|
||||
* Date : 16 February 2015 *
|
||||
* Website : http://www.angusj.com *
|
||||
* Copyright : Angus Johnson 2010-2014 *
|
||||
* Copyright : Angus Johnson 2010-2015 *
|
||||
* *
|
||||
* License: *
|
||||
* Use, modification & distribution is subject to Boost Software License Ver 1. *
|
||||
|
@ -34,7 +34,7 @@
|
|||
#ifndef clipper_hpp
|
||||
#define clipper_hpp
|
||||
|
||||
#define CLIPPER_VERSION "6.2.0"
|
||||
#define CLIPPER_VERSION "6.2.6"
|
||||
|
||||
//use_int32: When enabled 32bit ints are used instead of 64bit ints. This
|
||||
//improve performance but coordinate values are limited to the range +/- 46340
|
||||
|
@ -50,6 +50,7 @@
|
|||
//#define use_deprecated
|
||||
|
||||
#include <vector>
|
||||
#include <list>
|
||||
#include <set>
|
||||
#include <stdexcept>
|
||||
#include <cstring>
|
||||
|
@ -200,7 +201,6 @@ enum EdgeSide { esLeft = 1, esRight = 2};
|
|||
struct TEdge;
|
||||
struct IntersectNode;
|
||||
struct LocalMinimum;
|
||||
struct Scanbeam;
|
||||
struct OutPt;
|
||||
struct OutRec;
|
||||
struct Join;
|
||||
|
@ -232,7 +232,6 @@ protected:
|
|||
void PopLocalMinima();
|
||||
virtual void Reset();
|
||||
TEdge* ProcessBound(TEdge* E, bool IsClockwise);
|
||||
void DoMinimaLML(TEdge* E1, TEdge* E2, bool IsClosed);
|
||||
TEdge* DescendToMin(TEdge *&E);
|
||||
void AscendToMax(TEdge *&E, bool Appending, bool IsClosed);
|
||||
|
||||
|
@ -253,14 +252,20 @@ public:
|
|||
Clipper(int initOptions = 0);
|
||||
~Clipper();
|
||||
bool Execute(ClipType clipType,
|
||||
Paths &solution,
|
||||
PolyFillType subjFillType = pftEvenOdd,
|
||||
PolyFillType clipFillType = pftEvenOdd);
|
||||
Paths &solution,
|
||||
PolyFillType fillType = pftEvenOdd);
|
||||
bool Execute(ClipType clipType,
|
||||
PolyTree &polytree,
|
||||
PolyFillType subjFillType = pftEvenOdd,
|
||||
PolyFillType clipFillType = pftEvenOdd);
|
||||
bool ReverseSolution() {return m_ReverseOutput;};
|
||||
Paths &solution,
|
||||
PolyFillType subjFillType,
|
||||
PolyFillType clipFillType);
|
||||
bool Execute(ClipType clipType,
|
||||
PolyTree &polytree,
|
||||
PolyFillType fillType = pftEvenOdd);
|
||||
bool Execute(ClipType clipType,
|
||||
PolyTree &polytree,
|
||||
PolyFillType subjFillType,
|
||||
PolyFillType clipFillType);
|
||||
bool ReverseSolution() { return m_ReverseOutput; };
|
||||
void ReverseSolution(bool value) {m_ReverseOutput = value;};
|
||||
bool StrictlySimple() {return m_StrictSimple;};
|
||||
void StrictlySimple(bool value) {m_StrictSimple = value;};
|
||||
|
@ -272,13 +277,15 @@ protected:
|
|||
void Reset();
|
||||
virtual bool ExecuteInternal();
|
||||
private:
|
||||
PolyOutList m_PolyOuts;
|
||||
JoinList m_Joins;
|
||||
JoinList m_GhostJoins;
|
||||
IntersectList m_IntersectList;
|
||||
ClipType m_ClipType;
|
||||
PolyOutList m_PolyOuts;
|
||||
JoinList m_Joins;
|
||||
JoinList m_GhostJoins;
|
||||
IntersectList m_IntersectList;
|
||||
ClipType m_ClipType;
|
||||
typedef std::priority_queue<cInt> ScanbeamList;
|
||||
ScanbeamList m_Scanbeam;
|
||||
ScanbeamList m_Scanbeam;
|
||||
typedef std::list<cInt> MaximaList;
|
||||
MaximaList m_Maxima;
|
||||
TEdge *m_ActiveEdges;
|
||||
TEdge *m_SortedEdges;
|
||||
bool m_ExecuteLocked;
|
||||
|
@ -307,8 +314,8 @@ private:
|
|||
bool IsTopHorz(const cInt XPos);
|
||||
void SwapPositionsInAEL(TEdge *edge1, TEdge *edge2);
|
||||
void DoMaxima(TEdge *e);
|
||||
void ProcessHorizontals(bool IsTopOfScanbeam);
|
||||
void ProcessHorizontal(TEdge *horzEdge, bool isTopOfScanbeam);
|
||||
void ProcessHorizontals();
|
||||
void ProcessHorizontal(TEdge *horzEdge);
|
||||
void AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &pt);
|
||||
OutPt* AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &pt);
|
||||
OutRec* GetOutRec(int idx);
|
||||
|
@ -316,6 +323,7 @@ private:
|
|||
void IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &pt);
|
||||
OutRec* CreateOutRec();
|
||||
OutPt* AddOutPt(TEdge *e, const IntPoint &pt);
|
||||
OutPt* GetLastOutPt(TEdge *e);
|
||||
void DisposeAllOutRecs();
|
||||
void DisposeOutRec(PolyOutList::size_type index);
|
||||
bool ProcessIntersections(const cInt topY);
|
||||
|
@ -328,6 +336,7 @@ private:
|
|||
void DisposeIntersectNodes();
|
||||
bool FixupIntersectionOrder();
|
||||
void FixupOutPolygon(OutRec &outrec);
|
||||
void FixupOutPolyline(OutRec &outrec);
|
||||
bool IsHole(TEdge *e);
|
||||
bool FindOwnerFromSplitRecs(OutRec &outRec, OutRec *&currOrfl);
|
||||
void FixHoleLinkage(OutRec &outrec);
|
||||
|
|
|
@ -37,14 +37,10 @@ BridgeDetector::BridgeDetector(const ExPolygon &_expolygon, const ExPolygonColle
|
|||
Polygons grown;
|
||||
offset((Polygons)this->expolygon, &grown, this->extrusion_width);
|
||||
|
||||
// detect what edges lie on lower slices
|
||||
for (ExPolygons::const_iterator lower = this->lower_slices.expolygons.begin();
|
||||
lower != this->lower_slices.expolygons.end();
|
||||
++lower) {
|
||||
/* turn bridge contour and holes into polylines and then clip them
|
||||
with each lower slice's contour */
|
||||
intersection(grown, lower->contour, &this->_edges);
|
||||
}
|
||||
// detect what edges lie on lower slices by turning bridge contour and holes
|
||||
// into polylines and then clipping them with each lower slice's contour
|
||||
intersection(grown, this->lower_slices.contours(), &this->_edges);
|
||||
|
||||
#ifdef SLIC3R_DEBUG
|
||||
printf(" bridge has %zu support(s)\n", this->_edges.size());
|
||||
#endif
|
||||
|
|
|
@ -112,6 +112,16 @@ ExPolygonCollection::lines() const
|
|||
return lines;
|
||||
}
|
||||
|
||||
Polygons
|
||||
ExPolygonCollection::contours() const
|
||||
{
|
||||
Polygons contours;
|
||||
for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it) {
|
||||
contours.push_back(it->contour);
|
||||
}
|
||||
return contours;
|
||||
}
|
||||
|
||||
#ifdef SLIC3RXS
|
||||
REGISTER_CLASS(ExPolygonCollection, "ExPolygon::Collection");
|
||||
#endif
|
||||
|
|
|
@ -30,6 +30,7 @@ class ExPolygonCollection
|
|||
void simplify(double tolerance);
|
||||
Polygon convex_hull() const;
|
||||
Lines lines() const;
|
||||
Polygons contours() const;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -66,6 +66,15 @@ GCodeWriter::preamble()
|
|||
return gcode.str();
|
||||
}
|
||||
|
||||
std::string
|
||||
GCodeWriter::postamble()
|
||||
{
|
||||
std::ostringstream gcode;
|
||||
if (FLAVOR_IS(gcfMachinekit))
|
||||
gcode << "M2 ; end of program\n";
|
||||
return gcode.str();
|
||||
}
|
||||
|
||||
std::string
|
||||
GCodeWriter::set_temperature(unsigned int temperature, bool wait, int tool)
|
||||
{
|
||||
|
@ -83,7 +92,7 @@ GCodeWriter::set_temperature(unsigned int temperature, bool wait, int tool)
|
|||
|
||||
std::ostringstream gcode;
|
||||
gcode << code << " ";
|
||||
if (FLAVOR_IS(gcfMach3)) {
|
||||
if (FLAVOR_IS(gcfMach3) || FLAVOR_IS(gcfMachinekit)) {
|
||||
gcode << "P";
|
||||
} else {
|
||||
gcode << "S";
|
||||
|
@ -118,7 +127,7 @@ GCodeWriter::set_bed_temperature(unsigned int temperature, bool wait)
|
|||
|
||||
std::ostringstream gcode;
|
||||
gcode << code << " ";
|
||||
if (FLAVOR_IS(gcfMach3)) {
|
||||
if (FLAVOR_IS(gcfMach3) || FLAVOR_IS(gcfMachinekit)) {
|
||||
gcode << "P";
|
||||
} else {
|
||||
gcode << "S";
|
||||
|
@ -153,7 +162,7 @@ GCodeWriter::set_fan(unsigned int speed, bool dont_save)
|
|||
gcode << "M126";
|
||||
} else {
|
||||
gcode << "M106 ";
|
||||
if (FLAVOR_IS(gcfMach3)) {
|
||||
if (FLAVOR_IS(gcfMach3) || FLAVOR_IS(gcfMachinekit)) {
|
||||
gcode << "P";
|
||||
} else {
|
||||
gcode << "S";
|
||||
|
@ -434,7 +443,10 @@ GCodeWriter::_retract(double length, double restart_extra, const std::string &co
|
|||
double dE = this->_extruder->retract(length, restart_extra);
|
||||
if (dE != 0) {
|
||||
if (this->config.use_firmware_retraction) {
|
||||
gcode << "G10 ; retract\n";
|
||||
if (FLAVOR_IS(gcfMachinekit))
|
||||
gcode << "G22 ; retract\n";
|
||||
else
|
||||
gcode << "G10 ; retract\n";
|
||||
} else {
|
||||
gcode << "G1 " << this->_extrusion_axis << E_NUM(this->_extruder->E)
|
||||
<< " F" << this->_extruder->retract_speed_mm_min;
|
||||
|
@ -460,7 +472,10 @@ GCodeWriter::unretract()
|
|||
double dE = this->_extruder->unretract();
|
||||
if (dE != 0) {
|
||||
if (this->config.use_firmware_retraction) {
|
||||
gcode << "G11 ; unretract\n";
|
||||
if (FLAVOR_IS(gcfMachinekit))
|
||||
gcode << "G23 ; unretract\n";
|
||||
else
|
||||
gcode << "G11 ; unretract\n";
|
||||
gcode << this->reset_e();
|
||||
} else {
|
||||
// use G1 instead of G0 because G0 will blend the restart with the previous travel move
|
||||
|
@ -509,4 +524,4 @@ GCodeWriter::get_position() const
|
|||
REGISTER_CLASS(GCodeWriter, "GCode::Writer");
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ class GCodeWriter {
|
|||
void apply_print_config(const PrintConfig &print_config);
|
||||
void set_extruders(const std::vector<unsigned int> &extruder_ids);
|
||||
std::string preamble();
|
||||
std::string postamble();
|
||||
std::string set_temperature(unsigned int temperature, bool wait = false, int tool = -1);
|
||||
std::string set_bed_temperature(unsigned int temperature, bool wait = false);
|
||||
std::string set_fan(unsigned int speed, bool dont_save = false);
|
||||
|
|
|
@ -61,13 +61,18 @@ LayerRegion::prepare_fill_surfaces()
|
|||
the only meaningful information returned by psPerimeters. */
|
||||
|
||||
// if no solid layers are requested, turn top/bottom surfaces to internal
|
||||
if (this->_region->config.top_solid_layers == 0) {
|
||||
if (this->region()->config.top_solid_layers == 0) {
|
||||
for (Surfaces::iterator surface = this->fill_surfaces.surfaces.begin(); surface != this->fill_surfaces.surfaces.end(); ++surface) {
|
||||
if (surface->surface_type == stTop)
|
||||
surface->surface_type = stInternal;
|
||||
if (surface->surface_type == stTop) {
|
||||
if (this->layer()->object()->config.infill_only_where_needed) {
|
||||
surface->surface_type = stInternalVoid;
|
||||
} else {
|
||||
surface->surface_type = stInternal;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this->_region->config.bottom_solid_layers == 0) {
|
||||
if (this->region()->config.bottom_solid_layers == 0) {
|
||||
for (Surfaces::iterator surface = this->fill_surfaces.surfaces.begin(); surface != this->fill_surfaces.surfaces.end(); ++surface) {
|
||||
if (surface->surface_type == stBottom || surface->surface_type == stBottomBridge)
|
||||
surface->surface_type = stInternal;
|
||||
|
@ -75,9 +80,9 @@ LayerRegion::prepare_fill_surfaces()
|
|||
}
|
||||
|
||||
// turn too small internal regions into solid regions according to the user setting
|
||||
if (this->_region->config.fill_density.value > 0) {
|
||||
if (this->region()->config.fill_density.value > 0) {
|
||||
// scaling an area requires two calls!
|
||||
double min_area = scale_(scale_(this->_region->config.solid_infill_below_area.value));
|
||||
double min_area = scale_(scale_(this->region()->config.solid_infill_below_area.value));
|
||||
for (Surfaces::iterator surface = this->fill_surfaces.surfaces.begin(); surface != this->fill_surfaces.surfaces.end(); ++surface) {
|
||||
if (surface->surface_type == stInternal && surface->area() <= min_area)
|
||||
surface->surface_type = stInternalSolid;
|
||||
|
|
|
@ -166,13 +166,14 @@ Print::invalidate_state_by_config_options(const std::vector<t_config_option_key>
|
|||
if (*opt_key == "skirts"
|
||||
|| *opt_key == "skirt_height"
|
||||
|| *opt_key == "skirt_distance"
|
||||
|| *opt_key == "min_skirt_length") {
|
||||
|| *opt_key == "min_skirt_length"
|
||||
|| *opt_key == "ooze_prevention") {
|
||||
steps.insert(psSkirt);
|
||||
} else if (*opt_key == "brim_width") {
|
||||
steps.insert(psBrim);
|
||||
steps.insert(psSkirt);
|
||||
} else if (*opt_key == "nozzle_diameter") {
|
||||
steps.insert(psInitExtruders);
|
||||
osteps.insert(posSlice);
|
||||
} else if (*opt_key == "avoid_crossing_perimeters"
|
||||
|| *opt_key == "bed_shape"
|
||||
|| *opt_key == "bed_temperature"
|
||||
|
@ -266,11 +267,6 @@ Print::invalidate_step(PrintStep step)
|
|||
// propagate to dependent steps
|
||||
if (step == psSkirt) {
|
||||
this->invalidate_step(psBrim);
|
||||
} else if (step == psInitExtruders) {
|
||||
FOREACH_OBJECT(this, object) {
|
||||
(*object)->invalidate_step(posPerimeters);
|
||||
(*object)->invalidate_step(posSupportMaterial);
|
||||
}
|
||||
}
|
||||
|
||||
return invalidated;
|
||||
|
@ -309,13 +305,22 @@ Print::extruders() const
|
|||
std::set<size_t> extruders;
|
||||
|
||||
FOREACH_REGION(this, region) {
|
||||
extruders.insert((*region)->config.perimeter_extruder - 1);
|
||||
extruders.insert((*region)->config.infill_extruder - 1);
|
||||
extruders.insert((*region)->config.solid_infill_extruder - 1);
|
||||
// these checks reflect the same logic used in the GUI for enabling/disabling
|
||||
// extruder selection fields
|
||||
if ((*region)->config.perimeters.value > 0 || this->config.brim_width.value > 0)
|
||||
extruders.insert((*region)->config.perimeter_extruder - 1);
|
||||
|
||||
if ((*region)->config.fill_density.value > 0)
|
||||
extruders.insert((*region)->config.infill_extruder - 1);
|
||||
|
||||
if ((*region)->config.top_solid_layers.value > 0 || (*region)->config.bottom_solid_layers.value > 0)
|
||||
extruders.insert((*region)->config.solid_infill_extruder - 1);
|
||||
}
|
||||
FOREACH_OBJECT(this, object) {
|
||||
extruders.insert((*object)->config.support_material_extruder - 1);
|
||||
extruders.insert((*object)->config.support_material_interface_extruder - 1);
|
||||
if ((*object)->has_support_material()) {
|
||||
extruders.insert((*object)->config.support_material_extruder - 1);
|
||||
extruders.insert((*object)->config.support_material_interface_extruder - 1);
|
||||
}
|
||||
}
|
||||
|
||||
return extruders;
|
||||
|
@ -534,20 +539,16 @@ Print::apply_config(DynamicPrintConfig config)
|
|||
return invalidated;
|
||||
}
|
||||
|
||||
void
|
||||
Print::init_extruders()
|
||||
bool Print::has_infinite_skirt() const
|
||||
{
|
||||
if (this->state.is_done(psInitExtruders)) return;
|
||||
this->state.set_done(psInitExtruders);
|
||||
|
||||
// enforce tall skirt if using ooze_prevention
|
||||
// FIXME: this is not idempotent (i.e. switching ooze_prevention off will not revert skirt settings)
|
||||
if (this->config.ooze_prevention && this->extruders().size() > 1) {
|
||||
this->config.skirt_height.value = -1;
|
||||
if (this->config.skirts == 0) this->config.skirts.value = 1;
|
||||
}
|
||||
|
||||
this->state.set_done(psInitExtruders);
|
||||
return (this->config.skirt_height == -1 && this->config.skirts > 0)
|
||||
|| (this->config.ooze_prevention && this->extruders().size() > 1);
|
||||
}
|
||||
|
||||
bool Print::has_skirt() const
|
||||
{
|
||||
return (this->config.skirt_height > 0 && this->config.skirts > 0)
|
||||
|| this->has_infinite_skirt();
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -625,17 +626,37 @@ Print::validate() const
|
|||
}
|
||||
|
||||
{
|
||||
std::vector<double> layer_heights;
|
||||
// find the smallest nozzle diameter
|
||||
std::set<size_t> extruders = this->extruders();
|
||||
if (extruders.empty())
|
||||
throw PrintValidationException("The supplied settings will cause an empty print.");
|
||||
|
||||
std::set<double> nozzle_diameters;
|
||||
for (std::set<size_t>::iterator it = extruders.begin(); it != extruders.end(); ++it)
|
||||
nozzle_diameters.insert(this->config.nozzle_diameter.get_at(*it));
|
||||
double min_nozzle_diameter = *std::min_element(nozzle_diameters.begin(), nozzle_diameters.end());
|
||||
|
||||
FOREACH_OBJECT(this, i_object) {
|
||||
PrintObject* object = *i_object;
|
||||
layer_heights.push_back(object->config.layer_height);
|
||||
layer_heights.push_back(object->config.get_abs_value("first_layer_height"));
|
||||
}
|
||||
double max_layer_height = *std::max_element(layer_heights.begin(), layer_heights.end());
|
||||
|
||||
std::set<size_t> extruders = this->extruders();
|
||||
for (std::set<size_t>::iterator it = extruders.begin(); it != extruders.end(); ++it) {
|
||||
if (max_layer_height > this->config.nozzle_diameter.get_at(*it))
|
||||
|
||||
// validate first_layer_height
|
||||
double first_layer_height = object->config.get_abs_value("first_layer_height");
|
||||
double first_layer_min_nozzle_diameter;
|
||||
if (object->config.raft_layers > 0) {
|
||||
// if we have raft layers, only support material extruder is used on first layer
|
||||
size_t first_layer_extruder = object->config.raft_layers == 1
|
||||
? object->config.support_material_interface_extruder-1
|
||||
: object->config.support_material_extruder-1;
|
||||
first_layer_min_nozzle_diameter = this->config.nozzle_diameter.get_at(first_layer_extruder);
|
||||
} else {
|
||||
// if we don't have raft layers, any nozzle diameter is potentially used in first layer
|
||||
first_layer_min_nozzle_diameter = min_nozzle_diameter;
|
||||
}
|
||||
if (first_layer_height > first_layer_min_nozzle_diameter)
|
||||
throw PrintValidationException("First layer height can't be greater than nozzle diameter");
|
||||
|
||||
// validate layer_height
|
||||
if (object->config.layer_height.value > min_nozzle_diameter)
|
||||
throw PrintValidationException("Layer height can't be greater than nozzle diameter");
|
||||
}
|
||||
}
|
||||
|
@ -682,13 +703,15 @@ Print::total_bounding_box() const
|
|||
Flow brim_flow = this->brim_flow();
|
||||
extra = std::max(extra, this->config.brim_width.value + brim_flow.width/2);
|
||||
}
|
||||
if (this->config.skirts.value > 0) {
|
||||
if (this->has_skirt()) {
|
||||
int skirts = this->config.skirts.value;
|
||||
if (skirts == 0 && this->has_infinite_skirt()) skirts = 1;
|
||||
Flow skirt_flow = this->skirt_flow();
|
||||
extra = std::max(
|
||||
extra,
|
||||
this->config.brim_width.value
|
||||
+ this->config.skirt_distance.value
|
||||
+ this->config.skirts.value * skirt_flow.spacing()
|
||||
+ skirts * skirt_flow.spacing()
|
||||
+ skirt_flow.width/2
|
||||
);
|
||||
}
|
||||
|
@ -773,9 +796,7 @@ bool
|
|||
Print::has_support_material() const
|
||||
{
|
||||
FOREACH_OBJECT(this, object) {
|
||||
PrintObjectConfig &config = (*object)->config;
|
||||
if (config.support_material || config.raft_layers > 0 || config.support_material_enforce_layers > 0)
|
||||
return true;
|
||||
if ((*object)->has_support_material()) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ class ModelObject;
|
|||
|
||||
|
||||
enum PrintStep {
|
||||
psInitExtruders, psSkirt, psBrim,
|
||||
psSkirt, psBrim,
|
||||
};
|
||||
enum PrintObjectStep {
|
||||
posSlice, posPerimeters, posPrepareInfill,
|
||||
|
@ -134,6 +134,7 @@ class PrintObject
|
|||
bool invalidate_step(PrintObjectStep step);
|
||||
bool invalidate_all_steps();
|
||||
|
||||
bool has_support_material() const;
|
||||
void bridge_over_infill();
|
||||
|
||||
private:
|
||||
|
@ -189,7 +190,8 @@ class Print
|
|||
|
||||
void add_model_object(ModelObject* model_object, int idx = -1);
|
||||
bool apply_config(DynamicPrintConfig config);
|
||||
void init_extruders();
|
||||
bool has_infinite_skirt() const;
|
||||
bool has_skirt() const;
|
||||
void validate() const;
|
||||
BoundingBox bounding_box() const;
|
||||
BoundingBox total_bounding_box() const;
|
||||
|
|
|
@ -380,12 +380,14 @@ PrintConfigDef::build_def() {
|
|||
Options["gcode_flavor"].enum_values.push_back("makerware");
|
||||
Options["gcode_flavor"].enum_values.push_back("sailfish");
|
||||
Options["gcode_flavor"].enum_values.push_back("mach3");
|
||||
Options["gcode_flavor"].enum_values.push_back("machinekit");
|
||||
Options["gcode_flavor"].enum_values.push_back("no-extrusion");
|
||||
Options["gcode_flavor"].enum_labels.push_back("RepRap (Marlin/Sprinter/Repetier)");
|
||||
Options["gcode_flavor"].enum_labels.push_back("Teacup");
|
||||
Options["gcode_flavor"].enum_labels.push_back("MakerWare (MakerBot)");
|
||||
Options["gcode_flavor"].enum_labels.push_back("Sailfish (MakerBot)");
|
||||
Options["gcode_flavor"].enum_labels.push_back("Mach3/LinuxCNC");
|
||||
Options["gcode_flavor"].enum_labels.push_back("Machinekit");
|
||||
Options["gcode_flavor"].enum_labels.push_back("No extrusion");
|
||||
|
||||
Options["infill_acceleration"].type = coFloat;
|
||||
|
@ -426,7 +428,7 @@ PrintConfigDef::build_def() {
|
|||
Options["infill_only_where_needed"].type = coBool;
|
||||
Options["infill_only_where_needed"].label = "Only infill where needed";
|
||||
Options["infill_only_where_needed"].category = "Infill";
|
||||
Options["infill_only_where_needed"].tooltip = "This option will limit infill to the areas actually needed for supporting ceilings (it will act as internal support material).";
|
||||
Options["infill_only_where_needed"].tooltip = "This option will limit infill to the areas actually needed for supporting ceilings (it will act as internal support material). If enabled, slows down the G-code generation due to the multiple checks involved.";
|
||||
Options["infill_only_where_needed"].cli = "infill-only-where-needed!";
|
||||
|
||||
Options["infill_overlap"].type = coFloatOrPercent;
|
||||
|
@ -1028,6 +1030,40 @@ PrintConfigDef::build_def() {
|
|||
|
||||
t_optiondef_map PrintConfigDef::def = PrintConfigDef::build_def();
|
||||
|
||||
void
|
||||
DynamicPrintConfig::normalize() {
|
||||
if (this->has("extruder")) {
|
||||
int extruder = this->option("extruder")->getInt();
|
||||
this->erase("extruder");
|
||||
if (extruder != 0) {
|
||||
if (!this->has("infill_extruder"))
|
||||
this->option("infill_extruder", true)->setInt(extruder);
|
||||
if (!this->has("perimeter_extruder"))
|
||||
this->option("perimeter_extruder", true)->setInt(extruder);
|
||||
if (!this->has("support_material_extruder"))
|
||||
this->option("support_material_extruder", true)->setInt(extruder);
|
||||
if (!this->has("support_material_interface_extruder"))
|
||||
this->option("support_material_interface_extruder", true)->setInt(extruder);
|
||||
}
|
||||
}
|
||||
|
||||
if (!this->has("solid_infill_extruder") && this->has("infill_extruder"))
|
||||
this->option("solid_infill_extruder", true)->setInt(this->option("infill_extruder")->getInt());
|
||||
|
||||
if (this->has("spiral_vase") && this->opt<ConfigOptionBool>("spiral_vase", true)->value) {
|
||||
{
|
||||
// this should be actually done only on the spiral layers instead of all
|
||||
ConfigOptionBools* opt = this->opt<ConfigOptionBools>("retract_layer_change", true);
|
||||
opt->values.assign(opt->values.size(), false); // set all values to false
|
||||
}
|
||||
{
|
||||
this->opt<ConfigOptionInt>("perimeters", true)->value = 1;
|
||||
this->opt<ConfigOptionInt>("top_solid_layers", true)->value = 0;
|
||||
this->opt<ConfigOptionPercent>("fill_density", true)->value = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef SLIC3RXS
|
||||
REGISTER_CLASS(DynamicPrintConfig, "Config");
|
||||
REGISTER_CLASS(PrintObjectConfig, "Config::PrintObject");
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
namespace Slic3r {
|
||||
|
||||
enum GCodeFlavor {
|
||||
gcfRepRap, gcfTeacup, gcfMakerWare, gcfSailfish, gcfMach3, gcfNoExtrusion,
|
||||
gcfRepRap, gcfTeacup, gcfMakerWare, gcfSailfish, gcfMach3, gcfMachinekit, gcfNoExtrusion,
|
||||
};
|
||||
|
||||
enum InfillPattern {
|
||||
|
@ -29,6 +29,7 @@ template<> inline t_config_enum_values ConfigOptionEnum<GCodeFlavor>::get_enum_v
|
|||
keys_map["makerware"] = gcfMakerWare;
|
||||
keys_map["sailfish"] = gcfSailfish;
|
||||
keys_map["mach3"] = gcfMach3;
|
||||
keys_map["machinekit"] = gcfMachinekit;
|
||||
keys_map["no-extrusion"] = gcfNoExtrusion;
|
||||
return keys_map;
|
||||
}
|
||||
|
@ -78,38 +79,7 @@ class DynamicPrintConfig : public DynamicConfig
|
|||
this->def = &PrintConfigDef::def;
|
||||
};
|
||||
|
||||
void normalize() {
|
||||
if (this->has("extruder")) {
|
||||
int extruder = this->option("extruder")->getInt();
|
||||
this->erase("extruder");
|
||||
if (extruder != 0) {
|
||||
if (!this->has("infill_extruder"))
|
||||
this->option("infill_extruder", true)->setInt(extruder);
|
||||
if (!this->has("perimeter_extruder"))
|
||||
this->option("perimeter_extruder", true)->setInt(extruder);
|
||||
if (!this->has("support_material_extruder"))
|
||||
this->option("support_material_extruder", true)->setInt(extruder);
|
||||
if (!this->has("support_material_interface_extruder"))
|
||||
this->option("support_material_interface_extruder", true)->setInt(extruder);
|
||||
}
|
||||
}
|
||||
|
||||
if (!this->has("solid_infill_extruder") && this->has("infill_extruder"))
|
||||
this->option("solid_infill_extruder", true)->setInt(this->option("infill_extruder")->getInt());
|
||||
|
||||
if (this->has("spiral_vase") && this->opt<ConfigOptionBool>("spiral_vase", true)->value) {
|
||||
{
|
||||
// this should be actually done only on the spiral layers instead of all
|
||||
ConfigOptionBools* opt = this->opt<ConfigOptionBools>("retract_layer_change", true);
|
||||
opt->values.assign(opt->values.size(), false); // set all values to false
|
||||
}
|
||||
{
|
||||
this->opt<ConfigOptionInt>("perimeters", true)->value = 1;
|
||||
this->opt<ConfigOptionInt>("top_solid_layers", true)->value = 0;
|
||||
this->opt<ConfigOptionPercent>("fill_density", true)->value = 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
void normalize();
|
||||
};
|
||||
|
||||
class StaticPrintConfig : public virtual StaticConfig
|
||||
|
@ -410,7 +380,7 @@ class GCodeConfig : public virtual StaticPrintConfig
|
|||
|
||||
std::string get_extrusion_axis() const
|
||||
{
|
||||
if (this->gcode_flavor.value == gcfMach3) {
|
||||
if ((this->gcode_flavor.value == gcfMach3) || (this->gcode_flavor.value == gcfMachinekit)) {
|
||||
return "A";
|
||||
} else if (this->gcode_flavor.value == gcfNoExtrusion) {
|
||||
return "";
|
||||
|
|
|
@ -333,6 +333,14 @@ PrintObject::invalidate_all_steps()
|
|||
return invalidated;
|
||||
}
|
||||
|
||||
bool
|
||||
PrintObject::has_support_material() const
|
||||
{
|
||||
return this->config.support_material
|
||||
|| this->config.raft_layers > 0
|
||||
|| this->config.support_material_enforce_layers > 0;
|
||||
}
|
||||
|
||||
void
|
||||
PrintObject::bridge_over_infill()
|
||||
{
|
||||
|
@ -340,7 +348,7 @@ PrintObject::bridge_over_infill()
|
|||
size_t region_id = region - this->_print->regions.begin();
|
||||
|
||||
double fill_density = (*region)->config.fill_density.value;
|
||||
if (fill_density == 100 || fill_density == 0) continue;
|
||||
if (fill_density == 100) continue;
|
||||
|
||||
FOREACH_LAYER(this, layer_it) {
|
||||
if (layer_it == this->layers.begin()) continue;
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
#define SLIC3R_VERSION "1.2.6-dev"
|
||||
#define SLIC3R_VERSION "1.2.7-dev"
|
||||
|
||||
#define EPSILON 1e-4
|
||||
#define SCALING_FACTOR 0.000001
|
||||
|
|
|
@ -3,8 +3,9 @@
|
|||
use strict;
|
||||
use warnings;
|
||||
|
||||
use List::Util qw(sum);
|
||||
use Slic3r::XS;
|
||||
use Test::More tests => 19;
|
||||
use Test::More tests => 23;
|
||||
|
||||
my $square = Slic3r::Polygon->new( # ccw
|
||||
[200, 100],
|
||||
|
@ -178,8 +179,8 @@ if (0) { # Clipper does not preserve polyline orientation
|
|||
is scalar(@$result), 1, 'intersection_ppl - result is not empty';
|
||||
}
|
||||
|
||||
if (0) {
|
||||
# Disabled until Clipper bug #122 is fixed
|
||||
{
|
||||
# Clipper bug #122
|
||||
my $subject = [
|
||||
Slic3r::Polyline->new([1975,1975],[25,1975],[25,25],[1975,25],[1975,1975]),
|
||||
];
|
||||
|
@ -188,9 +189,51 @@ if (0) {
|
|||
Slic3r::Polygon->new([525,525],[525,1475],[1475,1475],[1475,525]),
|
||||
];
|
||||
my $result = Slic3r::Geometry::Clipper::intersection_pl($subject, $clip);
|
||||
###use XXX; YYY $subject->[0]->wkt, [map $_->wkt, @$clip], $result->[0]->wkt;
|
||||
is scalar(@$result), 1, 'intersection_pl - result is not empty';
|
||||
is scalar(@{$result->[0]}), 5, 'intersection_pl - result is not empty';
|
||||
}
|
||||
|
||||
{
|
||||
# Clipper bug #126
|
||||
my $subject = Slic3r::Polyline->new(
|
||||
[200000,19799999],[200000,200000],[24304692,200000],[15102879,17506106],[13883200,19799999],[200000,19799999],
|
||||
);
|
||||
my $clip = [
|
||||
Slic3r::Polygon->new([15257205,18493894],[14350057,20200000],[-200000,20200000],[-200000,-200000],[25196917,-200000]),
|
||||
];
|
||||
my $result = Slic3r::Geometry::Clipper::intersection_pl([$subject], $clip);
|
||||
is scalar(@$result), 1, 'intersection_pl - result is not empty';
|
||||
is $result->[0]->length, $subject->length, 'intersection_pl - result has same length as subject polyline';
|
||||
}
|
||||
|
||||
if (0) {
|
||||
# Disabled until Clipper bug #127 is fixed
|
||||
my $subject = [
|
||||
Slic3r::Polyline->new([-90000000,-100000000],[-90000000,100000000]), # vertical
|
||||
Slic3r::Polyline->new([-100000000,-10000000],[100000000,-10000000]), # horizontal
|
||||
Slic3r::Polyline->new([-100000000,0],[100000000,0]), # horizontal
|
||||
Slic3r::Polyline->new([-100000000,10000000],[100000000,10000000]), # horizontal
|
||||
];
|
||||
my $clip = Slic3r::Polygon->new( # a circular, convex, polygon
|
||||
[99452190,10452846],[97814760,20791169],[95105652,30901699],[91354546,40673664],[86602540,50000000],
|
||||
[80901699,58778525],[74314483,66913061],[66913061,74314483],[58778525,80901699],[50000000,86602540],
|
||||
[40673664,91354546],[30901699,95105652],[20791169,97814760],[10452846,99452190],[0,100000000],
|
||||
[-10452846,99452190],[-20791169,97814760],[-30901699,95105652],[-40673664,91354546],
|
||||
[-50000000,86602540],[-58778525,80901699],[-66913061,74314483],[-74314483,66913061],
|
||||
[-80901699,58778525],[-86602540,50000000],[-91354546,40673664],[-95105652,30901699],
|
||||
[-97814760,20791169],[-99452190,10452846],[-100000000,0],[-99452190,-10452846],
|
||||
[-97814760,-20791169],[-95105652,-30901699],[-91354546,-40673664],[-86602540,-50000000],
|
||||
[-80901699,-58778525],[-74314483,-66913061],[-66913061,-74314483],[-58778525,-80901699],
|
||||
[-50000000,-86602540],[-40673664,-91354546],[-30901699,-95105652],[-20791169,-97814760],
|
||||
[-10452846,-99452190],[0,-100000000],[10452846,-99452190],[20791169,-97814760],
|
||||
[30901699,-95105652],[40673664,-91354546],[50000000,-86602540],[58778525,-80901699],
|
||||
[66913061,-74314483],[74314483,-66913061],[80901699,-58778525],[86602540,-50000000],
|
||||
[91354546,-40673664],[95105652,-30901699],[97814760,-20791169],[99452190,-10452846],[100000000,0]
|
||||
);
|
||||
my $result = Slic3r::Geometry::Clipper::intersection_pl($subject, [$clip]);
|
||||
is scalar(@$result), scalar(@$subject), 'intersection_pl - expected number of polylines';
|
||||
is sum(map scalar(@$_), @$result), scalar(@$subject)*2,
|
||||
'intersection_pl - expected number of points in polylines';
|
||||
}
|
||||
|
||||
__END__
|
||||
|
|
|
@ -4,7 +4,7 @@ use strict;
|
|||
use warnings;
|
||||
|
||||
use Slic3r::XS;
|
||||
use Test::More tests => 108;
|
||||
use Test::More tests => 110;
|
||||
|
||||
foreach my $config (Slic3r::Config->new, Slic3r::Config::Full->new) {
|
||||
$config->set('layer_height', 0.3);
|
||||
|
@ -51,6 +51,8 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Full->new) {
|
|||
is $config->serialize('gcode_flavor'), 'teacup', 'serialize enum';
|
||||
$config->set_deserialize('gcode_flavor', 'mach3');
|
||||
is $config->get('gcode_flavor'), 'mach3', 'deserialize enum (gcode_flavor)';
|
||||
$config->set_deserialize('gcode_flavor', 'machinekit');
|
||||
is $config->get('gcode_flavor'), 'machinekit', 'deserialize enum (gcode_flavor)';
|
||||
|
||||
$config->set_deserialize('fill_pattern', 'line');
|
||||
is $config->get('fill_pattern'), 'line', 'deserialize enum (fill_pattern)';
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
%code{% THIS->apply_print_config(*print_config); %};
|
||||
void set_extruders(std::vector<unsigned int> extruder_ids);
|
||||
std::string preamble();
|
||||
std::string postamble();
|
||||
std::string set_temperature(unsigned int temperature, bool wait = false, int tool = -1);
|
||||
std::string set_bed_temperature(unsigned int temperature, bool wait = false);
|
||||
std::string set_fan(unsigned int speed, bool dont_save = false);
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
IV
|
||||
_constant()
|
||||
ALIAS:
|
||||
STEP_INIT_EXTRUDERS = psInitExtruders
|
||||
STEP_SLICE = posSlice
|
||||
STEP_PERIMETERS = posPerimeters
|
||||
STEP_PREPARE_INFILL = posPrepareInfill
|
||||
|
@ -199,7 +198,8 @@ _constant()
|
|||
void add_model_object(ModelObject* model_object, int idx = -1);
|
||||
bool apply_config(DynamicPrintConfig* config)
|
||||
%code%{ RETVAL = THIS->apply_config(*config); %};
|
||||
void init_extruders();
|
||||
bool has_infinite_skirt();
|
||||
bool has_skirt();
|
||||
void validate()
|
||||
%code%{
|
||||
try {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue