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