diff --git a/libs/kimath/include/geometry/shape_poly_set.h b/libs/kimath/include/geometry/shape_poly_set.h index 95d4488983..24a018fa46 100644 --- a/libs/kimath/include/geometry/shape_poly_set.h +++ b/libs/kimath/include/geometry/shape_poly_set.h @@ -865,12 +865,16 @@ class SHAPE_POLY_SET : public SHAPE void BooleanIntersection( const SHAPE_POLY_SET& a, const SHAPE_POLY_SET& b, POLYGON_MODE aFastMode ); - enum CORNER_STRATEGY + enum CORNER_STRATEGY ///< define how inflate transform build inflated polygon { - ALLOW_ACUTE_CORNERS, - CHOP_ACUTE_CORNERS, - ROUND_ACUTE_CORNERS, - ROUND_ALL_CORNERS + ALLOW_ACUTE_CORNERS, ///< just inflate the polygon. Acute angles create spikes + CHAMFER_ACUTE_CORNERS, ///< Acute angles are chamfered + ROUND_ACUTE_CORNERS, ///< Acute angles are rounded + 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 }; /** diff --git a/libs/kimath/src/convert_basic_shapes_to_polygon.cpp b/libs/kimath/src/convert_basic_shapes_to_polygon.cpp index 4ebad3922f..7acb635d4e 100644 --- a/libs/kimath/src/convert_basic_shapes_to_polygon.cpp +++ b/libs/kimath/src/convert_basic_shapes_to_polygon.cpp @@ -97,6 +97,12 @@ void TransformOvalToPolygon( SHAPE_POLY_SET& aCornerBuffer, wxPoint aStart, wxPo // of the segment. int radius = aWidth / 2; 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 double correction = GetCircletoPolyCorrectionFactor( numSegs ); diff --git a/libs/kimath/src/geometry/shape_poly_set.cpp b/libs/kimath/src/geometry/shape_poly_set.cpp index aab38e17d1..9798ac693e 100644 --- a/libs/kimath/src/geometry/shape_poly_set.cpp +++ b/libs/kimath/src/geometry/shape_poly_set.cpp @@ -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 // 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; + JoinType joinType; // The way corners are offsetted + double miterLimit = 2.0; // Smaller value when using jtMiter for joinType + 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 ) { diff --git a/pcbnew/tools/drc_clearance_test_functions.cpp b/pcbnew/tools/drc_clearance_test_functions.cpp index 950b6d7436..6b8cecd7cf 100644 --- a/pcbnew/tools/drc_clearance_test_functions.cpp +++ b/pcbnew/tools/drc_clearance_test_functions.cpp @@ -675,7 +675,13 @@ bool DRC::doTrackDrc( TRACK* aRefSeg, TRACKS::iterator aStartIt, TRACKS::iterato int clearance = std::max( ref_seg_clearance, zone->GetClearance() ); SHAPE_POLY_SET* outline = const_cast( &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 ) ); } } diff --git a/pcbnew/zone_filler.cpp b/pcbnew/zone_filler.cpp index 16cfbbc536..cd804a999b 100644 --- a/pcbnew/zone_filler.cpp +++ b/pcbnew/zone_filler.cpp @@ -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 ) { + // 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 edgeClearance = m_board->GetDesignSettings().m_CopperEdgeClearance; 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 EDA_RECT zone_boundingbox = aZone->GetBoundingBox(); 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 ); // 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) ) 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(); if( item_boundingbox.Intersects( zone_boundingbox ) ) @@ -686,10 +692,19 @@ 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; + // solid polygons are deflated and inflated during calculations. + // Polygons deflate usually do not create issues. + // Polygons inflate is a tricky transform, because it can create excessively long and narrow 'spikes' + // 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 thermalSpokes; 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 if( half_min_width - epsilon > epsilon ) { - testAreas.Deflate( half_min_width - epsilon, numSegs, cornerStrategy ); - testAreas.Inflate( half_min_width - epsilon, numSegs, cornerStrategy ); + testAreas.Deflate( half_min_width - epsilon, numSegs, intermediatecornerStrategy ); + testAreas.Inflate( half_min_width - epsilon, numSegs, intermediatecornerStrategy ); } // 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 ); // Prune features that don't meet minimum-width criteria 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 ) 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 ) { 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 // 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 ); 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 if( aZone->GetFillMode() == ZONE_FILL_MODE::HATCH_PATTERN ) diff --git a/pcbnew/zone_settings.h b/pcbnew/zone_settings.h index 8c128ce0ed..bb55edfb57 100644 --- a/pcbnew/zone_settings.h +++ b/pcbnew/zone_settings.h @@ -58,24 +58,25 @@ enum class ZONE_HATCH_STYLE class ZONE_SETTINGS { public: + // the actual zone outline shape can be slightly modified (smoothed): enum { SMOOTHING_UNDEFINED = -1, - SMOOTHING_NONE = 0, - SMOOTHING_CHAMFER, - SMOOTHING_FILLET, - SMOOTHING_LAST + SMOOTHING_NONE = 0, // Zone outline is used without change + SMOOTHING_CHAMFER, // Zone outline is used after chamfering corners + SMOOTHING_FILLET, // Zone outline is used after rounding corners + SMOOTHING_LAST // sentinel }; ZONE_FILL_MODE m_FillMode; 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_HatchFillTypeThickness; ///< Grid style shape: thickness of lines (if 0 -> solid shape) - int m_HatchFillTypeGap; ///< Grid style shape: clearance between lines (0 -> solid shape) - double m_HatchFillTypeOrientation; ///< Grid style shape: orientation of grid lines in degrees - int m_HatchFillTypeSmoothingLevel; ///< Grid pattern smoothing type, similar to corner smoothing type + 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) + double m_HatchFillTypeOrientation; ///< Grid style shape: orientation of grid lines in degrees + int m_HatchFillTypeSmoothingLevel; ///< Grid pattern smoothing type, similar to corner smoothing type ///< 0 = no smoothing, 1 = fillet, >= 2 = arc double m_HatchFillTypeSmoothingValue; ///< Grid pattern chamfer distance/fillet value ///< this is the ratio between the gap and the chamfer size