From 3784950603b0240deb9ffbc2aa7f1d5fea1bc2e1 Mon Sep 17 00:00:00 2001 From: Jeff Young Date: Sun, 23 Jun 2019 19:22:34 +0100 Subject: [PATCH] Added more sophisticated checking for thermal spoke connections. And, again, some more performance optimizations to make up for it. --- common/geometry/shape_line_chain.cpp | 4 +- common/geometry/shape_poly_set.cpp | 25 +++---- include/geometry/shape_poly_set.h | 20 ++++-- pcbnew/zone_filler.cpp | 102 +++++++++++++++------------ pcbnew/zone_filler.h | 19 ++++- 5 files changed, 101 insertions(+), 69 deletions(-) diff --git a/common/geometry/shape_line_chain.cpp b/common/geometry/shape_line_chain.cpp index 1b70e12f70..527c868e8d 100644 --- a/common/geometry/shape_line_chain.cpp +++ b/common/geometry/shape_line_chain.cpp @@ -390,7 +390,9 @@ bool SHAPE_LINE_CHAIN::PointInside( const VECTOR2I& aPt, int aAccuracy ) const } } - return inside && !PointOnEdge( aPt, aAccuracy ); + // If aAccuracy is > 0 then by definition we don't care whether or not the point is + // *exactly* on the edge -- which saves us considerable processing time + return inside && ( aAccuracy > 0 || !PointOnEdge( aPt ) ); } diff --git a/common/geometry/shape_poly_set.cpp b/common/geometry/shape_poly_set.cpp index 49f378859c..a2b4691bff 100644 --- a/common/geometry/shape_poly_set.cpp +++ b/common/geometry/shape_poly_set.cpp @@ -1391,19 +1391,20 @@ bool SHAPE_POLY_SET::CollideEdge( const VECTOR2I& aPoint, } -bool SHAPE_POLY_SET::Contains( const VECTOR2I& aP, int aSubpolyIndex, bool aIgnoreHoles ) const +bool SHAPE_POLY_SET::Contains( const VECTOR2I& aP, int aSubpolyIndex, bool aIgnoreHoles, + bool aIgnoreEdges ) const { if( m_polys.size() == 0 ) // empty set? return false; // If there is a polygon specified, check the condition against that polygon if( aSubpolyIndex >= 0 ) - return containsSingle( aP, aSubpolyIndex, aIgnoreHoles ); + return containsSingle( aP, aSubpolyIndex, aIgnoreHoles, aIgnoreEdges ); // In any other case, check it against all polygons in the set for( int polygonIdx = 0; polygonIdx < OutlineCount(); polygonIdx++ ) { - if( containsSingle( aP, polygonIdx, aIgnoreHoles ) ) + if( containsSingle( aP, polygonIdx, aIgnoreHoles, aIgnoreEdges ) ) return true; } @@ -1429,10 +1430,11 @@ void SHAPE_POLY_SET::RemoveVertex( VERTEX_INDEX aIndex ) } -bool SHAPE_POLY_SET::containsSingle( const VECTOR2I& aP, int aSubpolyIndex, bool aIgnoreHoles ) const +bool SHAPE_POLY_SET::containsSingle( const VECTOR2I& aP, int aSubpolyIndex, bool aIgnoreHoles, + bool aIgnoreEdges ) const { // Check that the point is inside the outline - if( pointInPolygon( aP, m_polys[aSubpolyIndex][0] ) ) + if( pointInPolygon( aP, m_polys[aSubpolyIndex][0], aIgnoreEdges ) ) { if( !aIgnoreHoles ) { @@ -1443,7 +1445,7 @@ bool SHAPE_POLY_SET::containsSingle( const VECTOR2I& aP, int aSubpolyIndex, bool // If the point is inside a hole (and not on its edge), // it is outside of the polygon - if( pointInPolygon( aP, hole ) ) + if( pointInPolygon( aP, hole, aIgnoreEdges ) ) return false; } } @@ -1455,9 +1457,10 @@ bool SHAPE_POLY_SET::containsSingle( const VECTOR2I& aP, int aSubpolyIndex, bool } -bool SHAPE_POLY_SET::pointInPolygon( const VECTOR2I& aP, const SHAPE_LINE_CHAIN& aPath ) const +bool SHAPE_POLY_SET::pointInPolygon( const VECTOR2I& aP, const SHAPE_LINE_CHAIN& aPath, + bool aIgnoreEdges ) const { - return aPath.PointInside( aP ); + return aPath.PointInside( aP, aIgnoreEdges ? 1 : 0 ); } @@ -1466,9 +1469,7 @@ void SHAPE_POLY_SET::Move( const VECTOR2I& aVector ) for( POLYGON& poly : m_polys ) { for( SHAPE_LINE_CHAIN& path : poly ) - { path.Move( aVector ); - } } } @@ -1478,9 +1479,7 @@ void SHAPE_POLY_SET::Rotate( double aAngle, const VECTOR2I& aCenter ) for( POLYGON& poly : m_polys ) { for( SHAPE_LINE_CHAIN& path : poly ) - { path.Rotate( aAngle, aCenter ); - } } } @@ -1492,9 +1491,7 @@ int SHAPE_POLY_SET::TotalVertices() const for( const POLYGON& poly : m_polys ) { for( const SHAPE_LINE_CHAIN& path : poly ) - { c += path.PointCount(); - } } return c; diff --git a/include/geometry/shape_poly_set.h b/include/geometry/shape_poly_set.h index ad06c51985..affe21876b 100644 --- a/include/geometry/shape_poly_set.h +++ b/include/geometry/shape_poly_set.h @@ -977,9 +977,12 @@ class SHAPE_POLY_SET : public SHAPE * @param aP is the point to check * @param aSubpolyIndex is the subpolygon to check, or -1 to check all * @param aIgnoreHoles controls whether or not internal holes are considered + * @param aIgnoreEdges controls whether or not a check for the point lying exactly on + * the polygon edge is made * @return true if the polygon contains the point */ - bool Contains( const VECTOR2I& aP, int aSubpolyIndex = -1, bool aIgnoreHoles = false ) const; + bool Contains( const VECTOR2I& aP, int aSubpolyIndex = -1, bool aIgnoreHoles = false, + bool aIgnoreEdges = false ) const; ///> Returns true if the set is empty (no polygons at all) bool IsEmpty() const @@ -1136,14 +1139,14 @@ class SHAPE_POLY_SET : public SHAPE * if aFastMode is PM_STRICTLY_SIMPLE (default) the result is (theorically) a strictly * simple polygon, but calculations can be really significantly time consuming */ - void booleanOp( ClipperLib::ClipType aType, - const SHAPE_POLY_SET& aOtherShape, POLYGON_MODE aFastMode ); + void booleanOp( ClipperLib::ClipType aType, const SHAPE_POLY_SET& aOtherShape, + POLYGON_MODE aFastMode ); - void booleanOp( ClipperLib::ClipType aType, - const SHAPE_POLY_SET& aShape, + void booleanOp( ClipperLib::ClipType aType, const SHAPE_POLY_SET& aShape, const SHAPE_POLY_SET& aOtherShape, POLYGON_MODE aFastMode ); - bool pointInPolygon( const VECTOR2I& aP, const SHAPE_LINE_CHAIN& aPath ) const; + bool pointInPolygon( const VECTOR2I& aP, const SHAPE_LINE_CHAIN& aPath, + bool aIgnoreEdges ) const; /** * containsSingle function @@ -1154,10 +1157,13 @@ class SHAPE_POLY_SET : public SHAPE * @param aSubpolyIndex is an integer specifying which polygon in the set has to be * checked. * @param aIgnoreHoles can be set to true to ignore internal holes in the polygon + * @param aIgnoreEdges can be set to true to skip checking whether or not the point + * lies directly on the edge * @return bool - true if aP is inside aSubpolyIndex-th polygon; false in any other * case. */ - bool containsSingle( const VECTOR2I& aP, int aSubpolyIndex, bool aIgnoreHoles = false ) const; + bool containsSingle( const VECTOR2I& aP, int aSubpolyIndex, bool aIgnoreHoles = false, + bool aIgnoreEdges = false ) const; /** * Operations ChamferPolygon and FilletPolygon are computed under the private chamferFillet diff --git a/pcbnew/zone_filler.cpp b/pcbnew/zone_filler.cpp index 47ab1e2139..f49c9a4acf 100644 --- a/pcbnew/zone_filler.cpp +++ b/pcbnew/zone_filler.cpp @@ -492,8 +492,7 @@ void ZONE_FILLER::knockoutThermals( const ZONE_CONTAINER* aZone, SHAPE_POLY_SET& * Removes clearance from the shape for copper items which share the zone's layer but are * not connected to it. */ -void ZONE_FILLER::knockoutCopperItems( const ZONE_CONTAINER* aZone, SHAPE_POLY_SET& aFill, - std::deque& aSpokes) +void ZONE_FILLER::knockoutCopperItems( const ZONE_CONTAINER* aZone, SHAPE_POLY_SET& aFill ) { SHAPE_POLY_SET holes; @@ -705,7 +704,7 @@ void ZONE_FILLER::computeRawFilledArea( const ZONE_CONTAINER* aZone, dumper->BeginGroup( "clipper-zone" ); SHAPE_POLY_SET solidAreas = aSmoothedOutline; - std::deque thermalSpokes; + std::deque thermalSpokes; int numSegs = std::max( GetArcToSegmentCount( outline_half_thickness, m_high_def, 360.0 ), 6 ); @@ -719,7 +718,7 @@ void ZONE_FILLER::computeRawFilledArea( const ZONE_CONTAINER* aZone, buildThermalSpokes( aZone, thermalSpokes ); - knockoutCopperItems( aZone, solidAreas, thermalSpokes ); + knockoutCopperItems( aZone, solidAreas ); if( s_DumpZonesWhenFilling ) dumper->Write( &solidAreas, "solid-areas-minus-clearances" ); @@ -728,11 +727,14 @@ void ZONE_FILLER::computeRawFilledArea( const ZONE_CONTAINER* aZone, { SHAPE_POLY_SET amalgamatedSpokes; - for( SHAPE_LINE_CHAIN& spoke : thermalSpokes ) + for( THERMAL_SPOKE& spoke : thermalSpokes ) { // Add together all spokes whose endpoints lie within the zone's filled area - if( solidAreas.Contains( spoke.Point(2) ) && solidAreas.Contains( spoke.Point(3) ) ) - amalgamatedSpokes.AddOutline( spoke ); + if( solidAreas.Contains( spoke.m_TestPtA, -1, false, true ) + && solidAreas.Contains( spoke.m_TestPtB, -1, false, true ) ) + { + amalgamatedSpokes.AddOutline( spoke.m_Outline ); + } } amalgamatedSpokes.Simplify( SHAPE_POLY_SET::PM_FAST ); @@ -851,7 +853,7 @@ bool ZONE_FILLER::fillSingleZone( ZONE_CONTAINER* aZone, SHAPE_POLY_SET& aRawPol * Function buildThermalSpokes */ void ZONE_FILLER::buildThermalSpokes( const ZONE_CONTAINER* aZone, - std::deque& aSpokesList ) + std::deque& aSpokesList ) { auto zoneBB = aZone->GetBoundingBox(); int zone_clearance = aZone->GetZoneClearance(); @@ -860,17 +862,17 @@ void ZONE_FILLER::buildThermalSpokes( const ZONE_CONTAINER* aZone, zoneBB.Inflate( biggest_clearance ); // half size of the pen used to draw/plot zones outlines - int pen_radius = aZone->GetMinThickness() / 2; + int pen_w = aZone->GetMinThickness() / 2; - // Is a point on the boundary of the polygon inside or outside? This small correction - // lets us avoid the question. - int boundaryCorrection = KiROUND( IU_PER_MM * 0.04 ); + // Is a point on the boundary of the polygon inside or outside? This small epsilon lets + // us avoid the question. + int epsilon = KiROUND( IU_PER_MM * 0.04 ); // We'd normally add in an arcCorrection for circles (since a finite number of segments - // is only an approximation of the circle radius). However, boundaryCorrection is already - // twice even our ARC_LOW_DEF error tolerance, so there's little benefit to it (and a small - // but existant performance penalty). - //int numSegs = std::max( GetArcToSegmentCount( pen_raidus, m_high_def, 360.0 ), 6 ); + // is only an approximation of the circle radius). However, epsilon is already twice even + // our LOW resolution error tolerance, so there's little benefit to it (and a small but + // existant performance penalty). + //int numSegs = std::max( GetArcToSegmentCount( pen_w, m_high_def, 360.0 ), 6 ); //double arcCorrection = GetCircletoPolyCorrectionFactor( numSegs ); for( auto module : m_board->Modules() ) @@ -883,17 +885,16 @@ void ZONE_FILLER::buildThermalSpokes( const ZONE_CONTAINER* aZone, int thermalReliefGap = aZone->GetThermalReliefGap( pad ); // Calculate thermal bridge half width - int spokeThickness = aZone->GetThermalReliefCopperBridge( pad ) - - aZone->GetMinThickness(); + int spoke_w = aZone->GetThermalReliefCopperBridge( pad ) - aZone->GetMinThickness(); - if( spokeThickness <= 0 ) + if( spoke_w <= 0 ) continue; - spokeThickness = spokeThickness / 2; + spoke_w = spoke_w / 2; // Quick test here to possibly save us some work BOX2I itemBB = pad->GetBoundingBox(); - itemBB.Inflate( thermalReliefGap + pen_radius + boundaryCorrection ); + itemBB.Inflate( thermalReliefGap + pen_w + epsilon ); if( !( itemBB.Intersects( zoneBB ) ) ) continue; @@ -907,7 +908,7 @@ void ZONE_FILLER::buildThermalSpokes( const ZONE_CONTAINER* aZone, pad->SetOrientation( 0.0 ); pad->SetPosition( - pad->GetOffset() ); BOX2I reliefBB = pad->GetBoundingBox(); - reliefBB.Inflate( thermalReliefGap + pen_radius + boundaryCorrection ); + reliefBB.Inflate( thermalReliefGap + pen_w + epsilon ); // For circle pads, the thermal stubs orientation is 45 deg if( pad->GetShape() == PAD_SHAPE_CIRCLE ) @@ -915,47 +916,60 @@ void ZONE_FILLER::buildThermalSpokes( const ZONE_CONTAINER* aZone, for( int i = 0; i < 4; i++ ) { - SHAPE_LINE_CHAIN spoke; + THERMAL_SPOKE spoke; // polygons are rectangles with width of copper bridge value switch( i ) { case 0: // lower stub - spoke.Append( +spokeThickness, 0 ); - spoke.Append( -spokeThickness, 0 ); - spoke.Append( -spokeThickness, reliefBB.GetBottom() ); - spoke.Append( +spokeThickness, reliefBB.GetBottom() ); + spoke.m_Outline.Append( +spoke_w, 0 ); + spoke.m_Outline.Append( -spoke_w, 0 ); + spoke.m_Outline.Append( -spoke_w, reliefBB.GetBottom() ); + spoke.m_Outline.Append( +spoke_w, reliefBB.GetBottom() ); + spoke.m_TestPtA = { epsilon - spoke_w, reliefBB.GetBottom() }; + spoke.m_TestPtB = { spoke_w - epsilon, reliefBB.GetBottom() }; break; case 1: // upper stub - spoke.Append( +spokeThickness, 0 ); - spoke.Append( -spokeThickness, 0 ); - spoke.Append( -spokeThickness, reliefBB.GetTop() ); - spoke.Append( +spokeThickness, reliefBB.GetTop() ); + spoke.m_Outline.Append( +spoke_w, 0 ); + spoke.m_Outline.Append( -spoke_w, 0 ); + spoke.m_Outline.Append( -spoke_w, reliefBB.GetTop() ); + spoke.m_Outline.Append( +spoke_w, reliefBB.GetTop() ); + spoke.m_TestPtA = { epsilon - spoke_w, reliefBB.GetTop() }; + spoke.m_TestPtB = { spoke_w - epsilon, reliefBB.GetTop() }; break; case 2: // right stub - spoke.Append( 0, spokeThickness ); - spoke.Append( 0, -spokeThickness ); - spoke.Append( reliefBB.GetRight(), -spokeThickness ); - spoke.Append( reliefBB.GetRight(), spokeThickness ); + spoke.m_Outline.Append( 0, spoke_w ); + spoke.m_Outline.Append( 0, -spoke_w ); + spoke.m_Outline.Append( reliefBB.GetRight(), -spoke_w ); + spoke.m_Outline.Append( reliefBB.GetRight(), spoke_w ); + spoke.m_TestPtA = { reliefBB.GetRight(), epsilon - spoke_w }; + spoke.m_TestPtB = { reliefBB.GetRight(), spoke_w - epsilon }; break; case 3: // left stub - spoke.Append( 0, spokeThickness ); - spoke.Append( 0, -spokeThickness ); - spoke.Append( reliefBB.GetLeft(), -spokeThickness ); - spoke.Append( reliefBB.GetLeft(), spokeThickness ); + spoke.m_Outline.Append( 0, spoke_w ); + spoke.m_Outline.Append( 0, -spoke_w ); + spoke.m_Outline.Append( reliefBB.GetLeft(), -spoke_w ); + spoke.m_Outline.Append( reliefBB.GetLeft(), spoke_w ); + spoke.m_TestPtA = { reliefBB.GetLeft(), epsilon - spoke_w }; + spoke.m_TestPtB = { reliefBB.GetLeft(), spoke_w - epsilon }; break; } - for( int ic = 0; ic < spoke.PointCount(); ic++ ) + for( int j = 0; j < spoke.m_Outline.PointCount(); j++ ) { - RotatePoint( spoke.Point( ic ), spokeAngle ); - spoke.Point( ic ) += padPos + pad->GetOffset(); + RotatePoint( spoke.m_Outline.Point( j ), spokeAngle ); + spoke.m_Outline.Point( j ) += padPos + pad->GetOffset(); } - spoke.SetClosed( true ); - aSpokesList.push_back( spoke ); + RotatePoint( spoke.m_TestPtA, spokeAngle ); + spoke.m_TestPtA += padPos + pad->GetOffset(); + RotatePoint( spoke.m_TestPtB, spokeAngle ); + spoke.m_TestPtB += padPos + pad->GetOffset(); + + spoke.m_Outline.SetClosed( true ); + aSpokesList.push_back( std::move( spoke ) ); } pad->SetPosition( padPos ); diff --git a/pcbnew/zone_filler.h b/pcbnew/zone_filler.h index 2d8073b12c..0ef41a891e 100644 --- a/pcbnew/zone_filler.h +++ b/pcbnew/zone_filler.h @@ -35,6 +35,20 @@ class COMMIT; class SHAPE_POLY_SET; class SHAPE_LINE_CHAIN; +struct THERMAL_SPOKE +{ + SHAPE_LINE_CHAIN m_Outline; + VECTOR2I m_TestPtA; + VECTOR2I m_TestPtB; + + THERMAL_SPOKE() + { + m_TestPtA = { 0, 0 }; + m_TestPtB = { 0, 0 }; + } +}; + + class ZONE_FILLER { public: @@ -52,8 +66,7 @@ private: void knockoutThermals( const ZONE_CONTAINER* aZone, SHAPE_POLY_SET& aFill ); - void knockoutCopperItems( const ZONE_CONTAINER* aZone, SHAPE_POLY_SET& aFill, - std::deque& aSpokes ); + void knockoutCopperItems( const ZONE_CONTAINER* aZone, SHAPE_POLY_SET& aFill ); /** * Function computeRawFilledArea @@ -73,7 +86,7 @@ private: * Function buildThermalSpokes * Constructs a list of all thermal spokes for the given zone. */ - void buildThermalSpokes( const ZONE_CONTAINER* aZone, std::deque& aSpokes ); + void buildThermalSpokes( const ZONE_CONTAINER* aZone, std::deque& aSpokes ); /** * Build the filled solid areas polygons from zone outlines (stored in m_Poly)