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?
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;

View File

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

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
* not connected to it.
*/
void ZONE_FILLER::knockoutCopperItems( const ZONE_CONTAINER* aZone, SHAPE_POLY_SET& aFill,
std::deque<SHAPE_LINE_CHAIN>& 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<SHAPE_LINE_CHAIN> thermalSpokes;
std::deque<THERMAL_SPOKE> 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<SHAPE_LINE_CHAIN>& aSpokesList )
std::deque<THERMAL_SPOKE>& 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 );

View File

@ -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<SHAPE_LINE_CHAIN>& 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<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)