Added more sophisticated checking for thermal spoke connections.

And, again, some more performance optimizations to make up for it.
This commit is contained in:
Jeff Young 2019-06-23 19:22:34 +01:00
parent 49610085ea
commit 3784950603
5 changed files with 102 additions and 70 deletions

View File

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

View File

@ -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? if( m_polys.size() == 0 ) // empty set?
return false; return false;
// If there is a polygon specified, check the condition against that polygon // If there is a polygon specified, check the condition against that polygon
if( aSubpolyIndex >= 0 ) 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 // In any other case, check it against all polygons in the set
for( int polygonIdx = 0; polygonIdx < OutlineCount(); polygonIdx++ ) for( int polygonIdx = 0; polygonIdx < OutlineCount(); polygonIdx++ )
{ {
if( containsSingle( aP, polygonIdx, aIgnoreHoles ) ) if( containsSingle( aP, polygonIdx, aIgnoreHoles, aIgnoreEdges ) )
return true; 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 // 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 ) 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), // If the point is inside a hole (and not on its edge),
// it is outside of the polygon // it is outside of the polygon
if( pointInPolygon( aP, hole ) ) if( pointInPolygon( aP, hole, aIgnoreEdges ) )
return false; 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,10 +1469,8 @@ void SHAPE_POLY_SET::Move( const VECTOR2I& aVector )
for( POLYGON& poly : m_polys ) for( POLYGON& poly : m_polys )
{ {
for( SHAPE_LINE_CHAIN& path : poly ) for( SHAPE_LINE_CHAIN& path : poly )
{
path.Move( aVector ); path.Move( aVector );
} }
}
} }
@ -1478,10 +1479,8 @@ void SHAPE_POLY_SET::Rotate( double aAngle, const VECTOR2I& aCenter )
for( POLYGON& poly : m_polys ) for( POLYGON& poly : m_polys )
{ {
for( SHAPE_LINE_CHAIN& path : poly ) for( SHAPE_LINE_CHAIN& path : poly )
{
path.Rotate( aAngle, aCenter ); path.Rotate( aAngle, aCenter );
} }
}
} }
@ -1492,10 +1491,8 @@ int SHAPE_POLY_SET::TotalVertices() const
for( const POLYGON& poly : m_polys ) for( const POLYGON& poly : m_polys )
{ {
for( const SHAPE_LINE_CHAIN& path : poly ) for( const SHAPE_LINE_CHAIN& path : poly )
{
c += path.PointCount(); c += path.PointCount();
} }
}
return c; return c;
} }

View File

@ -977,9 +977,12 @@ class SHAPE_POLY_SET : public SHAPE
* @param aP is the point to check * @param aP is the point to check
* @param aSubpolyIndex is the subpolygon to check, or -1 to check all * @param aSubpolyIndex is the subpolygon to check, or -1 to check all
* @param aIgnoreHoles controls whether or not internal holes are considered * @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 * @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) ///> Returns true if the set is empty (no polygons at all)
bool IsEmpty() const 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 * if aFastMode is PM_STRICTLY_SIMPLE (default) the result is (theorically) a strictly
* simple polygon, but calculations can be really significantly time consuming * simple polygon, but calculations can be really significantly time consuming
*/ */
void booleanOp( ClipperLib::ClipType aType, void booleanOp( ClipperLib::ClipType aType, const SHAPE_POLY_SET& aOtherShape,
POLYGON_MODE aFastMode );
void booleanOp( ClipperLib::ClipType aType, const SHAPE_POLY_SET& aShape,
const SHAPE_POLY_SET& aOtherShape, POLYGON_MODE aFastMode ); const SHAPE_POLY_SET& aOtherShape, POLYGON_MODE aFastMode );
void booleanOp( ClipperLib::ClipType aType, bool pointInPolygon( const VECTOR2I& aP, const SHAPE_LINE_CHAIN& aPath,
const SHAPE_POLY_SET& aShape, bool aIgnoreEdges ) const;
const SHAPE_POLY_SET& aOtherShape, POLYGON_MODE aFastMode );
bool pointInPolygon( const VECTOR2I& aP, const SHAPE_LINE_CHAIN& aPath ) const;
/** /**
* containsSingle function * 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 * @param aSubpolyIndex is an integer specifying which polygon in the set has to be
* checked. * checked.
* @param aIgnoreHoles can be set to true to ignore internal holes in the polygon * @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 * @return bool - true if aP is inside aSubpolyIndex-th polygon; false in any other
* case. * 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 * Operations ChamferPolygon and FilletPolygon are computed under the private chamferFillet

View File

@ -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 * Removes clearance from the shape for copper items which share the zone's layer but are
* not connected to it. * not connected to it.
*/ */
void ZONE_FILLER::knockoutCopperItems( const ZONE_CONTAINER* aZone, SHAPE_POLY_SET& aFill, void ZONE_FILLER::knockoutCopperItems( const ZONE_CONTAINER* aZone, SHAPE_POLY_SET& aFill )
std::deque<SHAPE_LINE_CHAIN>& aSpokes)
{ {
SHAPE_POLY_SET holes; SHAPE_POLY_SET holes;
@ -705,7 +704,7 @@ void ZONE_FILLER::computeRawFilledArea( const ZONE_CONTAINER* aZone,
dumper->BeginGroup( "clipper-zone" ); dumper->BeginGroup( "clipper-zone" );
SHAPE_POLY_SET solidAreas = aSmoothedOutline; SHAPE_POLY_SET solidAreas = aSmoothedOutline;
std::deque<SHAPE_LINE_CHAIN> thermalSpokes; std::deque<THERMAL_SPOKE> thermalSpokes;
int numSegs = std::max( GetArcToSegmentCount( outline_half_thickness, m_high_def, 360.0 ), 6 ); 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 ); buildThermalSpokes( aZone, thermalSpokes );
knockoutCopperItems( aZone, solidAreas, thermalSpokes ); knockoutCopperItems( aZone, solidAreas );
if( s_DumpZonesWhenFilling ) if( s_DumpZonesWhenFilling )
dumper->Write( &solidAreas, "solid-areas-minus-clearances" ); dumper->Write( &solidAreas, "solid-areas-minus-clearances" );
@ -728,11 +727,14 @@ void ZONE_FILLER::computeRawFilledArea( const ZONE_CONTAINER* aZone,
{ {
SHAPE_POLY_SET amalgamatedSpokes; 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 // Add together all spokes whose endpoints lie within the zone's filled area
if( solidAreas.Contains( spoke.Point(2) ) && solidAreas.Contains( spoke.Point(3) ) ) if( solidAreas.Contains( spoke.m_TestPtA, -1, false, true )
amalgamatedSpokes.AddOutline( spoke ); && solidAreas.Contains( spoke.m_TestPtB, -1, false, true ) )
{
amalgamatedSpokes.AddOutline( spoke.m_Outline );
}
} }
amalgamatedSpokes.Simplify( SHAPE_POLY_SET::PM_FAST ); amalgamatedSpokes.Simplify( SHAPE_POLY_SET::PM_FAST );
@ -851,7 +853,7 @@ bool ZONE_FILLER::fillSingleZone( ZONE_CONTAINER* aZone, SHAPE_POLY_SET& aRawPol
* Function buildThermalSpokes * Function buildThermalSpokes
*/ */
void ZONE_FILLER::buildThermalSpokes( const ZONE_CONTAINER* aZone, void ZONE_FILLER::buildThermalSpokes( const ZONE_CONTAINER* aZone,
std::deque<SHAPE_LINE_CHAIN>& aSpokesList ) std::deque<THERMAL_SPOKE>& aSpokesList )
{ {
auto zoneBB = aZone->GetBoundingBox(); auto zoneBB = aZone->GetBoundingBox();
int zone_clearance = aZone->GetZoneClearance(); int zone_clearance = aZone->GetZoneClearance();
@ -860,17 +862,17 @@ void ZONE_FILLER::buildThermalSpokes( const ZONE_CONTAINER* aZone,
zoneBB.Inflate( biggest_clearance ); zoneBB.Inflate( biggest_clearance );
// half size of the pen used to draw/plot zones outlines // 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 // Is a point on the boundary of the polygon inside or outside? This small epsilon lets
// lets us avoid the question. // us avoid the question.
int boundaryCorrection = KiROUND( IU_PER_MM * 0.04 ); int epsilon = KiROUND( IU_PER_MM * 0.04 );
// We'd normally add in an arcCorrection for circles (since a finite number of segments // 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 // is only an approximation of the circle radius). However, epsilon is already twice even
// twice even our ARC_LOW_DEF error tolerance, so there's little benefit to it (and a small // our LOW resolution error tolerance, so there's little benefit to it (and a small but
// but existant performance penalty). // existant performance penalty).
//int numSegs = std::max( GetArcToSegmentCount( pen_raidus, m_high_def, 360.0 ), 6 ); //int numSegs = std::max( GetArcToSegmentCount( pen_w, m_high_def, 360.0 ), 6 );
//double arcCorrection = GetCircletoPolyCorrectionFactor( numSegs ); //double arcCorrection = GetCircletoPolyCorrectionFactor( numSegs );
for( auto module : m_board->Modules() ) for( auto module : m_board->Modules() )
@ -883,17 +885,16 @@ void ZONE_FILLER::buildThermalSpokes( const ZONE_CONTAINER* aZone,
int thermalReliefGap = aZone->GetThermalReliefGap( pad ); int thermalReliefGap = aZone->GetThermalReliefGap( pad );
// Calculate thermal bridge half width // Calculate thermal bridge half width
int spokeThickness = aZone->GetThermalReliefCopperBridge( pad ) int spoke_w = aZone->GetThermalReliefCopperBridge( pad ) - aZone->GetMinThickness();
- aZone->GetMinThickness();
if( spokeThickness <= 0 ) if( spoke_w <= 0 )
continue; continue;
spokeThickness = spokeThickness / 2; spoke_w = spoke_w / 2;
// Quick test here to possibly save us some work // Quick test here to possibly save us some work
BOX2I itemBB = pad->GetBoundingBox(); BOX2I itemBB = pad->GetBoundingBox();
itemBB.Inflate( thermalReliefGap + pen_radius + boundaryCorrection ); itemBB.Inflate( thermalReliefGap + pen_w + epsilon );
if( !( itemBB.Intersects( zoneBB ) ) ) if( !( itemBB.Intersects( zoneBB ) ) )
continue; continue;
@ -907,7 +908,7 @@ void ZONE_FILLER::buildThermalSpokes( const ZONE_CONTAINER* aZone,
pad->SetOrientation( 0.0 ); pad->SetOrientation( 0.0 );
pad->SetPosition( - pad->GetOffset() ); pad->SetPosition( - pad->GetOffset() );
BOX2I reliefBB = pad->GetBoundingBox(); 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 // For circle pads, the thermal stubs orientation is 45 deg
if( pad->GetShape() == PAD_SHAPE_CIRCLE ) 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++ ) for( int i = 0; i < 4; i++ )
{ {
SHAPE_LINE_CHAIN spoke; THERMAL_SPOKE spoke;
// polygons are rectangles with width of copper bridge value // polygons are rectangles with width of copper bridge value
switch( i ) switch( i )
{ {
case 0: // lower stub case 0: // lower stub
spoke.Append( +spokeThickness, 0 ); spoke.m_Outline.Append( +spoke_w, 0 );
spoke.Append( -spokeThickness, 0 ); spoke.m_Outline.Append( -spoke_w, 0 );
spoke.Append( -spokeThickness, reliefBB.GetBottom() ); spoke.m_Outline.Append( -spoke_w, reliefBB.GetBottom() );
spoke.Append( +spokeThickness, 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; break;
case 1: // upper stub case 1: // upper stub
spoke.Append( +spokeThickness, 0 ); spoke.m_Outline.Append( +spoke_w, 0 );
spoke.Append( -spokeThickness, 0 ); spoke.m_Outline.Append( -spoke_w, 0 );
spoke.Append( -spokeThickness, reliefBB.GetTop() ); spoke.m_Outline.Append( -spoke_w, reliefBB.GetTop() );
spoke.Append( +spokeThickness, 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; break;
case 2: // right stub case 2: // right stub
spoke.Append( 0, spokeThickness ); spoke.m_Outline.Append( 0, spoke_w );
spoke.Append( 0, -spokeThickness ); spoke.m_Outline.Append( 0, -spoke_w );
spoke.Append( reliefBB.GetRight(), -spokeThickness ); spoke.m_Outline.Append( reliefBB.GetRight(), -spoke_w );
spoke.Append( reliefBB.GetRight(), spokeThickness ); 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; break;
case 3: // left stub case 3: // left stub
spoke.Append( 0, spokeThickness ); spoke.m_Outline.Append( 0, spoke_w );
spoke.Append( 0, -spokeThickness ); spoke.m_Outline.Append( 0, -spoke_w );
spoke.Append( reliefBB.GetLeft(), -spokeThickness ); spoke.m_Outline.Append( reliefBB.GetLeft(), -spoke_w );
spoke.Append( reliefBB.GetLeft(), spokeThickness ); 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; break;
} }
for( int ic = 0; ic < spoke.PointCount(); ic++ ) for( int j = 0; j < spoke.m_Outline.PointCount(); j++ )
{ {
RotatePoint( spoke.Point( ic ), spokeAngle ); RotatePoint( spoke.m_Outline.Point( j ), spokeAngle );
spoke.Point( ic ) += padPos + pad->GetOffset(); spoke.m_Outline.Point( j ) += padPos + pad->GetOffset();
} }
spoke.SetClosed( true ); RotatePoint( spoke.m_TestPtA, spokeAngle );
aSpokesList.push_back( spoke ); 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 ); pad->SetPosition( padPos );

View File

@ -35,6 +35,20 @@ class COMMIT;
class SHAPE_POLY_SET; class SHAPE_POLY_SET;
class SHAPE_LINE_CHAIN; 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 class ZONE_FILLER
{ {
public: public:
@ -52,8 +66,7 @@ private:
void knockoutThermals( const ZONE_CONTAINER* aZone, SHAPE_POLY_SET& aFill ); void knockoutThermals( const ZONE_CONTAINER* aZone, SHAPE_POLY_SET& aFill );
void knockoutCopperItems( const ZONE_CONTAINER* aZone, SHAPE_POLY_SET& aFill, void knockoutCopperItems( const ZONE_CONTAINER* aZone, SHAPE_POLY_SET& aFill );
std::deque<SHAPE_LINE_CHAIN>& aSpokes );
/** /**
* Function computeRawFilledArea * Function computeRawFilledArea
@ -73,7 +86,7 @@ private:
* Function buildThermalSpokes * Function buildThermalSpokes
* Constructs a list of all thermal spokes for the given zone. * Constructs a list of all thermal spokes for the given zone.
*/ */
void buildThermalSpokes( const ZONE_CONTAINER* aZone, std::deque<SHAPE_LINE_CHAIN>& aSpokes ); void buildThermalSpokes( const ZONE_CONTAINER* aZone, std::deque<THERMAL_SPOKE>& aSpokes );
/** /**
* Build the filled solid areas polygons from zone outlines (stored in m_Poly) * Build the filled solid areas polygons from zone outlines (stored in m_Poly)