KiCad PCB EDA Suite
Loading...
Searching...
No Matches
drc_chain_topology.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20#include "drc_chain_topology.h"
21
22#include <algorithm>
23#include <map>
24#include <queue>
25#include <stack>
26#include <unordered_map>
27#include <utility>
28
29#include <board.h>
31#include <footprint.h>
33#include <lset.h>
34#include <net_chain_bridging.h>
35#include <netinfo.h>
36#include <pad.h>
37#include <pcb_track.h>
38
39
40namespace
41{
42// Coalesce coincident graph nodes by quantizing coordinates to this many IU
43// (roughly 1 micron at typical pcbnew IU scale). Track endpoints set by the
44// router are pixel-exact so 1 IU is the right tolerance.
45constexpr int NODE_COALESCE_EPSILON = 1;
46
47
48struct NodeKey
49{
50 int x;
51 int y;
52 int layer;
53
54 bool operator==( const NodeKey& aOther ) const
55 {
56 return x == aOther.x && y == aOther.y && layer == aOther.layer;
57 }
58};
59
60
61struct NodeKeyHash
62{
63 std::size_t operator()( const NodeKey& aKey ) const noexcept
64 {
65 // Mix the three fields into a 64-bit hash; bits are well-distributed
66 // for typical pcbnew coordinates.
67 std::size_t h = static_cast<std::size_t>( static_cast<uint32_t>( aKey.x ) ) * 0x9E3779B185EBCA87ULL;
68 h ^= static_cast<std::size_t>( static_cast<uint32_t>( aKey.y ) ) * 0xC2B2AE3D27D4EB4FULL;
69 h ^= static_cast<std::size_t>( aKey.layer ) * 0x165667B19E3779F9ULL;
70 return h;
71 }
72};
73
74
75struct Edge
76{
77 int nodeA;
78 int nodeB;
79 double length;
80 double delay;
81 BOARD_CONNECTED_ITEM* sourceItem; // can be null for synthetic split halves
82 PCB_LAYER_ID layer; // signal layer for stub markers
83};
84
85
86struct Graph
87{
88 std::vector<VECTOR2I> nodePos;
89 std::vector<PCB_LAYER_ID> nodeLayer;
90 std::vector<Edge> edges;
91 std::vector<std::vector<int>> adj; // node -> list of edge indices
92
93 // Pad anchors: nodes that originated from terminal pads. Used to tag the
94 // chain's two trunk endpoints.
95 std::map<PAD*, std::vector<int>> padNodes;
96
97 int addNode( const VECTOR2I& aPos, PCB_LAYER_ID aLayer,
98 std::unordered_map<NodeKey, int, NodeKeyHash>& aIndex )
99 {
100 int rx = ( aPos.x / NODE_COALESCE_EPSILON ) * NODE_COALESCE_EPSILON;
101 int ry = ( aPos.y / NODE_COALESCE_EPSILON ) * NODE_COALESCE_EPSILON;
102 NodeKey key { rx, ry, static_cast<int>( aLayer ) };
103
104 auto it = aIndex.find( key );
105
106 if( it != aIndex.end() )
107 return it->second;
108
109 int idx = static_cast<int>( nodePos.size() );
110 nodePos.emplace_back( aPos );
111 nodeLayer.emplace_back( aLayer );
112 adj.emplace_back();
113 aIndex.emplace( key, idx );
114 return idx;
115 }
116
117 int addEdge( int aNodeA, int aNodeB, double aLength, double aDelay,
118 BOARD_CONNECTED_ITEM* aSrc, PCB_LAYER_ID aLayer )
119 {
120 if( aNodeA == aNodeB )
121 return -1;
122
123 int idx = static_cast<int>( edges.size() );
124 edges.push_back( Edge{ aNodeA, aNodeB, aLength, aDelay, aSrc, aLayer } );
125 adj[aNodeA].push_back( idx );
126 adj[aNodeB].push_back( idx );
127 return idx;
128 }
129};
130
131
132double euclidean( const VECTOR2I& a, const VECTOR2I& b )
133{
134 return ( VECTOR2D( a ) - VECTOR2D( b ) ).EuclideanNorm();
135}
136
137
138// Per-track item delay: derive an IU-per-mm delay from the existing per-item
139// length-calculation API. Falls back to the chain-wide default if the
140// per-item query reports no delay (e.g., uninitialized stackup).
141double trackDelay( const PCB_TRACK* aTrack, double aLength,
142 LENGTH_DELAY_CALCULATION* aCalc, double aFallbackDelayPerMm )
143{
144 if( !aCalc || aLength <= 0.0 )
145 return 0.0;
146
147 LENGTH_DELAY_CALCULATION_ITEM lengthItem = aCalc->GetLengthCalculationItem( aTrack );
148
149 if( lengthItem.Type() == LENGTH_DELAY_CALCULATION_ITEM::TYPE::UNKNOWN )
150 return aFallbackDelayPerMm * aLength / pcbIUScale.IU_PER_MM;
151
152 constexpr PATH_OPTIMISATIONS opts = {
153 .OptimiseVias = false, .MergeTracks = false, .OptimiseTracesInPads = false,
154 .InferViaInPad = false
155 };
156
157 std::vector<LENGTH_DELAY_CALCULATION_ITEM> items{ lengthItem };
159 items, opts, nullptr, nullptr,
162
163 return static_cast<double>( stats.TrackDelay );
164}
165
166
167// Build per-via per-adjacent-layer edges with apportioned delay per the plan.
168// Returns the number of edges added.
169int addViaEdges( Graph& aGraph, PCB_VIA* aVia,
171 std::unordered_map<NodeKey, int, NodeKeyHash>& aIndex )
172{
173 LSEQ stack = aVia->GetLayerSet().CuStack();
174
175 if( stack.size() < 2 )
176 return 0;
177
178 // Compute total via length and delay once via the per-item query so per-via
179 // overrides from GetPropagationDelays() are honored.
180 double totalDelay = 0.0;
181 double totalLength = 0.0;
182
183 if( aCalc )
184 {
186
187 if( lengthItem.Type() != LENGTH_DELAY_CALCULATION_ITEM::TYPE::UNKNOWN )
188 {
189 constexpr PATH_OPTIMISATIONS opts = {
190 .OptimiseVias = false, .MergeTracks = false, .OptimiseTracesInPads = false,
191 .InferViaInPad = false
192 };
193
194 std::vector<LENGTH_DELAY_CALCULATION_ITEM> items{ lengthItem };
196 items, opts, nullptr, nullptr,
199
200 totalDelay = static_cast<double>( stats.ViaDelay );
201 totalLength = static_cast<double>( stats.ViaLength );
202 }
203 }
204
205 // If the per-item query reports no via length, fall back to summing
206 // StackupHeight across the adjacent-layer pairs and apportion delay
207 // equally.
208 std::vector<int> heights;
209 int heightSum = 0;
210
211 for( size_t i = 0; i + 1 < stack.size(); ++i )
212 {
213 int h = aCalc ? aCalc->StackupHeight( stack[i], stack[i + 1] ) : 0;
214 heights.push_back( h );
215 heightSum += h;
216 }
217
218 if( totalLength <= 0.0 )
219 totalLength = static_cast<double>( heightSum );
220
221 int edges = 0;
222
223 for( size_t i = 0; i + 1 < stack.size(); ++i )
224 {
225 int nA = aGraph.addNode( aVia->GetPosition(), stack[i], aIndex );
226 int nB = aGraph.addNode( aVia->GetPosition(), stack[i + 1], aIndex );
227
228 double segLen = static_cast<double>( heights[i] );
229 double segDelay = 0.0;
230
231 if( totalLength > 0.0 )
232 segDelay = totalDelay * ( segLen / totalLength );
233 else if( !heights.empty() )
234 segDelay = totalDelay / static_cast<double>( heights.size() ); // equal split fallback
235
236 if( aGraph.addEdge( nA, nB, segLen, segDelay, aVia, stack[i] ) >= 0 )
237 edges++;
238 }
239
240 return edges;
241}
242
243
244// Append nodes for a pad: one per copper layer in its LSET. Returns the list
245// of (layer, nodeIdx) pairs.
246std::vector<std::pair<PCB_LAYER_ID, int>>
247addPadNodes( Graph& aGraph, PAD* aPad,
248 std::unordered_map<NodeKey, int, NodeKeyHash>& aIndex )
249{
250 std::vector<std::pair<PCB_LAYER_ID, int>> nodes;
251
252 LSET cu = aPad->GetLayerSet() & LSET::AllCuMask();
253
254 for( PCB_LAYER_ID layer : cu.CuStack() )
255 {
256 int n = aGraph.addNode( aPad->GetCenter(), layer, aIndex );
257 nodes.emplace_back( layer, n );
258 }
259
260 return nodes;
261}
262
263
264// Returns true if the parametric position t (0..1) is strictly inside the
265// open interval (epsilon, 1-epsilon) — i.e. the contact point is on the
266// interior of the segment, not at an endpoint.
267bool isInteriorParametric( double t )
268{
269 constexpr double kEdgeFraction = 1e-4;
270 return t > kEdgeFraction && t < 1.0 - kEdgeFraction;
271}
272
273
274// Run T-junction detection on the current edge set: for each edge endpoint
275// not coincident with another node, check whether it lies on the interior of
276// any other track edge on the same layer; if so, split that edge. Mutates
277// `graph` in-place by adding nodes / edges.
278void splitTrackTJunctions( Graph& aGraph,
279 std::unordered_map<NodeKey, int, NodeKeyHash>& aIndex )
280{
281 // Copy the current edge index list because we may mutate edges as we go.
282 // We only consider track edges (sourceItem is a PCB_TRACK and not an arc)
283 // because mid-segment T-joins on arcs are exotic and outside v1 scope.
284 std::vector<int> trackEdges;
285
286 for( int i = 0; i < static_cast<int>( aGraph.edges.size() ); ++i )
287 {
288 const Edge& e = aGraph.edges[i];
289
290 if( e.sourceItem && e.sourceItem->Type() == PCB_TRACE_T )
291 trackEdges.push_back( i );
292 }
293
294 for( int endpointEdge : trackEdges )
295 {
296 for( int endpointNodeSel = 0; endpointNodeSel < 2; ++endpointNodeSel )
297 {
298 const Edge& srcEdge = aGraph.edges[endpointEdge];
299 int endpointNode =
300 endpointNodeSel == 0 ? srcEdge.nodeA : srcEdge.nodeB;
301 VECTOR2I endpointPos = aGraph.nodePos[endpointNode];
302 PCB_LAYER_ID endpointLayer = aGraph.nodeLayer[endpointNode];
303
304 // Check this endpoint against every other track edge on the same
305 // layer. Iterate by index so any newly inserted edges from a
306 // previous split are re-considered.
307 for( int otherIdx = 0;
308 otherIdx < static_cast<int>( aGraph.edges.size() );
309 ++otherIdx )
310 {
311 if( otherIdx == endpointEdge )
312 continue;
313
314 Edge& other = aGraph.edges[otherIdx];
315
316 if( !other.sourceItem || other.sourceItem->Type() != PCB_TRACE_T )
317 continue;
318
319 if( other.layer != endpointLayer )
320 continue;
321
322 VECTOR2I aPos = aGraph.nodePos[other.nodeA];
323 VECTOR2I bPos = aGraph.nodePos[other.nodeB];
324
325 // Parametric projection of endpoint onto the other segment.
326 VECTOR2D ab = VECTOR2D( bPos ) - VECTOR2D( aPos );
327 double len2 = ab.SquaredEuclideanNorm();
328
329 if( len2 <= 0.0 )
330 continue;
331
332 VECTOR2D ap = VECTOR2D( endpointPos ) - VECTOR2D( aPos );
333 double t = ap.Dot( ab ) / len2;
334
335 if( !isInteriorParametric( t ) )
336 continue;
337
338 VECTOR2D projD = VECTOR2D( aPos ) + ab * t;
339 VECTOR2I proj { static_cast<int>( std::round( projD.x ) ),
340 static_cast<int>( std::round( projD.y ) ) };
341
342 // Use the track's effective shape (which is width-aware) for
343 // the actual contact test — KiCad's connectivity engine does
344 // the same (connectivity_algo.cpp's
345 // GetEffectiveShape->Collide pattern), so two tracks that
346 // touch only by their copper width but not exactly on the
347 // centerline are still recognized as a T-junction. The
348 // centerline projection above gave us the candidate split
349 // point; Collide gates whether to actually split.
350 if( PCB_TRACK* otherTrack =
351 dynamic_cast<PCB_TRACK*>( other.sourceItem ) )
352 {
353 std::shared_ptr<SHAPE> shape =
354 otherTrack->GetEffectiveShape( endpointLayer );
355
356 if( !shape || !shape->Collide( endpointPos, 0 ) )
357 {
358 // Fall back to the centerline epsilon test for cases
359 // where GetEffectiveShape isn't available (synthetic
360 // edges from earlier splits with no source item).
361 if( euclidean( proj, endpointPos ) > NODE_COALESCE_EPSILON )
362 continue;
363 }
364 }
365 else if( euclidean( proj, endpointPos ) > NODE_COALESCE_EPSILON )
366 {
367 continue;
368 }
369
370 // Split the other edge at the projected point. Replace the
371 // single Edge with two halves that share a new junction node,
372 // each carrying half the length / delay scaled by t.
373 double fullLen = other.length;
374 double fullDelay = other.delay;
375 int oldA = other.nodeA;
376 int oldB = other.nodeB;
377 BOARD_CONNECTED_ITEM* oldSrc = other.sourceItem;
378 PCB_LAYER_ID oldLayer = other.layer;
379
380 int junctionNode =
381 aGraph.addNode( proj, endpointLayer, aIndex );
382
383 if( junctionNode == oldA || junctionNode == oldB )
384 continue; // would have collapsed back; skip
385
386 // Remove the old edge from adjacency lists; we'll rewrite it.
387 auto removeFromAdj = [&]( int node, int edgeIdx ) {
388 auto& v = aGraph.adj[node];
389 v.erase( std::remove( v.begin(), v.end(), edgeIdx ),
390 v.end() );
391 };
392
393 removeFromAdj( oldA, otherIdx );
394 removeFromAdj( oldB, otherIdx );
395
396 // Re-purpose `other` as the first half (oldA -> junction).
397 aGraph.edges[otherIdx].nodeA = oldA;
398 aGraph.edges[otherIdx].nodeB = junctionNode;
399 aGraph.edges[otherIdx].length = fullLen * t;
400 aGraph.edges[otherIdx].delay = fullDelay * t;
401 aGraph.adj[oldA].push_back( otherIdx );
402 aGraph.adj[junctionNode].push_back( otherIdx );
403
404 // Add the second half as a new edge (junction -> oldB).
405 aGraph.addEdge( junctionNode, oldB,
406 fullLen * ( 1.0 - t ),
407 fullDelay * ( 1.0 - t ),
408 oldSrc, oldLayer );
409
410 // Connect the original endpoint to the junction explicitly.
411 // (The endpoint's edge already terminates at endpointNode and
412 // endpointNode == junctionNode after coalescing because they
413 // share (x, y, layer); the addNode lookup above should have
414 // returned the same index. If not, we add a zero-length tie
415 // to avoid a graph break.)
416 if( endpointNode != junctionNode )
417 {
418 aGraph.addEdge( endpointNode, junctionNode, 0.0, 0.0,
419 nullptr, endpointLayer );
420 }
421 }
422 }
423 }
424}
425
426
427// Standard DFS that records parent edges and detects cycles by encountering a
428// back-edge to a non-parent visited node. Returns true if a cycle is
429// detected. Fills `parentNode[i]` and `parentEdge[i]` for every reachable
430// node (or -1).
431bool dfsSpanningTree( const Graph& aGraph, int aRoot,
432 std::vector<int>& aParentNode,
433 std::vector<int>& aParentEdge,
434 std::vector<bool>& aVisited )
435{
436 aParentNode.assign( aGraph.nodePos.size(), -1 );
437 aParentEdge.assign( aGraph.nodePos.size(), -1 );
438 aVisited.assign( aGraph.nodePos.size(), false );
439
440 std::stack<std::pair<int, int>> stack; // (node, parentEdgeIdx)
441 stack.push( { aRoot, -1 } );
442
443 bool cycle = false;
444
445 while( !stack.empty() )
446 {
447 auto [node, fromEdge] = stack.top();
448 stack.pop();
449
450 if( aVisited[node] )
451 continue;
452
453 aVisited[node] = true;
454 aParentEdge[node] = fromEdge;
455
456 if( fromEdge >= 0 )
457 {
458 const Edge& e = aGraph.edges[fromEdge];
459 aParentNode[node] = ( e.nodeA == node ) ? e.nodeB : e.nodeA;
460 }
461
462 for( int eIdx : aGraph.adj[node] )
463 {
464 if( eIdx == fromEdge )
465 continue;
466
467 const Edge& e = aGraph.edges[eIdx];
468 int next = ( e.nodeA == node ) ? e.nodeB : e.nodeA;
469
470 if( aVisited[next] )
471 {
472 cycle = true;
473 continue;
474 }
475
476 stack.push( { next, eIdx } );
477 }
478 }
479
480 return cycle;
481}
482
483} // namespace
484
485
486CHAIN_TOPOLOGY::CHAIN_TOPOLOGY( BOARD* aBoard, const wxString& aChainName,
487 const std::set<BOARD_CONNECTED_ITEM*>& aChainItems ) :
488 m_chainName( aChainName )
489{
490 if( !aBoard || aChainName.IsEmpty() || aChainItems.empty() )
491 {
492 m_status = STATUS::NO_ITEMS;
493 return;
494 }
495
496 // Locate the two terminal pads from the first member-net we encounter
497 // that has them set. Per chain semantics every member-net carries the
498 // same terminal-pad pair, so reading from any one is sufficient.
499 PAD* terminalA = nullptr;
500 PAD* terminalB = nullptr;
501
502 for( BOARD_CONNECTED_ITEM* item : aChainItems )
503 {
504 NETINFO_ITEM* ni = item->GetNet();
505
506 if( !ni )
507 continue;
508
509 if( PAD* p0 = ni->GetTerminalPad( 0 ) )
510 terminalA = p0;
511
512 if( PAD* p1 = ni->GetTerminalPad( 1 ) )
513 terminalB = p1;
514
515 if( terminalA && terminalB )
516 break;
517 }
518
519 if( !terminalA || !terminalB )
520 {
521 m_status = STATUS::NO_TERMINAL_PADS;
522 return;
523 }
524
525 // Build the graph.
526 Graph graph;
527 std::unordered_map<NodeKey, int, NodeKeyHash> index;
529 double fallbackDelayPerMm = ChainBridgingDelayPerMm( aBoard, m_chainName );
530
531 // Collect pads belonging to chain-member nets — including ones not in
532 // aChainItems (terminal pads sit on the chain but might not be in the
533 // matched-length sweep's per-net set if they own no routing segments
534 // attached to a tracked rule). We need those pad nodes for the graph
535 // anchors regardless.
536 std::set<PAD*> seenPads;
537
538 auto registerAllPadLayers = [&]( PAD* pad )
539 {
540 if( !pad )
541 return;
542
543 if( !seenPads.insert( pad ).second )
544 return;
545
546 addPadNodes( graph, pad, index );
547 // Track which nodes belong to this pad for later terminal lookup.
548 std::vector<int>& list = graph.padNodes[pad];
549 list.clear();
550 LSET cu = pad->GetLayerSet() & LSET::AllCuMask();
551
552 for( PCB_LAYER_ID layer : cu.CuStack() )
553 {
554 int n = graph.addNode( pad->GetCenter(), layer, index );
555 list.push_back( n );
556 }
557 };
558
559 registerAllPadLayers( terminalA );
560 registerAllPadLayers( terminalB );
561
562 // Edges from tracks, arcs, vias, and pads in the matched item set.
563 for( BOARD_CONNECTED_ITEM* item : aChainItems )
564 {
565 if( !item )
566 continue;
567
568 switch( item->Type() )
569 {
570 case PCB_TRACE_T:
571 case PCB_ARC_T:
572 {
573 PCB_TRACK* track = static_cast<PCB_TRACK*>( item );
574 int nA = graph.addNode( track->GetStart(), track->GetLayer(), index );
575 int nB = graph.addNode( track->GetEnd(), track->GetLayer(), index );
576 double len = track->GetLength();
577 double dly = trackDelay( track, len, calc, fallbackDelayPerMm );
578 graph.addEdge( nA, nB, len, dly, track, track->GetLayer() );
579 break;
580 }
581 case PCB_VIA_T:
582 {
583 PCB_VIA* via = static_cast<PCB_VIA*>( item );
584 addViaEdges( graph, via, calc, index );
585 break;
586 }
587 case PCB_PAD_T:
588 {
589 PAD* pad = static_cast<PAD*>( item );
590 registerAllPadLayers( pad );
591 break;
592 }
593 default:
594 break;
595 }
596 }
597
598 // Bridge edges through series passives.
599 std::vector<CHAIN_BRIDGE> bridges = EnumerateChainBridges( aBoard, m_chainName );
600
601 for( const CHAIN_BRIDGE& br : bridges )
602 {
603 registerAllPadLayers( br.padA );
604 registerAllPadLayers( br.padB );
605
606 // Pick the bridge's anchor layer: footprint placement layer for SMD
607 // pads, topmost shared copper layer for THT.
608 FOOTPRINT* fp = br.padA->GetParentFootprint();
609 PCB_LAYER_ID anchorLayer = fp ? fp->GetLayer() : F_Cu;
610
611 LSET commonCu = ( br.padA->GetLayerSet() & br.padB->GetLayerSet() ) & LSET::AllCuMask();
612
613 if( !commonCu.test( anchorLayer ) )
614 {
615 LSEQ stack = commonCu.CuStack();
616 if( !stack.empty() )
617 anchorLayer = stack[0];
618 else
619 continue;
620 }
621
622 int nA = graph.addNode( br.padA->GetCenter(), anchorLayer, index );
623 int nB = graph.addNode( br.padB->GetCenter(), anchorLayer, index );
624 graph.addEdge( nA, nB, br.length, br.delay, nullptr, anchorLayer );
625 }
626
627 // T-junction split pass on the populated graph.
628 splitTrackTJunctions( graph, index );
629
630 // Tag terminal nodes — pick any layer in the pad's LSET (the pad has zero
631 // intrinsic length so any anchor layer works for path queries; tracks
632 // terminating at the pad on layer L will share the same (x, y, L) node).
633 auto firstPadNode = []( const std::vector<int>& nodes ) -> int
634 {
635 return nodes.empty() ? -1 : nodes.front();
636 };
637
638 int rootA = firstPadNode( graph.padNodes[terminalA] );
639 int rootB = firstPadNode( graph.padNodes[terminalB] );
640
641 if( rootA < 0 || rootB < 0 )
642 {
644 return;
645 }
646
647 // Bridge zero-length edges between every pair of pad-anchor layers so
648 // that a pad on multiple layers (e.g. THT) is one logical graph node.
649 auto stitchPadLayers = [&]( PAD* aPad )
650 {
651 const std::vector<int>& nodes = graph.padNodes[aPad];
652
653 for( size_t i = 1; i < nodes.size(); ++i )
654 graph.addEdge( nodes[0], nodes[i], 0.0, 0.0, nullptr,
655 graph.nodeLayer[nodes[i]] );
656 };
657
658 for( auto& [pad, nodes] : graph.padNodes )
659 stitchPadLayers( pad );
660
661 // DFS spanning tree from terminalA.
662 std::vector<int> parentNode;
663 std::vector<int> parentEdge;
664 std::vector<bool> visited;
665
666 bool cycle = dfsSpanningTree( graph, rootA, parentNode, parentEdge, visited );
667
668 if( cycle )
669 {
671 return;
672 }
673
674 if( !visited[rootB] )
675 {
677 return;
678 }
679
680 // Recover trunk: walk from rootB back to rootA via parent pointers.
681 std::vector<int> trunkEdges;
682 std::vector<int> trunkNodes;
683 {
684 int cur = rootB;
685
686 while( cur != rootA && cur >= 0 )
687 {
688 trunkNodes.push_back( cur );
689 int e = parentEdge[cur];
690
691 if( e < 0 )
692 break;
693
694 trunkEdges.push_back( e );
695 cur = parentNode[cur];
696 }
697
698 if( cur != rootA )
699 {
701 return;
702 }
703
704 trunkNodes.push_back( rootA );
705 }
706
707 // Sum trunk length / delay.
708 m_trunkLength = 0.0;
709 m_trunkDelay = 0.0;
710
711 for( int eIdx : trunkEdges )
712 {
713 m_trunkLength += graph.edges[eIdx].length;
714 m_trunkDelay += graph.edges[eIdx].delay;
715 }
716
717 // Stub identification: BFS from each non-trunk node not yet on a stub
718 // component, following non-trunk edges only, and find the unique
719 // trunk-touching node.
720 std::set<int> trunkNodeSet( trunkNodes.begin(), trunkNodes.end() );
721 std::set<int> trunkEdgeSet( trunkEdges.begin(), trunkEdges.end() );
722 std::vector<bool> nodeInStubComponent( graph.nodePos.size(), false );
723
724 for( int startNode = 0;
725 startNode < static_cast<int>( graph.nodePos.size() );
726 ++startNode )
727 {
728 if( !visited[startNode] )
729 continue;
730 if( trunkNodeSet.count( startNode ) )
731 continue;
732 if( nodeInStubComponent[startNode] )
733 continue;
734
735 // BFS this stub component (via edges *not* in trunkEdgeSet) and
736 // collect all of its nodes + items + length/delay + every trunk node
737 // it touches.
738 std::set<int> compNodes;
739 std::set<int> compEdges;
740 std::set<int> trunkTouches;
741 std::vector<BOARD_CONNECTED_ITEM*> items;
742 double compLength = 0.0;
743 double compDelay = 0.0;
744 PCB_LAYER_ID anyLayer = UNDEFINED_LAYER;
745
746 std::queue<int> q;
747 q.push( startNode );
748 compNodes.insert( startNode );
749
750 while( !q.empty() )
751 {
752 int n = q.front();
753 q.pop();
754
755 for( int eIdx : graph.adj[n] )
756 {
757 const Edge& e = graph.edges[eIdx];
758 int other = ( e.nodeA == n ) ? e.nodeB : e.nodeA;
759
760 if( trunkEdgeSet.count( eIdx ) )
761 {
762 // Edge into the trunk — only record the touch. The
763 // edge itself belongs to the trunk and is not part of
764 // the stub component.
765 if( trunkNodeSet.count( other ) )
766 trunkTouches.insert( other );
767
768 continue;
769 }
770
771 // Accumulate this stub edge exactly once across the BFS.
772 // Tracking visited edges (not visited nodes) is the right
773 // primitive: nothing about traversal order can correctly be
774 // inferred from node-index comparisons because BFS may
775 // reach a higher-index node from a lower one.
776 if( compEdges.insert( eIdx ).second )
777 {
778 compLength += e.length;
779 compDelay += e.delay;
780
781 if( e.sourceItem )
782 items.push_back( e.sourceItem );
783
784 if( anyLayer == UNDEFINED_LAYER )
785 anyLayer = e.layer;
786 }
787
788 if( trunkNodeSet.count( other ) )
789 {
790 trunkTouches.insert( other );
791 continue;
792 }
793
794 if( compNodes.insert( other ).second )
795 q.push( other );
796 }
797 }
798
799 for( int n : compNodes )
800 nodeInStubComponent[n] = true;
801
802 if( trunkTouches.empty() )
803 {
804 // Floating copper — DRCE_UNCONNECTED_ITEMS will catch it.
805 continue;
806 }
807
808 if( trunkTouches.size() >= 2 )
809 {
810 // Two or more trunk-touching points imply a cycle that the
811 // earlier DFS missed. Defensive bail.
813 return;
814 }
815
816 int branchNode = *trunkTouches.begin();
817
818 STUB stub;
819 stub.branchPoint = graph.nodePos[branchNode];
820 stub.branchLayer = anyLayer != UNDEFINED_LAYER ? anyLayer
821 : graph.nodeLayer[branchNode];
822 stub.items = std::move( items );
823 stub.length = compLength;
824 stub.delay = compDelay;
825
826 m_stubs.push_back( std::move( stub ) );
827 }
828
830}
int index
bool operator==(const wxAuiPaneInfo &aLhs, const wxAuiPaneInfo &aRhs)
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:121
A base class derived from BOARD_ITEM for items that can be connected and have a net,...
PCB_LAYER_ID GetLayer() const override
Return the primary layer this item is on.
FOOTPRINT * GetParentFootprint() const
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:372
LENGTH_DELAY_CALCULATION * GetLengthCalculation() const
Returns the track length calculator.
Definition board.h:1516
bool IsEmpty() const
Definition board.cpp:662
CHAIN_TOPOLOGY(BOARD *aBoard, const wxString &aChainName, const std::set< BOARD_CONNECTED_ITEM * > &aChainItems)
std::vector< STUB > m_stubs
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:108
PCB_LAYER_ID GetLayer() const override
Return the primary layer this item is on.
Definition footprint.h:417
Lightweight class which holds a pad, via, or a routed trace outline.
TYPE Type() const
Gets the routing item type.
Class which calculates lengths (and associated routing statistics) in a BOARD context.
int StackupHeight(PCB_LAYER_ID aFirstLayer, PCB_LAYER_ID aSecondLayer) const
Returns the stackup distance between the two given layers.
LENGTH_DELAY_STATS CalculateLengthDetails(std::vector< LENGTH_DELAY_CALCULATION_ITEM > &aItems, PATH_OPTIMISATIONS aOptimisations, const PAD *aStartPad=nullptr, const PAD *aEndPad=nullptr, LENGTH_DELAY_LAYER_OPT aLayerOpt=LENGTH_DELAY_LAYER_OPT::NO_LAYER_DETAIL, LENGTH_DELAY_DOMAIN_OPT aDomain=LENGTH_DELAY_DOMAIN_OPT::NO_DELAY_DETAIL, LENGTH_DELAY_ITEM_DETAILS *aPerItemLengthDelays=nullptr) const
Calculates the electrical length of the given items.
LENGTH_DELAY_CALCULATION_ITEM GetLengthCalculationItem(const BOARD_CONNECTED_ITEM *aBoardItem) const
Return a LENGTH_CALCULATION_ITEM constructed from the given BOARD_CONNECTED_ITEM.
LSEQ is a sequence (and therefore also a set) of PCB_LAYER_IDs.
Definition lseq.h:47
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
static const LSET & AllCuMask()
return AllCuMask( MAX_CU_LAYERS );
Definition lset.cpp:604
LSEQ CuStack() const
Return a sequence of copper layers in starting from the front/top and extending to the back/bottom.
Definition lset.cpp:259
Definition pad.h:61
LSET GetLayerSet() const override
Return a std::bitset of all layers on which the item physically resides.
Definition pad.h:552
VECTOR2I GetCenter() const override
This defaults to the center of the bounding box if not overridden.
Definition pad.h:324
virtual double GetLength() const
Get the length of the track using the hypotenuse calculation.
const VECTOR2I & GetStart() const
Definition pcb_track.h:93
const VECTOR2I & GetEnd() const
Definition pcb_track.h:90
VECTOR2I GetPosition() const override
Definition pcb_track.h:553
virtual LSET GetLayerSet() const override
Return a std::bitset of all layers on which the item physically resides.
constexpr extended_type SquaredEuclideanNorm() const
Compute the squared euclidean norm of the vector, which is defined as (x ** 2 + y ** 2).
Definition vector2d.h:303
constexpr extended_type Dot(const VECTOR2< T > &aVector) const
Compute dot product of self with aVector.
Definition vector2d.h:542
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ UNDEFINED_LAYER
Definition layer_ids.h:57
@ F_Cu
Definition layer_ids.h:60
double ChainBridgingDelayPerMm(const BOARD *aBoard, const wxString &aNetChain)
Pick a single per-IU-per-mm delay for a given chain.
std::vector< CHAIN_BRIDGE > EnumerateChainBridges(const BOARD *aBoard, const wxString &aNetChain)
Enumerate every per-pad-pair bridge edge contributed by every footprint on the board to the named cha...
CITER next(CITER it)
Definition ptree.cpp:120
One series-component bridge edge inside a chain graph.
std::vector< BOARD_CONNECTED_ITEM * > items
Holds length measurement result details and statistics.
Struct to control which optimisations the length calculation code runs on the given path objects.
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:90
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition typeinfo.h:80
@ PCB_ARC_T
class PCB_ARC, an arc track segment on a copper layer
Definition typeinfo.h:91
@ PCB_TRACE_T
class PCB_TRACK, a track segment (segment on a copper layer)
Definition typeinfo.h:89
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683
VECTOR2< double > VECTOR2D
Definition vector2d.h:682