Keep track of single-pad-islands so we can discount spokes to them.

This commit is contained in:
Jeff Young 2023-03-25 10:44:46 +00:00
parent 4e27e91b2f
commit d6dd58fff9
13 changed files with 174 additions and 174 deletions

View File

@ -263,6 +263,7 @@ void BOARD::IncrementTimeStamp()
m_DRCMaxPhysicalClearance = 0;
m_DRCZones.clear();
m_DRCCopperZones.clear();
m_ZoneIsolatedIslandsMap.clear();
m_CopperZoneRTreeCache.clear();
m_CopperItemRTreeCache = std::make_unique<DRC_RTREE>();
}

View File

@ -60,6 +60,7 @@ class CONNECTIVITY_DATA;
class COMPONENT;
class PROJECT;
class PROGRESS_REPORTER;
struct ISOLATED_ISLANDS;
// The default value for m_outlinesChainingEpsilon to convert a board outlines to polygons
// It is the max dist between 2 end points to see them connected
@ -1177,6 +1178,7 @@ public:
int m_DRCMaxClearance;
int m_DRCMaxPhysicalClearance;
ZONE* m_SolderMask;
std::map<ZONE*, std::map<PCB_LAYER_ID, ISOLATED_ISLANDS>> m_ZoneIsolatedIslandsMap;
private:
// The default copy constructor & operator= are inadequate,

View File

@ -613,53 +613,26 @@ void CN_CONNECTIVITY_ALGO::PropagateNets( BOARD_COMMIT* aCommit )
}
void CN_CONNECTIVITY_ALGO::FindIsolatedCopperIslands( ZONE* aZone, PCB_LAYER_ID aLayer,
std::vector<int>& aIslands )
{
if( aZone->GetFilledPolysList( aLayer )->IsEmpty() )
return;
aIslands.clear();
Remove( aZone );
Add( aZone );
m_connClusters = SearchClusters( CSM_CONNECTIVITY_CHECK );
for( const std::shared_ptr<CN_CLUSTER>& cluster : m_connClusters )
{
if( cluster->Contains( aZone ) && cluster->IsOrphaned() )
{
for( CN_ITEM* z : *cluster )
{
if( z->Parent() == aZone && z->Layer() == aLayer )
aIslands.push_back( static_cast<CN_ZONE_LAYER*>(z)->SubpolyIndex() );
}
}
}
wxLogTrace( wxT( "CN" ), wxT( "Found %u isolated islands\n" ), (unsigned) aIslands.size() );
}
void CN_CONNECTIVITY_ALGO::FindIsolatedCopperIslands( std::vector<CN_ZONE_ISOLATED_ISLAND_LIST>& aZones,
void CN_CONNECTIVITY_ALGO::FillIsolatedIslandsMap(
std::map<ZONE*, std::map<PCB_LAYER_ID, ISOLATED_ISLANDS>>& aMap,
bool aConnectivityAlreadyRebuilt )
{
int progressDelta = 50;
int ii = 0;
progressDelta = std::max( progressDelta, (int) aZones.size() / 4 );
progressDelta = std::max( progressDelta, (int) aMap.size() / 4 );
if( !aConnectivityAlreadyRebuilt )
{
for( CN_ZONE_ISOLATED_ISLAND_LIST& z : aZones )
for( const auto& [ zone, islands ] : aMap )
{
Remove( z.m_zone );
Add( z.m_zone );
Remove( zone );
Add( zone );
ii++;
if( m_progressReporter && ( ii % progressDelta ) == 0 )
{
m_progressReporter->SetCurrentProgress( (double) ii / (double) aZones.size() );
m_progressReporter->SetCurrentProgress( (double) ii / (double) aMap.size() );
m_progressReporter->KeepRefreshing( false );
}
@ -670,24 +643,25 @@ void CN_CONNECTIVITY_ALGO::FindIsolatedCopperIslands( std::vector<CN_ZONE_ISOLAT
m_connClusters = SearchClusters( CSM_CONNECTIVITY_CHECK );
for( CN_ZONE_ISOLATED_ISLAND_LIST& zone : aZones )
for( auto& [ zone, zoneIslands ] : aMap )
{
for( PCB_LAYER_ID layer : zone.m_zone->GetLayerSet().Seq() )
for( auto& [ layer, layerIslands ] : zoneIslands )
{
if( zone.m_zone->GetFilledPolysList( layer )->IsEmpty() )
if( zone->GetFilledPolysList( layer )->IsEmpty() )
continue;
for( const std::shared_ptr<CN_CLUSTER>& cluster : m_connClusters )
{
if( cluster->Contains( zone.m_zone ) && cluster->IsOrphaned() )
for( CN_ITEM* item : *cluster )
{
for( CN_ITEM* z : *cluster )
if( item->Parent() == zone && item->Layer() == layer )
{
if( z->Parent() == zone.m_zone && z->Layer() == layer )
{
zone.m_islands[layer].push_back(
static_cast<CN_ZONE_LAYER*>( z )->SubpolyIndex() );
}
CN_ZONE_LAYER* z = static_cast<CN_ZONE_LAYER*>( item );
if( cluster->IsOrphaned() )
layerIslands.m_IsolatedOutlines.push_back( z->SubpolyIndex() );
else if( z->HasSingleConnection() )
layerIslands.m_SingleConnectionOutlines.push_back( z->SubpolyIndex() );
}
}
}

View File

@ -221,17 +221,10 @@ public:
*/
void PropagateNets( BOARD_COMMIT* aCommit = nullptr );
void FindIsolatedCopperIslands( ZONE* aZone, PCB_LAYER_ID aLayer, std::vector<int>& aIslands );
/**
* Find the copper islands that are not connected to a net.
*
* These are added to the m_islands vector.
* N.B. This must be called after aZones has been refreshed.
*
* @param: aZones is the set of zones to search for islands.
* Fill in the isolated islands map with copper islands that are not connected to a net.
*/
void FindIsolatedCopperIslands( std::vector<CN_ZONE_ISOLATED_ISLAND_LIST>& aZones,
void FillIsolatedIslandsMap( std::map<ZONE*, std::map<PCB_LAYER_ID, ISOLATED_ISLANDS>>& aMap,
bool aConnectivityAlreadyRebuilt );
const CLUSTERS& GetClusters();

View File

@ -309,18 +309,10 @@ int CONNECTIVITY_DATA::GetNetCount() const
}
void CONNECTIVITY_DATA::FindIsolatedCopperIslands( ZONE* aZone, std::vector<int>& aIslands )
{
// TODO(JE) ZONES
#if 0
m_connAlgo->FindIsolatedCopperIslands( aZone, aIslands );
#endif
}
void CONNECTIVITY_DATA::FindIsolatedCopperIslands( std::vector<CN_ZONE_ISOLATED_ISLAND_LIST>& aZones,
void CONNECTIVITY_DATA::FillIsolatedIslandsMap( std::map<ZONE*, std::map<PCB_LAYER_ID, ISOLATED_ISLANDS>>& aMap,
bool aConnectivityAlreadyRebuilt )
{
m_connAlgo->FindIsolatedCopperIslands( aZones, aConnectivityAlreadyRebuilt );
m_connAlgo->FillIsolatedIslandsMap( aMap, aConnectivityAlreadyRebuilt );
}

View File

@ -64,21 +64,6 @@ struct CN_DISJOINT_NET_ENTRY
};
/**
* A structure used for calculating isolated islands on a given zone across all its layers
*/
struct CN_ZONE_ISOLATED_ISLAND_LIST
{
CN_ZONE_ISOLATED_ISLAND_LIST( ZONE* aZone ) :
m_zone( aZone )
{}
ZONE* m_zone;
std::map<PCB_LAYER_ID, std::vector<int>> m_islands;
};
struct RN_DYNAMIC_LINE
{
int netCode;
@ -176,13 +161,10 @@ public:
void PropagateNets( BOARD_COMMIT* aCommit = nullptr );
/**
* Function FindIsolatedCopperIslands()
* Searches for copper islands in zone aZone that are not connected to any pad.
* @param aZone zone to test
* @param aIslands list of islands that have no connections (outline indices in the polygon set)
* Fill the isolate islands list for each layer of each zone. Isolated islands are individual
* polygons in a zone fill that don't connect to a net.
*/
void FindIsolatedCopperIslands( ZONE* aZone, std::vector<int>& aIslands );
void FindIsolatedCopperIslands( std::vector<CN_ZONE_ISOLATED_ISLAND_LIST>& aZones,
void FillIsolatedIslandsMap( std::map<ZONE*, std::map<PCB_LAYER_ID, ISOLATED_ISLANDS>>& aMap,
bool aConnectivityAlreadyRebuilt = false );
/**

View File

@ -109,6 +109,23 @@ const VECTOR2I CN_ZONE_LAYER::GetAnchor( int n ) const
}
bool CN_ZONE_LAYER::HasSingleConnection()
{
int count = 0;
for( CN_ITEM* item : ConnectedItems() )
{
if( item->Valid() )
count++;
if( count > 1 )
break;
}
return count == 1;
}
void CN_ITEM::RemoveInvalidRefs()
{
for( auto it = m_connected.begin(); it != m_connected.end(); /* increment in loop */ )

View File

@ -387,6 +387,8 @@ public:
return collision;
}
bool HasSingleConnection();
private:
int m_subpolyIndex;
PCB_LAYER_ID m_layer;

View File

@ -26,7 +26,7 @@
#include <footprint.h>
#include <thread_pool.h>
#include <zone.h>
#include <connectivity/connectivity_data.h>
#include <drc/drc_engine.h>
#include <drc/drc_rtree.h>
#include <drc/drc_cache_generator.h>
@ -201,6 +201,23 @@ bool DRC_CACHE_GENERATOR::Run()
}
}
m_board->m_ZoneIsolatedIslandsMap.clear();
for( ZONE* zone : m_board->Zones() )
{
if( !zone->GetIsRuleArea() )
{
for( PCB_LAYER_ID layer : zone->GetLayerSet().Seq() )
m_board->m_ZoneIsolatedIslandsMap[ zone ][ layer ] = ISOLATED_ISLANDS();
}
}
std::shared_ptr<CONNECTIVITY_DATA> connectivity = m_board->GetConnectivity();
connectivity->ClearRatsnest();
connectivity->Build( m_board, m_drcEngine->GetProgressReporter() );
connectivity->FillIsolatedIslandsMap( m_board->m_ZoneIsolatedIslandsMap, true );
return !m_drcEngine->IsCancelled();
}

View File

@ -26,7 +26,7 @@
#include <connectivity/connectivity_data.h>
#include <connectivity/connectivity_algo.h>
#include <zone.h>
#include <drc/drc_engine.h>
#include <drc/drc_item.h>
#include <drc/drc_rule.h>
@ -72,24 +72,11 @@ bool DRC_TEST_PROVIDER_CONNECTIVITY::Run()
return false; // DRC cancelled
BOARD* board = m_drcEngine->GetBoard();
std::shared_ptr<CONNECTIVITY_DATA> connectivity = board->GetConnectivity();
std::vector<CN_ZONE_ISOLATED_ISLAND_LIST> islandsList;
for( ZONE* zone : board->Zones() )
{
if( !zone->GetIsRuleArea() )
islandsList.emplace_back( CN_ZONE_ISOLATED_ISLAND_LIST( zone ) );
}
// Rebuild (from scratch, ignoring dirty flags) just in case. This really needs to be reliable.
connectivity->ClearRatsnest();
connectivity->Build( board, m_drcEngine->GetProgressReporter() );
connectivity->FindIsolatedCopperIslands( islandsList, true );
int progressDelta = 250;
int ii = 0;
int count = board->Tracks().size() + islandsList.size();
int count = board->Tracks().size() + board->m_ZoneIsolatedIslandsMap.size();
ii += count; // We gave half of this phase to CONNECTIVITY_DATA::Build()
count += count;
@ -122,7 +109,7 @@ bool DRC_TEST_PROVIDER_CONNECTIVITY::Run()
}
/* test starved zones */
for( CN_ZONE_ISOLATED_ISLAND_LIST& zone : islandsList )
for( const auto& [ zone, zoneIslands ] : board->m_ZoneIsolatedIslandsMap )
{
if( m_drcEngine->IsErrorLimitExceeded( DRCE_ISOLATED_COPPER ) )
break;
@ -130,21 +117,18 @@ bool DRC_TEST_PROVIDER_CONNECTIVITY::Run()
if( !reportProgress( ii++, count, progressDelta ) )
return false; // DRC cancelled
for( PCB_LAYER_ID layer : zone.m_zone->GetLayerSet().Seq() )
for( const auto& [ layer, layerIslands ] : zoneIslands )
{
if( !zone.m_islands.count( layer ) )
continue;
std::shared_ptr<SHAPE_POLY_SET> poly = zone.m_zone->GetFilledPolysList( layer );
for( int idx : zone.m_islands.at( layer ) )
for( int polyIdx : layerIslands.m_IsolatedOutlines )
{
if( m_drcEngine->IsErrorLimitExceeded( DRCE_ISOLATED_COPPER ) )
break;
std::shared_ptr<SHAPE_POLY_SET> poly = zone->GetFilledPolysList( layer );
std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_ISOLATED_COPPER );
drcItem->SetItems( zone.m_zone );
reportViolation( drcItem, poly->Outline( idx ).CPoint( 0 ), layer );
drcItem->SetItems( zone );
reportViolation( drcItem, poly->Outline( polyIdx ).CPoint( 0 ), layer );
}
}
}

View File

@ -81,6 +81,17 @@ void DRC_TEST_PROVIDER_ZONE_CONNECTIONS::testZoneLayer( ZONE* aZone, PCB_LAYER_I
DRC_CONSTRAINT constraint;
const std::shared_ptr<SHAPE_POLY_SET>& zoneFill = aZone->GetFilledPolysList( aLayer );
ISOLATED_ISLANDS isolatedIslands;
auto zoneIter = board->m_ZoneIsolatedIslandsMap.find( aZone );
if( zoneIter != board->m_ZoneIsolatedIslandsMap.end() )
{
auto layerIter = zoneIter->second.find( aLayer );
if( layerIter != zoneIter->second.end() )
isolatedIslands = layerIter->second;
}
for( FOOTPRINT* footprint : board->Footprints() )
{
@ -132,7 +143,15 @@ void DRC_TEST_PROVIDER_ZONE_CONNECTIONS::testZoneLayer( ZONE* aZone, PCB_LAYER_I
std::vector<SHAPE_LINE_CHAIN::INTERSECTION> intersections;
for( int jj = 0; jj < zoneFill->OutlineCount(); ++jj )
{
// If we connect to an island that only connects to a single item then we *are*
// that item. Thermal spokes to this (otherwise isolated) island don't provide
// electrical connectivity to anything, so we don't count them.
if( alg::contains( isolatedIslands.m_SingleConnectionOutlines, jj ) )
continue;
zoneFill->Outline( jj ).Intersect( padOutline, intersections, true, &padBBox );
}
int spokes = intersections.size() / 2;
@ -197,6 +216,8 @@ bool DRC_TEST_PROVIDER_ZONE_CONNECTIONS::Run()
size_t total_effort = 0;
for( ZONE* zone : board->m_DRCCopperZones )
{
if( !zone->IsTeardropArea() )
{
for( PCB_LAYER_ID layer : zone->GetLayerSet().Seq() )
{
@ -204,6 +225,7 @@ bool DRC_TEST_PROVIDER_ZONE_CONNECTIONS::Run()
total_effort += zone->GetFilledPolysList( layer )->FullPointCount();
}
}
}
total_effort = std::max( (size_t) 1, total_effort );

View File

@ -44,6 +44,24 @@ class ZONE;
class MSG_PANEL_ITEM;
/**
* A struct recording the isolated and single-pad islands within a zone. Each array holds
* indexes into the outlines of a SHAPE_POLY_SET for a zone fill on a particular layer.
*
* Isolated outlines are those whose *connectivity cluster* contains no pads. These generate
* DRC violations.
*
* Single-connection outlines are those with a *direct* connection to only a single item. These
* participate in thermal spoke counting as a pad spoke to an *otherwise* unconnected island
* provides no connectivity to the pad.
*/
struct ISOLATED_ISLANDS
{
std::vector<int> m_IsolatedOutlines;
std::vector<int> m_SingleConnectionOutlines;
};
/**
* Handle a list of polygons defining a copper zone.
*

View File

@ -92,7 +92,7 @@ bool ZONE_FILLER::Fill( std::vector<ZONE*>& aZones, bool aCheck, wxWindow* aPare
std::vector<std::pair<ZONE*, PCB_LAYER_ID>> toFill;
std::map<std::pair<ZONE*, PCB_LAYER_ID>, MD5_HASH> oldFillHashes;
std::vector<CN_ZONE_ISOLATED_ISLAND_LIST> islandsList;
std::map<ZONE*, std::map<PCB_LAYER_ID, ISOLATED_ISLANDS>> isolatedIslandsMap;
std::shared_ptr<CONNECTIVITY_DATA> connectivity = m_board->GetConnectivity();
@ -328,9 +328,9 @@ bool ZONE_FILLER::Fill( std::vector<ZONE*>& aZones, bool aCheck, wxWindow* aPare
// Add the zone to the list of zones to test or refill
toFill.emplace_back( std::make_pair( zone, layer ) );
}
islandsList.emplace_back( CN_ZONE_ISOLATED_ISLAND_LIST( zone ) );
isolatedIslandsMap[ zone ][ layer ] = ISOLATED_ISLANDS();
}
// Remove existing fill first to prevent drawing invalid polygons on some platforms
zone->UnFill();
@ -534,7 +534,7 @@ bool ZONE_FILLER::Fill( std::vector<ZONE*>& aZones, bool aCheck, wxWindow* aPare
}
connectivity->SetProgressReporter( m_progressReporter );
connectivity->FindIsolatedCopperIslands( islandsList );
connectivity->FillIsolatedIslandsMap( isolatedIslandsMap );
connectivity->SetProgressReporter( nullptr );
if( m_progressReporter && m_progressReporter->IsCancelled() )
@ -552,26 +552,15 @@ bool ZONE_FILLER::Fill( std::vector<ZONE*>& aZones, bool aCheck, wxWindow* aPare
// Now remove isolated copper islands according to the isolated islands strategy assigned
// by the user (always, never, below-certain-size).
//
for( CN_ZONE_ISOLATED_ISLAND_LIST& zone : islandsList )
for( const auto& [ zone, zoneIslands ] : isolatedIslandsMap )
{
// If *all* the polygons are islands, do not remove any of them
bool allIslands = true;
for( PCB_LAYER_ID layer : zone.m_zone->GetLayerSet().Seq() )
for( const auto& [ layer, layerIslands ] : zoneIslands )
{
std::shared_ptr<SHAPE_POLY_SET> poly = zone.m_zone->GetFilledPolysList( layer );
if( !zone.m_islands.count( layer ) )
{
if( poly->OutlineCount() > 0 )
allIslands = false;
continue;
}
std::vector<int>& islands = zone.m_islands.at( layer );
if( islands.size() != static_cast<size_t>( poly->OutlineCount() ) )
if( layerIslands.m_IsolatedOutlines.size()
!= static_cast<size_t>( zone->GetFilledPolysList( layer )->OutlineCount() ) )
{
allIslands = false;
break;
@ -581,23 +570,23 @@ bool ZONE_FILLER::Fill( std::vector<ZONE*>& aZones, bool aCheck, wxWindow* aPare
if( allIslands )
continue;
for( PCB_LAYER_ID layer : zone.m_zone->GetLayerSet().Seq() )
for( const auto& [ layer, layerIslands ] : zoneIslands )
{
if( m_debugZoneFiller && LSET::InternalCuMask().Contains( layer ) )
continue;
if( !zone.m_islands.count( layer ) )
if( layerIslands.m_IsolatedOutlines.empty() )
continue;
std::vector<int>& islands = zone.m_islands.at( layer );
std::vector<int> islands = layerIslands.m_IsolatedOutlines;
// The list of polygons to delete must be explored from last to first in list,
// to allow deleting a polygon from list without breaking the remaining of the list
std::sort( islands.begin(), islands.end(), std::greater<int>() );
std::shared_ptr<SHAPE_POLY_SET> poly = zone.m_zone->GetFilledPolysList( layer );
long long int minArea = zone.m_zone->GetMinIslandArea();
ISLAND_REMOVAL_MODE mode = zone.m_zone->GetIslandRemovalMode();
std::shared_ptr<SHAPE_POLY_SET> poly = zone->GetFilledPolysList( layer );
long long int minArea = zone->GetMinIslandArea();
ISLAND_REMOVAL_MODE mode = zone->GetIslandRemovalMode();
for( int idx : islands )
{
@ -608,11 +597,11 @@ bool ZONE_FILLER::Fill( std::vector<ZONE*>& aZones, bool aCheck, wxWindow* aPare
else if ( mode == ISLAND_REMOVAL_MODE::AREA && outline.Area( true ) < minArea )
poly->DeletePolygonAndTriangulationData( idx, false );
else
zone.m_zone->SetIsIsland( layer, idx );
zone->SetIsIsland( layer, idx );
}
poly->UpdateTriangulationDataHash();
zone.m_zone->CalculateFilledArea();
zone->CalculateFilledArea();
if( m_progressReporter && m_progressReporter->IsCancelled() )
return false;
@ -646,7 +635,8 @@ bool ZONE_FILLER::Fill( std::vector<ZONE*>& aZones, bool aCheck, wxWindow* aPare
}
}
auto island_lambda = [&]( int aStart, int aEnd ) -> island_check_return
auto island_lambda =
[&]( int aStart, int aEnd ) -> island_check_return
{
island_check_return retval;
@ -666,12 +656,14 @@ bool ZONE_FILLER::Fill( std::vector<ZONE*>& aZones, bool aCheck, wxWindow* aPare
island.AddOutline( test_poly );
intersection.BooleanIntersection( m_boardOutline, island, SHAPE_POLY_SET::POLYGON_MODE::PM_FAST );
intersection.BooleanIntersection( m_boardOutline, island,
SHAPE_POLY_SET::POLYGON_MODE::PM_FAST );
// Nominally, all of these areas should be either inside or outside the board outline. So this test
// should be able to just compare areas (if they are equal, you are inside). But in practice,
// we sometimes can have slight overlap at the edges. So testing against half-size area is
// a fail-safe
// Nominally, all of these areas should be either inside or outside the
// board outline. So this test should be able to just compare areas (if
// they are equal, you are inside). But in practice, we sometimes have
// slight overlap at the edges, so testing against half-size area acts as
// a fail-safe.
if( intersection.Area() < island_area / 2.0 )
retval.emplace_back( poly, jj );
}
@ -1575,10 +1567,14 @@ bool ZONE_FILLER::fillCopperZone( const ZONE* aZone, PCB_LAYER_ID aLayer, PCB_LA
DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In15_Cu, wxT( "after-reinflating" ) );
/* -------------------------------------------------------------------------------------
* Ensure additive changes (thermal stubs and particularly inflating acute corners) do not
* add copper outside the zone boundary or inside the clearance holes
* Ensure additive changes (thermal stubs and inflating acute corners) do not add copper
* outside the zone boundary, inside the clearance holes, or between otherwise isolated
* islands
*/
for( PAD* pad : thermalConnectionPads )
addHoleKnockout( pad, 0, clearanceHoles );
aFillPolys.BooleanIntersection( aMaxExtents, SHAPE_POLY_SET::PM_FAST );
DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In16_Cu, wxT( "after-trim-to-outline" ) );
aFillPolys.BooleanSubtract( clearanceHoles, SHAPE_POLY_SET::PM_FAST );