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