Add more control over how corners are handled when inflating polygons.
This commit is contained in:
parent
a61673a144
commit
2c5876ee8c
|
@ -544,7 +544,8 @@ void SHAPE_POLY_SET::BooleanIntersection( const SHAPE_POLY_SET& a,
|
|||
}
|
||||
|
||||
|
||||
void SHAPE_POLY_SET::InflateWithLinkedHoles( int aFactor, int aCircleSegmentsCount, POLYGON_MODE aFastMode )
|
||||
void SHAPE_POLY_SET::InflateWithLinkedHoles( int aFactor, int aCircleSegmentsCount,
|
||||
POLYGON_MODE aFastMode )
|
||||
{
|
||||
Simplify( aFastMode );
|
||||
Inflate( aFactor, aCircleSegmentsCount );
|
||||
|
@ -552,23 +553,23 @@ void SHAPE_POLY_SET::InflateWithLinkedHoles( int aFactor, int aCircleSegmentsCou
|
|||
}
|
||||
|
||||
|
||||
void SHAPE_POLY_SET::Inflate( int aFactor, int aCircleSegmentsCount, bool aPreserveCorners )
|
||||
void SHAPE_POLY_SET::Inflate( int aAmount, int aCircleSegmentsCount,
|
||||
CORNER_STRATEGY aCornerStrategy )
|
||||
{
|
||||
// A static table to avoid repetitive calculations of the coefficient
|
||||
// 1.0 - cos( M_PI/aCircleSegmentsCount)
|
||||
// 1.0 - cos( M_PI / aCircleSegmentsCount )
|
||||
// aCircleSegmentsCount is most of time <= 64 and usually 8, 12, 16, 32
|
||||
#define SEG_CNT_MAX 64
|
||||
static double arc_tolerance_factor[SEG_CNT_MAX + 1];
|
||||
|
||||
ClipperOffset c;
|
||||
|
||||
// N.B. using jtSquare here does not create square corners; they end up mitered by aFactor.
|
||||
// Setting jtMiter with a sufficiently high MiterLimit will preserve corners, but things
|
||||
// get ugly at very acute angles (and we don't really want to support those anyway for peeling
|
||||
// concerns). Setting a MiterLimit of 1.4145 preserves corners up to 90 degrees; we set the
|
||||
// limit a bit above that.
|
||||
JoinType joinType = aPreserveCorners ? jtMiter : jtRound;
|
||||
double miterLimit = 1.5;
|
||||
// N.B. see the Clipper documentation for jtSquare/jtMiter/jtRound. They are poorly named
|
||||
// and are not what you'd think they are.
|
||||
// http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Types/JoinType.htm
|
||||
JoinType joinType = aCornerStrategy == ROUND_ALL_CORNERS ? jtRound : jtMiter;
|
||||
double miterLimit = aCornerStrategy == ALLOW_ACUTE_CORNERS ? 10 : 1.5;
|
||||
JoinType miterFallback = aCornerStrategy == ROUND_ACUTE_CORNERS ? jtRound : jtSquare;
|
||||
|
||||
for( const POLYGON& poly : m_polys )
|
||||
{
|
||||
|
@ -578,10 +579,9 @@ void SHAPE_POLY_SET::Inflate( int aFactor, int aCircleSegmentsCount, bool aPrese
|
|||
|
||||
PolyTree solution;
|
||||
|
||||
// Calculate the arc tolerance (arc error) from the seg count by circle.
|
||||
// the seg count is nn = M_PI / acos(1.0 - c.ArcTolerance / abs(aFactor))
|
||||
// see:
|
||||
// www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Classes/ClipperOffset/Properties/ArcTolerance.htm
|
||||
// Calculate the arc tolerance (arc error) from the seg count by circle. The seg count is
|
||||
// nn = M_PI / acos(1.0 - c.ArcTolerance / abs(aAmount))
|
||||
// http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Classes/ClipperOffset/Properties/ArcTolerance.htm
|
||||
|
||||
if( aCircleSegmentsCount < 6 ) // avoid incorrect aCircleSegmentsCount values
|
||||
aCircleSegmentsCount = 6;
|
||||
|
@ -598,9 +598,10 @@ void SHAPE_POLY_SET::Inflate( int aFactor, int aCircleSegmentsCount, bool aPrese
|
|||
else
|
||||
coeff = arc_tolerance_factor[aCircleSegmentsCount];
|
||||
|
||||
c.ArcTolerance = std::abs( aFactor ) * coeff;
|
||||
c.ArcTolerance = std::abs( aAmount ) * coeff;
|
||||
c.MiterLimit = miterLimit;
|
||||
c.Execute( solution, aFactor );
|
||||
c.MiterFallback = miterFallback;
|
||||
c.Execute( solution, aAmount );
|
||||
|
||||
importTree( &solution );
|
||||
}
|
||||
|
|
|
@ -165,11 +165,16 @@ class SHAPE_POLY_SET : public SHAPE
|
|||
|
||||
operator bool() const
|
||||
{
|
||||
return ( ( m_currentPolygon < m_lastPolygon ) || ( m_currentPolygon == m_poly->OutlineCount() - 1 &&
|
||||
( m_currentContour < static_cast<int>( m_poly->CPolygon( m_currentPolygon ).size() ) - 1 ||
|
||||
( m_currentVertex < m_poly->CPolygon( m_currentPolygon )[m_currentContour].PointCount() )
|
||||
)
|
||||
) );
|
||||
if( m_currentPolygon < m_lastPolygon )
|
||||
return true;
|
||||
|
||||
if( m_currentPolygon != m_poly->OutlineCount() - 1 )
|
||||
return false;
|
||||
|
||||
auto currentPolygon = m_poly->CPolygon( m_currentPolygon );
|
||||
|
||||
return m_currentContour < (int) currentPolygon.size() - 1
|
||||
|| m_currentVertex < currentPolygon[m_currentContour].PointCount();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -859,29 +864,40 @@ class SHAPE_POLY_SET : public SHAPE
|
|||
void BooleanIntersection( const SHAPE_POLY_SET& a, const SHAPE_POLY_SET& b,
|
||||
POLYGON_MODE aFastMode );
|
||||
|
||||
/**
|
||||
* Performs outline inflation/deflation, using (optionally) round corners.
|
||||
* Polygons can have holes, but not linked holes with main outlines,
|
||||
* if aFactor < 0.
|
||||
* When aFactor is < 0 a bad shape can result from these extra-segments used to
|
||||
* link holes to main outlines
|
||||
* Use InflateWithLinkedHoles for these polygons, especially if aFactor < 0
|
||||
*
|
||||
* @param aFactor - number of units to offset edges
|
||||
* @param aCircleSegmentsCount - number of segments per 360° to use in curve approx
|
||||
* @param aPreserveCorners - If true, use square joints to keep angles preserved
|
||||
*/
|
||||
void Inflate( int aFactor, int aCircleSegmentsCount, bool aPreserveCorners = false );
|
||||
|
||||
void Deflate( int aFactor, int aCircleSegmentsCount, bool aPreserveCorners = false )
|
||||
enum CORNER_STRATEGY
|
||||
{
|
||||
Inflate( -aFactor, aPreserveCorners, aPreserveCorners );
|
||||
ALLOW_ACUTE_CORNERS,
|
||||
CHOP_ACUTE_CORNERS,
|
||||
ROUND_ACUTE_CORNERS,
|
||||
ROUND_ALL_CORNERS
|
||||
};
|
||||
|
||||
/**
|
||||
* Performs outline inflation/deflation. Polygons can have holes, but not linked holes
|
||||
* with main outlines, if aFactor < 0. For those use InflateWithLinkedHoles() to avoid
|
||||
* odd corners where the link segments meet the outline.
|
||||
*
|
||||
* @param aAmount - number of units to offset edges
|
||||
* @param aCircleSegmentsCount - number of segments per 360° to use in curve approx
|
||||
* @param aCornerStrategy - ALLOW_ACUTE_CORNERS to preserve all angles,
|
||||
* CHOP_ACUTE_CORNERS to chop angles less than 90°,
|
||||
* ROUND_ACUTE_CORNERS to round off angles less than 90°,
|
||||
* ROUND_ALL_CORNERS to round regardless of angles
|
||||
*/
|
||||
void Inflate( int aAmount, int aCircleSegmentsCount,
|
||||
CORNER_STRATEGY aCornerStrategy = CHOP_ACUTE_CORNERS );
|
||||
|
||||
void Deflate( int aAmount, int aCircleSegmentsCount,
|
||||
CORNER_STRATEGY aCornerStrategy = CHOP_ACUTE_CORNERS )
|
||||
{
|
||||
Inflate( -aAmount, aCircleSegmentsCount, aCornerStrategy );
|
||||
}
|
||||
|
||||
///> Performs outline inflation/deflation, using round corners.
|
||||
///> Polygons can have holes, and/or linked holes with main outlines.
|
||||
///> The resulting polygons are laso polygons with linked holes to main outlines
|
||||
///> For aFastMode meaning, see function booleanOp
|
||||
/**
|
||||
* Performs outline inflation/deflation, using round corners. Polygons can have holes,
|
||||
* and/or linked holes with main outlines. The resulting polygons are laso polygons with
|
||||
* linked holes to main outlines. For aFastMode meaning, see function booleanOp .
|
||||
*/
|
||||
void InflateWithLinkedHoles( int aFactor, int aCircleSegmentsCount, POLYGON_MODE aFastMode );
|
||||
|
||||
///> Converts a set of polygons with holes to a singe outline with "slits"/"fractures"
|
||||
|
|
|
@ -1186,7 +1186,7 @@ ZONE_CONTAINER* EAGLE_PLUGIN::loadPolygon( wxXmlNode* aPolyNode )
|
|||
// We trace the zone such that the copper is completely inside.
|
||||
if( p.width.ToPcbUnits() > 0 )
|
||||
{
|
||||
polygon.Inflate( p.width.ToPcbUnits() / 2, 32, true );
|
||||
polygon.Inflate( p.width.ToPcbUnits() / 2, 32, SHAPE_POLY_SET::ALLOW_ACUTE_CORNERS );
|
||||
polygon.Fracture( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
|
||||
}
|
||||
|
||||
|
|
|
@ -693,6 +693,11 @@ void ZONE_FILLER::computeRawFilledArea( const ZONE_CONTAINER* aZone,
|
|||
int epsilon = Millimeter2iu( 0.001 );
|
||||
int numSegs = std::max( GetArcToSegmentCount( half_min_width, m_high_def, 360.0 ), 6 );
|
||||
|
||||
SHAPE_POLY_SET::CORNER_STRATEGY cornerStrategy = SHAPE_POLY_SET::CHOP_ACUTE_CORNERS;
|
||||
|
||||
if( aZone->GetCornerSmoothingType() == ZONE_SETTINGS::SMOOTHING_FILLET )
|
||||
cornerStrategy = SHAPE_POLY_SET::ROUND_ACUTE_CORNERS;
|
||||
|
||||
std::deque<SHAPE_LINE_CHAIN> thermalSpokes;
|
||||
SHAPE_POLY_SET clearanceHoles;
|
||||
|
||||
|
@ -723,8 +728,11 @@ void ZONE_FILLER::computeRawFilledArea( const ZONE_CONTAINER* aZone,
|
|||
testAreas.BooleanSubtract( clearanceHoles, SHAPE_POLY_SET::PM_FAST );
|
||||
|
||||
// Prune features that don't meet minimum-width criteria
|
||||
testAreas.Deflate( half_min_width - epsilon, numSegs, true );
|
||||
testAreas.Inflate( half_min_width - epsilon, numSegs, true );
|
||||
if( half_min_width - epsilon > epsilon )
|
||||
{
|
||||
testAreas.Deflate( half_min_width - epsilon, numSegs, cornerStrategy );
|
||||
testAreas.Inflate( half_min_width - epsilon, numSegs, cornerStrategy );
|
||||
}
|
||||
|
||||
// Spoke-end-testing is hugely expensive so we generate cached bounding-boxes to speed
|
||||
// things up a bit.
|
||||
|
@ -759,7 +767,8 @@ void ZONE_FILLER::computeRawFilledArea( const ZONE_CONTAINER* aZone,
|
|||
|
||||
aRawPolys.BooleanSubtract( clearanceHoles, SHAPE_POLY_SET::PM_FAST );
|
||||
// Prune features that don't meet minimum-width criteria
|
||||
aRawPolys.Deflate( half_min_width - epsilon, numSegs, true );
|
||||
if( half_min_width - epsilon > epsilon )
|
||||
aRawPolys.Deflate( half_min_width - epsilon, numSegs, cornerStrategy );
|
||||
|
||||
if( s_DumpZonesWhenFilling )
|
||||
dumper->Write( &aRawPolys, "solid-areas-before-hatching" );
|
||||
|
@ -777,10 +786,15 @@ void ZONE_FILLER::computeRawFilledArea( const ZONE_CONTAINER* aZone,
|
|||
// If we're stroking the zone with a min_width stroke then this will naturally
|
||||
// inflate the zone by half_min_width
|
||||
}
|
||||
else if( half_min_width - epsilon > epsilon ) // avoid very small outline thickness
|
||||
else if( half_min_width - epsilon > epsilon )
|
||||
{
|
||||
aRawPolys.Simplify( SHAPE_POLY_SET::PM_FAST );
|
||||
aRawPolys.Inflate( half_min_width - epsilon, numSegs, true );
|
||||
aRawPolys.Inflate( half_min_width - epsilon, numSegs, cornerStrategy );
|
||||
|
||||
// If we've deflated/inflated by something near our corner radius then we will have
|
||||
// ended up with too-sharp corners. Apply outline smoothing again.
|
||||
if( aZone->GetMinThickness() > aZone->GetCornerRadius() )
|
||||
aRawPolys.BooleanIntersection( aSmoothedOutline, SHAPE_POLY_SET::PM_FAST );
|
||||
}
|
||||
|
||||
aRawPolys.Fracture( SHAPE_POLY_SET::PM_FAST );
|
||||
|
|
|
@ -5369,8 +5369,10 @@ void ClipperOffset::OffsetPoint( int j, int& k, JoinType jointype )
|
|||
|
||||
if( r >= m_miterLim )
|
||||
DoMiter( j, k, r );
|
||||
else
|
||||
else if( MiterFallback == jtRound )
|
||||
DoRound( j, k );
|
||||
else
|
||||
DoSquare( j, k );
|
||||
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -418,6 +418,7 @@ public:
|
|||
void Clear();
|
||||
|
||||
double MiterLimit;
|
||||
JoinType MiterFallback;
|
||||
double ArcTolerance;
|
||||
|
||||
private:
|
||||
|
|
Loading…
Reference in New Issue