Browse Source

Back to the drawing board for zone filling.

This one doesn't cut any corners so should be "correct".  And it
turns out to be pretty fast.
pull/15/head
Jeff Young 7 years ago
parent
commit
107d206dba
  1. 142
      pcbnew/zone_filler.cpp
  2. 19
      pcbnew/zone_filler.h

142
pcbnew/zone_filler.cpp

@ -463,7 +463,7 @@ void ZONE_FILLER::addKnockout( BOARD_ITEM* aItem, int aGap, bool aIgnoreLineWidt
* Removes thermal reliefs from the shape for any pads connected to the zone. Does NOT add
* in spokes, which must be done later.
*/
void ZONE_FILLER::knockoutThermals( const ZONE_CONTAINER* aZone, SHAPE_POLY_SET& aFill )
void ZONE_FILLER::knockoutThermalReliefs( const ZONE_CONTAINER* aZone, SHAPE_POLY_SET& aFill )
{
SHAPE_POLY_SET holes;
@ -477,10 +477,7 @@ void ZONE_FILLER::knockoutThermals( const ZONE_CONTAINER* aZone, SHAPE_POLY_SET&
}
holes.Simplify( SHAPE_POLY_SET::PM_FAST );
// Use SHAPE_POLY_SET::PM_STRICTLY_SIMPLE to generate strictly simple polygons
// needed by Gerber files and Fracture()
aFill.BooleanSubtract( holes, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
aFill.BooleanSubtract( holes, SHAPE_POLY_SET::PM_FAST );
}
@ -488,10 +485,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
* not connected to it.
*/
void ZONE_FILLER::knockoutCopperItems( const ZONE_CONTAINER* aZone, SHAPE_POLY_SET& aFill )
void ZONE_FILLER::buildCopperItemClearances( const ZONE_CONTAINER* aZone, SHAPE_POLY_SET& aHoles )
{
SHAPE_POLY_SET holes;
int zone_clearance = aZone->GetClearance();
int edgeClearance = m_board->GetDesignSettings().m_CopperEdgeClearance;
int zone_to_edgecut_clearance = std::max( aZone->GetZoneClearance(), edgeClearance );
@ -547,7 +542,7 @@ void ZONE_FILLER::knockoutCopperItems( const ZONE_CONTAINER* aZone, SHAPE_POLY_S
item_boundingbox.Inflate( pad->GetClearance() );
if( item_boundingbox.Intersects( zone_boundingbox ) )
addKnockout( pad, gap, holes );
addKnockout( pad, gap, aHoles );
}
}
}
@ -566,7 +561,7 @@ void ZONE_FILLER::knockoutCopperItems( const ZONE_CONTAINER* aZone, SHAPE_POLY_S
EDA_RECT item_boundingbox = track->GetBoundingBox();
if( item_boundingbox.Intersects( zone_boundingbox ) )
track->TransformShapeWithClearanceToPolygon( holes, gap, m_low_def );
track->TransformShapeWithClearanceToPolygon( aHoles, gap, m_low_def );
}
// Add graphic item clearances. They are by definition unconnected, and have no clearance
@ -592,7 +587,7 @@ void ZONE_FILLER::knockoutCopperItems( const ZONE_CONTAINER* aZone, SHAPE_POLY_S
ignoreLineWidth = true;
}
addKnockout( aItem, gap, ignoreLineWidth, holes );
addKnockout( aItem, gap, ignoreLineWidth, aHoles );
};
for( auto module : m_board->Modules() )
@ -644,14 +639,10 @@ void ZONE_FILLER::knockoutCopperItems( const ZONE_CONTAINER* aZone, SHAPE_POLY_S
if( zone->GetIsKeepout() || sameNet )
useNetClearance = false;
zone->TransformOutlinesShapeWithClearanceToPolygon( holes, minClearance, useNetClearance );
zone->TransformOutlinesShapeWithClearanceToPolygon( aHoles, minClearance, useNetClearance );
}
holes.Simplify( SHAPE_POLY_SET::PM_FAST );
// Use SHAPE_POLY_SET::PM_STRICTLY_SIMPLE to generate strictly simple polygons
// needed by Gerber files and Fracture()
aFill.BooleanSubtract( holes, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
aHoles.Simplify( SHAPE_POLY_SET::PM_FAST );
}
@ -673,8 +664,8 @@ void ZONE_FILLER::computeRawFilledArea( const ZONE_CONTAINER* aZone,
{
m_high_def = m_board->GetDesignSettings().m_MaxError;
m_low_def = std::min( ARC_LOW_DEF, int( m_high_def*1.5 ) ); // Reasonable value
int outline_half_thickness = aZone->GetMinThickness() / 2;
int numSegs = std::max( GetArcToSegmentCount( outline_half_thickness, m_high_def, 360.0 ), 6 );
std::unique_ptr<SHAPE_FILE_IO> dumper( new SHAPE_FILE_IO(
s_DumpZonesWhenFilling ? "zones_dump.txt" : "", SHAPE_FILE_IO::IOM_APPEND ) );
@ -683,55 +674,63 @@ void ZONE_FILLER::computeRawFilledArea( const ZONE_CONTAINER* aZone,
dumper->BeginGroup( "clipper-zone" );
SHAPE_POLY_SET solidAreas = aSmoothedOutline;
std::deque<THERMAL_SPOKE> thermalSpokes;
std::deque<SHAPE_LINE_CHAIN> thermalSpokes;
SHAPE_POLY_SET clearanceHoles;
int numSegs = std::max( GetArcToSegmentCount( outline_half_thickness, m_high_def, 360.0 ), 6 );
knockoutThermals( aZone, solidAreas );
knockoutThermalReliefs( aZone, solidAreas );
if( s_DumpZonesWhenFilling )
dumper->Write( &solidAreas, "solid-areas-minus-thermal-reliefs" );
buildThermalSpokes( aZone, thermalSpokes );
knockoutCopperItems( aZone, solidAreas );
buildCopperItemClearances( aZone, clearanceHoles );
if( s_DumpZonesWhenFilling )
dumper->Write( &solidAreas, "solid-areas-minus-clearances" );
dumper->Write( &solidAreas, "clearance holes" );
if( thermalSpokes.size() )
SHAPE_POLY_SET testAreas = solidAreas;
testAreas.BooleanSubtract( clearanceHoles, SHAPE_POLY_SET::PM_FAST );
// Remove areas that don't meet minimum-width criteria
testAreas.Inflate( -outline_half_thickness, numSegs, true );
testAreas.Inflate( outline_half_thickness, numSegs, true );
buildThermalSpokes( aZone, thermalSpokes );
for( const SHAPE_LINE_CHAIN& spoke : thermalSpokes )
{
SHAPE_POLY_SET amalgamatedSpokes;
if( testAreas.Contains( spoke.CPoint( 3 ), -1, false, true ) )
{
solidAreas.AddOutline( spoke );
continue;
}
for( THERMAL_SPOKE& spoke : thermalSpokes )
for( const SHAPE_LINE_CHAIN& other : thermalSpokes )
{
// Add together all spokes whose endpoints lie within the zone's filled area
if( solidAreas.Contains( spoke.m_TestPtA, -1, false, true )
&& solidAreas.Contains( spoke.m_TestPtB, -1, false, true ) )
if( &other != &spoke && other.PointInside( spoke.CPoint( 3 ), 1 ) )
{
amalgamatedSpokes.AddOutline( spoke.m_Outline );
solidAreas.AddOutline( spoke );
break;
}
}
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 );
}
solidAreas.Inflate( -outline_half_thickness, numSegs );
solidAreas.Simplify( SHAPE_POLY_SET::PM_FAST );
if( s_DumpZonesWhenFilling )
dumper->Write( &solidAreas, "solid-areas-with-thermal-spokes" );
solidAreas.BooleanSubtract( clearanceHoles, SHAPE_POLY_SET::PM_FAST );
solidAreas.Inflate( -outline_half_thickness, numSegs );
if( s_DumpZonesWhenFilling )
dumper->Write( &solidAreas, "solid-areas-before-hatching" );
// Now remove the non filled areas due to the hatch pattern
if( aZone->GetFillMode() == ZFM_HATCH_PATTERN )
addHatchFillTypeOnZone( aZone, solidAreas );
if( s_DumpZonesWhenFilling )
dumper->Write( &solidAreas, "solid-areas-minus-hatching" );
dumper->Write( &solidAreas, "solid-areas-after-hatching" );
SHAPE_POLY_SET areas_fractured = solidAreas;
@ -810,7 +809,7 @@ bool ZONE_FILLER::fillSingleZone( ZONE_CONTAINER* aZone, SHAPE_POLY_SET& aRawPol
* Function buildThermalSpokes
*/
void ZONE_FILLER::buildThermalSpokes( const ZONE_CONTAINER* aZone,
std::deque<THERMAL_SPOKE>& aSpokesList )
std::deque<SHAPE_LINE_CHAIN>& aSpokesList )
{
auto zoneBB = aZone->GetBoundingBox();
int zone_clearance = aZone->GetZoneClearance();
@ -858,59 +857,50 @@ void ZONE_FILLER::buildThermalSpokes( const ZONE_CONTAINER* aZone,
for( int i = 0; i < 4; i++ )
{
THERMAL_SPOKE spoke;
SHAPE_LINE_CHAIN spoke;
// polygons are rectangles with width of copper bridge value
switch( i )
{
case 0: // lower stub
spoke.m_Outline.Append( +spoke_half_w, 0 );
spoke.m_Outline.Append( -spoke_half_w, 0 );
spoke.m_Outline.Append( -spoke_half_w, reliefBB.GetBottom() );
spoke.m_Outline.Append( +spoke_half_w, reliefBB.GetBottom() );
spoke.m_TestPtA = { epsilon - spoke_half_w, reliefBB.GetBottom() };
spoke.m_TestPtB = { spoke_half_w - epsilon, reliefBB.GetBottom() };
spoke.Append( +spoke_half_w, 0 );
spoke.Append( -spoke_half_w, 0 );
spoke.Append( -spoke_half_w, reliefBB.GetBottom() );
spoke.Append( 0, reliefBB.GetBottom() ); // test pt
spoke.Append( +spoke_half_w, reliefBB.GetBottom() );
break;
case 1: // upper stub
spoke.m_Outline.Append( +spoke_half_w, 0 );
spoke.m_Outline.Append( -spoke_half_w, 0 );
spoke.m_Outline.Append( -spoke_half_w, reliefBB.GetTop() );
spoke.m_Outline.Append( +spoke_half_w, reliefBB.GetTop() );
spoke.m_TestPtA = { epsilon - spoke_half_w, reliefBB.GetTop() };
spoke.m_TestPtB = { spoke_half_w - epsilon, reliefBB.GetTop() };
spoke.Append( +spoke_half_w, 0 );
spoke.Append( -spoke_half_w, 0 );
spoke.Append( -spoke_half_w, reliefBB.GetTop() );
spoke.Append( 0, reliefBB.GetTop() ); // test pt
spoke.Append( +spoke_half_w, reliefBB.GetTop() );
break;
case 2: // right stub
spoke.m_Outline.Append( 0, spoke_half_w );
spoke.m_Outline.Append( 0, -spoke_half_w );
spoke.m_Outline.Append( reliefBB.GetRight(), -spoke_half_w );
spoke.m_Outline.Append( reliefBB.GetRight(), spoke_half_w );
spoke.m_TestPtA = { reliefBB.GetRight(), epsilon - spoke_half_w };
spoke.m_TestPtB = { reliefBB.GetRight(), spoke_half_w - epsilon };
spoke.Append( 0, spoke_half_w );
spoke.Append( 0, -spoke_half_w );
spoke.Append( reliefBB.GetRight(), -spoke_half_w );
spoke.Append( reliefBB.GetRight(), 0 ); // test pt
spoke.Append( reliefBB.GetRight(), spoke_half_w );
break;
case 3: // left stub
spoke.m_Outline.Append( 0, spoke_half_w );
spoke.m_Outline.Append( 0, -spoke_half_w );
spoke.m_Outline.Append( reliefBB.GetLeft(), -spoke_half_w );
spoke.m_Outline.Append( reliefBB.GetLeft(), spoke_half_w );
spoke.m_TestPtA = { reliefBB.GetLeft(), epsilon - spoke_half_w };
spoke.m_TestPtB = { reliefBB.GetLeft(), spoke_half_w - epsilon };
spoke.Append( 0, spoke_half_w );
spoke.Append( 0, -spoke_half_w );
spoke.Append( reliefBB.GetLeft(), -spoke_half_w );
spoke.Append( reliefBB.GetLeft(), 0 ); // test pt
spoke.Append( reliefBB.GetLeft(), spoke_half_w );
break;
}
for( int j = 0; j < spoke.m_Outline.PointCount(); j++ )
for( int j = 0; j < spoke.PointCount(); j++ )
{
RotatePoint( spoke.m_Outline.Point( j ), spokeAngle );
spoke.m_Outline.Point( j ) += padPos + pad->GetOffset();
RotatePoint( spoke.Point( j ), spokeAngle );
spoke.Point( j ) += padPos + pad->GetOffset();
}
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 );
spoke.SetClosed( true );
aSpokesList.push_back( std::move( spoke ) );
}

19
pcbnew/zone_filler.h

@ -35,19 +35,6 @@ 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
{
@ -64,9 +51,9 @@ private:
void addKnockout( BOARD_ITEM* aItem, int aGap, bool aIgnoreLineWidth, SHAPE_POLY_SET& aHoles );
void knockoutThermals( const ZONE_CONTAINER* aZone, SHAPE_POLY_SET& aFill );
void knockoutThermalReliefs( const ZONE_CONTAINER* aZone, SHAPE_POLY_SET& aFill );
void knockoutCopperItems( const ZONE_CONTAINER* aZone, SHAPE_POLY_SET& aFill );
void buildCopperItemClearances( const ZONE_CONTAINER* aZone, SHAPE_POLY_SET& aHoles );
/**
* Function computeRawFilledArea
@ -86,7 +73,7 @@ private:
* Function buildThermalSpokes
* Constructs a list of all thermal spokes for the given zone.
*/
void buildThermalSpokes( const ZONE_CONTAINER* aZone, std::deque<THERMAL_SPOKE>& aSpokes );
void buildThermalSpokes( const ZONE_CONTAINER* aZone, std::deque<SHAPE_LINE_CHAIN>& aSpokes );
/**
* Build the filled solid areas polygons from zone outlines (stored in m_Poly)

Loading…
Cancel
Save