KiCad PCB EDA Suite
Loading...
Searching...
No Matches
conn_netchain_manager.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 modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * 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
21#include "conn_netchain_input.h"
22#include "conn_facade.h"
23#include <advanced_config.h>
24#include <algorithm>
25#include <iterator>
26#include <queue>
27#include <unordered_map>
28#include <stdexcept>
29#include <type_traits>
30#include <sch_label.h>
31#include <sch_line.h>
32#include <sch_pin.h>
33#include <sch_screen.h>
34#include <sch_symbol.h>
35#include <schematic.h>
36#include <project.h>
39#include <trace_helpers.h>
40#include <wx/log.h>
41
43{
44 // SCH_NETCHAIN::m_symbols holds non-owning SCH_SYMBOL pointers. Once the connectivity
45 // pass clears the rest of the graph the schematic items can be freed before
46 // RebuildNetChains() repopulates the chain caches, so drop the stale pointers now.
47 for( std::unique_ptr<SCH_NETCHAIN>& chain : m_committedNetChains )
48 {
49 if( chain )
50 chain->ClearSymbols();
51 }
52
54 m_bridgeEdges.clear();
55 m_netChainsBuilt = false;
56}
57
59{
60 if( this == &aOther )
61 return;
62
63 // Committed chains and override maps belong to the persistent schematic state, so they
64 // must travel across an incremental graph merge. Potential chains are moved alongside
65 // them to keep the merged graph self-consistent until the next RebuildNetChains() pass.
66 for( std::unique_ptr<SCH_NETCHAIN>& chain : aOther.m_committedNetChains )
67 {
68 if( chain )
69 m_committedNetChains.push_back( std::move( chain ) );
70 }
71
72 aOther.m_committedNetChains.clear();
73
74 for( std::unique_ptr<SCH_NETCHAIN>& chain : aOther.m_potentialNetChains )
75 {
76 if( chain )
77 m_potentialNetChains.push_back( std::move( chain ) );
78 }
79
80 aOther.m_potentialNetChains.clear();
81
82 m_bridgeEdges.insert( m_bridgeEdges.end(), aOther.m_bridgeEdges.begin(), aOther.m_bridgeEdges.end() );
83 aOther.m_bridgeEdges.clear();
84
86
87 for( auto& [key, value] : aOther.m_netChainNetClassOverrides )
88 m_netChainNetClassOverrides.insert_or_assign( key, value );
89
90 for( auto& [key, value] : aOther.m_netChainColorOverrides )
91 m_netChainColorOverrides.insert_or_assign( key, value );
92
93 for( auto& [key, value] : aOther.m_netChainTerminalRefOverrides )
94 m_netChainTerminalRefOverrides.insert_or_assign( key, value );
95
96 for( auto& [key, value] : aOther.m_netChainMemberNetOverrides )
97 m_netChainMemberNetOverrides.insert_or_assign( key, value );
98}
99
101{
103 {
105 std::vector<SCH_PIN*> pins;
106 };
107
109 std::vector<SYMBOL_PINS> symbols;
110};
111
112
114 const std::vector<SHEET_SYMBOLS>& aSheets )
115{
117
118 // Walk every 2-pin passthrough symbol on every sheet, building a flat list of bridge
119 // edges between distinct subgraph nets.
120
121 result.edges.reserve( 256 );
122
123 for( const SHEET_SYMBOLS& view : aSheets )
124 {
125 const auto& sheet = *view.input;
126 SCH_SCREEN* sc = sheet.path.LastScreen();
127
128 auto findWireOnScreen = [&]( SCH_PIN* aPin, SCH_LINE*& aWire ) -> bool
129 {
130 const VECTOR2I p = aPin->GetPosition();
131
132 auto consider = [&]( SCH_ITEM* cand ) -> bool
133 {
134 if( cand->Type() != SCH_LINE_T )
135 return false;
136
137 SCH_LINE* line = static_cast<SCH_LINE*>( cand );
138
139 if( line->GetLayer() != LAYER_WIRE )
140 return false;
141
142 const VECTOR2I s = line->GetStartPoint();
143 const VECTOR2I e = line->GetEndPoint();
144
145 if( s.y == e.y && p.y == s.y )
146 {
147 int minx = std::min( s.x, e.x );
148 int maxx = std::max( s.x, e.x );
149
150 if( p.x >= minx && p.x <= maxx )
151 {
152 aWire = line;
153 return true;
154 }
155 }
156 else if( s.x == e.x && p.x == s.x )
157 {
158 int miny = std::min( s.y, e.y );
159 int maxy = std::max( s.y, e.y );
160
161 if( p.y >= miny && p.y <= maxy )
162 {
163 aWire = line;
164 return true;
165 }
166 }
167
168 return false;
169 };
170
171 for( SCH_ITEM* c : sc->Items().Overlapping( SCH_LINE_T, p ) )
172 if( consider( c ) )
173 return true;
174
175 for( SCH_ITEM* c : sc->Items().OfType( SCH_LINE_T ) )
176 if( consider( c ) )
177 return true;
178
179 return false;
180 };
181
182 for( const auto& [symbol, pins] : view.symbols )
183 {
184 if( pins.size() != 2 )
185 continue;
186
187 if( symbol->GetPassthroughMode() == SCH_SYMBOL::PASSTHROUGH_MODE::BLOCK )
188 continue;
189
190 SCH_LINE* wireA = nullptr;
191 SCH_LINE* wireB = nullptr;
192
193 if( !findWireOnScreen( pins[0], wireA ) || !findWireOnScreen( pins[1], wireB ) )
194 continue;
195
196 bool allow = false;
197
198 if( symbol->GetPassthroughMode() == SCH_SYMBOL::PASSTHROUGH_MODE::FORCE )
199 {
200 allow = true;
201 }
202 else
203 {
204 if( pins[0]->IsPower() || pins[1]->IsPower() )
205 continue;
206
207 VECTOR2I aS = wireA->GetStartPoint();
208 VECTOR2I aE = wireA->GetEndPoint();
209 VECTOR2I bS = wireB->GetStartPoint();
210 VECTOR2I bE = wireB->GetEndPoint();
211
212 if( aS.x == aE.x && bS.x == bE.x && aS.x == bS.x )
213 allow = true;
214 else if( aS.y == aE.y && bS.y == bE.y && aS.y == bS.y )
215 allow = true;
216 }
217
218 if( !allow )
219 continue;
220
221 const wxString& netA = sheet.Key( pins[0] );
222 const wxString& netB = sheet.Key( pins[1] );
223
224 if( netA.IsEmpty() || netB.IsEmpty() || netA == netB )
225 continue;
226
227 result.edges.push_back( { netA, netB, symbol, sc } );
228 }
229 }
230
231 // Mark power subgraphs by walking every pin across every sheet. Any subgraph touched by a
232 // power-class pin (or a power-symbol parent) is treated as a power node and its incident
233 // bridge edges are excluded below.
234
235 std::set<wxString> powerNets;
236
237 for( const SHEET_SYMBOLS& view : aSheets )
238 {
239 const auto& sheet = *view.input;
240
241 for( const auto& entry : view.symbols )
242 {
243 for( SCH_PIN* p : entry.pins )
244 {
245 if( p->IsPower() || ( p->GetParentSymbol() && p->GetParentSymbol()->IsPower() ) )
246 {
247 if( const auto* net = sheet.Find( p ) )
248 powerNets.insert( net->key );
249 }
250 }
251 }
252 }
253
254 // Build the filtered adjacency. Edges that touch a power subgraph are dropped, and any
255 // non-power endpoint of such a dropped edge is recorded as power-adjacent so the leaf-prune
256 // pass below can iteratively remove power stubs.
257
258 std::set<wxString> powerAdjacentNets;
259
260 for( const BRIDGE_EDGE& be : result.edges )
261 {
262 if( powerNets.contains( be.a ) || powerNets.contains( be.b ) )
263 {
264 if( !powerNets.contains( be.a ) )
265 powerAdjacentNets.insert( be.a );
266
267 if( !powerNets.contains( be.b ) )
268 powerAdjacentNets.insert( be.b );
269
270 continue;
271 }
272
273 result.adjacency[be.a].push_back( { be.b, be.sym } );
274 result.adjacency[be.b].push_back( { be.a, be.sym } );
275 }
276
277 // Iteratively prune degree-1 power-adjacent leaves. Skip pruning entirely for very small
278 // graphs to avoid wiping out legitimate two-net chains.
279
280 std::map<wxString, int> degree;
281
282 for( const auto& kv : result.adjacency )
283 degree[kv.first] = static_cast<int>( kv.second.size() );
284
285 if( result.adjacency.size() <= 2 )
286 powerAdjacentNets.clear();
287
288 if( powerAdjacentNets.size() <= 2 )
289 powerAdjacentNets.clear();
290
291 std::queue<wxString> q;
292 std::set<wxString> removed;
293
294 for( const auto& kv : degree )
295 {
296 if( kv.second <= 1 && powerAdjacentNets.contains( kv.first ) )
297 q.push( kv.first );
298 }
299
300 while( !q.empty() )
301 {
302 wxString n = q.front();
303 q.pop();
304
305 if( removed.contains( n ) )
306 continue;
307
308 removed.insert( n );
309
310 for( const BRIDGE_NEIGHBOR& e : result.adjacency[n] )
311 {
312 if( removed.contains( e.other ) )
313 continue;
314
315 if( degree.count( e.other ) )
316 {
317 degree[e.other]--;
318
319 if( degree[e.other] <= 1 && powerAdjacentNets.contains( e.other ) )
320 q.push( e.other );
321 }
322 }
323 }
324
325 if( !removed.empty() )
326 {
327 std::map<wxString, std::vector<BRIDGE_NEIGHBOR>> newAdj;
328
329 for( const auto& kv : result.adjacency )
330 {
331 if( removed.contains( kv.first ) )
332 continue;
333
334 for( const BRIDGE_NEIGHBOR& e : kv.second )
335 {
336 if( removed.contains( e.other ) )
337 continue;
338
339 newAdj[kv.first].push_back( e );
340 }
341 }
342
343 result.adjacency.swap( newAdj );
344 }
345
346 return result;
347}
348
349
351 const NETCHAIN_INPUT& aConnectivity,
352 const std::function<void( NETCHAIN_MANAGER& )>& aBeforePublish )
353{
355 throw std::logic_error( "Cannot publish a nested netchain candidate" );
356
357 if( !m_schematic )
358 return;
359
360 std::unordered_map<SCH_SYMBOL*, wxString> symbolNames;
361 NETCHAIN_MANAGER candidate( m_schematic );
362 candidate.m_pendingSymbolNames = &symbolNames;
367 candidate.m_committedNetChains.reserve( m_committedNetChains.size() );
368
369 for( const auto& chain : m_committedNetChains )
370 {
371 candidate.m_committedNetChains.push_back( chain ? std::make_unique<SCH_NETCHAIN>( *chain ) : nullptr );
372
373 // Source items may have been removed since the last successful refresh.
374 if( candidate.m_committedNetChains.back() )
375 candidate.m_committedNetChains.back()->ClearSymbols();
376 }
377
378 candidate.rebuild( aConnectivity );
379
380 if( aBeforePublish )
381 aBeforePublish( candidate );
382
383 const size_t existing = m_committedNetChains.size();
384
385 if( candidate.m_committedNetChains.size() < existing )
386 throw std::logic_error( "Committed netchains removed during rebuild" );
387
388 for( size_t i = 0; i < existing; ++i )
389 {
390 const auto& current = m_committedNetChains[i];
391 const auto& updated = candidate.m_committedNetChains[i];
392
393 if( bool( current ) != bool( updated )
394 || ( current && current->GetName() != updated->GetName() ) )
395 {
396 throw std::logic_error( "Committed netchain identity changed during rebuild" );
397 }
398 }
399
400 m_committedNetChains.reserve( candidate.m_committedNetChains.size() );
401 using std::swap;
402
403 static_assert( std::is_nothrow_swappable_v<SCH_NETCHAIN> );
404 static_assert( std::is_nothrow_move_constructible_v<wxString> );
405
406 // Keep committed object addresses stable; nothing below may allocate or invoke a callback.
407 for( size_t i = 0; i < existing; ++i )
408 {
409 if( m_committedNetChains[i] )
410 swap( *m_committedNetChains[i], *candidate.m_committedNetChains[i] );
411 }
412
413 for( size_t i = existing; i < candidate.m_committedNetChains.size(); ++i )
414 m_committedNetChains.push_back( std::move( candidate.m_committedNetChains[i] ) );
415
417 m_bridgeEdges.swap( candidate.m_bridgeEdges );
422
423 for( auto& [symbol, name] : symbolNames )
424 symbol->SetNetChainName( std::move( name ) );
425
426 m_netChainsBuilt = true;
427}
428
429
431{
432 if( !aSymbol )
433 return;
434
436 ( *m_pendingSymbolNames )[aSymbol] = aName;
437 else
438 aSymbol->SetNetChainName( aName );
439}
440
441
443 SCH_NETCHAIN& aChain, const CHAIN_TERMINAL_REFS* aSavedRefs )
444{
445 if( !m_schematic )
446 return false;
447
448 SCH_PIN* pins[2] = { nullptr, nullptr };
449 SCH_SHEET_PATH paths[2];
450 const KIID ids[2] = { aChain.GetTerminalPinA(), aChain.GetTerminalPinB() };
451 const SCH_SHEET_LIST hierarchy = m_schematic->Hierarchy();
452
453 for( int endpoint = 0; endpoint < 2; ++endpoint )
454 {
455 const KIID_PATH& storedPath = aChain.GetTerminalPath( endpoint );
456
457 for( const SCH_SHEET_PATH& path : hierarchy )
458 {
459 SCH_SCREEN* screen = path.LastScreen();
460
461 if( !screen || ( !aSavedRefs && !storedPath.empty() && storedPath != path.PathRef() ) )
462 continue;
463
464 if( !aSavedRefs )
465 {
466 auto* pin = dynamic_cast<SCH_PIN*>( screen->GetConnectivityItem( ids[endpoint] ) );
467
468 if( !pin )
469 continue;
470
471 const auto activePins = static_cast<SCH_SYMBOL*>( pin->GetParentSymbol() )->GetPins( &path );
472
473 if( std::find( activePins.begin(), activePins.end(), pin ) == activePins.end() )
474 continue;
475
476 if( pins[endpoint] )
477 return false;
478
479 pins[endpoint] = pin;
480 paths[endpoint] = path;
481 continue;
482 }
483
484 const CHAIN_TERMINAL_REF& ref = endpoint == 0 ? aSavedRefs->first : aSavedRefs->second;
485
486 for( SCH_ITEM* item : screen->Items().OfType( SCH_SYMBOL_T ) )
487 {
488 auto* symbol = static_cast<SCH_SYMBOL*>( item );
489
490 if( symbol->GetRef( &path ) != ref.ref )
491 continue;
492
493 for( SCH_PIN* pin : symbol->GetPins( &path ) )
494 {
495 if( pin->GetNumber() != ref.pin )
496 continue;
497
498 // A saved reference or pathless UUID must identify exactly one instance.
499 if( pins[endpoint] )
500 return false;
501
502 pins[endpoint] = pin;
503 paths[endpoint] = path;
504 }
505 }
506 }
507
508 if( !pins[endpoint] )
509 return false;
510 }
511
512 aChain.SetTerminalPins( pins[0]->m_Uuid, pins[1]->m_Uuid );
513 aChain.SetTerminalPaths( paths[0].Path(), paths[1].Path() );
514 aChain.SetTerminalRefs( pins[0]->GetParentSymbol()->GetRef( &paths[0] ), pins[0]->GetNumber(),
515 pins[1]->GetParentSymbol()->GetRef( &paths[1] ), pins[1]->GetNumber() );
516 return true;
517}
518
519
521{
522 if( !resolveTerminals( aChain ) )
523 return false;
524
525 storeTerminalRefs( aChain );
526 return true;
527}
528
529
538
539
541{
543 { aChain.GetTerminalRef( 0 ), aChain.GetTerminalPinNum( 0 ) },
544 { aChain.GetTerminalRef( 1 ), aChain.GetTerminalPinNum( 1 ) }
545 };
546}
547
548
549void SCH_CONNECTIVITY::NETCHAIN_MANAGER::storeMemberNets( const wxString& aName, const std::set<wxString>& aNets )
550{
551 std::set<wxString> persistable;
552
553 std::copy_if( aNets.begin(), aNets.end(), std::inserter( persistable, persistable.end() ),
555
556 if( persistable.empty() )
557 m_netChainMemberNetOverrides.erase( aName );
558 else
559 m_netChainMemberNetOverrides[aName] = std::move( persistable );
560}
561
562
564{
565 const bool trace = wxLog::IsAllowedTraceMask( traceSchNetChain );
566 std::set<wxString> unresolvedTerminals;
567
568 for( const auto& chain : m_committedNetChains )
569 {
570 if( !chain )
571 continue;
572
574 {
575 chain->ReplaceNets( {} );
576 unresolvedTerminals.insert( chain->GetName() );
577 }
578 }
579
580 std::unordered_map<wxString, SCH_NETCHAIN*> netToNetChain;
581
582 // Chains may cross sheets; inspect the complete input hierarchy.
583 std::vector<SHEET_SYMBOLS> sheetSymbols;
584 sheetSymbols.reserve( aConnectivity.sheets.size() );
585
586 for( const auto& sheet : aConnectivity.sheets )
587 {
588 SCH_SCREEN* screen = sheet.path.LastScreen();
589
590 if( !screen )
591 continue;
592
593 SHEET_SYMBOLS& view = sheetSymbols.emplace_back( SHEET_SYMBOLS{ &sheet, {} } );
594
595 for( SCH_ITEM* item : screen->Items().OfType( SCH_SYMBOL_T ) )
596 {
597 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
598 view.symbols.push_back( { symbol, symbol->GetPins( &sheet.path ) } );
599 setSymbolName( symbol, wxEmptyString );
600 }
601 }
602
603 wxLogTrace( traceSchNetChain, "RebuildNetChains: screens=%zu (global build)", sheetSymbols.size() );
604 wxLogTrace( traceSchNetChain, "RebuildNetChains: debug start passes (pre-pass chains=%zu)",
605 m_committedNetChains.size() );
606
607 // Build net chains by scanning eligible 2-pin symbols on every sheet, using the original
608 // parallel-wire passthrough heuristic. This is effectively the old pass 1 but repeated for
609 // each screen, giving global coverage while preserving expected grouping semantics.
610 wxLogTrace( traceSchNetChain, "RebuildNetChains: pass 1 (per-sheet 2-pin symbols)" );
611
612 BRIDGE_GRAPH bridgeGraph = buildBridgeAdjacency( sheetSymbols );
613 auto& bridgeEdges = bridgeGraph.edges;
614 auto& adjacency = bridgeGraph.adjacency;
615
616 wxLogTrace( traceSchNetChain, "RebuildNetChains: bridgeEdges=%zu adjacency=%zu",
617 bridgeEdges.size(), adjacency.size() );
618
619 // Targeted stub pruning: reduce any component >4 nets by removing minimal number of "stub" leaves
620 // (degree 1 whose neighbor has degree >2). This satisfies legacy test expecting longest branch kept.
621 {
622 // First, discover connected components over current adjacency.
623 wxLogTrace( traceSchNetChain, "RebuildNetChains: targeted stub pruning start (adj=%zu)", adjacency.size() );
624 std::set<wxString> seen;
625 std::set<wxString> globalPrune;
626 for( const auto& kv : adjacency )
627 {
628 const wxString& start = kv.first;
629 if( seen.contains( start ) ) continue;
630
631 wxLogTrace( traceSchNetChain, " component BFS start '%s'", start );
632
633 std::vector<wxString> comp; std::queue<wxString> q; q.push( start ); seen.insert( start );
634 while( !q.empty() )
635 {
636 wxString cur = q.front(); q.pop(); comp.push_back( cur );
637
638 for( const BRIDGE_NEIGHBOR& e : adjacency.at( cur ) )
639 {
640 if( !seen.contains( e.other ) )
641 {
642 seen.insert( e.other );
643 q.push( e.other );
644 }
645 }
646 }
647
648 wxLogTrace( traceSchNetChain, " component size=%zu", comp.size() );
649
650 if( comp.size() <= 4 ) continue;
651 std::map<wxString,int> degree;
652 for( const wxString& n : comp ) degree[n] = (int) adjacency.at( n ).size();
653 std::vector<wxString> candidates;
654 for( const wxString& n : comp )
655 {
656 const auto& nbrs = adjacency.at( n );
657 if( nbrs.size() == 1 )
658 {
659 const wxString neigh = nbrs[0].other;
660 if( degree.count( neigh ) && degree[neigh] > 2 ) candidates.push_back( n );
661 }
662 }
663
664 wxLogTrace( traceSchNetChain, " candidates=%zu", candidates.size() );
665
666 if( candidates.empty() ) continue;
667 std::sort( candidates.begin(), candidates.end(),
668 []( const wxString& a, const wxString& b )
669 {
670 return a.CmpNoCase( b ) < 0;
671 } );
672 size_t needPrune = comp.size() - 4; if( needPrune > candidates.size() ) needPrune = candidates.size();
673
674 wxLogTrace( traceSchNetChain, " pruning need=%zu", needPrune );
675
676 for( size_t i = 0; i < needPrune; ++i ) globalPrune.insert( candidates[i] );
677 }
678 if( !globalPrune.empty() )
679 {
680 std::map<wxString,std::vector<BRIDGE_NEIGHBOR>> newAdj;
681 for( const auto& kv2 : adjacency )
682 {
683 if( globalPrune.contains( kv2.first ) ) continue;
684 for( const BRIDGE_NEIGHBOR& e : kv2.second )
685 {
686 if( globalPrune.contains( e.other ) ) continue;
687 newAdj[kv2.first].push_back( e );
688 }
689 }
690 adjacency.swap( newAdj );
691 wxLogTrace( traceSchNetChain, "RebuildNetChains: pruned %zu targeted stub nets", globalPrune.size() );
692 }
693 }
694
695 // ---------- Small helpers ----------
696 auto neighbors_of = [&]( const wxString& n ) -> const std::vector<BRIDGE_NEIGHBOR>*
697 {
698 if( auto it = adjacency.find(n); it != adjacency.end() ) return &it->second;
699 return nullptr;
700 };
701
702 // Structural filtering already done by excluding edges; isolated power nets are implicitly ignored.
703 m_potentialNetChains.clear();
704
705 // Recompute nets list after filtering
706 std::set<wxString> netsAll;
707 for( const auto& kv : adjacency ) netsAll.insert( kv.first );
708
709 // Connected component extraction over filtered adjacency (all remaining nets are non-power)
710 std::set<wxString> visited;
711 for( const wxString& start : netsAll )
712 {
713 if( visited.contains( start ) ) continue;
714 std::queue<wxString> q; q.push( start );
715 std::set<wxString> comp; comp.insert( start ); visited.insert( start );
716 while( !q.empty() )
717 {
718 wxString cur = q.front(); q.pop();
719 if( auto nbrs = neighbors_of( cur ) )
720 {
721 for( const BRIDGE_NEIGHBOR& e : *nbrs )
722 {
723 if( visited.contains( e.other ) ) continue;
724 visited.insert( e.other );
725 comp.insert( e.other );
726 q.push( e.other );
727 }
728 }
729 }
730 if( comp.size() >= 2 )
731 {
732 auto sig = std::make_unique<SCH_NETCHAIN>();
733 for( const wxString& n : comp ) sig->AddNet( n );
734 m_potentialNetChains.push_back( std::move( sig ) );
735 }
736 }
737 // Build netToNetChain map for potential net chains
738 netToNetChain.reserve( adjacency.size() );
739 for( const auto& sigUP : m_potentialNetChains )
740 if( sigUP ) for( const wxString& n : sigUP->GetNets() ) netToNetChain[n] = sigUP.get();
741
742 for( const BRIDGE_EDGE& edge : bridgeEdges )
743 {
744 const auto first = netToNetChain.find( edge.a );
745 const auto second = netToNetChain.find( edge.b );
746
747 if( first != netToNetChain.end() && second != netToNetChain.end()
748 && first->second == second->second && edge.sym )
749 {
750 first->second->AddSymbol( edge.sym );
751 }
752 }
753
754 m_bridgeEdges = std::move( bridgeEdges );
755
756 if( trace )
757 {
758 wxLogTrace( traceSchNetChain, "RebuildNetChains: pre-label potentialNetChains=%zu",
759 m_potentialNetChains.size() );
760
761 for( const auto& sigUP : m_potentialNetChains )
762 {
763 if( !sigUP )
764 continue;
765
766 wxString netsStr;
767 int count = 0;
768
769 for( const wxString& n : sigUP->GetNets() )
770 {
771 if( count < 32 )
772 {
773 netsStr += n;
774 netsStr += wxS( " " );
775 }
776 else
777 {
778 netsStr += wxS( "..." );
779 break;
780 }
781
782 ++count;
783 }
784
785 wxLogTrace( traceSchNetChain, " chain %p name='%s' nets=%zu [%s]", (void*) sigUP.get(),
786 sigUP->GetName(), sigUP->GetNets().size(), netsStr );
787 }
788 }
789
790
791 // Names already in use by committed chains. A plain SCH_LABEL whose text matches a
792 // committed chain's name must NOT steal that name from the committed chain; the
793 // downstream restore pass uses these names as keys and would skip the potential
794 // chain entirely on collision, silently losing it.
795 std::set<wxString> committedNames;
796
797 for( const auto& chain : m_committedNetChains )
798 {
799 if( chain )
800 committedNames.insert( chain->GetName() );
801 }
802
803 for( const auto& sheet : aConnectivity.sheets )
804 {
805 SCH_SCREEN* screen = sheet.path.LastScreen();
806
807 if( !screen )
808 continue;
809
810 for( SCH_ITEM* item : screen->Items().OfType( SCH_LABEL_T ) )
811 {
812 const auto* connection = sheet.Find( item );
813
814 if( !connection )
815 continue;
816
817 const SCH_TEXT* label = static_cast<const SCH_TEXT*>( item );
818 const wxString& net = connection->name;
819
820 // Defensive: guard against pathological names
821 if( !net.IsEmpty() && net.Length() < 2048 && netToNetChain.count( net ) )
822 {
823 wxString name = label->GetText();
824
825 if( name.Length() > 512 )
826 name.Truncate( 512 );
827
828 if( name.StartsWith( wxS( "/" ) ) )
829 name = name.Mid( 1 );
830
831 // Skip if a committed chain already owns this name; let the terminal-ref /
832 // saved-net-name restore logic below resolve the committed chain on its own.
833 SCH_NETCHAIN* chain = netToNetChain[net];
834
835 if( !committedNames.contains( name )
836 && ( chain->GetName().IsEmpty() || name < chain->GetName() ) )
837 {
838 chain->SetName( name );
839 }
840 }
841 }
842 }
843
844 int idx = 1;
845
846 wxLogTrace( traceSchNetChain, "RebuildNetChains: pass 3 (default naming)" );
847 for( std::unique_ptr<SCH_NETCHAIN>& sig : m_potentialNetChains )
848 {
849 if( sig->GetName().IsEmpty() )
850 {
851 sig->SetName( wxString::Format( wxT( "NetChain%d" ), idx ) );
852 idx++;
853 }
854 }
855
856 wxLogTrace( traceSchNetChain, "RebuildNetChains: pass 4 (terminal pins)" );
857 struct PIN_INFO
858 {
859 SCH_PIN* pin;
860 SCH_SYMBOL* sym;
861 const SCH_SHEET_PATH* sheet;
862 VECTOR2I position;
863 };
864 std::map<SCH_NETCHAIN*, std::vector<PIN_INFO>> chainPins;
865
866 if( !m_potentialNetChains.empty() )
867 {
868 for( const SHEET_SYMBOLS& view : sheetSymbols )
869 {
870 const auto& sheet = *view.input;
871 const SCH_SHEET_PATH& sheetPath = sheet.path;
872
873 for( const auto& [sym, pins] : view.symbols )
874 {
875 for( SCH_PIN* p : pins )
876 {
877 const auto chain = netToNetChain.find( sheet.Key( p ) );
878
879 if( chain != netToNetChain.end() )
880 chainPins[chain->second].push_back( { p, sym, &sheetPath, p->GetPosition() } );
881 }
882 }
883 }
884 }
885
886 for( std::unique_ptr<SCH_NETCHAIN>& sig : m_potentialNetChains )
887 {
888 // Preserve sheet/item/pin traversal order when equally distant terminals compete.
889 const auto& pins = chainPins[sig.get()];
890
891 int64_t best = -1;
892 KIID a, b;
893 size_t bestI = 0, bestJ = 0;
894
895 for( size_t i = 0; i < pins.size(); ++i )
896 {
897 for( size_t j = i + 1; j < pins.size(); ++j )
898 {
899 VECTOR2I pa = pins[i].position;
900 VECTOR2I pb = pins[j].position;
901 int64_t dx = pa.x - pb.x;
902 int64_t dy = pa.y - pb.y;
903 int64_t d = dx * dx + dy * dy;
904
905 if( d > best )
906 {
907 best = d;
908 a = pins[i].pin->m_Uuid;
909 b = pins[j].pin->m_Uuid;
910 bestI = i;
911 bestJ = j;
912 }
913 }
914 }
915
916 sig->SetTerminalPins( a, b );
917
918 if( best >= 0 && bestI < pins.size() && bestJ < pins.size() )
919 {
920 sig->SetTerminalPaths( pins[bestI].sheet->Path(), pins[bestJ].sheet->Path() );
921 sig->SetTerminalRefs( pins[bestI].sym->GetRef( pins[bestI].sheet ), pins[bestI].pin->GetNumber(),
922 pins[bestJ].sym->GetRef( pins[bestJ].sheet ), pins[bestJ].pin->GetNumber() );
923 }
924 }
925
926 wxLogTrace( traceSchNetChain, "RebuildNetChains: pass 5 (apply symbol names)" );
927 for( auto& sigUP : m_potentialNetChains )
928 {
929 SCH_NETCHAIN* sig = sigUP.get();
930 for( SCH_SYMBOL* sym : sig->GetSymbols() )
931 {
932 if( sym )
933 setSymbolName( sym, sig->GetName() );
934 }
935
936 if( trace )
937 {
938 wxString netsStr;
939
940 for( const wxString& n : sig->GetNets() )
941 netsStr += n + wxS( " " );
942
943 wxLogTrace( traceSchNetChain, "FinalChain %p nets(%zu): %s", (void*) sig,
944 sig->GetNets().size(), netsStr );
945 }
946 }
947
948 wxLogTrace( traceSchNetChain, "RebuildNetChains: built %zu potential net chains", m_potentialNetChains.size() );
949
950 // Restore committed chains from file.
951 // Priority 1: match by terminal ref+pin (survives net renames)
952 // Priority 2: match by saved net names (survives component renames)
953 {
954 std::set<wxString> alreadyCommitted;
955
956 for( const auto& chain : m_committedNetChains )
957 {
958 if( chain )
959 alreadyCommitted.insert( chain->GetName() );
960 }
961
962 // Build ref+pin → net lookup from current schematic
963 std::map<std::pair<wxString, wxString>, wxString> refPinToNet;
964
965 if( !m_netChainTerminalRefOverrides.empty() )
966 {
967 for( const SHEET_SYMBOLS& view : sheetSymbols )
968 {
969 const auto& sheet = *view.input;
970
971 for( const auto& [sym, pins] : view.symbols )
972 {
973 const wxString ref = sym->GetRef( &sheet.path );
974
975 for( SCH_PIN* pin : pins )
976 {
977 if( const auto* net = sheet.Find( pin ) )
978 refPinToNet[{ ref, pin->GetNumber() }] = net->key;
979 }
980 }
981 }
982 }
983
984 // O(1) lookup of committed chains by name so the restore passes don't linearly
985 // scan m_committedNetChains for every override entry.
986 std::unordered_map<wxString, SCH_NETCHAIN*> committedByName;
987
988 for( const auto& chain : m_committedNetChains )
989 {
990 if( chain )
991 committedByName[chain->GetName()] = chain.get();
992 }
993
994 // Names refreshed in pass 2a so pass 2b (manual fallback) doesn't overwrite the
995 // potential-based payload with its broader member-net symbol collection.
996 std::set<wxString> refreshedThisPass;
997
998 for( const auto& [chainName, termRefs] : m_netChainTerminalRefOverrides )
999 {
1000 if( unresolvedTerminals.contains( chainName ) )
1001 continue;
1002
1003 SCH_NETCHAIN* match = nullptr;
1004 const auto committed = committedByName.find( chainName );
1005
1006 if( committed != committedByName.end() )
1007 {
1008 const SCH_NETCHAIN& chain = *committed->second;
1009 wxString keys[2];
1010
1011 for( int endpoint = 0; endpoint < 2; ++endpoint )
1012 {
1013 const KIID& id = endpoint == 0 ? chain.GetTerminalPinA() : chain.GetTerminalPinB();
1014
1015 for( const auto& sheet : aConnectivity.sheets )
1016 {
1017 SCH_SCREEN* screen = sheet.path.LastScreen();
1018
1019 if( screen && sheet.path.PathRef() == chain.GetTerminalPath( endpoint ) )
1020 {
1021 keys[endpoint] = sheet.Key( screen->GetConnectivityItem( id ) );
1022 break;
1023 }
1024 }
1025 }
1026
1027 match = findPotentialChain( m_potentialNetChains, keys[0], keys[1] );
1028 }
1029 else
1030 {
1031 match = resolvePotentialChainByTerminals( termRefs, refPinToNet,
1032 m_potentialNetChains, chainName );
1033 }
1034
1035 if( !match )
1036 continue;
1037
1038 if( alreadyCommitted.count( chainName ) )
1039 {
1040 auto it = committedByName.find( chainName );
1041
1042 if( it != committedByName.end() && it->second )
1043 {
1044 refreshCommittedChainFromPotential( it->second, *match );
1045 refreshedThisPass.insert( chainName );
1046 }
1047
1048 continue;
1049 }
1050
1051 if( CreateNetChainFromPotential( match, chainName ) )
1052 {
1053 alreadyCommitted.insert( chainName );
1054 refreshedThisPass.insert( chainName );
1055 }
1056 }
1057
1058 // Manual chains have no inferred potential; rebuild from the persisted
1059 // member-net list by collecting symbols whose pins land on those nets.
1060 for( const auto& [chainName, memberNets] : m_netChainMemberNetOverrides )
1061 {
1062 if( memberNets.empty() || unresolvedTerminals.contains( chainName ) )
1063 continue;
1064
1065 // Skip chains pass 2a already refreshed; the potential's symbol set is more
1066 // precise than the broad member-net match collected here.
1067 if( alreadyCommitted.count( chainName ) && refreshedThisPass.count( chainName ) )
1068 continue;
1069
1070 auto termIt = m_netChainTerminalRefOverrides.find( chainName );
1071
1072 if( termIt == m_netChainTerminalRefOverrides.end() )
1073 continue;
1074
1075 const CHAIN_TERMINAL_REFS& termRefs = termIt->second;
1076
1077 SCH_PIN* terminalPinA = nullptr;
1078 SCH_PIN* terminalPinB = nullptr;
1079 std::set<SCH_SYMBOL*> symbols;
1080
1081 for( const SHEET_SYMBOLS& view : sheetSymbols )
1082 {
1083 const auto& sheet = *view.input;
1084
1085 for( const auto& [sym, pins] : view.symbols )
1086 {
1087 const wxString ref = sym->GetRef( &sheet.path );
1088 bool symContributes = false;
1089
1090 for( SCH_PIN* pin : pins )
1091 {
1092 const auto* net = sheet.Find( pin );
1093
1094 if( !net )
1095 continue;
1096
1097 if( memberNets.count( net->name ) )
1098 symContributes = true;
1099
1100 if( ref == termRefs.first.ref && pin->GetNumber() == termRefs.first.pin )
1101 terminalPinA = pin;
1102
1103 if( ref == termRefs.second.ref && pin->GetNumber() == termRefs.second.pin )
1104 terminalPinB = pin;
1105 }
1106
1107 if( symContributes )
1108 symbols.insert( sym );
1109 }
1110 }
1111
1112 if( !terminalPinA || !terminalPinB || symbols.empty() )
1113 {
1114 wxLogTrace( traceSchNetChain,
1115 "RebuildNetChains: cannot restore manual chain '%s' "
1116 "(terminals or member nets unresolved)",
1117 chainName );
1118 continue;
1119 }
1120
1121 if( alreadyCommitted.count( chainName ) )
1122 {
1123 auto it = committedByName.find( chainName );
1124
1125 if( it != committedByName.end() && it->second )
1126 {
1127 refreshCommittedChainPayload( it->second, memberNets, symbols );
1128 }
1129
1130 continue;
1131 }
1132
1133 CreateManualNetChain( chainName, symbols, memberNets, terminalPinA->m_Uuid,
1134 terminalPinB->m_Uuid, termRefs.first.ref, termRefs.first.pin,
1135 termRefs.second.ref, termRefs.second.pin );
1136 alreadyCommitted.insert( chainName );
1137 }
1138 }
1139
1140 // Committed chain names take priority over potential chain names set by pass 5.
1141 for( const auto& chain : m_committedNetChains )
1142 {
1143 if( chain )
1144 {
1145 for( SCH_SYMBOL* sym : chain->GetSymbols() )
1146 {
1147 if( sym )
1148 setSymbolName( sym, chain->GetName() );
1149 }
1150 }
1151 }
1152}
1153
1154
1156 const std::vector<std::unique_ptr<SCH_NETCHAIN>>& aPotentials, const wxString& aNetA, const wxString& aNetB )
1157{
1158 for( const auto& potential : aPotentials )
1159 {
1160 if( potential && potential->GetNets().contains( aNetA ) && potential->GetNets().contains( aNetB ) )
1161 return potential.get();
1162 }
1163
1164 return nullptr;
1165}
1166
1167
1169 const CHAIN_TERMINAL_REFS& aTermRefs, const std::map<std::pair<wxString, wxString>, wxString>& aRefPinToNet,
1170 const std::vector<std::unique_ptr<SCH_NETCHAIN>>& aPotentials, const wxString& aChainName )
1171{
1172 auto itFrom = aRefPinToNet.find( { aTermRefs.first.ref, aTermRefs.first.pin } );
1173 auto itTo = aRefPinToNet.find( { aTermRefs.second.ref, aTermRefs.second.pin } );
1174
1175 if( itFrom == aRefPinToNet.end() || itTo == aRefPinToNet.end() )
1176 {
1177 wxLogTrace( traceSchNetChain, "RebuildNetChains: cannot restore chain '%s' (terminal %s.%s/%s.%s unresolved)",
1178 aChainName, aTermRefs.first.ref, aTermRefs.first.pin, aTermRefs.second.ref, aTermRefs.second.pin );
1179 return nullptr;
1180 }
1181
1182 if( SCH_NETCHAIN* match = findPotentialChain( aPotentials, itFrom->second, itTo->second ) )
1183 return match;
1184
1185 wxLogTrace( traceSchNetChain, "RebuildNetChains: no potential chain spans both terminals of '%s' (%s/%s)",
1186 aChainName, itFrom->second, itTo->second );
1187 return nullptr;
1188}
1189
1190
1192{
1193 if( aName.IsEmpty() )
1194 return false;
1195
1196 auto it = std::find_if( m_committedNetChains.begin(), m_committedNetChains.end(),
1197 [&]( const std::unique_ptr<SCH_NETCHAIN>& aChain )
1198 {
1199 return aChain && aChain->GetName() == aName;
1200 } );
1201
1202 if( it == m_committedNetChains.end() )
1203 return false;
1204
1205 // Otherwise RebuildNetChains() re-promotes these symbols under the deleted name
1206 for( SCH_SYMBOL* sym : ( *it )->GetSymbols() )
1207 {
1208 if( sym )
1209 setSymbolName( sym, wxEmptyString );
1210 }
1211
1212 m_committedNetChains.erase( it );
1213
1214 m_netChainNetClassOverrides.erase( aName );
1215 m_netChainColorOverrides.erase( aName );
1216 m_netChainTerminalRefOverrides.erase( aName );
1217 m_netChainMemberNetOverrides.erase( aName );
1218
1219 if( std::shared_ptr<NET_SETTINGS> netSettings = liveNetSettings() )
1220 netSettings->SetNetChainClass( aName, wxEmptyString );
1221
1222 return true;
1223}
1224
1225
1226bool SCH_CONNECTIVITY::NETCHAIN_MANAGER::RenameCommittedNetChain( const wxString& aOld, const wxString& aNew )
1227{
1228 if( aOld.IsEmpty() || !SCH_NETCHAIN::IsValidName( aNew ) || aOld == aNew )
1229 return false;
1230
1231 auto findByName = [&]( const wxString& aName ) -> SCH_NETCHAIN*
1232 {
1233 for( const std::unique_ptr<SCH_NETCHAIN>& chain : m_committedNetChains )
1234 {
1235 if( chain && chain->GetName() == aName )
1236 return chain.get();
1237 }
1238
1239 return nullptr;
1240 };
1241
1242 SCH_NETCHAIN* existing = findByName( aOld );
1243
1244 if( !existing )
1245 return false;
1246
1247 if( findByName( aNew ) )
1248 return false;
1249
1250 existing->SetName( aNew );
1251
1252 for( SCH_SYMBOL* sym : existing->GetSymbols() )
1253 {
1254 if( sym )
1255 setSymbolName( sym, aNew );
1256 }
1257
1258 rekeyOverrideMaps( aOld, aNew );
1259
1260 if( std::shared_ptr<NET_SETTINGS> netSettings = liveNetSettings() )
1261 {
1262 const wxString chainClass = netSettings->GetNetChainClass( aOld );
1263
1264 if( !chainClass.IsEmpty() )
1265 {
1266 netSettings->SetNetChainClass( aOld, wxEmptyString );
1267 netSettings->SetNetChainClass( aNew, chainClass );
1268 }
1269 }
1270
1271 return true;
1272}
1273
1274
1275void SCH_CONNECTIVITY::NETCHAIN_MANAGER::rekeyOverrideMaps( const wxString& aOld, const wxString& aNew )
1276{
1277 if( aOld == aNew )
1278 return;
1279
1280 auto rekey = [&]( auto& aMap )
1281 {
1282 auto it = aMap.find( aOld );
1283
1284 if( it != aMap.end() )
1285 {
1286 auto val = std::move( it->second );
1287 aMap.erase( it );
1288 aMap[aNew] = std::move( val );
1289 }
1290 };
1291
1293 rekey( m_netChainColorOverrides );
1296}
1297
1298
1300 SCH_NETCHAIN* aTarget, const std::set<wxString>& aNets,
1301 const std::set<SCH_SYMBOL*>& aSymbols )
1302{
1303 if( !aTarget )
1304 return;
1305
1306 std::set<wxString> filtered;
1307
1308 for( const wxString& net : aNets )
1309 {
1310 if( !net.IsEmpty() )
1311 filtered.insert( net );
1312 }
1313
1314 aTarget->ReplaceNets( filtered );
1315
1316 aTarget->ClearSymbols();
1317
1318 for( SCH_SYMBOL* sym : aSymbols )
1319 aTarget->AddSymbol( sym );
1320
1321 for( SCH_SYMBOL* sym : aTarget->GetSymbols() )
1322 setSymbolName( sym, aTarget->GetName() );
1323}
1324
1325
1327 const SCH_NETCHAIN& aSource )
1328{
1329 refreshCommittedChainPayload( aTarget, aSource.GetNets(), aSource.GetSymbols() );
1330
1331 // Keep the fallback used when terminal-based inference stops resolving in sync with renames.
1332 storeMemberNets( aTarget->GetName(), aSource.GetNets() );
1333}
1334
1335
1337 const wxString& aName )
1338{
1339 if( !aPotential )
1340 return nullptr;
1341 auto sig = std::make_unique<SCH_NETCHAIN>();
1342 for( const wxString& n : aPotential->GetNets() )
1343 sig->AddNet( n );
1344 for( SCH_SYMBOL* sym : aPotential->GetSymbols() )
1345 sig->AddSymbol( sym );
1346 sig->SetName( aName );
1347 sig->SetTerminalPins( aPotential->GetTerminalPinA(), aPotential->GetTerminalPinB() );
1348 sig->SetTerminalRefs( aPotential->GetTerminalRef( 0 ), aPotential->GetTerminalPinNum( 0 ),
1349 aPotential->GetTerminalRef( 1 ), aPotential->GetTerminalPinNum( 1 ) );
1350
1351 sig->SetTerminalPaths( aPotential->GetTerminalPath( 0 ), aPotential->GetTerminalPath( 1 ) );
1352
1353 if( auto saved = m_netChainTerminalRefOverrides.find( aName ); saved != m_netChainTerminalRefOverrides.end() )
1354 {
1355 if( !resolveTerminals( *sig, &saved->second ) )
1356 return nullptr;
1357 }
1358
1359 // Apply any parsed netclass override for this chain name.
1360 auto ncIt = m_netChainNetClassOverrides.find( aName );
1361
1362 if( ncIt != m_netChainNetClassOverrides.end() )
1363 sig->SetNetClass( ncIt->second );
1364
1365 auto colIt = m_netChainColorOverrides.find( aName );
1366
1367 if( colIt != m_netChainColorOverrides.end() )
1368 sig->SetColor( colIt->second );
1369
1370 for( SCH_SYMBOL* sym : sig->GetSymbols() )
1371 setSymbolName( sym, sig->GetName() );
1372
1373 // Register terminal refs in the override map so a subsequent unconditional Recalculate
1374 // (which calls Reset() and clears the chain's symbol list) can find this chain in the
1375 // restore pass and refresh it in place. Runtime-created chains otherwise live only in
1376 // m_committedNetChains and would be missed by the override-driven restore loop.
1377 storeTerminalRefs( *sig );
1378
1379 // Restore fallback if the topology shifts, filtered to match what the s-expr writer saves
1380 storeMemberNets( aName, sig->GetNets() );
1381
1382 SCH_NETCHAIN* raw = sig.get();
1383 m_committedNetChains.push_back( std::move( sig ) );
1384 return raw;
1385}
1386
1387
1389 const std::set<SCH_SYMBOL*>& aSymbols,
1390 const std::set<wxString>& aNets,
1391 const KIID& aTerminalPinA,
1392 const KIID& aTerminalPinB,
1393 const wxString& aRefA,
1394 const wxString& aPinNumA,
1395 const wxString& aRefB,
1396 const wxString& aPinNumB )
1397{
1398 if( !SCH_NETCHAIN::IsValidName( aName ) )
1399 return nullptr;
1400
1401 if( GetNetChainByName( aName ) )
1402 return nullptr;
1403
1404 // GetNetChainForNet returns the first match, so a net may belong to only one chain
1405 for( const wxString& net : aNets )
1406 {
1407 if( net.IsEmpty() )
1408 continue;
1409
1410 if( GetNetChainForNet( net ) )
1411 return nullptr;
1412 }
1413
1414 auto sig = std::make_unique<SCH_NETCHAIN>();
1415 sig->SetName( aName );
1416
1417 for( const wxString& net : aNets )
1418 {
1419 if( net.IsEmpty() )
1420 continue;
1421
1422 sig->AddNet( net );
1423 }
1424
1425 for( SCH_SYMBOL* sym : aSymbols )
1426 sig->AddSymbol( sym );
1427
1428 sig->SetTerminalPins( aTerminalPinA, aTerminalPinB );
1429 sig->SetTerminalRefs( aRefA, aPinNumA, aRefB, aPinNumB );
1430 const CHAIN_TERMINAL_REFS savedRefs{ { aRefA, aPinNumA }, { aRefB, aPinNumB } };
1431
1432 if( !resolveTerminals( *sig, &savedRefs ) )
1433 return nullptr;
1434
1435 auto ncIt = m_netChainNetClassOverrides.find( aName );
1436
1437 if( ncIt != m_netChainNetClassOverrides.end() )
1438 sig->SetNetClass( ncIt->second );
1439
1440 auto colIt = m_netChainColorOverrides.find( aName );
1441
1442 if( colIt != m_netChainColorOverrides.end() )
1443 sig->SetColor( colIt->second );
1444
1445 for( SCH_SYMBOL* sym : sig->GetSymbols() )
1446 setSymbolName( sym, sig->GetName() );
1447
1448 // The restore pass after an unconditional Recalculate finds chains only through these maps
1449 storeTerminalRefs( *sig );
1450 m_netChainMemberNetOverrides[aName] = sig->GetNets();
1451
1452 SCH_NETCHAIN* raw = sig.get();
1453 m_committedNetChains.push_back( std::move( sig ) );
1454 return raw;
1455}
1456
1457
1459{
1460 wxLogTrace( traceSchNetChain, "SCH_CONNECTIVITY::NETCHAIN_MANAGER::GetNetChainForNet(%s)", aNet );
1461 for( std::unique_ptr<SCH_NETCHAIN>& sig : m_committedNetChains )
1462 {
1463 if( !sig )
1464 continue;
1465
1466 if( sig->GetNets().count( aNet ) )
1467 {
1468 wxLogTrace( traceSchNetChain, "GetNetChainForNet: found chain '%s'", sig->GetName() );
1469 return sig.get();
1470 }
1471 }
1472
1473 wxLogTrace( traceSchNetChain, "GetNetChainForNet: no chain found" );
1474 return nullptr;
1475}
1476
1477
1478std::shared_ptr<NET_SETTINGS> SCH_CONNECTIVITY::NETCHAIN_MANAGER::liveNetSettings() const
1479{
1480 // Staged and temporary graphs must not publish project netclass assignments
1481 if( !m_schematic || this != &m_schematic->NetChains() )
1482 return nullptr;
1483
1484 return m_schematic->Project().GetProjectFile().NetSettings();
1485}
1486
1487
1489{
1490 std::shared_ptr<NET_SETTINGS> netSettings = liveNetSettings();
1491
1492 if( !netSettings )
1493 return;
1494
1495 bool anyOverride = std::any_of( m_committedNetChains.begin(), m_committedNetChains.end(),
1496 []( const std::unique_ptr<SCH_NETCHAIN>& aChain )
1497 {
1498 return aChain && !aChain->GetNetClass().IsEmpty();
1499 } );
1500
1501 // Leave the effective-netclass cache alone on chainless rebuilds
1502 if( !anyOverride && !netSettings->HasChainPatternAssignments( NET_CHAIN_SOURCE::SCHEMATIC ) )
1503 return;
1504
1505 netSettings->ClearChainPatternAssignments( NET_CHAIN_SOURCE::SCHEMATIC );
1506
1507 for( const std::unique_ptr<SCH_NETCHAIN>& chain : m_committedNetChains )
1508 {
1509 if( !chain )
1510 continue;
1511
1512 const wxString& netclass = chain->GetNetClass();
1513
1514 if( netclass.IsEmpty() || !netSettings->HasNetclass( netclass ) )
1515 continue;
1516
1517 for( const wxString& net : chain->GetNets() )
1518 {
1519 // Synthetic per-run keys embed a subgraph code and never match a resolved net name
1520 if( net.StartsWith( SCH_NETCHAIN::SYNTHETIC_NET_PREFIX ) )
1521 continue;
1522
1523 netSettings->SetChainPatternAssignment( NET_CHAIN_SOURCE::SCHEMATIC, net, netclass );
1524 }
1525 }
1526}
1527
1528
1530{
1531 wxLogTrace( traceSchNetChain, "SCH_CONNECTIVITY::NETCHAIN_MANAGER::GetNetChainByName(%s)", aName );
1532 for( std::unique_ptr<SCH_NETCHAIN>& sig : m_committedNetChains )
1533 {
1534 if( !sig )
1535 continue;
1536
1537 if( sig->GetName() == aName )
1538 {
1539 wxLogTrace( traceSchNetChain, "GetNetChainByName: found" );
1540 return sig.get();
1541 }
1542 }
1543
1544 wxLogTrace( traceSchNetChain, "GetNetChainByName: not found" );
1545 return nullptr;
1546}
1547
1548
1550{
1551 if( ADVANCED_CFG::GetCfg().m_ConnectivityEngine )
1552 {
1553 if( SCHEMATIC* schematic = aItem.Schematic() )
1554 {
1555 const auto net = schematic->Connectivity().GetSubgraphForItem( aItem.m_Uuid, aPath.Path() );
1556 return net ? SCH_NETCHAIN::MakeKey( net->Name(), net->Component() ) : wxString();
1557 }
1558 }
1559
1560 const SCH_CONNECTION* net = aItem.Connection( &aPath );
1561 return net ? SCH_NETCHAIN::MakeKey( net->Name(), net->SubgraphCode() ) : wxString();
1562}
1563
1564
1565std::map<SCH_SYMBOL*, SCH_SCREEN*>
1566SCH_CONNECTIVITY::NETCHAIN_MANAGER::GetBridgeSymbols( const SCH_NETCHAIN& aChain, const wxString& aNetKey ) const
1567{
1568 std::map<SCH_SYMBOL*, SCH_SCREEN*> bridges;
1569
1570 if( !aChain.GetNets().contains( aNetKey ) )
1571 return bridges;
1572
1573 for( const BRIDGE_EDGE& edge : m_bridgeEdges )
1574 {
1575 if( edge.a != aNetKey && edge.b != aNetKey )
1576 continue;
1577
1578 const wxString& other = edge.a == aNetKey ? edge.b : edge.a;
1579
1580 if( aChain.GetNets().contains( other ) )
1581 bridges.emplace( edge.sym, edge.screen );
1582 }
1583
1584 return bridges;
1585}
1586
1587
1589{
1590 if( !m_schematic || aPin == niluuid || aSheet.empty() )
1591 return {};
1592
1593 const std::optional<SCH_SHEET_PATH> path = m_schematic->Hierarchy().GetSheetPathByKIIDPath( aSheet );
1594
1595 if( !path || !path->LastScreen() )
1596 return {};
1597
1598 auto* pin = dynamic_cast<SCH_PIN*>( path->LastScreen()->GetConnectivityItem( aPin ) );
1599 return pin ? NetKeyForItem( *pin, *path ) : wxString();
1600}
1601
1602
1604{
1606
1607 if( !chain || aChange.endpoint < 0 || aChange.endpoint > 1 )
1608 return false;
1609
1610 // Chains never hold an empty key, so an unresolvable terminal fails here too
1611 if( !chain->GetNets().contains( NetKeyForTerminal( aChange.pin, aChange.sheet ) ) )
1612 return false;
1613
1614 SCH_NETCHAIN candidate = *chain;
1615 candidate.SetTerminalPins( aChange.endpoint == 0 ? aChange.pin : chain->GetTerminalPinA(),
1616 aChange.endpoint == 1 ? aChange.pin : chain->GetTerminalPinB() );
1617 candidate.SetTerminalPaths( aChange.endpoint == 0 ? aChange.sheet : chain->GetTerminalPath( 0 ),
1618 aChange.endpoint == 1 ? aChange.sheet : chain->GetTerminalPath( 1 ) );
1619
1620 if( candidate.GetTerminalPinA() == candidate.GetTerminalPinB()
1621 && candidate.GetTerminalPath( 0 ) == candidate.GetTerminalPath( 1 ) )
1622 return false;
1623
1624 if( !resolveTerminals( candidate ) )
1625 return false;
1626
1627 *chain = std::move( candidate );
1629 return true;
1630}
1631
1632
1634 SCH_PIN* aPinA, const SCH_SHEET_PATH& aPathA, SCH_PIN* aPinB, const SCH_SHEET_PATH& aPathB )
1635{
1636 if( !aPinA || !aPinB )
1637 return nullptr;
1638
1639 const wxString netA = NetKeyForItem( *aPinA, aPathA );
1640 const wxString netB = NetKeyForItem( *aPinB, aPathB );
1641
1642 if( netA.IsEmpty() || netB.IsEmpty() )
1643 return nullptr;
1644
1645 return findPotentialChain( m_potentialNetChains, netA, netB );
1646}
const char * name
static const ADVANCED_CFG & GetCfg()
Get the singleton instance's config, which is shared by all consumers.
const KIID m_Uuid
Definition eda_item.h:599
virtual const wxString & GetText() const
Return the string associated with the text object.
Definition eda_text.h:118
EE_TYPE Overlapping(const BOX2I &aRect) const
Definition sch_rtree.h:253
EE_TYPE OfType(KICAD_T aType) const
Definition sch_rtree.h:248
Definition kiid.h:46
Holds all the data relating to one schematic.
Definition schematic.h:149
Each graphical item can have a SCH_CONNECTION describing its logical connection (to a bus or net).
int SubgraphCode() const
wxString Name(bool aIgnoreSheet=false) const
void refreshCommittedChainPayload(SCH_NETCHAIN *aTarget, const std::set< wxString > &aNets, const std::set< class SCH_SYMBOL * > &aSymbols)
Replace the derived-view payload on aTarget with explicitly supplied member nets, and symbols.
std::map< wxString, wxString > m_netChainNetClassOverrides
void rekeyOverrideMaps(const wxString &aOld, const wxString &aNew)
Move every net-chain override map entry keyed by aOld to aNew.
std::vector< std::unique_ptr< SCH_NETCHAIN > > m_potentialNetChains
last built potential (uncommitted) net chains
SCH_NETCHAIN * GetNetChainForNet(const wxString &aNet)
bool refreshTerminalReferences(SCH_NETCHAIN &aChain)
std::map< wxString, std::set< wxString > > m_netChainMemberNetOverrides
void storeMemberNets(const wxString &aName, const std::set< wxString > &aNets)
Record the persistable subset of aNets as the restore fallback for aName.
void storeTerminalRefs(const SCH_NETCHAIN &aChain)
SCH_NETCHAIN * GetNetChainByName(const wxString &aName)
static SCH_NETCHAIN * findPotentialChain(const std::vector< std::unique_ptr< SCH_NETCHAIN > > &aPotentials, const wxString &aNetA, const wxString &aNetB)
SCH_NETCHAIN * FindPotentialNetChainBetweenPins(SCH_PIN *aPinA, const SCH_SHEET_PATH &aPathA, SCH_PIN *aPinB, const SCH_SHEET_PATH &aPathB)
void setSymbolName(SCH_SYMBOL *aSymbol, const wxString &aName)
void rebuild(const NETCHAIN_INPUT &aConnectivity)
BRIDGE_GRAPH buildBridgeAdjacency(const std::vector< SHEET_SYMBOLS > &aSheets)
Build the bridge graph used for net-chain discovery.
bool ReplaceNetChainTerminalPin(const TERMINAL_CHANGE &aChange)
std::shared_ptr< NET_SETTINGS > liveNetSettings() const
Project net settings, or null for staged and temporary managers.
wxString NetKeyForTerminal(const KIID &aPin, const KIID_PATH &aSheet) const
Resolve a terminal pin UUID on a sheet instance to its chain member key.
std::map< wxString, CHAIN_TERMINAL_REFS > m_netChainTerminalRefOverrides
std::map< SCH_SYMBOL *, SCH_SCREEN * > GetBridgeSymbols(const SCH_NETCHAIN &aChain, const wxString &aNetKey) const
Symbols from the last rebuild whose passthrough joins aNetKey to another member of aChain,...
void Rebuild(const NETCHAIN_INPUT &aConnectivity, const std::function< void(NETCHAIN_MANAGER &)> &aBeforePublish={})
std::vector< std::unique_ptr< SCH_NETCHAIN > > m_committedNetChains
std::map< wxString, KIGFX::COLOR4D > m_netChainColorOverrides
void Merge(NETCHAIN_MANAGER &aOther)
static SCH_NETCHAIN * resolvePotentialChainByTerminals(const CHAIN_TERMINAL_REFS &aTermRefs, const std::map< std::pair< wxString, wxString >, wxString > &aRefPinToNet, const std::vector< std::unique_ptr< SCH_NETCHAIN > > &aPotentials, const wxString &aChainName)
Disambiguate the saved (refA.pinA, refB.pinB) terminal pair against the current set of potential net ...
bool DeleteCommittedNetChain(const wxString &aName)
Delete a committed net chain by name.
bool resolveTerminals(SCH_NETCHAIN &aChain, const CHAIN_TERMINAL_REFS *aSavedRefs=nullptr)
SCH_NETCHAIN * CreateManualNetChain(const wxString &aName, const std::set< class SCH_SYMBOL * > &aSymbols, const std::set< wxString > &aNets, const KIID &aTerminalPinA, const KIID &aTerminalPinB, const wxString &aRefA, const wxString &aPinNumA, const wxString &aRefB, const wxString &aPinNumB)
Commit a manually-defined net chain that the inferred-potential pass did not produce.
void refreshCommittedChainFromPotential(SCH_NETCHAIN *aTarget, const SCH_NETCHAIN &aSource)
Refresh aTarget from an inferred potential chain and resync its persisted member-net fallback.
std::vector< BRIDGE_EDGE > m_bridgeEdges
raw bridge edges from the last rebuild
std::pair< CHAIN_TERMINAL_REF, CHAIN_TERMINAL_REF > CHAIN_TERMINAL_REFS
static wxString NetKeyForItem(const SCH_ITEM &aItem, const SCH_SHEET_PATH &aPath)
SCH_NETCHAIN * CreateNetChainFromPotential(SCH_NETCHAIN *aPotential, const wxString &aName)
Promote a potential net chain to an actual user net chain with the provided name.
void ApplyNetChainNetclasses()
Mirror each committed net chain's netclass override into the project NET_SETTINGS as a chain-derived ...
std::unordered_map< SCH_SYMBOL *, wxString > * m_pendingSymbolNames
bool RenameCommittedNetChain(const wxString &aOld, const wxString &aNew)
Rename a committed net chain.
Base class for any item which can be embedded within the SCHEMATIC container class,...
Definition sch_item.h:170
const SYMBOL * GetParentSymbol() const
Definition sch_item.cpp:350
SCHEMATIC * Schematic() const
Search the item hierarchy to find a SCHEMATIC.
Definition sch_item.cpp:302
SCH_LAYER_ID GetLayer() const
Return the layer this item is on.
Definition sch_item.h:353
SCH_CONNECTION * Connection(const SCH_SHEET_PATH *aSheet=nullptr) const
Retrieve the connection associated with this object in the given sheet.
Definition sch_item.cpp:580
Segment description base class to describe items which have 2 end points (track, wire,...
Definition sch_line.h:39
VECTOR2I GetEndPoint() const
Definition sch_line.h:145
VECTOR2I GetStartPoint() const
Definition sch_line.h:136
A net chain is a collection of nets that are connected together through passive components.
const KIID & GetTerminalPinB() const
static wxString MakeKey(const wxString &aName, uint32_t aComponent)
const std::set< wxString > & GetNets() const
void AddSymbol(class SCH_SYMBOL *aSymbol)
const wxString & GetTerminalRef(int aIdx) const
const wxString & GetName() const
static bool IsValidName(const wxString &aName)
void SetTerminalPins(const KIID &aPinA, const KIID &aPinB)
void ClearSymbols()
void ReplaceNets(const std::set< wxString > &aNew)
const KIID_PATH & GetTerminalPath(int aIdx) const
const std::set< class SCH_SYMBOL * > & GetSymbols() const
static constexpr wxStringCharType SYNTHETIC_NET_PREFIX[]
Prefix used when synthesising net names for unnamed subgraphs.
void SetTerminalPaths(const KIID_PATH &aPathA, const KIID_PATH &aPathB)
static bool IsPersistableNet(const wxString &aNet)
Synthetic keys do not survive a reload, so only named nets are written out.
const wxString & GetTerminalPinNum(int aIdx) const
const KIID & GetTerminalPinA() const
void SetName(const wxString &aName)
void SetTerminalRefs(const wxString &aRefA, const wxString &aPinA, const wxString &aRefB, const wxString &aPinB)
VECTOR2I GetPosition() const override
Definition sch_pin.cpp:378
bool IsPower() const
Check if the pin is either a global or local power pin.
Definition sch_pin.cpp:507
EE_RTREE & Items()
Get the full RTree, usually for iterating.
Definition sch_screen.h:122
SCH_ITEM * GetConnectivityItem(const KIID &aId) const
Resolve a drawing item or a connectable child on this screen; ambiguous IDs return null.
A container for handling SCH_SHEET_PATH objects in a flattened hierarchy.
Handle access to a stack of flattened SCH_SHEET objects by way of a path for creating a flattened sch...
KIID_PATH Path() const
Get the sheet path as an KIID_PATH.
Schematic symbol object.
Definition sch_symbol.h:73
std::vector< const SCH_PIN * > GetPins(const SCH_SHEET_PATH *aSheet) const
Retrieve a list of the SCH_PINs for the given sheet path.
void SetNetChainName(wxString aName) noexcept
Definition sch_symbol.h:866
virtual bool IsPower() const =0
const wxChar *const traceSchNetChain
Flag to enable tracing of schematic net chain rebuild and ERC cross-chain checks.
KIID niluuid(0)
@ LAYER_WIRE
Definition layer_ids.h:474
Immediate-use netchain input; shared-screen items are qualified by their instance.
std::map< wxString, std::vector< BRIDGE_NEIGHBOR > > adjacency
std::string path
KIBIS_COMPONENT * comp
KIBIS_PIN * pin
static std::vector< int > candidates(const SEGMENT_INDEX &aIndex, const SEG &aQuery, int aPadding)
const SHAPE_LINE_CHAIN chain
wxString result
Test unit parsing edge cases and error handling.
wxLogTrace helper definitions.
#define kv
@ SCH_LINE_T
Definition typeinfo.h:159
@ SCH_SYMBOL_T
Definition typeinfo.h:168
@ SCH_LABEL_T
Definition typeinfo.h:163
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708