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:
parent
88e0ef548d
commit
48ae188b15
|
@ -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
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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 );
|
||||||
|
|
||||||
|
|
|
@ -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 )
|
||||||
{
|
{
|
||||||
|
|
|
@ -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 ) );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 )
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue