kicad-source/pcbnew/router/pns_topology.cpp
Seth Hillbrand 4c5b44545f Slight modification to followTrivialPath
Now keeps track of branch points, recursing through them to find the
longest path it can in both directions.  This helps to ignore
overlapping segments

Fixes https://gitlab.com/kicad/code/kicad/-/issues/7809
2025-08-17 20:19:40 -07:00

656 lines
19 KiB
C++

/*
* KiRouter - a push-and-(sometimes-)shove PCB router
*
* Copyright (C) 2013-2015 CERN
* Copyright The KiCad Developers, see AUTHORS.txt for contributors.
* Author: Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#include "pns_line.h"
#include "pns_segment.h"
#include "pns_arc.h"
#include "pns_node.h"
#include "pns_joint.h"
#include "pns_solid.h"
#include "pns_router.h"
#include "pns_utils.h"
#include "pns_diff_pair.h"
#include "pns_topology.h"
#include <board.h>
#include <length_delay_calculation/length_delay_calculation.h>
#include <pad.h>
namespace PNS {
bool TOPOLOGY::SimplifyLine( LINE* aLine )
{
if( !aLine->IsLinked() || !aLine->SegmentCount() )
return false;
LINKED_ITEM* root = aLine->GetLink( 0 );
LINE l = m_world->AssembleLine( root, nullptr, false, false, false );
SHAPE_LINE_CHAIN simplified( l.CLine() );
simplified.Simplify();
if( simplified.PointCount() != l.PointCount() )
{
m_world->Remove( l );
LINE lnew( l );
lnew.SetShape( simplified );
m_world->Add( lnew );
return true;
}
return false;
}
const TOPOLOGY::JOINT_SET TOPOLOGY::ConnectedJoints( const JOINT* aStart )
{
std::deque<const JOINT*> searchQueue;
JOINT_SET processed;
searchQueue.push_back( aStart );
processed.insert( aStart );
while( !searchQueue.empty() )
{
const JOINT* current = searchQueue.front();
searchQueue.pop_front();
for( ITEM* item : current->LinkList() )
{
if( item->OfKind( ITEM::SEGMENT_T ) )
{
const JOINT* a = m_world->FindJoint( item->Anchor( 0 ), item );;
const JOINT* b = m_world->FindJoint( item->Anchor( 1 ), item );;
const JOINT* next = ( *a == *current ) ? b : a;
if( processed.find( next ) == processed.end() )
{
processed.insert( next );
searchQueue.push_back( next );
}
}
}
}
return processed;
}
bool TOPOLOGY::NearestUnconnectedAnchorPoint( const LINE* aTrack, VECTOR2I& aPoint,
PNS_LAYER_RANGE& aLayers, ITEM*& aItem )
{
LINE track( *aTrack );
VECTOR2I end;
if( !track.PointCount() )
return false;
std::unique_ptr<NODE> tmpNode( m_world->Branch() );
track.ClearLinks();
tmpNode->Add( track );
const JOINT* jt = tmpNode->FindJoint( track.CLastPoint(), &track );
if( !jt || m_world->GetRuleResolver()->NetCode( jt->Net() ) <= 0 )
return false;
if( ( !track.EndsWithVia() && jt->LinkCount() >= 2 )
|| ( track.EndsWithVia() && jt->LinkCount() >= 3 ) ) // we got something connected
{
end = jt->Pos();
aLayers = jt->Layers();
aItem = jt->LinkList()[0];
}
else
{
int anchor;
TOPOLOGY topo( tmpNode.get() );
ITEM* it = topo.NearestUnconnectedItem( jt, &anchor );
if( !it )
return false;
end = it->Anchor( anchor );
aLayers = it->Layers();
aItem = it;
}
aPoint = end;
return true;
}
bool TOPOLOGY::LeadingRatLine( const LINE* aTrack, SHAPE_LINE_CHAIN& aRatLine )
{
VECTOR2I end;
// Ratline doesn't care about the layer
PNS_LAYER_RANGE layers;
ITEM* unusedItem;
if( !NearestUnconnectedAnchorPoint( aTrack, end, layers, unusedItem ) )
return false;
aRatLine.Clear();
aRatLine.Append( aTrack->CLastPoint() );
aRatLine.Append( end );
return true;
}
ITEM* TOPOLOGY::NearestUnconnectedItem( const JOINT* aStart, int* aAnchor, int aKindMask )
{
std::set<ITEM*> disconnected;
m_world->AllItemsInNet( aStart->Net(), disconnected );
for( const JOINT* jt : ConnectedJoints( aStart ) )
{
for( ITEM* link : jt->LinkList() )
{
if( disconnected.find( link ) != disconnected.end() )
disconnected.erase( link );
}
}
int best_dist = INT_MAX;
ITEM* best = nullptr;
for( ITEM* item : disconnected )
{
if( item->OfKind( aKindMask ) )
{
for( int i = 0; i < item->AnchorCount(); i++ )
{
VECTOR2I p = item->Anchor( i );
int d = ( p - aStart->Pos() ).EuclideanNorm();
if( d < best_dist )
{
best_dist = d;
best = item;
if( aAnchor )
*aAnchor = i;
}
}
}
}
return best;
}
TOPOLOGY::PATH_RESULT TOPOLOGY::followBranch( const JOINT* aJoint, LINKED_ITEM* aPrev,
std::set<ITEM*>& aVisited,
bool aFollowLockedSegments )
{
PATH_RESULT best;
ITEM* via = nullptr;
ITEM_SET links( aJoint->CLinks() );
for( ITEM* link : links )
{
if( link->OfKind( ITEM::VIA_T ) && !aVisited.contains( link ) )
via = link;
}
for( ITEM* link : links )
{
if( link->OfKind( ITEM::SEGMENT_T | ITEM::ARC_T )
&& link != aPrev && !aVisited.contains( link ) )
{
LINE l = m_world->AssembleLine( static_cast<LINKED_ITEM*>( link ), nullptr,
false, aFollowLockedSegments );
if( l.CPoint( 0 ) != aJoint->Pos() )
l.Reverse();
const JOINT* next = m_world->FindJoint( l.CLastPoint(), &l );
for( LINKED_ITEM* ll : l.Links() )
aVisited.insert( ll );
if( via )
aVisited.insert( via );
PATH_RESULT sub = followBranch( next, l.Links().back(), aVisited, aFollowLockedSegments );
ITEM_SET tmp;
if( via )
tmp.Add( via );
tmp.Add( l );
for( ITEM* it : sub.m_items )
tmp.Add( it );
int len = l.CLine().Length() + sub.m_length;
if( len > best.m_length )
{
best.m_length = len;
best.m_end = sub.m_end;
best.m_items = tmp;
}
for( LINKED_ITEM* ll : l.Links() )
aVisited.erase( ll );
if( via )
aVisited.erase( via );
}
}
if( !best.m_end )
best.m_end = aJoint;
return best;
}
ITEM_SET TOPOLOGY::followTrivialPath( LINE* aLine2, const JOINT** aTerminalJointA,
const JOINT** aTerminalJointB,
bool aFollowLockedSegments )
{
assert( aLine2->IsLinked() );
ITEM_SET path;
path.Add( *aLine2 );
std::set<ITEM*> visited;
for( LINKED_ITEM* link : aLine2->Links() )
visited.insert( link );
const JOINT* jtA = m_world->FindJoint( aLine2->CPoint( 0 ), aLine2 );
const JOINT* jtB = m_world->FindJoint( aLine2->CLastPoint(), aLine2 );
PATH_RESULT left = followBranch( jtA, aLine2->Links().front(), visited, aFollowLockedSegments );
PATH_RESULT right = followBranch( jtB, aLine2->Links().back(), visited, aFollowLockedSegments );
if( aTerminalJointA )
*aTerminalJointA = left.m_end;
if( aTerminalJointB )
*aTerminalJointB = right.m_end;
for( int i = left.m_items.Size() - 1; i >= 0; i-- )
path.Prepend( left.m_items[i] );
for( ITEM* item : right.m_items )
path.Add( item );
return path;
}
const ITEM_SET TOPOLOGY::AssembleTrivialPath( ITEM* aStart,
std::pair<const JOINT*, const JOINT*>* aTerminalJoints,
bool aFollowLockedSegments )
{
ITEM_SET path;
LINKED_ITEM* seg = nullptr;
if( aStart->Kind() == ITEM::VIA_T )
{
VIA* via = static_cast<VIA*>( aStart );
const JOINT* jt = m_world->FindJoint( via->Pos(), via );
if( !jt->IsNonFanoutVia() )
return ITEM_SET();
ITEM_SET links( jt->CLinks() );
for( ITEM* item : links )
{
if( item->OfKind( ITEM::SEGMENT_T | ITEM::ARC_T ) )
{
seg = static_cast<LINKED_ITEM*>( item );
break;
}
}
}
else if( aStart->OfKind( ITEM::SEGMENT_T | ITEM::ARC_T ) )
{
seg = static_cast<LINKED_ITEM*>( aStart );
}
if( !seg )
return ITEM_SET();
// Assemble a line following through locked segments
// TODO: consider if we want to allow tuning lines with different widths in the future
LINE l = m_world->AssembleLine( seg, nullptr, false, aFollowLockedSegments );
const JOINT* jointA = nullptr;
const JOINT* jointB = nullptr;
path = followTrivialPath( &l, &jointA, &jointB, aFollowLockedSegments );
if( aTerminalJoints )
{
wxASSERT( jointA && jointB );
*aTerminalJoints = std::make_pair( jointA, jointB );
}
return path;
}
const ITEM_SET TOPOLOGY::AssembleTuningPath( ROUTER_IFACE* aRouterIface, ITEM* aStart, SOLID** aStartPad,
SOLID** aEndPad )
{
std::pair<const JOINT*, const JOINT*> joints;
ITEM_SET initialPath = AssembleTrivialPath( aStart, &joints, true );
PAD* padA = nullptr;
PAD* padB = nullptr;
auto getPadFromJoint =
[]( const JOINT* aJoint, PAD** aTargetPad, SOLID** aTargetSolid )
{
for( ITEM* item : aJoint->LinkList() )
{
if( item->OfKind( ITEM::SOLID_T ) )
{
BOARD_ITEM* bi = static_cast<SOLID*>( item )->Parent();
if( bi->Type() == PCB_PAD_T )
{
*aTargetPad = static_cast<PAD*>( bi );
if( aTargetSolid )
*aTargetSolid = static_cast<SOLID*>( item );
}
break;
}
}
};
if( joints.first )
getPadFromJoint( joints.first, &padA, aStartPad );
if( joints.second )
getPadFromJoint( joints.second, &padB, aEndPad );
if( !padA && !padB )
return initialPath;
auto processPad = [&]( PAD* aPad, int aLayer )
{
for( int idx = 0; idx < initialPath.Size(); idx++ )
{
if( initialPath[idx]->Kind() != ITEM::LINE_T )
continue;
LINE* line = static_cast<LINE*>( initialPath[idx] );
SHAPE_LINE_CHAIN& slc = line->Line();
const PCB_LAYER_ID pcbLayer = aRouterIface->GetBoardLayerFromPNSLayer( line->Layer() );
LENGTH_DELAY_CALCULATION::OptimiseTraceInPad( slc, aPad, pcbLayer );
}
};
if( padA )
processPad( padA, joints.first->Layer() );
if( padB )
processPad( padB, joints.second->Layer() );
return initialPath;
}
const ITEM_SET TOPOLOGY::ConnectedItems( const JOINT* aStart, int aKindMask )
{
return ITEM_SET();
}
const ITEM_SET TOPOLOGY::ConnectedItems( ITEM* aStart, int aKindMask )
{
return ITEM_SET();
}
bool commonParallelProjection( SEG p, SEG n, SEG &pClip, SEG& nClip );
bool TOPOLOGY::AssembleDiffPair( ITEM* aStart, DIFF_PAIR& aPair )
{
NET_HANDLE refNet = aStart->Net();
NET_HANDLE coupledNet = m_world->GetRuleResolver()->DpCoupledNet( refNet );
LINKED_ITEM* startItem = dynamic_cast<LINKED_ITEM*>( aStart );
if( !coupledNet || !startItem )
return false;
LINE lp = m_world->AssembleLine( startItem );
std::vector<ITEM*> pItems;
std::vector<ITEM*> nItems;
for( ITEM* item : lp.Links() )
{
if( item->OfKind( ITEM::SEGMENT_T | ITEM::ARC_T ) && item->Layers() == startItem->Layers() )
pItems.push_back( item );
}
std::set<ITEM*> coupledItems;
m_world->AllItemsInNet( coupledNet, coupledItems );
for( ITEM* item : coupledItems )
{
if( item->OfKind( ITEM::SEGMENT_T | ITEM::ARC_T ) && item->Layers() == startItem->Layers() )
nItems.push_back( item );
}
LINKED_ITEM* refItem = nullptr;
LINKED_ITEM* coupledItem = nullptr;
SEG::ecoord minDist_sq = std::numeric_limits<SEG::ecoord>::max();
SEG::ecoord minDistTarget_sq = std::numeric_limits<SEG::ecoord>::max();
VECTOR2I targetPoint = aStart->Shape( -1 )->Centre();
auto findNItem = [&]( ITEM* p_item )
{
for( ITEM* n_item : nItems )
{
SEG::ecoord dist_sq = std::numeric_limits<SEG::ecoord>::max();
if( n_item->Kind() != p_item->Kind() )
continue;
if( p_item->Kind() == ITEM::SEGMENT_T )
{
const SEGMENT* p_seg = static_cast<const SEGMENT*>( p_item );
const SEGMENT* n_seg = static_cast<const SEGMENT*>( n_item );
if( n_seg->Width() != p_seg->Width() )
continue;
if( !p_seg->Seg().ApproxParallel( n_seg->Seg(), DP_PARALLELITY_THRESHOLD ) )
continue;
SEG p_clip, n_clip;
if( !commonParallelProjection( p_seg->Seg(), n_seg->Seg(), p_clip, n_clip ) )
continue;
dist_sq = n_seg->Seg().SquaredDistance( p_seg->Seg() );
}
else if( p_item->Kind() == ITEM::ARC_T )
{
const ARC* p_arc = static_cast<const ARC*>( p_item );
const ARC* n_arc = static_cast<const ARC*>( n_item );
if( n_arc->Width() != p_arc->Width() )
continue;
VECTOR2I centerDiff = n_arc->CArc().GetCenter() - p_arc->CArc().GetCenter();
SEG::ecoord centerDist_sq = centerDiff.SquaredEuclideanNorm();
if( centerDist_sq > SEG::Square( DP_PARALLELITY_THRESHOLD ) )
continue;
dist_sq = SEG::Square( p_arc->CArc().GetRadius() - n_arc->CArc().GetRadius() );
}
if( dist_sq <= minDist_sq )
{
SEG::ecoord distTarget_sq = n_item->Shape( -1 )->SquaredDistance( targetPoint );
if( distTarget_sq < minDistTarget_sq )
{
minDistTarget_sq = distTarget_sq;
minDist_sq = dist_sq;
refItem = static_cast<LINKED_ITEM*>( p_item );
coupledItem = static_cast<LINKED_ITEM*>( n_item );
}
}
}
};
findNItem( startItem );
if( !coupledItem )
{
LINKED_ITEM* linked = static_cast<LINKED_ITEM*>( startItem );
std::set<ITEM*> linksToTest;
for( int i = 0; i < linked->AnchorCount(); i++ )
{
const JOINT* jt = m_world->FindJoint( linked->Anchor( i ), linked );
if( !jt )
continue;
for( ITEM* link : jt->LinkList() )
{
if( link != linked )
linksToTest.emplace( link );
}
}
for( ITEM* link : linksToTest )
findNItem( link );
}
if( !coupledItem )
return false;
LINE ln = m_world->AssembleLine( coupledItem );
if( m_world->GetRuleResolver()->DpNetPolarity( refNet ) < 0 )
std::swap( lp, ln );
int gap = -1;
if( refItem && refItem->Kind() == ITEM::SEGMENT_T )
{
// Segments are parallel -> compute pair gap
const VECTOR2I refDir = refItem->Anchor( 1 ) - refItem->Anchor( 0 );
const VECTOR2I displacement = refItem->Anchor( 1 ) - coupledItem->Anchor( 1 );
gap = (int) std::abs( refDir.Cross( displacement ) / refDir.EuclideanNorm() ) - lp.Width();
}
else if( refItem && refItem->Kind() == ITEM::ARC_T )
{
const ARC* refArc = static_cast<ARC*>( refItem );
const ARC* coupledArc = static_cast<ARC*>( coupledItem );
gap = (int) std::abs( refArc->CArc().GetRadius() - coupledArc->CArc().GetRadius() ) - lp.Width();
}
aPair = DIFF_PAIR( lp, ln );
aPair.SetWidth( lp.Width() );
aPair.SetLayers( lp.Layers() );
aPair.SetGap( gap );
return true;
}
const TOPOLOGY::CLUSTER TOPOLOGY::AssembleCluster( ITEM* aStart, int aLayer, double aAreaExpansionLimit, NET_HANDLE aExcludedNet )
{
CLUSTER cluster;
std::deque<ITEM*> pending;
COLLISION_SEARCH_OPTIONS opts;
opts.m_differentNetsOnly = false;
opts.m_overrideClearance = 0;
pending.push_back( aStart );
BOX2I clusterBBox = aStart->Shape( aLayer )->BBox();
int64_t initialArea = clusterBBox.GetArea();
while( !pending.empty() )
{
NODE::OBSTACLES obstacles;
ITEM* top = pending.front();
pending.pop_front();
cluster.m_items.insert( top );
m_world->QueryColliding( top, obstacles, opts ); // only query touching objects
for( const OBSTACLE& obs : obstacles )
{
bool trackOnTrack = ( obs.m_item->Net() != top->Net() ) && obs.m_item->OfKind( ITEM::SEGMENT_T ) && top->OfKind( ITEM::SEGMENT_T );
if( trackOnTrack )
continue;
if( aExcludedNet && obs.m_item->Net() == aExcludedNet )
continue;
if( obs.m_item->OfKind( ITEM::SEGMENT_T | ITEM::ARC_T ) && obs.m_item->Layers().Overlaps( aLayer ) )
{
auto line = m_world->AssembleLine( static_cast<LINKED_ITEM*>(obs.m_item) );
clusterBBox.Merge( line.CLine().BBox() );
}
else
{
clusterBBox.Merge( obs.m_item->Shape( aLayer )->BBox() );
}
const int64_t currentArea = clusterBBox.GetArea();
const double areaRatio = (double) currentArea / (double) ( initialArea + 1 );
if( aAreaExpansionLimit > 0.0 && areaRatio > aAreaExpansionLimit )
break;
if( cluster.m_items.find( obs.m_item ) == cluster.m_items.end() &&
obs.m_item->Layers().Overlaps( aLayer ) && !( obs.m_item->Marker() & MK_HEAD ) )
{
cluster.m_items.insert( obs.m_item );
pending.push_back( obs.m_item );
}
}
}
return cluster;
}
}