/* * This program source code file is part of KICAD, a free EDA CAD application. * * Copyright (C) 2013-2017 CERN * @author Maciej Suminski * @author Tomasz Wlostowski * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, you may find one here: * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html * or you may search the http://www.gnu.org website for the version 2 license, * or you may write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ // #define CONNECTIVITY_DEBUG #ifndef __CONNECTIVITY_ALGO_H #define __CONNECTIVITY_ALGO_H #include #include #include #include #include #include #include #include #include #include #include #include #include class CN_ITEM; class CN_CONNECTIVITY_ALGO_IMPL; class CN_RATSNEST_NODES; class CN_CLUSTER; class BOARD; class BOARD_CONNECTED_ITEM; class BOARD_ITEM; class ZONE_CONTAINER; class PROGRESS_REPORTER; class CN_ANCHOR { public: CN_ANCHOR() { m_item = nullptr; } CN_ANCHOR( const VECTOR2I& aPos, CN_ITEM* aItem ) { m_pos = aPos; m_item = aItem; assert( m_item ); } bool Valid() const; CN_ITEM* Item() const { return m_item; } BOARD_CONNECTED_ITEM* Parent() const; const VECTOR2I& Pos() const { return m_pos; } bool IsDirty() const; /// Returns tag, common identifier for connected nodes inline int GetTag() const { return m_tag; } /// Sets tag, common identifier for connected nodes inline void SetTag( int aTag ) { m_tag = aTag; } /// Decides whether this node can be a ratsnest line target inline void SetNoLine( bool aEnable ) { m_noline = aEnable; } /// Returns true if this node can be a target for ratsnest lines inline const bool& GetNoLine() const { return m_noline; } inline void SetCluster( std::shared_ptr aCluster ) { m_cluster = aCluster; } inline std::shared_ptr GetCluster() const { return m_cluster; } /** * has meaning only for tracks and vias. * @return true if this anchor is dangling * The anchor point is dangling if the parent is a track * and this anchor point is not connected to another item * ( track, vas pad or zone) or if the parent is a via and this anchor point * is connected to only one track and not to another item */ bool IsDangling() const; // Tag used for unconnected items. static const int TAG_UNCONNECTED = -1; private: /// Position of the anchor VECTOR2I m_pos; /// Item owning the anchor CN_ITEM* m_item = nullptr; /// Tag for quick connection resolution int m_tag = -1; /// Whether it the node can be a target for ratsnest lines bool m_noline = false; /// Cluster to which the anchor belongs std::shared_ptr m_cluster; }; typedef std::shared_ptr CN_ANCHOR_PTR; typedef std::vector CN_ANCHORS; class CN_EDGE { public: CN_EDGE() {}; CN_EDGE( CN_ANCHOR_PTR aSource, CN_ANCHOR_PTR aTarget, int aWeight = 0 ) : m_source( aSource ), m_target( aTarget ), m_weight( aWeight ) {} CN_ANCHOR_PTR GetSourceNode() const { return m_source; } CN_ANCHOR_PTR GetTargetNode() const { return m_target; } int GetWeight() const { return m_weight; } void SetSourceNode( const CN_ANCHOR_PTR& aNode ) { m_source = aNode; } void SetTargetNode( const CN_ANCHOR_PTR& aNode ) { m_target = aNode; } void SetWeight( unsigned int weight ) { m_weight = weight; } void SetVisible( bool aVisible ) { m_visible = aVisible; } bool IsVisible() const { return m_visible; } const VECTOR2I GetSourcePos() const { return m_source->Pos(); } const VECTOR2I GetTargetPos() const { return m_target->Pos(); } private: CN_ANCHOR_PTR m_source; CN_ANCHOR_PTR m_target; unsigned int m_weight = 0; bool m_visible = true; }; class CN_CLUSTER { private: bool m_conflicting = false; int m_originNet = 0; CN_ITEM* m_originPad = nullptr; std::vector m_items; public: CN_CLUSTER(); ~CN_CLUSTER(); bool HasValidNet() const { return m_originNet >= 0; } int OriginNet() const { return m_originNet; } wxString OriginNetName() const; bool Contains( const CN_ITEM* aItem ); bool Contains( const BOARD_CONNECTED_ITEM* aItem ); void Dump(); int Size() const { return m_items.size(); } bool HasNet() const { return m_originNet >= 0; } bool IsOrphaned() const { return m_originPad == nullptr; } bool IsConflicting() const { return m_conflicting; } void Add( CN_ITEM* item ); using ITER = decltype(m_items)::iterator; ITER begin() { return m_items.begin(); }; ITER end() { return m_items.end(); }; }; typedef std::shared_ptr CN_CLUSTER_PTR; // basic connectivity item class CN_ITEM : public INTRUSIVE_LIST { private: BOARD_CONNECTED_ITEM* m_parent; using CONNECTED_ITEMS = std::vector; ///> list of items physically connected (touching) CONNECTED_ITEMS m_connected; CN_ANCHORS m_anchors; ///> visited flag for the BFS scan bool m_visited; ///> can the net propagator modify the netcode? bool m_canChangeNet; ///> valid flag, used to identify garbage items (we use lazy removal) bool m_valid; ///> dirty flag, used to identify recently added item not yet scanned into the connectivity search bool m_dirty; public: void Dump(); CN_ITEM( BOARD_CONNECTED_ITEM* aParent, bool aCanChangeNet, int aAnchorCount = 2 ) { m_parent = aParent; m_canChangeNet = aCanChangeNet; m_visited = false; m_valid = true; m_dirty = true; m_anchors.reserve( 2 ); } virtual ~CN_ITEM() {}; CN_ANCHOR_PTR AddAnchor( const VECTOR2I& aPos ) { m_anchors.emplace_back( std::make_shared( aPos, this ) ); //printf("%p add %d\n", this, m_anchors.size() ); return m_anchors.back(); } CN_ANCHORS& Anchors() { return m_anchors; } void SetValid( bool aValid ) { m_valid = aValid; } bool Valid() const { return m_valid; } void SetDirty( bool aDirty ) { m_dirty = aDirty; } bool Dirty() const { return m_dirty; } BOARD_CONNECTED_ITEM* Parent() const { return m_parent; } const CONNECTED_ITEMS& ConnectedItems() const { return m_connected; } void ClearConnections() { m_connected.clear(); } void SetVisited( bool aVisited ) { m_visited = aVisited; } bool Visited() const { return m_visited; } bool CanChangeNet() const { return m_canChangeNet; } static void Connect( CN_ITEM* a, CN_ITEM* b ) { bool foundA = false, foundB = false; for( auto item : a->m_connected ) { if( item == b ) { foundA = true; break; } } for( auto item : b->m_connected ) { if( item == a ) { foundB = true; break; } } if( !foundA ) a->m_connected.push_back( b ); if( !foundB ) b->m_connected.push_back( a ); } void RemoveInvalidRefs(); virtual int AnchorCount() const; virtual const VECTOR2I GetAnchor( int n ) const; int Net() const; }; typedef std::shared_ptr CN_ITEM_PTR; class CN_LIST { private: bool m_dirty; std::vector m_anchors; CN_ANCHORS m_layer_anchors[PCB_LAYER_ID_COUNT]; protected: std::vector m_items; void addAnchor( VECTOR2I pos, CN_ITEM* item ) { CN_ANCHOR_PTR new_anchor = item->AddAnchor( pos ); m_anchors.push_back( new_anchor ); for( int i = 0; i < PCB_LAYER_ID_COUNT; i++ ) { if( ( item->Parent()->GetLayerSet() & LSET( 1, i ) ).any() ) m_layer_anchors[i].push_back( new_anchor ); } } private: void sort() { if( m_dirty ) { std::sort( m_anchors.begin(), m_anchors.end() ); for( auto i = 0; i < PCB_LAYER_ID_COUNT; i++ ) { std::sort( m_layer_anchors[i].begin(), m_layer_anchors[i].end() ); } m_dirty = false; } } public: CN_LIST() { m_dirty = false; } void Clear() { for( auto item : m_items ) delete item; m_items.clear(); } using ITER = decltype(m_items)::iterator; ITER begin() { return m_items.begin(); }; ITER end() { return m_items.end(); }; CN_ITEM* operator[] ( int aIndex ) { return m_items[aIndex]; } std::vector& Anchors() { return m_anchors; } template void FindNearby( VECTOR2I aPosition, int aDistMax, T aFunc, LSET aLayers = LSET::AllLayersMask(), bool aDirtyOnly = false ); template void FindNearby( BOX2I aBBox, T aFunc, LSET aLayers = LSET::AllLayersMask(), bool aDirtyOnly = false ); void SetDirty( bool aDirty = true ) { m_dirty = aDirty; } bool IsDirty() const { return m_dirty; } void ClearConnections() { for( auto& anchor : m_anchors ) anchor->Item()->ClearConnections(); } void RemoveInvalidItems( std::vector& aGarbage ); void ClearDirtyFlags() { for( auto item : m_items ) item->SetDirty( false ); SetDirty( false ); } void MarkAllAsDirty() { for( auto item : m_items ) item->SetDirty( true ); SetDirty( true ); } int Size() const { return m_items.size(); } }; class CN_PAD_LIST : public CN_LIST { public: CN_ITEM* Add( D_PAD* pad ) { auto item = new CN_ITEM( pad, false, 2 ); addAnchor( pad->ShapePos(), item ); m_items.push_back( item ); SetDirty(); return item; } }; class CN_TRACK_LIST : public CN_LIST { public: CN_ITEM* Add( TRACK* track ) { auto item = new CN_ITEM( track, true ); m_items.push_back( item ); addAnchor( track->GetStart(), item ); addAnchor( track->GetEnd(), item ); SetDirty(); return item; } }; class CN_VIA_LIST : public CN_LIST { public: CN_ITEM* Add( VIA* via ) { auto item = new CN_ITEM( via, true ); m_items.push_back( item ); addAnchor( via->GetStart(), item ); SetDirty(); return item; } }; class CN_ZONE : public CN_ITEM { public: CN_ZONE( ZONE_CONTAINER* aParent, bool aCanChangeNet, int aSubpolyIndex ) : CN_ITEM( aParent, aCanChangeNet ), m_subpolyIndex( aSubpolyIndex ) { SHAPE_LINE_CHAIN outline = aParent->GetFilledPolysList().COutline( aSubpolyIndex ); outline.SetClosed( true ); outline.Simplify(); m_cachedPoly.reset( new POLY_GRID_PARTITION( outline, 16 ) ); } int SubpolyIndex() const { return m_subpolyIndex; } bool ContainsAnchor( const CN_ANCHOR_PTR anchor ) const { return ContainsPoint( anchor->Pos() ); } bool ContainsPoint( const VECTOR2I p ) const { auto zone = static_cast ( Parent() ); return m_cachedPoly->ContainsPoint( p, zone->GetMinThickness() ); } const BOX2I& BBox() const { return m_cachedPoly->BBox(); } virtual int AnchorCount() const override; virtual const VECTOR2I GetAnchor( int n ) const override; private: std::vector m_testOutlinePoints; std::unique_ptr m_cachedPoly; int m_subpolyIndex; }; class CN_ZONE_LIST : public CN_LIST { public: CN_ZONE_LIST() {} const std::vector Add( ZONE_CONTAINER* zone ) { const auto& polys = zone->GetFilledPolysList(); std::vector rv; for( int j = 0; j < polys.OutlineCount(); j++ ) { CN_ZONE* zitem = new CN_ZONE( zone, false, j ); const auto& outline = zone->GetFilledPolysList().COutline( j ); for( int k = 0; k < outline.PointCount(); k++ ) addAnchor( outline.CPoint( k ), zitem ); m_items.push_back( zitem ); rv.push_back( zitem ); SetDirty(); } return rv; } template void FindNearbyZones( BOX2I aBBox, T aFunc, bool aDirtyOnly = false ); }; template void CN_LIST::FindNearby( BOX2I aBBox, T aFunc, LSET aLayers, bool aDirtyOnly ) { sort(); CN_ANCHORS *anchor_set = &m_anchors; PCB_LAYER_ID layer = aLayers.ExtractLayer(); if( layer > 0 ) anchor_set = &(m_layer_anchors[ layer ]); if( (*anchor_set).size() == 0 ) return; CN_ANCHOR_PTR lower_ptr = std::make_shared ( aBBox.GetPosition(), (*anchor_set)[0]->Item() ); auto lower_it = std::lower_bound( anchor_set->begin(), anchor_set->end(), lower_ptr, []( const CN_ANCHOR_PTR& a, const CN_ANCHOR_PTR& b ) -> bool { return a->Pos().x < b->Pos().x; } ); for( auto it = lower_it; it != anchor_set->end(); it++) { if( (*it)->Pos().x > aBBox.GetRight() ) break; if( (*it)->Valid() && ( !aDirtyOnly || (*it)->IsDirty() ) && aBBox.Contains( (*it)->Pos() ) ) aFunc( *it ); } } template void CN_ZONE_LIST::FindNearbyZones( BOX2I aBBox, T aFunc, bool aDirtyOnly ) { for( auto item : m_items ) { auto zone = static_cast( item ); if( aBBox.Intersects( zone->BBox() ) ) { if( !aDirtyOnly || zone->Dirty() ) { aFunc( zone ); } } } } template void CN_LIST::FindNearby( VECTOR2I aPosition, int aDistMax, T aFunc, LSET aLayers, bool aDirtyOnly ) { /* Search items in m_Candidates that position is <= aDistMax from aPosition * (Rectilinear distance) * m_Candidates is sorted by X then Y values, so binary search is made for the first * element. Then a linear iteration is made to identify all element that are also * in the correct y range. */ sort(); CN_ANCHORS *anchor_set = &m_anchors; PCB_LAYER_ID layer = aLayers.ExtractLayer(); if( layer > 0 ) anchor_set = &(m_layer_anchors[ layer ]); if( (*anchor_set).size() == 0 ) return; CN_ANCHOR_PTR lower = std::make_shared ( aPosition - VECTOR2I( aDistMax, 0 ), (*anchor_set)[0]->Item() ); auto lower_it = std::lower_bound( anchor_set->begin(), anchor_set->end(), lower, []( const CN_ANCHOR_PTR& a, const CN_ANCHOR_PTR& b ) -> bool { return a->Pos().x < b->Pos().x; } ); for( auto it = lower_it; it != anchor_set->end(); it++ ) { if( (*it)->Pos().x > aDistMax + aPosition.x ) break; if( (*it)->Valid() && std::abs( (*it)->Pos().y - aPosition.y ) < aDistMax && ( !aDirtyOnly || (*it)->IsDirty() ) ) aFunc( *it ); } } class CN_CONNECTIVITY_ALGO { public: enum CLUSTER_SEARCH_MODE { CSM_PROPAGATE, CSM_CONNECTIVITY_CHECK, CSM_RATSNEST }; using CLUSTERS = std::vector; private: bool m_lastSearchWithZones = false; class ITEM_MAP_ENTRY { public: ITEM_MAP_ENTRY( CN_ITEM* aItem = nullptr ) { if( aItem ) m_items.push_back( aItem ); } void MarkItemsAsInvalid() { for( auto item : m_items ) { item->SetValid( false ); } } void Link( CN_ITEM* aItem ) { m_items.push_back( aItem ); } const std::list GetItems() const { return m_items; } std::list m_items; }; CN_PAD_LIST m_padList; CN_TRACK_LIST m_trackList; CN_VIA_LIST m_viaList; CN_ZONE_LIST m_zoneList; using ITEM_MAP_PAIR = std::pair ; std::unordered_map m_itemMap; CLUSTERS m_connClusters; CLUSTERS m_ratsnestClusters; std::vector m_dirtyNets; PROGRESS_REPORTER* m_progressReporter = nullptr; void searchConnections( bool aIncludeZones = false ); void update(); void propagateConnections(); template void add( Container& c, BItem brditem ) { auto item = c.Add( brditem ); m_itemMap[ brditem ] = ITEM_MAP_ENTRY( item ); } bool addConnectedItem( BOARD_CONNECTED_ITEM* aItem ); bool isDirty() const; void markItemNetAsDirty( const BOARD_ITEM* aItem ); public: CN_CONNECTIVITY_ALGO(); ~CN_CONNECTIVITY_ALGO(); bool ItemExists( const BOARD_CONNECTED_ITEM* aItem ) { return m_itemMap.find( aItem ) != m_itemMap.end(); } ITEM_MAP_ENTRY& ItemEntry( const BOARD_CONNECTED_ITEM* aItem ) { return m_itemMap[ aItem ]; } bool IsNetDirty( int aNet ) const { if( aNet < 0 ) return false; return m_dirtyNets[ aNet ]; } void ClearDirtyFlags() { for( auto i = m_dirtyNets.begin(); i != m_dirtyNets.end(); ++i ) *i = false; } void GetDirtyClusters( CLUSTERS& aClusters ) { for( auto cl : m_ratsnestClusters ) { int net = cl->OriginNet(); if( net >= 0 && m_dirtyNets[net] ) aClusters.push_back( cl ); } } int NetCount() const { return m_dirtyNets.size(); } void Build( BOARD* aBoard ); void Build( const std::vector& aItems ); void Clear(); bool Remove( BOARD_ITEM* aItem ); bool Add( BOARD_ITEM* aItem ); const CLUSTERS SearchClusters( CLUSTER_SEARCH_MODE aMode, const KICAD_T aTypes[], int aSingleNet ); const CLUSTERS SearchClusters( CLUSTER_SEARCH_MODE aMode ); void PropagateNets(); void FindIsolatedCopperIslands( ZONE_CONTAINER* aZone, std::vector& aIslands ); void FindIsolatedCopperIslands( std::vector& aZones ); bool CheckConnectivity( std::vector& aReport ); const CLUSTERS& GetClusters(); int GetUnconnectedCount(); CN_PAD_LIST& PadList() { return m_padList; } void ForEachAnchor( const std::function& aFunc ); void ForEachItem( const std::function& aFunc ); void MarkNetAsDirty( int aNet ); void SetProgressReporter( PROGRESS_REPORTER* aReporter ); }; bool operator<( const CN_ANCHOR_PTR& a, const CN_ANCHOR_PTR& b ); #endif