Add more control over how corners are handled when inflating polygons.

This commit is contained in:
Jeff Young 2019-07-16 16:41:21 +01:00
parent a61673a144
commit 2c5876ee8c
6 changed files with 82 additions and 48 deletions

View File

@ -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 );
}

View File

@ -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"

View File

@ -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 );
}

View File

@ -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 );

View File

@ -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;
}

View File

@ -418,6 +418,7 @@ public:
void Clear();
double MiterLimit;
JoinType MiterFallback;
double ArcTolerance;
private: