Zone filler: fix a few issues, in Smoothed Polygons mode.

- Fix non optimal shape of solid polygons after inflate transform.
- Add a small extra clearance ((2 microns) when creating clearance areas
(especially, convert arc to polygons create small differences)
- Add a small threshold (1 micron) in drc test distances to avoid false positives
- fix a minor issue in TransformOvalToPolygon that created sometimes a not perfect shape

Fixes #3812
https://gitlab.com/kicad/code/kicad/issues/3812
This commit is contained in:
jean-pierre charras 2020-01-26 18:01:11 +01:00
parent 88e0ef548d
commit 48ae188b15
6 changed files with 91 additions and 30 deletions

View File

@ -865,12 +865,16 @@ class SHAPE_POLY_SET : public SHAPE
void BooleanIntersection( const SHAPE_POLY_SET& a, const SHAPE_POLY_SET& b, void BooleanIntersection( const SHAPE_POLY_SET& a, const SHAPE_POLY_SET& b,
POLYGON_MODE aFastMode ); POLYGON_MODE aFastMode );
enum CORNER_STRATEGY enum CORNER_STRATEGY ///< define how inflate transform build inflated polygon
{ {
ALLOW_ACUTE_CORNERS, ALLOW_ACUTE_CORNERS, ///< just inflate the polygon. Acute angles create spikes
CHOP_ACUTE_CORNERS, CHAMFER_ACUTE_CORNERS, ///< Acute angles are chamfered
ROUND_ACUTE_CORNERS, ROUND_ACUTE_CORNERS, ///< Acute angles are rounded
ROUND_ALL_CORNERS CHAMFER_ALL_CORNERS, ///< All angles are chamfered.
///< The distance between new and old polygon edges is not
///< constant, but do not change a lot
ROUND_ALL_CORNERS ///< All angles are rounded.
///< The distance between new and old polygon edges is constant
}; };
/** /**

View File

@ -97,6 +97,12 @@ void TransformOvalToPolygon( SHAPE_POLY_SET& aCornerBuffer, wxPoint aStart, wxPo
// of the segment. // of the segment.
int radius = aWidth / 2; int radius = aWidth / 2;
int numSegs = std::max( GetArcToSegmentCount( radius, aError, 360.0 ), 6 ); int numSegs = std::max( GetArcToSegmentCount( radius, aError, 360.0 ), 6 );
// Because we want to create 2 arcs (one at each segment end) numSegs must be
// a even value (we will used numSegs/2 later)
if( numSegs % 2 != 0 )
numSegs++;
int delta = 3600 / numSegs; // rotate angle in 0.1 degree int delta = 3600 / numSegs; // rotate angle in 0.1 degree
double correction = GetCircletoPolyCorrectionFactor( numSegs ); double correction = GetCircletoPolyCorrectionFactor( numSegs );

View File

@ -538,9 +538,38 @@ void SHAPE_POLY_SET::Inflate( int aAmount, int aCircleSegmentsCount,
// N.B. see the Clipper documentation for jtSquare/jtMiter/jtRound. They are poorly named // N.B. see the Clipper documentation for jtSquare/jtMiter/jtRound. They are poorly named
// and are not what you'd think they are. // and are not what you'd think they are.
// http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Types/JoinType.htm // http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Types/JoinType.htm
JoinType joinType = aCornerStrategy == ROUND_ALL_CORNERS ? jtRound : jtMiter; JoinType joinType; // The way corners are offsetted
double miterLimit = aCornerStrategy == ALLOW_ACUTE_CORNERS ? 10 : 1.5; double miterLimit = 2.0; // Smaller value when using jtMiter for joinType
JoinType miterFallback = aCornerStrategy == ROUND_ACUTE_CORNERS ? jtRound : jtSquare; JoinType miterFallback;
switch( aCornerStrategy )
{
case ALLOW_ACUTE_CORNERS:
joinType = jtMiter;
miterLimit = 10; // Allows large spikes
miterFallback = jtSquare;
break;
case CHAMFER_ACUTE_CORNERS: // Acute angles are chamfered
joinType = jtMiter;
miterFallback = jtRound;
break;
case ROUND_ACUTE_CORNERS: // Acute angles are rounded
joinType = jtMiter;
miterFallback = jtSquare;
break;
case CHAMFER_ALL_CORNERS: // All angles are chamfered.
joinType = jtSquare;
miterFallback = jtSquare;
break;
case ROUND_ALL_CORNERS: // All angles are rounded.
joinType = jtRound;
miterFallback = jtSquare;
break;
}
for( const POLYGON& poly : m_polys ) for( const POLYGON& poly : m_polys )
{ {

View File

@ -675,7 +675,13 @@ bool DRC::doTrackDrc( TRACK* aRefSeg, TRACKS::iterator aStartIt, TRACKS::iterato
int clearance = std::max( ref_seg_clearance, zone->GetClearance() ); int clearance = std::max( ref_seg_clearance, zone->GetClearance() );
SHAPE_POLY_SET* outline = const_cast<SHAPE_POLY_SET*>( &zone->GetFilledPolysList() ); SHAPE_POLY_SET* outline = const_cast<SHAPE_POLY_SET*>( &zone->GetFilledPolysList() );
if( outline->Distance( refSeg, ref_seg_width ) < clearance ) int error = clearance - outline->Distance( refSeg, ref_seg_width );
// to avoid false positive, due to rounding issues and approxiamtions
// in distance and clearance calculations, use a small threshold for distance
// (1 micron)
#define THRESHOLD_DIST Millimeter2iu( 0.001 )
if( error > THRESHOLD_DIST )
addMarkerToPcb( m_markerFactory.NewMarker( aRefSeg, zone, DRCE_TRACK_NEAR_ZONE ) ); addMarkerToPcb( m_markerFactory.NewMarker( aRefSeg, zone, DRCE_TRACK_NEAR_ZONE ) );
} }
} }

View File

@ -514,6 +514,12 @@ void ZONE_FILLER::knockoutThermalReliefs( const ZONE_CONTAINER* aZone, SHAPE_POL
*/ */
void ZONE_FILLER::buildCopperItemClearances( const ZONE_CONTAINER* aZone, SHAPE_POLY_SET& aHoles ) void ZONE_FILLER::buildCopperItemClearances( const ZONE_CONTAINER* aZone, SHAPE_POLY_SET& aHoles )
{ {
// a small extra clearance to be sure actual track clearance is not smaller
// than requested clearance due to many approximations in calculations,
// like arc to segment approx, rounding issues...
// 2 microns are a good value
int extra_margin = Millimeter2iu( 0.002 );
int zone_clearance = aZone->GetClearance(); int zone_clearance = aZone->GetClearance();
int edgeClearance = m_board->GetDesignSettings().m_CopperEdgeClearance; int edgeClearance = m_board->GetDesignSettings().m_CopperEdgeClearance;
int zone_to_edgecut_clearance = std::max( aZone->GetZoneClearance(), edgeClearance ); int zone_to_edgecut_clearance = std::max( aZone->GetZoneClearance(), edgeClearance );
@ -522,7 +528,7 @@ void ZONE_FILLER::buildCopperItemClearances( const ZONE_CONTAINER* aZone, SHAPE_
// the bounding box is the zone bounding box + the biggest clearance found in Netclass list // the bounding box is the zone bounding box + the biggest clearance found in Netclass list
EDA_RECT zone_boundingbox = aZone->GetBoundingBox(); EDA_RECT zone_boundingbox = aZone->GetBoundingBox();
int biggest_clearance = m_board->GetDesignSettings().GetBiggestClearanceValue(); int biggest_clearance = m_board->GetDesignSettings().GetBiggestClearanceValue();
biggest_clearance = std::max( biggest_clearance, zone_clearance ); biggest_clearance = std::max( biggest_clearance, zone_clearance ) + extra_margin;
zone_boundingbox.Inflate( biggest_clearance ); zone_boundingbox.Inflate( biggest_clearance );
// Use a dummy pad to calculate hole clearance when a pad has a hole but is not on the // Use a dummy pad to calculate hole clearance when a pad has a hole but is not on the
@ -570,7 +576,7 @@ void ZONE_FILLER::buildCopperItemClearances( const ZONE_CONTAINER* aZone, SHAPE_
if( track->GetNetCode() == aZone->GetNetCode() && ( aZone->GetNetCode() != 0) ) if( track->GetNetCode() == aZone->GetNetCode() && ( aZone->GetNetCode() != 0) )
continue; continue;
int gap = std::max( zone_clearance, track->GetClearance() ); int gap = std::max( zone_clearance, track->GetClearance() ) + extra_margin;
EDA_RECT item_boundingbox = track->GetBoundingBox(); EDA_RECT item_boundingbox = track->GetBoundingBox();
if( item_boundingbox.Intersects( zone_boundingbox ) ) if( item_boundingbox.Intersects( zone_boundingbox ) )
@ -686,10 +692,19 @@ void ZONE_FILLER::computeRawFilledArea( const ZONE_CONTAINER* aZone,
int epsilon = Millimeter2iu( 0.001 ); int epsilon = Millimeter2iu( 0.001 );
int numSegs = std::max( GetArcToSegmentCount( half_min_width, m_high_def, 360.0 ), 6 ); 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; // solid polygons are deflated and inflated during calculations.
// Polygons deflate usually do not create issues.
if( aZone->GetCornerSmoothingType() == ZONE_SETTINGS::SMOOTHING_FILLET ) // Polygons inflate is a tricky transform, because it can create excessively long and narrow 'spikes'
cornerStrategy = SHAPE_POLY_SET::ROUND_ACUTE_CORNERS; // especially for acute angles.
// But in very case, the inflate transform caannot create bigger shapes than initial shapes.
// so the corner strategy is very important.
// The best is SHAPE_POLY_SET::ROUND_ALL_CORNERS.
// unfortunately, it creates a lot of small segments.
// SHAPE_POLY_SET::ALLOW_ACUTE_CORNERS is not acceptable
// So for intermediate transforms, we use CHAMFER_ALL_CORNERS.
// For final transform, we use ROUND_ALL_CORNERS
SHAPE_POLY_SET::CORNER_STRATEGY intermediatecornerStrategy = SHAPE_POLY_SET::CHAMFER_ALL_CORNERS;
SHAPE_POLY_SET::CORNER_STRATEGY finalcornerStrategy = SHAPE_POLY_SET::ROUND_ALL_CORNERS;
std::deque<SHAPE_LINE_CHAIN> thermalSpokes; std::deque<SHAPE_LINE_CHAIN> thermalSpokes;
SHAPE_POLY_SET clearanceHoles; SHAPE_POLY_SET clearanceHoles;
@ -723,8 +738,8 @@ void ZONE_FILLER::computeRawFilledArea( const ZONE_CONTAINER* aZone,
// Prune features that don't meet minimum-width criteria // Prune features that don't meet minimum-width criteria
if( half_min_width - epsilon > epsilon ) if( half_min_width - epsilon > epsilon )
{ {
testAreas.Deflate( half_min_width - epsilon, numSegs, cornerStrategy ); testAreas.Deflate( half_min_width - epsilon, numSegs, intermediatecornerStrategy );
testAreas.Inflate( half_min_width - epsilon, numSegs, cornerStrategy ); testAreas.Inflate( half_min_width - epsilon, numSegs, intermediatecornerStrategy );
} }
// Spoke-end-testing is hugely expensive so we generate cached bounding-boxes to speed // Spoke-end-testing is hugely expensive so we generate cached bounding-boxes to speed
@ -764,7 +779,7 @@ void ZONE_FILLER::computeRawFilledArea( const ZONE_CONTAINER* aZone,
aRawPolys.BooleanSubtract( clearanceHoles, SHAPE_POLY_SET::PM_FAST ); aRawPolys.BooleanSubtract( clearanceHoles, SHAPE_POLY_SET::PM_FAST );
// Prune features that don't meet minimum-width criteria // Prune features that don't meet minimum-width criteria
if( half_min_width - epsilon > epsilon ) if( half_min_width - epsilon > epsilon )
aRawPolys.Deflate( half_min_width - epsilon, numSegs, cornerStrategy ); aRawPolys.Deflate( half_min_width - epsilon, numSegs, intermediatecornerStrategy );
if( s_DumpZonesWhenFilling ) if( s_DumpZonesWhenFilling )
dumper->Write( &aRawPolys, "solid-areas-before-hatching" ); dumper->Write( &aRawPolys, "solid-areas-before-hatching" );
@ -785,7 +800,7 @@ void ZONE_FILLER::computeRawFilledArea( const ZONE_CONTAINER* aZone,
else if( half_min_width - epsilon > epsilon ) else if( half_min_width - epsilon > epsilon )
{ {
aRawPolys.Simplify( SHAPE_POLY_SET::PM_FAST ); aRawPolys.Simplify( SHAPE_POLY_SET::PM_FAST );
aRawPolys.Inflate( half_min_width - epsilon, numSegs, cornerStrategy ); aRawPolys.Inflate( half_min_width - epsilon, numSegs, finalcornerStrategy );
// If we've deflated/inflated by something near our corner radius then we will have // 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. // ended up with too-sharp corners. Apply outline smoothing again.
@ -839,9 +854,9 @@ bool ZONE_FILLER::fillSingleZone( ZONE_CONTAINER* aZone, SHAPE_POLY_SET& aRawPol
int numSegs = std::max( GetArcToSegmentCount( half_min_width, m_high_def, 360.0 ), 6 ); int numSegs = std::max( GetArcToSegmentCount( half_min_width, m_high_def, 360.0 ), 6 );
if( m_brdOutlinesValid ) if( m_brdOutlinesValid )
smoothedPoly.BooleanIntersection( m_boardOutline, SHAPE_POLY_SET::PM_FAST ); smoothedPoly.BooleanIntersection( m_boardOutline, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
smoothedPoly.Deflate( half_min_width - epsilon, numSegs ); smoothedPoly.Deflate( half_min_width/* - epsilon*/, numSegs );
// Remove the non filled areas due to the hatch pattern // Remove the non filled areas due to the hatch pattern
if( aZone->GetFillMode() == ZONE_FILL_MODE::HATCH_PATTERN ) if( aZone->GetFillMode() == ZONE_FILL_MODE::HATCH_PATTERN )

View File

@ -58,19 +58,20 @@ enum class ZONE_HATCH_STYLE
class ZONE_SETTINGS class ZONE_SETTINGS
{ {
public: public:
// the actual zone outline shape can be slightly modified (smoothed):
enum { enum {
SMOOTHING_UNDEFINED = -1, SMOOTHING_UNDEFINED = -1,
SMOOTHING_NONE = 0, SMOOTHING_NONE = 0, // Zone outline is used without change
SMOOTHING_CHAMFER, SMOOTHING_CHAMFER, // Zone outline is used after chamfering corners
SMOOTHING_FILLET, SMOOTHING_FILLET, // Zone outline is used after rounding corners
SMOOTHING_LAST SMOOTHING_LAST // sentinel
}; };
ZONE_FILL_MODE m_FillMode; ZONE_FILL_MODE m_FillMode;
int m_ZonePriority; ///< Priority (0 ... N) of the zone int m_ZonePriority; ///< Priority (0 ... N) of the zone
int m_ZoneClearance; ///< Clearance value int m_ZoneClearance; ///< Minimal clearance value
int m_ZoneMinThickness; ///< Min thickness value in filled areas int m_ZoneMinThickness; ///< Min thickness value in filled areas
int m_HatchFillTypeThickness; ///< Grid style shape: thickness of lines (if 0 -> solid shape) int m_HatchFillTypeThickness; ///< Grid style shape: thickness of lines (if 0 -> solid shape)
int m_HatchFillTypeGap; ///< Grid style shape: clearance between lines (0 -> solid shape) int m_HatchFillTypeGap; ///< Grid style shape: clearance between lines (0 -> solid shape)