/* * This program source code file is part of KICAD, a free EDA CAD application. * * Copyright (C) 2013 CERN * @author Maciej Suminski * * 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 */ /** * @file ratsnest_data.cpp * @brief Class that computes missing connections on a PCB. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include uint64_t getDistance( const RN_NODE_PTR& aNode1, const RN_NODE_PTR& aNode2 ) { // Drop the least significant bits to avoid overflow int64_t x = ( aNode1->GetX() - aNode2->GetX() ) >> 16; int64_t y = ( aNode1->GetY() - aNode2->GetY() ) >> 16; // We do not need sqrt() here, as the distance is computed only for comparison return ( x * x + y * y ); } bool sortDistance( const RN_NODE_PTR& aOrigin, const RN_NODE_PTR& aNode1, const RN_NODE_PTR& aNode2 ) { return getDistance( aOrigin, aNode1 ) < getDistance( aOrigin, aNode2 ); } bool sortWeight( const RN_EDGE_PTR& aEdge1, const RN_EDGE_PTR& aEdge2 ) { return aEdge1->getWeight() < aEdge2->getWeight(); } bool sortArea( const RN_POLY& aP1, const RN_POLY& aP2 ) { return aP1.m_bbox.GetArea() < aP2.m_bbox.GetArea(); } bool isEdgeConnectingNode( const RN_EDGE_PTR& aEdge, const RN_NODE_PTR& aNode ) { return ( aEdge->getSourceNode().get() == aNode.get() ) || ( aEdge->getTargetNode().get() == aNode.get() ); } std::vector* kruskalMST( RN_LINKS::RN_EDGE_LIST& aEdges, const std::vector& aNodes ) { unsigned int nodeNumber = aNodes.size(); unsigned int mstExpectedSize = nodeNumber - 1; unsigned int mstSize = 0; // The output std::vector* mst = new std::vector; mst->reserve( mstExpectedSize ); // Set tags for marking cycles boost::unordered_map tags; unsigned int tag = 0; BOOST_FOREACH( const RN_NODE_PTR& node, aNodes ) tags[node] = tag++; // Lists of nodes connected together (subtrees) to detect cycles in the graph std::vector > cycles( nodeNumber ); for( unsigned int i = 0; i < nodeNumber; ++i ) cycles[i].push_back( i ); // Kruskal algorithm requires edges to be sorted by their weight aEdges.sort( sortWeight ); while( mstSize < mstExpectedSize && !aEdges.empty() ) { RN_EDGE_PTR& dt = *aEdges.begin(); int srcTag = tags[dt->getSourceNode()]; int trgTag = tags[dt->getTargetNode()]; // Check if by adding this edge we are going to join two different forests if( srcTag != trgTag ) { // Update tags std::list::iterator it, itEnd; for( it = cycles[trgTag].begin(), itEnd = cycles[trgTag].end(); it != itEnd; ++it ) tags[aNodes[*it]] = srcTag; // Move nodes that were marked with old tag to the list marked with the new tag cycles[srcTag].splice( cycles[srcTag].end(), cycles[trgTag] ); if( dt->getWeight() == 0 ) // Skip already existing connections (weight == 0) mstExpectedSize--; else { mst->push_back( dt ); ++mstSize; } } // Remove the edge that was just processed aEdges.erase( aEdges.begin() ); } // Probably we have discarded some of edges, so reduce the size mst->resize( mstSize ); return mst; } void RN_NET::validateEdge( RN_EDGE_PTR& aEdge ) { RN_NODE_PTR source = aEdge->getSourceNode(); RN_NODE_PTR target = aEdge->getTargetNode(); bool valid = true; // If any of nodes belonging to the edge has the flag set, // change it to the closest node that has flag cleared if( source->GetFlag() ) { valid = false; std::list closest = GetClosestNodes( source, WITHOUT_FLAG() ); BOOST_FOREACH( RN_NODE_PTR& node, closest ) { if( node && node != target ) { source = node; break; } } } if( target->GetFlag() ) { valid = false; std::list closest = GetClosestNodes( target, WITHOUT_FLAG() ); BOOST_FOREACH( RN_NODE_PTR& node, closest ) { if( node && node != source ) { target = node; break; } } } // Replace an invalid edge with new, valid one if( !valid ) aEdge.reset( new RN_EDGE_MST( source, target ) ); } const RN_NODE_PTR& RN_LINKS::AddNode( int aX, int aY ) { RN_NODE_SET::iterator node; bool wasNewElement; boost::tie( node, wasNewElement ) = m_nodes.emplace( boost::make_shared( aX, aY ) ); (*node)->IncRefCount(); // TODO use the shared_ptr use_count return *node; } void RN_LINKS::RemoveNode( const RN_NODE_PTR& aNode ) { aNode->DecRefCount(); // TODO use the shared_ptr use_count if( aNode->GetRefCount() == 0 ) m_nodes.erase( aNode ); } const RN_EDGE_PTR& RN_LINKS::AddConnection( const RN_NODE_PTR& aNode1, const RN_NODE_PTR& aNode2, unsigned int aDistance ) { m_edges.push_back( boost::make_shared( aNode1, aNode2, aDistance ) ); return m_edges.back(); } void RN_NET::compute() { const RN_LINKS::RN_NODE_SET& boardNodes = m_links.GetNodes(); const RN_LINKS::RN_EDGE_LIST& boardEdges = m_links.GetConnections(); // Special case that does need so complicated algorithm if( boardNodes.size() == 2 ) { m_rnEdges.reset( new std::vector( 0 ) ); // Check if the only possible connection exists if( boardEdges.size() == 0 ) { RN_LINKS::RN_NODE_SET::iterator last = ++boardNodes.begin(); // There can be only one possible connection, but it is missing m_rnEdges->push_back( boost::make_shared( *boardNodes.begin(), *last ) ); } return; } else if( boardNodes.size() <= 1 ) // This case is even simpler { m_rnEdges.reset( new std::vector( 0 ) ); return; } // Move and sort (sorting speeds up) all nodes to a vector for the Delaunay triangulation std::vector nodes( boardNodes.size() ); std::partial_sort_copy( boardNodes.begin(), boardNodes.end(), nodes.begin(), nodes.end() ); TRIANGULATOR triangulator; triangulator.createDelaunay( nodes.begin(), nodes.end() ); boost::scoped_ptr triangEdges( triangulator.getEdges() ); // Compute weight/distance for edges resulting from triangulation RN_LINKS::RN_EDGE_LIST::iterator eit, eitEnd; for( eit = (*triangEdges).begin(), eitEnd = (*triangEdges).end(); eit != eitEnd; ++eit ) (*eit)->setWeight( getDistance( (*eit)->getSourceNode(), (*eit)->getTargetNode() ) ); // Add the currently existing connections list to the results of triangulation std::copy( boardEdges.begin(), boardEdges.end(), std::front_inserter( *triangEdges ) ); // Get the minimal spanning tree m_rnEdges.reset( kruskalMST( *triangEdges, nodes ) ); } void RN_NET::clearNode( const RN_NODE_PTR& aNode ) { std::vector::iterator newEnd; // Remove all ratsnest edges for associated with the node newEnd = std::remove_if( m_rnEdges->begin(), m_rnEdges->end(), boost::bind( isEdgeConnectingNode, _1, aNode ) ); m_rnEdges->resize( std::distance( m_rnEdges->begin(), newEnd ) ); } RN_POLY::RN_POLY( const CPolyPt* aBegin, const CPolyPt* aEnd, const ZONE_CONTAINER* aParent, RN_LINKS& aConnections, const BOX2I& aBBox ) : m_parent( aParent), m_begin( aBegin ), m_end( aEnd ), m_bbox( aBBox ) { m_node = aConnections.AddNode( m_begin->x, m_begin->y ); // Mark it as not feasible as a destination of ratsnest edges // (edges coming out from a polygon vertex look weird) m_node->SetFlag( true ); } bool RN_POLY::HitTest( const RN_NODE_PTR& aNode ) const { long xt = aNode->GetX(); long yt = aNode->GetY(); // If the point lies outside the bounding box, there is no point to check it further if( !m_bbox.Contains( xt, yt ) ) return false; long xNew, yNew, xOld, yOld, x1, y1, x2, y2; bool inside = false; // For the first loop we have to use the last point as the previous point xOld = m_end->x; yOld = m_end->y; for( const CPolyPt* point = m_begin; point <= m_end; ++point ) { xNew = point->x; yNew = point->y; // Swap points if needed, so always x2 >= x1 if( xNew > xOld ) { x1 = xOld; y1 = yOld; x2 = xNew; y2 = yNew; } else { x1 = xNew; y1 = yNew; x2 = xOld; y2 = yOld; } if( ( xNew < xt ) == ( xt <= xOld ) /* edge "open" at left end */ && ( yt - y1 ) * ( x2 - x1 ) < ( y2 - y1 ) * ( xt - x1 ) ) { inside = !inside; } xOld = xNew; yOld = yNew; } return inside; } void RN_NET::Update() { // Add edges resulting from nodes being connected by zones processZones(); compute(); BOOST_FOREACH( RN_EDGE_PTR& edge, *m_rnEdges ) validateEdge( edge ); m_dirty = false; } void RN_NET::AddItem( const D_PAD* aPad ) { RN_NODE_PTR nodePtr = m_links.AddNode( aPad->GetPosition().x, aPad->GetPosition().y ); m_pads[aPad] = nodePtr; m_dirty = true; } void RN_NET::AddItem( const SEGVIA* aVia ) { m_vias[aVia] = m_links.AddNode( aVia->GetPosition().x, aVia->GetPosition().y ); m_dirty = true; } void RN_NET::AddItem( const TRACK* aTrack ) { RN_NODE_PTR start = m_links.AddNode( aTrack->GetStart().x, aTrack->GetStart().y ); RN_NODE_PTR end = m_links.AddNode( aTrack->GetEnd().x, aTrack->GetEnd().y ); m_tracks[aTrack] = m_links.AddConnection( start, end ); m_dirty = true; } void RN_NET::AddItem( const ZONE_CONTAINER* aZone ) { // Prepare a list of polygons (every zone can contain one or more polygons) const std::vector& polyPoints = aZone->GetFilledPolysList().GetList(); if( polyPoints.size() == 0 ) return; // Origin and end of bounding box for a polygon VECTOR2I origin( polyPoints[0].x, polyPoints[0].y ); VECTOR2I end( polyPoints[0].x, polyPoints[0].y ); int idxStart = 0; // Extract polygons from zones for( unsigned int i = 0; i < polyPoints.size(); ++i ) { const CPolyPt& point = polyPoints[i]; // Determine bounding box if( point.x < origin.x ) origin.x = point.x; else if( point.x > end.x ) end.x = point.x; if( point.y < origin.y ) origin.y = point.y; else if( point.y > end.y ) end.y = point.y; if( point.end_contour ) { // The last vertex is enclosing the polygon (it repeats at the beginning and // at the end), so we skip it m_zonePolygons[aZone].push_back( RN_POLY( &polyPoints[idxStart], &point, aZone, m_links, BOX2I( origin, end - origin ) ) ); idxStart = i + 1; origin.x = polyPoints[idxStart].x; origin.y = polyPoints[idxStart].y; end.x = polyPoints[idxStart].x; end.y = polyPoints[idxStart].y; } } m_dirty = true; } void RN_NET::RemoveItem( const D_PAD* aPad ) { RN_NODE_PTR& node = m_pads[aPad]; if( !node ) return; // Remove edges associated with the node clearNode( node ); m_links.RemoveNode( node ); m_pads.erase( aPad ); m_dirty = true; } void RN_NET::RemoveItem( const SEGVIA* aVia ) { RN_NODE_PTR& node = m_vias[aVia]; if( !node ) return; // Remove edges associated with the node clearNode( node ); m_links.RemoveNode( node ); m_vias.erase( aVia ); m_dirty = true; } void RN_NET::RemoveItem( const TRACK* aTrack ) { RN_EDGE_PTR& edge = m_tracks[aTrack]; if( !edge ) return; // Save nodes, so they can be cleared later const RN_NODE_PTR& aBegin = edge->getSourceNode(); const RN_NODE_PTR& aEnd = edge->getTargetNode(); m_links.RemoveConnection( edge ); // Remove nodes associated with the edge. It is done in a safe way, there is a check // if nodes are not used by other edges. clearNode( aBegin ); clearNode( aEnd ); m_links.RemoveNode( aBegin ); m_links.RemoveNode( aEnd ); m_tracks.erase( aTrack ); m_dirty = true; } void RN_NET::RemoveItem( const ZONE_CONTAINER* aZone ) { // Remove all subpolygons that make the zone std::deque& polygons = m_zonePolygons[aZone]; BOOST_FOREACH( RN_POLY& polygon, polygons ) m_links.RemoveNode( polygon.GetNode() ); polygons.clear(); // Remove all connections added by the zone std::deque& edges = m_zoneConnections[aZone]; BOOST_FOREACH( RN_EDGE_PTR& edge, edges ) m_links.RemoveConnection( edge ); edges.clear(); m_dirty = true; } const RN_NODE_PTR RN_NET::GetClosestNode( const RN_NODE_PTR& aNode ) const { const RN_LINKS::RN_NODE_SET& nodes = m_links.GetNodes(); RN_LINKS::RN_NODE_SET::const_iterator it, itEnd; unsigned int minDistance = std::numeric_limits::max(); RN_NODE_PTR closest; for( it = nodes.begin(), itEnd = nodes.end(); it != itEnd; ++it ) { // Obviously the distance between node and itself is the shortest, // that's why we have to skip it if( *it != aNode ) { unsigned int distance = getDistance( *it, aNode ); if( distance < minDistance ) { minDistance = distance; closest = *it; } } } return closest; } const RN_NODE_PTR RN_NET::GetClosestNode( const RN_NODE_PTR& aNode, const RN_NODE_FILTER& aFilter ) const { const RN_LINKS::RN_NODE_SET& nodes = m_links.GetNodes(); RN_LINKS::RN_NODE_SET::const_iterator it, itEnd; unsigned int minDistance = std::numeric_limits::max(); RN_NODE_PTR closest; for( it = nodes.begin(), itEnd = nodes.end(); it != itEnd; ++it ) { RN_NODE_PTR baseNode = *it; // Obviously the distance between node and itself is the shortest, // that's why we have to skip it if( *it != aNode && aFilter( baseNode ) ) { unsigned int distance = getDistance( *it, aNode ); if( distance < minDistance ) { minDistance = distance; closest = *it; } } } return closest; } std::list RN_NET::GetClosestNodes( const RN_NODE_PTR& aNode, int aNumber ) const { std::list closest; const RN_LINKS::RN_NODE_SET& nodes = m_links.GetNodes(); // Copy nodes BOOST_FOREACH( const RN_NODE_PTR& node, nodes ) closest.push_back( node ); // Sort by the distance from aNode closest.sort( boost::bind( sortDistance, aNode, _1, _2 ) ); // Remove the first node (==aNode), as it is surely located within the smallest distance closest.pop_front(); // Trim the result to the asked size if( aNumber > 0 ) closest.resize( std::min( (size_t)aNumber, nodes.size() ) ); return closest; } std::list RN_NET::GetClosestNodes( const RN_NODE_PTR& aNode, const RN_NODE_FILTER& aFilter, int aNumber ) const { std::list closest; const RN_LINKS::RN_NODE_SET& nodes = m_links.GetNodes(); // Copy nodes BOOST_FOREACH( const RN_NODE_PTR& node, nodes ) closest.push_back( node ); // Sort by the distance from aNode closest.sort( boost::bind( sortDistance, aNode, _1, _2 ) ); // Remove the first node (==aNode), as it is surely located within the smallest distance closest.pop_front(); // Filter out by condition std::remove_if( closest.begin(), closest.end(), aFilter ); // Trim the result to the asked size if( aNumber > 0 ) closest.resize( std::min( static_cast( aNumber ), nodes.size() ) ); return closest; } std::list RN_NET::GetNodes( const BOARD_CONNECTED_ITEM* aItem ) const { std::list nodes; switch( aItem->Type() ) { case PCB_PAD_T: { const D_PAD* pad = static_cast( aItem ); nodes.push_back( m_pads.at( pad ) ); } break; case PCB_VIA_T: { const SEGVIA* via = static_cast( aItem ); nodes.push_back( m_vias.at( via ) ); } break; case PCB_TRACE_T: { const TRACK* track = static_cast( aItem ); RN_EDGE_PTR edge = m_tracks.at( track ); nodes.push_back( edge->getSourceNode() ); nodes.push_back( edge->getTargetNode() ); } break; default: break; } return nodes; } void RN_NET::ClearSimple() { BOOST_FOREACH( const RN_NODE_PTR& node, m_simpleNodes ) node->SetFlag( false ); m_simpleNodes.clear(); } void RN_DATA::AddSimple( const BOARD_CONNECTED_ITEM* aItem ) { int net = aItem->GetNet(); if( net < 1 ) // do not process unconnected items return; // Get list of nodes responding to the item std::list nodes = m_nets[net].GetNodes( aItem ); std::list::iterator it, itEnd; for( it = nodes.begin(), itEnd = nodes.end(); it != itEnd; ++it ) m_nets[net].AddSimpleNode( *it ); } void RN_DATA::AddSimple( const MODULE* aModule ) { for( const D_PAD* pad = aModule->Pads().GetFirst(); pad; pad = pad->Next() ) AddSimple( pad ); } void RN_NET::processZones() { BOOST_FOREACH( std::deque& edges, m_zoneConnections | boost::adaptors::map_values ) { BOOST_FOREACH( RN_EDGE_PTR& edge, edges ) m_links.RemoveConnection( edge ); edges.clear(); } RN_LINKS::RN_NODE_SET candidates = m_links.GetNodes(); BOOST_FOREACH( std::deque& polygons, m_zonePolygons | boost::adaptors::map_values ) { RN_LINKS::RN_NODE_SET::iterator point, pointEnd; std::deque::iterator poly, polyEnd; // Sorting by area should speed up the processing, as smaller polygons are computed // faster and may reduce the number of points for further checks std::sort( polygons.begin(), polygons.end(), sortArea ); for( poly = polygons.begin(), polyEnd = polygons.end(); poly != polyEnd; ++poly ) { point = candidates.begin(); pointEnd = candidates.end(); while( point != pointEnd ) { if( poly->HitTest( *point ) ) { const RN_EDGE_PTR& connection = m_links.AddConnection( poly->GetNode(), *point ); m_zoneConnections[poly->GetParent()].push_back( connection ); // This point already belongs to a polygon, we do not need to check it anymore point = candidates.erase( point ); pointEnd = candidates.end(); } else { ++point; } } } } } void RN_DATA::updateNet( int aNetCode ) { assert( aNetCode < (int) m_nets.size() ); if( aNetCode < 1 ) return; m_nets[aNetCode].ClearSimple(); m_nets[aNetCode].Update(); } void RN_DATA::Update( const BOARD_CONNECTED_ITEM* aItem ) { int net = aItem->GetNet(); if( net < 1 ) // do not process unconnected items return; switch( aItem->Type() ) { case PCB_PAD_T: { const D_PAD* pad = static_cast( aItem ); m_nets[net].RemoveItem( pad ); m_nets[net].AddItem( pad ); } break; case PCB_TRACE_T: { const TRACK* track = static_cast( aItem ); m_nets[net].RemoveItem( track ); m_nets[net].AddItem( track ); } break; case PCB_VIA_T: { const SEGVIA* via = static_cast( aItem ); m_nets[net].RemoveItem( via ); m_nets[net].AddItem( via ); } break; case PCB_ZONE_AREA_T: { const ZONE_CONTAINER* zone = static_cast( aItem ); m_nets[net].RemoveItem( zone); m_nets[net].AddItem( zone ); } break; default: break; } } void RN_DATA::Update( const MODULE* aModule ) { for( const D_PAD* pad = aModule->Pads().GetFirst(); pad; pad = pad->Next() ) { int net = pad->GetNet(); if( net > 0 ) // do not process unconnected items { m_nets[net].RemoveItem( pad ); m_nets[net].AddItem( pad ); } } } void RN_DATA::ProcessBoard() { m_nets.clear(); m_nets.resize( m_board->GetNetCount() ); // Iterate over all items that may need to be connected for( MODULE* module = m_board->m_Modules; module; module = module->Next() ) { for( D_PAD* pad = module->Pads().GetFirst(); pad; pad = pad->Next() ) m_nets[pad->GetNet()].AddItem( pad ); } for( TRACK* track = m_board->m_Track; track; track = track->Next() ) { if( track->Type() == PCB_VIA_T ) m_nets[track->GetNet()].AddItem( static_cast( track ) ); else if( track->Type() == PCB_TRACE_T ) m_nets[track->GetNet()].AddItem( track ); } for( int i = 0; i < m_board->GetAreaCount(); ++i ) { ZONE_CONTAINER* zone = m_board->GetArea( i ); m_nets[zone->GetNet()].AddItem( zone ); } } void RN_DATA::Recalculate( int aNet ) { if( aNet < 0 ) // Recompute everything { // Start with net number 1, as 0 stand for not connected for( unsigned int i = 1; i < m_board->GetNetCount(); ++i ) { if( m_nets[i].IsDirty() ) updateNet( i ); } } else if( aNet > 0 ) // Recompute only specific net { updateNet( aNet ); } } void RN_DATA::ClearSimple() { BOOST_FOREACH( RN_NET& net, m_nets ) net.ClearSimple(); }