More zone fill performance enhancements.

Knocking out unconnected spokes turns out to be very expensive, so
keep them in a list and only apply them if they're connected.
This commit is contained in:
Jeff Young 2019-06-22 15:03:17 +01:00
parent 14986d1e0e
commit 70e6d95c7f
2 changed files with 95 additions and 174 deletions

View File

@ -492,7 +492,8 @@ 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;
@ -548,7 +549,7 @@ void ZONE_FILLER::knockoutCopperItems( const ZONE_CONTAINER* aZone, SHAPE_POLY_S
pad = &dummypad; pad = &dummypad;
} }
else if( pad->GetNetCode() != aZone->GetNetCode() if( pad->GetNetCode() != aZone->GetNetCode()
|| pad->GetNetCode() <= 0 || pad->GetNetCode() <= 0
|| aZone->GetPadConnection( pad ) == PAD_ZONE_CONN_NONE ) || aZone->GetPadConnection( pad ) == PAD_ZONE_CONN_NONE )
{ {
@ -682,10 +683,10 @@ void ZONE_FILLER::knockoutCopperItems( const ZONE_CONTAINER* aZone, SHAPE_POLY_S
* so that when drawing outline with segments having a thickness of m_ZoneMinThickness the * so that when drawing outline with segments having a thickness of m_ZoneMinThickness the
* outlines will match exactly the initial outlines * outlines will match exactly the initial outlines
* 2 - Knocks out thermal reliefs around thermally-connected pads * 2 - Knocks out thermal reliefs around thermally-connected pads
* 3 - Adds thermal spokes * 3 - Builds a set of thermal spoke for the whole zone
* 4 - Knocks out unconnected copper items * 4 - Knocks out unconnected copper items, deleting any affected spokes
* 5 - Removes unconnected copper islands * 5 - Removes unconnected copper islands, deleting any affected spokes
* 6 - Removes unconnected thermal spokes * 6 - Adds in the remaining spokes
*/ */
void ZONE_FILLER::computeRawFilledArea( const ZONE_CONTAINER* aZone, void ZONE_FILLER::computeRawFilledArea( const ZONE_CONTAINER* aZone,
const SHAPE_POLY_SET& aSmoothedOutline, const SHAPE_POLY_SET& aSmoothedOutline,
@ -704,50 +705,52 @@ 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;
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 );
solidAreas.Inflate( -outline_half_thickness, numSegs ); solidAreas.Inflate( -outline_half_thickness, numSegs );
solidAreas.Simplify( SHAPE_POLY_SET::PM_FAST ); solidAreas.Simplify( SHAPE_POLY_SET::PM_FAST );
// ORDER IS IMPORTANT HERE:
//
// Pad thermals MUST NOT knockout spokes of other pads. Therefore we do ALL the knockouts
// first, and THEN add in all the spokes.
//
// Other copper item clearances MUST knockout spokes. They are therefore done AFTER all
// the thermal spokes.
//
// Finally any spokes which are now dangling can be taken back out.
knockoutThermals( aZone, solidAreas ); knockoutThermals( aZone, solidAreas );
if( s_DumpZonesWhenFilling ) if( s_DumpZonesWhenFilling )
dumper->Write( &solidAreas, "solid-areas-minus-thermal-reliefs" ); dumper->Write( &solidAreas, "solid-areas-minus-thermal-reliefs" );
SHAPE_POLY_SET spokes; buildThermalSpokes( aZone, thermalSpokes );
buildThermalSpokes( spokes, aZone, solidAreas, false );
spokes.Simplify( SHAPE_POLY_SET::PM_FAST ); knockoutCopperItems( aZone, solidAreas, thermalSpokes );
// Use SHAPE_POLY_SET::PM_STRICTLY_SIMPLE to generate strictly simple polygons
// needed by Gerber files and Fracture()
solidAreas.BooleanAdd( spokes, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
if( s_DumpZonesWhenFilling )
dumper->Write( &solidAreas, "solid-areas-with-thermal-spokes" );
knockoutCopperItems( aZone, solidAreas );
if( s_DumpZonesWhenFilling ) if( s_DumpZonesWhenFilling )
dumper->Write( &solidAreas, "solid-areas-minus-clearances" ); dumper->Write( &solidAreas, "solid-areas-minus-clearances" );
if( thermalSpokes.size() )
{
SHAPE_POLY_SET amalgamatedSpokes;
for( SHAPE_LINE_CHAIN& spoke : thermalSpokes )
{
// Add together all spokes which connect to the zone's filled area
if( solidAreas.Contains( spoke.Point( 2 ) ) || solidAreas.Contains( spoke.Point( 3 ) ) )
amalgamatedSpokes.AddOutline( spoke );
}
amalgamatedSpokes.Simplify( SHAPE_POLY_SET::PM_FAST );
// Use SHAPE_POLY_SET::PM_STRICTLY_SIMPLE to generate strictly simple polygons
// needed by Gerber files and Fracture()
solidAreas.BooleanAdd( amalgamatedSpokes, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
}
if( s_DumpZonesWhenFilling )
dumper->Write( &solidAreas, "solid-areas-with-thermal-spokes" );
// Now remove the non filled areas due to the hatch pattern // Now remove the non filled areas due to the hatch pattern
if( aZone->GetFillMode() == ZFM_HATCH_PATTERN ) if( aZone->GetFillMode() == ZFM_HATCH_PATTERN )
addHatchFillTypeOnZone( aZone, solidAreas ); addHatchFillTypeOnZone( aZone, solidAreas );
if( s_DumpZonesWhenFilling ) if( s_DumpZonesWhenFilling )
dumper->Write( &solidAreas, "solid-areas-minus-holes" ); dumper->Write( &solidAreas, "solid-areas-minus-hatching" );
// This code for non copper zones is currently a dead code: // This code for non copper zones is currently a dead code:
// computeRawFilledAreas() is no longer called for non copper zones // computeRawFilledAreas() is no longer called for non copper zones
@ -771,55 +774,6 @@ void ZONE_FILLER::computeRawFilledArea( const ZONE_CONTAINER* aZone,
return; return;
} }
// Test thermal stubs connections and add polygons to remove unconnected stubs.
// (this is a refinement for thermal relief shapes)
// Note: we are using not fractured solid area polygons, to avoid a side effect of extra segments
// created by Fracture(): if a tested point used in buildUnconnectedThermalStubsPolygonList
// is on a extra segment, the tested point is seen outside the solid area, but it is inside.
// This is not a bug, just the fact when a point is on a polygon outline, it is hard to say
// if it is inside or outside the polygon.
SHAPE_POLY_SET danglingThermals;
if( aZone->GetNetCode() > 0 )
buildThermalSpokes( danglingThermals, aZone, solidAreas, true );
// remove copper areas corresponding to not connected stubs
if( !danglingThermals.IsEmpty() )
{
danglingThermals.Simplify( SHAPE_POLY_SET::PM_FAST );
// Remove unconnected stubs. Use SHAPE_POLY_SET::PM_STRICTLY_SIMPLE to
// generate strictly simple polygons
// needed by Gerber files and Fracture()
solidAreas.BooleanSubtract( danglingThermals, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
if( s_DumpZonesWhenFilling )
dumper->Write( &danglingThermals, "dangling-thermal-spokes" );
// put these areas in m_FilledPolysList
SHAPE_POLY_SET th_fractured = solidAreas;
// Inflate polygon to recreate the polygon (without the too narrow areas)
// if the filled polygons have a outline thickness = 0
int inflate_value = aZone->GetFilledPolysUseThickness() ? 0 :outline_half_thickness;
if( inflate_value <= Millimeter2iu( 0.001 ) ) // avoid very small outline thickness
inflate_value = 0;
if( inflate_value )
{
th_fractured.Simplify( SHAPE_POLY_SET::PM_FAST );
th_fractured.Inflate( outline_half_thickness, 16 );
}
th_fractured.Fracture( SHAPE_POLY_SET::PM_FAST );
if( s_DumpZonesWhenFilling )
dumper->Write( &th_fractured, "th_fractured" );
aFinalPolys = th_fractured;
}
else
{
SHAPE_POLY_SET areas_fractured = solidAreas; SHAPE_POLY_SET areas_fractured = solidAreas;
// Inflate polygon to recreate the polygon (without the too narrow areas) // Inflate polygon to recreate the polygon (without the too narrow areas)
@ -841,7 +795,6 @@ void ZONE_FILLER::computeRawFilledArea( const ZONE_CONTAINER* aZone,
dumper->Write( &areas_fractured, "areas_fractured" ); dumper->Write( &areas_fractured, "areas_fractured" );
aFinalPolys = areas_fractured; aFinalPolys = areas_fractured;
}
aRawPolys = aFinalPolys; aRawPolys = aFinalPolys;
@ -896,19 +849,11 @@ bool ZONE_FILLER::fillSingleZone( ZONE_CONTAINER* aZone, SHAPE_POLY_SET& aRawPol
/** /**
* Function buildThermalSpokes * Function buildThermalSpokes
* Creates a set of polygons corresponding to stubs created by thermal shapes on pads
* which are not connected to a zone (dangling bridges)
* @param aCornerBuffer = a SHAPE_POLY_SET where to store polygons
* @param aZone = a pointer to the ZONE_CONTAINER to examine.
*/ */
void ZONE_FILLER::buildThermalSpokes( SHAPE_POLY_SET& aCornerBuffer, void ZONE_FILLER::buildThermalSpokes( const ZONE_CONTAINER* aZone,
const ZONE_CONTAINER* aZone, std::deque<SHAPE_LINE_CHAIN>& aSpokesList )
const SHAPE_POLY_SET& aRawFilledArea,
bool aDanglingOnly )
{ {
BOX2I itemBB; auto zoneBB = aZone->GetBoundingBox();
VECTOR2I ptTest[4];
auto zoneBB = aRawFilledArea.BBox();
int zone_clearance = aZone->GetZoneClearance(); int zone_clearance = aZone->GetZoneClearance();
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 );
@ -916,7 +861,11 @@ void ZONE_FILLER::buildThermalSpokes( SHAPE_POLY_SET& aCornerBuffer,
int outline_half_thickness = aZone->GetMinThickness() / 2; int outline_half_thickness = aZone->GetMinThickness() / 2;
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 );
double correction = GetCircletoPolyCorrectionFactor( numSegs ); double circleCorrection = GetCircletoPolyCorrectionFactor( numSegs );
// 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 );
// 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_radius = aZone->GetMinThickness() / 2;
@ -930,12 +879,6 @@ void ZONE_FILLER::buildThermalSpokes( SHAPE_POLY_SET& aCornerBuffer,
int thermalReliefGap = aZone->GetThermalReliefGap( pad ); int thermalReliefGap = aZone->GetThermalReliefGap( pad );
itemBB = pad->GetBoundingBox();
itemBB.Inflate( thermalReliefGap );
if( !( itemBB.Intersects( zoneBB ) ) )
continue;
// Calculate thermal bridge half width // Calculate thermal bridge half width
int spokeThickness = aZone->GetThermalReliefCopperBridge( pad ) int spokeThickness = aZone->GetThermalReliefCopperBridge( pad )
- aZone->GetMinThickness(); - aZone->GetMinThickness();
@ -945,13 +888,18 @@ void ZONE_FILLER::buildThermalSpokes( SHAPE_POLY_SET& aCornerBuffer,
spokeThickness = spokeThickness / 2; spokeThickness = spokeThickness / 2;
// Thermal bridges are like a segment from a starting point inside the pad // Thermal spokes consist of segments from the pad origin to points just outside
// to an ending point outside the pad // the thermal relief.
// Calculate the ending points of the thermal spokes, outside the pad // Calculate the ending points of the thermal spokes, outside the thermal relief
itemBB.Offset( -pad->ShapePos() ); BOX2I reliefBB = pad->GetBoundingBox();
int extra = pen_radius + KiROUND( IU_PER_MM * 0.04 ); reliefBB.Inflate( thermalReliefGap + pen_radius + boundaryCorrection );
itemBB.Inflate( extra, extra );
// Quick test here to possibly save us some work
if( !( reliefBB.Intersects( zoneBB ) ) )
continue;
reliefBB.Offset( -pad->ShapePos() );
// This is a CIRCLE pad tweak // This is a CIRCLE pad tweak
// for circle pads, the thermal stubs orientation is 45 deg // for circle pads, the thermal stubs orientation is 45 deg
@ -959,75 +907,53 @@ void ZONE_FILLER::buildThermalSpokes( SHAPE_POLY_SET& aCornerBuffer,
if( pad->GetShape() == PAD_SHAPE_CIRCLE ) if( pad->GetShape() == PAD_SHAPE_CIRCLE )
{ {
extra = KiROUND( itemBB.GetX() * correction ) - itemBB.GetX(); reliefBB.Inflate( KiROUND( reliefBB.GetX() * circleCorrection ) - reliefBB.GetX() );
itemBB.Inflate( extra, extra );
fAngle = s_thermalRot; fAngle = s_thermalRot;
} }
// compute north, south, west and east points for zone connection. for( int i = 0; i < 4; i++ )
ptTest[0] = VECTOR2I( 0, itemBB.GetBottom() ); {
ptTest[1] = VECTOR2I( 0, itemBB.GetTop() ); SHAPE_LINE_CHAIN spoke;
ptTest[2] = VECTOR2I( itemBB.GetRight(), 0 );
ptTest[3] = VECTOR2I( itemBB.GetLeft(), 0 );
auto addStub = [&] ( int aSide ) {
SHAPE_LINE_CHAIN spokes;
// polygons are rectangles with width of copper bridge value // polygons are rectangles with width of copper bridge value
switch( aSide ) switch( i )
{ {
case 0: // lower stub case 0: // lower stub
spokes.Append( -spokeThickness, itemBB.GetBottom() ); spoke.Append( +spokeThickness, 0 );
spokes.Append( +spokeThickness, itemBB.GetBottom() ); spoke.Append( -spokeThickness, 0 );
spokes.Append( +spokeThickness, 0 ); spoke.Append( -spokeThickness, reliefBB.GetBottom() );
spokes.Append( -spokeThickness, 0 ); spoke.Append( +spokeThickness, reliefBB.GetBottom() );
break; break;
case 1: // upper stub case 1: // upper stub
spokes.Append( -spokeThickness, itemBB.GetTop() ); spoke.Append( +spokeThickness, 0 );
spokes.Append( +spokeThickness, itemBB.GetTop() ); spoke.Append( -spokeThickness, 0 );
spokes.Append( +spokeThickness, 0 ); spoke.Append( -spokeThickness, reliefBB.GetTop() );
spokes.Append( -spokeThickness, 0 ); spoke.Append( +spokeThickness, reliefBB.GetTop() );
break; break;
case 2: // right stub case 2: // right stub
spokes.Append( itemBB.GetRight(), -spokeThickness ); spoke.Append( 0, spokeThickness );
spokes.Append( itemBB.GetRight(), spokeThickness ); spoke.Append( 0, -spokeThickness );
spokes.Append( 0, spokeThickness ); spoke.Append( reliefBB.GetRight(), -spokeThickness );
spokes.Append( 0, -spokeThickness ); spoke.Append( reliefBB.GetRight(), spokeThickness );
break; break;
case 3: // left stub case 3: // left stub
spokes.Append( itemBB.GetLeft(), -spokeThickness ); spoke.Append( 0, spokeThickness );
spokes.Append( itemBB.GetLeft(), spokeThickness ); spoke.Append( 0, -spokeThickness );
spokes.Append( 0, spokeThickness ); spoke.Append( reliefBB.GetLeft(), -spokeThickness );
spokes.Append( 0, -spokeThickness ); spoke.Append( reliefBB.GetLeft(), spokeThickness );
break; break;
} }
aCornerBuffer.NewOutline(); for( int ic = 0; ic < spoke.PointCount(); ic++ )
// add computed polygon to list
for( int ic = 0; ic < spokes.PointCount(); ic++ )
{ {
auto cpos = spokes.CPoint( ic ); RotatePoint( spoke.Point( ic ), fAngle );
RotatePoint( cpos, fAngle ); spoke.Point( ic ) += pad->ShapePos();
cpos += pad->ShapePos();
aCornerBuffer.Append( cpos );
}
};
for( int i = 0; i < 4; i++ )
{
if( aDanglingOnly )
{
RotatePoint( ptTest[i], fAngle );
ptTest[i] += pad->ShapePos();
if( aRawFilledArea.Contains( ptTest[i] ) )
continue;
} }
addStub( i ); spoke.SetClosed( true );
aSpokesList.push_back( spoke );
} }
} }
} }

View File

@ -52,7 +52,8 @@ 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
@ -70,15 +71,9 @@ private:
/** /**
* Function buildThermalSpokes * Function buildThermalSpokes
* Creates a set of polygons corresponding to stubs created by thermal shapes on pads * Constructs a list of all thermal spokes for the given zone.
* which are not connected to a zone (dangling bridges)
* @param aCornerBuffer = a SHAPE_POLY_SET where to store polygons
* @param aPcb = the board.
* @param aZone = a pointer to the ZONE_CONTAINER to examine.
* @param aDanglingSpokesOnly = true to add only dangling spokes to aCornerBuffer
*/ */
void buildThermalSpokes( SHAPE_POLY_SET& aCornerBuffer, const ZONE_CONTAINER* aZone, void buildThermalSpokes( const ZONE_CONTAINER* aZone, std::deque<SHAPE_LINE_CHAIN>& aSpokes );
const SHAPE_POLY_SET& aRawFilledArea, bool aDanglingSpokesOnly);
/** /**
* 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)