Fix several issues with multilayer zones

- Properly refill if layer set is modified
- Fix some threading issues with island removal
- Fix copy constructor

Fixes https://gitlab.com/kicad/code/kicad/-/issues/4765
This commit is contained in:
Jon Evans 2020-06-30 22:21:59 -04:00
parent e320a3f112
commit f8bfb2bc16
8 changed files with 140 additions and 105 deletions

View File

@ -87,6 +87,10 @@ ZONE_CONTAINER& ZONE_CONTAINER::operator=( const ZONE_CONTAINER& aOther )
m_Poly = new SHAPE_POLY_SET( *aOther.m_Poly );
m_isKeepout = aOther.m_isKeepout;
SetLayerSet( aOther.GetLayerSet() );
m_zoneName = aOther.m_zoneName;
m_IsFilled = aOther.m_IsFilled;
m_CornerSelection = nullptr; // for corner moving, corner index to (null if no selection)
m_ZoneClearance = aOther.m_ZoneClearance; // clearance value
m_ZoneMinThickness = aOther.m_ZoneMinThickness;
@ -99,11 +103,14 @@ ZONE_CONTAINER& ZONE_CONTAINER::operator=( const ZONE_CONTAINER& aOther )
SetHatchPitch( aOther.GetHatchPitch() );
m_HatchLines = aOther.m_HatchLines; // copy vector <SEG>
m_FilledPolysList = aOther.m_FilledPolysList;
m_RawPolysList = aOther.m_RawPolysList;
m_filledPolysHash = aOther.m_filledPolysHash;
m_FillSegmList = aOther.m_FillSegmList; // vector <> copy
m_insulatedIslands = aOther.m_insulatedIslands;
for( PCB_LAYER_ID layer : aOther.GetLayerSet().Seq() )
{
m_FilledPolysList[layer] = aOther.m_FilledPolysList.at( layer );
m_RawPolysList[layer] = aOther.m_RawPolysList.at( layer );
m_filledPolysHash[layer] = aOther.m_filledPolysHash.at( layer );
m_FillSegmList[layer] = aOther.m_FillSegmList.at( layer ); // vector <> copy
m_insulatedIslands[layer] = aOther.m_insulatedIslands.at( layer );
}
m_HatchFillTypeThickness = aOther.m_HatchFillTypeThickness;
m_HatchFillTypeGap = aOther.m_HatchFillTypeGap;
@ -111,8 +118,6 @@ ZONE_CONTAINER& ZONE_CONTAINER::operator=( const ZONE_CONTAINER& aOther )
m_HatchFillTypeSmoothingLevel = aOther.m_HatchFillTypeSmoothingLevel;
m_HatchFillTypeSmoothingValue = aOther.m_HatchFillTypeSmoothingValue;
SetLayerSet( aOther.GetLayerSet() );
return *this;
}
@ -137,6 +142,8 @@ void ZONE_CONTAINER::initDataFromSrcInCopyCtor( const ZONE_CONTAINER& aZone )
m_isKeepout = aZone.m_isKeepout;
SetLayerSet( aZone.GetLayerSet() );
m_zoneName = aZone.m_zoneName;
m_Poly = new SHAPE_POLY_SET( *aZone.m_Poly );
// For corner moving, corner index to drag, or nullptr if no selection
@ -151,11 +158,15 @@ void ZONE_CONTAINER::initDataFromSrcInCopyCtor( const ZONE_CONTAINER& aZone )
m_PadConnection = aZone.m_PadConnection;
m_ThermalReliefGap = aZone.m_ThermalReliefGap;
m_ThermalReliefCopperBridge = aZone.m_ThermalReliefCopperBridge;
m_FilledPolysList = aZone.m_FilledPolysList;
m_RawPolysList = aZone.m_RawPolysList;
m_filledPolysHash = aZone.m_filledPolysHash;
m_FillSegmList = aZone.m_FillSegmList; // vector <> copy
m_insulatedIslands = aZone.m_insulatedIslands;
for( PCB_LAYER_ID layer : aZone.GetLayerSet().Seq() )
{
m_FilledPolysList[layer] = aZone.m_FilledPolysList.at( layer );
m_RawPolysList[layer] = aZone.m_RawPolysList.at( layer );
m_filledPolysHash[layer] = aZone.m_filledPolysHash.at( layer );
m_FillSegmList[layer] = aZone.m_FillSegmList.at( layer ); // vector <> copy
m_insulatedIslands[layer] = aZone.m_insulatedIslands.at( layer );
}
m_doNotAllowCopperPour = aZone.m_doNotAllowCopperPour;
m_doNotAllowVias = aZone.m_doNotAllowVias;
@ -267,6 +278,12 @@ void ZONE_CONTAINER::SetLayerSet( LSET aLayerSet )
UnFill();
m_FillSegmList.clear();
m_FilledPolysList.clear();
m_RawPolysList.clear();
m_filledPolysHash.clear();
m_insulatedIslands.clear();
for( PCB_LAYER_ID layer : aLayerSet.Seq() )
{
m_FillSegmList[layer] = {};

View File

@ -30,6 +30,7 @@
#define CLASS_ZONE_H_
#include <mutex>
#include <vector>
#include <gr_basic.h>
#include <class_board_item.h>
@ -176,6 +177,11 @@ public:
return m_area;
}
std::mutex& GetLock()
{
return m_lock;
}
bool IsFilled() const { return m_IsFilled; }
void SetIsFilled( bool isFilled ) { m_IsFilled = isFilled; }
@ -929,6 +935,9 @@ protected:
bool m_hv45; // constrain edges to horizontal, vertical or 45º
double m_area; // The filled zone area
/// Lock used for multi-threaded filling on multi-layer zones
std::mutex m_lock;
};

View File

@ -552,38 +552,33 @@ void CN_CONNECTIVITY_ALGO::FindIsolatedCopperIslands( ZONE_CONTAINER* aZone,
void CN_CONNECTIVITY_ALGO::FindIsolatedCopperIslands( std::vector<CN_ZONE_ISOLATED_ISLAND_LIST>& aZones )
{
for ( auto& z : aZones )
Remove( z.m_zone );
for ( auto& z : aZones )
for( auto& z : aZones )
{
for( PCB_LAYER_ID layer : z.m_zone->GetLayerSet().Seq() )
{
if( !z.m_zone->GetFilledPolysList( layer ).IsEmpty() )
{
Add( z.m_zone );
break;
}
}
Remove( z.m_zone );
Add( z.m_zone );
}
m_connClusters = SearchClusters( CSM_CONNECTIVITY_CHECK );
for ( auto& zone : aZones )
for( auto& zone : aZones )
{
PCB_LAYER_ID layer = zone.m_layer;
if( zone.m_zone->GetFilledPolysList( layer ).IsEmpty() )
continue;
for( const auto& cluster : m_connClusters )
for( PCB_LAYER_ID layer : zone.m_zone->GetLayerSet().Seq() )
{
if( cluster->Contains( zone.m_zone ) && cluster->IsOrphaned() )
if( zone.m_zone->GetFilledPolysList( layer ).IsEmpty() )
continue;
for( const auto& cluster : m_connClusters )
{
for( auto z : *cluster )
if( cluster->Contains( zone.m_zone ) && cluster->IsOrphaned() )
{
if( z->Parent() == zone.m_zone && z->Layer() == layer )
zone.m_islands.push_back( static_cast<CN_ZONE*>( z )->SubpolyIndex() );
for( auto z : *cluster )
{
if( z->Parent() == zone.m_zone && z->Layer() == layer )
{
zone.m_islands[layer].push_back(
static_cast<CN_ZONE*>( z )->SubpolyIndex() );
}
}
}
}
}

View File

@ -61,21 +61,17 @@ struct CN_DISJOINT_NET_ENTRY
};
/**
* A structure used for filling a copper zone on one layer.
* Multilayer zones will have one of these for each active layer.
* 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_CONTAINER* aZone, PCB_LAYER_ID aLayer ) :
m_zone( aZone ),
m_layer( aLayer )
CN_ZONE_ISOLATED_ISLAND_LIST( ZONE_CONTAINER* aZone ) :
m_zone( aZone )
{}
ZONE_CONTAINER* m_zone;
PCB_LAYER_ID m_layer;
std::vector<int> m_islands;
std::map<PCB_LAYER_ID, std::vector<int>> m_islands;
};
struct RN_DYNAMIC_LINE

View File

@ -100,7 +100,9 @@ void ZONE_FILLER::InstallNewProgressReporter( wxWindow* aParent, const wxString&
bool ZONE_FILLER::Fill( const std::vector<ZONE_CONTAINER*>& aZones, bool aCheck )
{
std::vector<CN_ZONE_ISOLATED_ISLAND_LIST> toFill;
std::vector<std::pair<ZONE_CONTAINER*, PCB_LAYER_ID>> toFill;
std::vector<CN_ZONE_ISOLATED_ISLAND_LIST> islandsList;
auto connectivity = m_board->GetConnectivity();
bool filledPolyWithOutline = not m_board->GetDesignSettings().m_ZoneUseNoOutlineInFill;
@ -112,7 +114,7 @@ bool ZONE_FILLER::Fill( const std::vector<ZONE_CONTAINER*>& aZones, bool aCheck
if( m_progressReporter )
{
m_progressReporter->Report( aCheck ? _( "Checking zone fills..." ) : _( "Building zone fills..." ) );
m_progressReporter->SetMaxProgress( toFill.size() );
m_progressReporter->SetMaxProgress( aZones.size() );
}
// The board outlines is used to clip solid areas inside the board (when outlines are valid)
@ -145,9 +147,11 @@ bool ZONE_FILLER::Fill( const std::vector<ZONE_CONTAINER*>& aZones, bool aCheck
zone->BuildHashValue( layer );
// Add the zone to the list of zones to test or refill
toFill.emplace_back( CN_ZONE_ISOLATED_ISLAND_LIST( zone, layer ) );
toFill.emplace_back( std::make_pair( zone, layer ) );
}
islandsList.emplace_back( CN_ZONE_ISOLATED_ISLAND_LIST( zone ) );
// Remove existing fill first to prevent drawing invalid polygons
// on some platforms
zone->UnFill();
@ -164,14 +168,16 @@ bool ZONE_FILLER::Fill( const std::vector<ZONE_CONTAINER*>& aZones, bool aCheck
for( size_t i = nextItem++; i < toFill.size(); i = nextItem++ )
{
PCB_LAYER_ID layer = toFill[i].m_layer;
ZONE_CONTAINER* zone = toFill[i].m_zone;
PCB_LAYER_ID layer = toFill[i].second;
ZONE_CONTAINER* zone = toFill[i].first;
zone->SetFilledPolysUseThickness( filledPolyWithOutline );
SHAPE_POLY_SET rawPolys, finalPolys;
fillSingleZone( zone, layer, rawPolys, finalPolys );
std::unique_lock<std::mutex> zoneLock( zone->GetLock() );
zone->SetRawPolysList( layer, rawPolys );
zone->SetFilledPolysList( layer, finalPolys );
zone->SetIsFilled( true );
@ -215,68 +221,70 @@ bool ZONE_FILLER::Fill( const std::vector<ZONE_CONTAINER*>& aZones, bool aCheck
}
connectivity->SetProgressReporter( m_progressReporter );
connectivity->FindIsolatedCopperIslands( toFill );
connectivity->FindIsolatedCopperIslands( islandsList );
// Now remove insulated copper islands and islands outside the board edge
bool outOfDate = false;
for( auto& zone : toFill )
for( auto& zone : islandsList )
{
std::sort( zone.m_islands.begin(), zone.m_islands.end(), std::greater<int>() );
SHAPE_POLY_SET poly = zone.m_zone->GetFilledPolysList( zone.m_layer );
long long int minArea = zone.m_zone->GetMinIslandArea();
ISLAND_REMOVAL_MODE mode = zone.m_zone->GetIslandRemovalMode();
// Remove solid areas outside the board cutouts and the insulated islands
// only zones with net code > 0 can have insulated islands by definition
if( zone.m_zone->GetNetCode() > 0 )
for( PCB_LAYER_ID layer : zone.m_zone->GetLayerSet().Seq() )
{
// solid areas outside the board cutouts are also removed, because they are usually
// insulated islands
for( auto idx : zone.m_islands )
{
#if 0 // for tests only
double metricMin = minArea * ( MM_PER_IU * MM_PER_IU );
double metricArea = poly.Outline( idx ).Area() * ( MM_PER_IU * MM_PER_IU );
std::cout << ( metricArea < metricMin ) << std::endl;
#endif
if( !zone.m_islands.count( layer ) )
continue;
if( mode == ISLAND_REMOVAL_MODE::ALWAYS
|| ( mode == ISLAND_REMOVAL_MODE::AREA
&& poly.Outline( idx ).Area() < minArea )
|| !m_boardOutline.Contains( poly.Polygon( idx ).front().CPoint( 0 ) ) )
poly.DeletePolygon( idx );
else
zone.m_zone->SetIsIsland( zone.m_layer, idx );
}
}
// Zones with no net can have areas outside the board cutouts.
// By definition, Zones with no net have no isolated island
// (in fact all filled areas are isolated islands)
// but they can have some areas outside the board cutouts.
// A filled area outside the board cutouts has all points outside cutouts,
// so we only need to check one point for each filled polygon.
// Note also non copper zones are already clipped
else if( m_brdOutlinesValid && zone.m_zone->IsOnCopperLayer() )
{
for( int idx = 0; idx < poly.OutlineCount(); )
std::vector<int>& islands = zone.m_islands.at( layer );
std::sort( islands.begin(), islands.end(), std::greater<int>() );
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();
// Remove solid areas outside the board cutouts and the insulated islands
// only zones with net code > 0 can have insulated islands by definition
if( zone.m_zone->GetNetCode() > 0 )
{
if( poly.Polygon( idx ).empty()
|| !m_boardOutline.Contains( poly.Polygon( idx ).front().CPoint( 0 ) ) )
// solid areas outside the board cutouts are also removed, because they are usually
// insulated islands
for( auto idx : islands )
{
poly.DeletePolygon( idx );
if( mode == ISLAND_REMOVAL_MODE::ALWAYS
|| ( mode == ISLAND_REMOVAL_MODE::AREA
&& poly.Outline( idx ).Area() < minArea )
|| !m_boardOutline.Contains( poly.Polygon( idx ).front().CPoint( 0 ) ) )
poly.DeletePolygon( idx );
else
zone.m_zone->SetIsIsland( layer, idx );
}
else
idx++;
}
// Zones with no net can have areas outside the board cutouts.
// By definition, Zones with no net have no isolated island
// (in fact all filled areas are isolated islands)
// but they can have some areas outside the board cutouts.
// A filled area outside the board cutouts has all points outside cutouts,
// so we only need to check one point for each filled polygon.
// Note also non copper zones are already clipped
else if( m_brdOutlinesValid && zone.m_zone->IsOnCopperLayer() )
{
for( int idx = 0; idx < poly.OutlineCount(); )
{
if( poly.Polygon( idx ).empty()
|| !m_boardOutline.Contains( poly.Polygon( idx ).front().CPoint( 0 ) ) )
{
poly.DeletePolygon( idx );
}
else
idx++;
}
}
zone.m_zone->SetFilledPolysList( layer, poly );
zone.m_zone->CalculateFilledArea();
if( aCheck && zone.m_zone->GetHashValue( layer ) != poly.GetHash() )
outOfDate = true;
}
zone.m_zone->SetFilledPolysList( zone.m_layer, poly );
zone.m_zone->CalculateFilledArea();
if( aCheck && zone.m_zone->GetHashValue( zone.m_layer ) != poly.GetHash() )
outOfDate = true;
}
if( aCheck && outOfDate )
@ -312,9 +320,9 @@ bool ZONE_FILLER::Fill( const std::vector<ZONE_CONTAINER*>& aZones, bool aCheck
{
size_t num = 0;
for( size_t i = nextItem++; i < toFill.size(); i = nextItem++ )
for( size_t i = nextItem++; i < islandsList.size(); i = nextItem++ )
{
toFill[i].m_zone->CacheTriangulation();
islandsList[i].m_zone->CacheTriangulation();
num++;
if( m_progressReporter )
@ -361,7 +369,7 @@ bool ZONE_FILLER::Fill( const std::vector<ZONE_CONTAINER*>& aZones, bool aCheck
else
{
for( auto& i : toFill )
connectivity->Update( i.m_zone );
connectivity->Update( i.first );
connectivity->RecalculateRatsnest();
}

View File

@ -129,7 +129,8 @@ void PCB_EDIT_FRAME::Edit_Zone_Params( ZONE_CONTAINER* aZone )
continue;
}
if( zone->IsFilled() )
// aZone won't be filled if the layer set was modified, but it needs to be updated
if( zone->IsFilled() || zone == aZone )
zones_to_refill.push_back( zone );
}

View File

@ -62,7 +62,7 @@
bool ZONE_CONTAINER::IsSame( const ZONE_CONTAINER& aZoneToCompare )
{
// compare basic parameters:
if( GetLayer() != aZoneToCompare.GetLayer() )
if( GetLayerSet() != aZoneToCompare.GetLayerSet() )
return false;
if( GetNetCode() != aZoneToCompare.GetNetCode() )
@ -111,6 +111,15 @@ bool ZONE_CONTAINER::IsSame( const ZONE_CONTAINER& aZoneToCompare )
if( m_ThermalReliefCopperBridge != aZoneToCompare.m_ThermalReliefCopperBridge )
return false;
if( m_zoneName != aZoneToCompare.m_zoneName )
return false;
if( m_islandRemovalMode != aZoneToCompare.m_islandRemovalMode )
return false;
if( m_minIslandArea != aZoneToCompare.m_minIslandArea )
return false;
// Compare outlines
wxASSERT( m_Poly ); // m_Poly == NULL Should never happen
@ -149,7 +158,7 @@ int SaveCopyOfZones( PICKED_ITEMS_LIST& aPickList, BOARD* aPcb, int aNetCode, LA
if( aNetCode >= 0 && aNetCode != zone->GetNetCode() )
continue;
if( aLayer >= 0 && aLayer != zone->GetLayer() )
if( aLayer >= 0 && !zone->GetLayerSet().test( aLayer ) )
continue;
ZONE_CONTAINER* zoneDup = new ZONE_CONTAINER( *zone );

View File

@ -127,7 +127,7 @@ bool BOARD::CombineAllAreasInNet( PICKED_ITEMS_LIST* aDeletedList, int aNetCode,
if( curr_area->GetIsKeepout() != area2->GetIsKeepout() )
continue;
if( curr_area->GetLayer() != area2->GetLayer() )
if( curr_area->GetLayerSet() != area2->GetLayerSet() )
continue;
BOX2I b2 = area2->Outline()->BBox();