KiCad PCB EDA Suite
Loading...
Searching...
No Matches
connection_graph.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 (C) 2018 CERN
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * @author Jon Evans <[email protected]>
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License
11 * as published by the Free Software Foundation; either version 2
12 * of the License, or (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 */
22
23#include <algorithm>
24#include <list>
25#include <functional>
26#include <future>
27#include <map>
28#include <ranges>
29#include <set>
30#include <tuple>
31#include <unordered_map>
32#include <unordered_set>
33#include <vector>
34
35#include <app_monitor.h>
36#include <core/profile.h>
37#include <core/kicad_algo.h>
38#include <common.h>
39#include <erc/erc.h>
40#include <pin_type.h>
41#include <sch_bus_entry.h>
42#include <sch_symbol.h>
43#include <sch_edit_frame.h>
44#include <sch_line.h>
45#include <sch_marker.h>
46#include <sch_pin.h>
47#include <sch_rule_area.h>
48#include <trace_helpers.h>
49#include <wx/log.h>
50#include <sch_netchain.h>
51#include <sch_label.h>
52#include <sch_sheet.h>
53#include <sch_sheet_path.h>
54#include <sch_sheet_pin.h>
55#include <sch_text.h>
56#include <schematic.h>
57#include <symbol.h>
58#include <connection_graph.h>
61#include <widgets/ui_common.h>
62#include <string_utils.h>
63#include <thread_pool.h>
64#include <wx/log.h>
65
66#include <advanced_config.h> // for realtime connectivity switch in release builds
67
68
73static const wxChar DanglingProfileMask[] = wxT( "CONN_PROFILE" );
74
75
80static const wxChar ConnTrace[] = wxT( "CONN" );
81
82
83wxString CONNECTION_GRAPH::MakeNetChainKey( const wxString& aRawNetName, long aSubgraphCode )
84{
85 if( !aRawNetName.IsEmpty() && aRawNetName.Find( wxS( "<NO NET>" ) ) == wxNOT_FOUND )
86 return aRawNetName;
87
88 return wxString( SCH_NETCHAIN::SYNTHETIC_NET_PREFIX ) << aSubgraphCode;
89}
90
91
93{
94 if( !aSubGraph )
95 return wxEmptyString;
96
97 return MakeNetChainKey( aSubGraph->GetNetName(), aSubGraph->m_code );
98}
99
100
101// Internal shim so the existing private call sites read unchanged.
102static inline wxString netChainKeyFor( const wxString& aRawNetName, long aSubgraphCode )
103{
104 return CONNECTION_GRAPH::MakeNetChainKey( aRawNetName, aSubgraphCode );
105}
106
107
109{
110 // Ensure destruction happens in a translation unit that includes full SCH_NETCHAIN
111 // definition to avoid incomplete type issues with std::unique_ptr<SCH_NETCHAIN>.
112 Reset();
113}
114
115
117{
118 m_items.erase( aItem );
119 m_drivers.erase( aItem );
120
121 if( aItem == m_driver )
122 {
123 m_driver = nullptr;
124 m_driver_connection = nullptr;
125 }
126
127 if( aItem->Type() == SCH_SHEET_PIN_T )
128 m_hier_pins.erase( static_cast<SCH_SHEET_PIN*>( aItem ) );
129
130 if( aItem->Type() == SCH_HIER_LABEL_T )
131 m_hier_ports.erase( static_cast<SCH_HIERLABEL*>( aItem ) );
132}
133
134
136{
137 m_items.erase( aOldItem );
138 m_items.insert( aNewItem );
139
140 m_drivers.erase( aOldItem );
141 m_drivers.insert( aNewItem );
142
143 if( aOldItem == m_driver )
144 {
145 m_driver = aNewItem;
147 }
148
149 SCH_CONNECTION* old_conn = aOldItem->Connection( &m_sheet );
150 SCH_CONNECTION* new_conn = aNewItem->GetOrInitConnection( m_sheet, m_graph );
151
152 if( old_conn && new_conn )
153 {
154 new_conn->Clone( *old_conn );
155
156 if( old_conn->IsDriver() )
157 new_conn->SetDriver( aNewItem );
158
159 new_conn->ClearDirty();
160 }
161
162 if( aOldItem->Type() == SCH_SHEET_PIN_T )
163 {
164 m_hier_pins.erase( static_cast<SCH_SHEET_PIN*>( aOldItem ) );
165 m_hier_pins.insert( static_cast<SCH_SHEET_PIN*>( aNewItem ) );
166 }
167
168 if( aOldItem->Type() == SCH_HIER_LABEL_T )
169 {
170 m_hier_ports.erase( static_cast<SCH_HIERLABEL*>( aOldItem ) );
171 m_hier_ports.insert( static_cast<SCH_HIERLABEL*>( aNewItem ) );
172 }
173}
174
175
195static int compareDrivers( SCH_ITEM* aA, SCH_CONNECTION* aAConn, const wxString& aAName,
196 SCH_ITEM* aB, SCH_CONNECTION* aBConn, const wxString& aBName )
197{
200
201 if( pa != pb )
202 return pa > pb ? -1 : 1;
203
204 if( aAConn->IsBus() && aBConn->IsBus() )
205 {
206 bool a_in_b = aAConn->IsSubsetOf( aBConn );
207 bool b_in_a = aBConn->IsSubsetOf( aAConn );
208
209 if( b_in_a && !a_in_b )
210 return -1;
211
212 if( a_in_b && !b_in_a )
213 return 1;
214 }
215
216 if( aA->Type() == SCH_PIN_T && aB->Type() == SCH_PIN_T )
217 {
218 SCH_PIN* pinA = static_cast<SCH_PIN*>( aA );
219 SCH_PIN* pinB = static_cast<SCH_PIN*>( aB );
220
221 SYMBOL* parentA = pinA->GetLibPin() ? pinA->GetLibPin()->GetParentSymbol() : nullptr;
222 SYMBOL* parentB = pinB->GetLibPin() ? pinB->GetLibPin()->GetParentSymbol() : nullptr;
223
224 bool aGlobal = parentA && parentA->IsGlobalPower();
225 bool bGlobal = parentB && parentB->IsGlobalPower();
226
227 if( aGlobal != bGlobal )
228 return aGlobal ? -1 : 1;
229
230 bool aLocal = parentA && parentA->IsLocalPower();
231 bool bLocal = parentB && parentB->IsLocalPower();
232
233 if( aLocal != bLocal )
234 return aLocal ? -1 : 1;
235 }
236
237 if( aA->Type() == SCH_SHEET_PIN_T && aB->Type() == SCH_SHEET_PIN_T )
238 {
239 SCH_SHEET_PIN* sheetPinA = static_cast<SCH_SHEET_PIN*>( aA );
240 SCH_SHEET_PIN* sheetPinB = static_cast<SCH_SHEET_PIN*>( aB );
241
242 if( sheetPinA->GetShape() != sheetPinB->GetShape() )
243 {
244 if( sheetPinA->GetShape() == LABEL_FLAG_SHAPE::L_OUTPUT )
245 return -1;
246
247 if( sheetPinB->GetShape() == LABEL_FLAG_SHAPE::L_OUTPUT )
248 return 1;
249 }
250 }
251
252 bool aLowQuality = aAName.Contains( wxS( "-Pad" ) );
253 bool bLowQuality = aBName.Contains( wxS( "-Pad" ) );
254
255 if( aLowQuality != bLowQuality )
256 return aLowQuality ? 1 : -1;
257
258 if( aAName < aBName )
259 return -1;
260
261 if( aBName < aAName )
262 return 1;
263
264 return 0;
265}
266
267
268bool CONNECTION_SUBGRAPH::ResolveDrivers( bool aCheckMultipleDrivers )
269{
270 std::lock_guard lock( m_driver_mutex );
271
272 // Collect candidate drivers of highest priority in a simple vector which will be
273 // sorted later. Using a vector makes the ranking logic explicit and easier to
274 // maintain than relying on the ordering semantics of std::set.
275 PRIORITY highest_priority = PRIORITY::INVALID;
276 std::vector<SCH_ITEM*> candidates;
277 std::set<SCH_ITEM*> strong_drivers;
278
279 m_driver = nullptr;
280
281 // Hierarchical labels are lower priority than local labels here,
282 // because on the first pass we want local labels to drive subgraphs
283 // so that we can identify same-sheet neighbors and link them together.
284 // Hierarchical labels will end up overriding the final net name if
285 // a higher-level sheet has a different name during the hierarchical
286 // pass.
287
288 for( SCH_ITEM* item : m_drivers )
289 {
290 PRIORITY item_priority = GetDriverPriority( item );
291
292 if( item_priority == PRIORITY::PIN )
293 {
294 SCH_PIN* pin = static_cast<SCH_PIN*>( item );
295
296 if( !static_cast<SCH_SYMBOL*>( pin->GetParentSymbol() )->IsInNetlist() )
297 continue;
298 }
299
300 if( item_priority >= PRIORITY::HIER_LABEL )
301 strong_drivers.insert( item );
302
303 if( item_priority > highest_priority )
304 {
305 candidates.clear();
306 candidates.push_back( item );
307 highest_priority = item_priority;
308 }
309 else if( !candidates.empty() && ( item_priority == highest_priority ) )
310 {
311 candidates.push_back( item );
312 }
313 }
314
315 if( highest_priority >= PRIORITY::HIER_LABEL )
316 m_strong_driver = true;
317
318 // Power pins are 5, global labels are 6
319 m_local_driver = ( highest_priority < PRIORITY::GLOBAL_POWER_PIN );
320
321 if( !candidates.empty() )
322 {
323 // Delegate to the shared compareDrivers helper so this site and the global-label
324 // transitive-closure pre-pass in buildConnectionGraph agree on every tie-break.
325 auto candidate_cmp = [&]( SCH_ITEM* a, SCH_ITEM* b )
326 {
327 return compareDrivers( a, a->Connection( &m_sheet ), GetNameForDriver( a ),
328 b, b->Connection( &m_sheet ), GetNameForDriver( b ) ) < 0;
329 };
330
331 std::sort( candidates.begin(), candidates.end(), candidate_cmp );
332
333 m_driver = candidates.front();
334 }
335
336 if( strong_drivers.size() > 1 )
337 m_multiple_drivers = true;
338
339 // Drop weak drivers
340 if( m_strong_driver )
341 {
342 m_drivers.clear();
343 m_drivers.insert( strong_drivers.begin(), strong_drivers.end() );
344 }
345
346 // Cache driver connection
347 if( m_driver )
348 {
349 m_driver_connection = m_driver->Connection( &m_sheet );
350 m_driver_connection->ConfigureFromLabel( GetNameForDriver( m_driver ) );
351 m_driver_connection->SetDriver( m_driver );
352 m_driver_connection->ClearDirty();
353 }
354 else if( !m_is_bus_member )
355 {
356 m_driver_connection = nullptr;
357 }
358
359 return ( m_driver != nullptr );
360}
361
362
364 SCH_ITEM*>>& aItems,
365 std::set<CONNECTION_SUBGRAPH*>& aSubgraphs )
366{
367 CONNECTION_SUBGRAPH* sg = this;
368
369 while( sg->m_absorbed_by )
370 {
371 wxCHECK2( sg->m_graph == sg->m_absorbed_by->m_graph, continue );
372 sg = sg->m_absorbed_by;
373 }
374
375 // If we are unable to insert the subgraph into the set, then we have already
376 // visited it and don't need to add it again.
377 if( aSubgraphs.insert( sg ).second == false )
378 return;
379
380 aSubgraphs.insert( sg->m_absorbed_subgraphs.begin(), sg->m_absorbed_subgraphs.end() );
381
382 for( SCH_ITEM* item : sg->m_items )
383 aItems.emplace( m_sheet, item );
384
385 for( CONNECTION_SUBGRAPH* child_sg : sg->m_hier_children )
386 child_sg->getAllConnectedItems( aItems, aSubgraphs );
387}
388
389
391{
392 if( !m_driver || m_dirty )
393 return "";
394
395 if( !m_driver->Connection( &m_sheet ) )
396 {
397#ifdef CONNECTIVITY_DEBUG
398 wxASSERT_MSG( false, wxS( "Tried to get the net name of an item with no connection" ) );
399#endif
400
401 return "";
402 }
403
404 return m_driver->Connection( &m_sheet )->Name();
405}
406
407
408std::vector<SCH_ITEM*> CONNECTION_SUBGRAPH::GetAllBusLabels() const
409{
410 std::vector<SCH_ITEM*> labels;
411
412 for( SCH_ITEM* item : m_drivers )
413 {
414 switch( item->Type() )
415 {
416 case SCH_LABEL_T:
418 case SCH_HIER_LABEL_T:
419 {
420 CONNECTION_TYPE type = item->Connection( &m_sheet )->Type();
421
422 // Only consider bus vectors
423 if( type == CONNECTION_TYPE::BUS || type == CONNECTION_TYPE::BUS_GROUP )
424 labels.push_back( item );
425
426 break;
427 }
428
429 default:
430 break;
431 }
432 }
433
434 return labels;
435}
436
437
438std::vector<SCH_ITEM*> CONNECTION_SUBGRAPH::GetVectorBusLabels() const
439{
440 std::vector<SCH_ITEM*> labels;
441
442 for( SCH_ITEM* item : m_drivers )
443 {
444 switch( item->Type() )
445 {
446 case SCH_LABEL_T:
448 case SCH_HIER_LABEL_T:
449 {
450 SCH_CONNECTION* label_conn = item->Connection( &m_sheet );
451
452 // Only consider bus vectors
453 if( label_conn->Type() == CONNECTION_TYPE::BUS )
454 labels.push_back( item );
455
456 break;
457 }
458
459 default:
460 break;
461 }
462 }
463
464 return labels;
465}
466
467
469{
470 switch( aItem->Type() )
471 {
472 case SCH_PIN_T:
473 {
474 SCH_PIN* pin = static_cast<SCH_PIN*>( aItem );
475 bool forceNoConnect = m_no_connect != nullptr;
476
477 return pin->GetDefaultNetName( m_sheet, forceNoConnect );
478 }
479
480 case SCH_LABEL_T:
482 case SCH_HIER_LABEL_T:
483 {
484 SCH_LABEL_BASE* label = static_cast<SCH_LABEL_BASE*>( aItem );
485
486 // NB: any changes here will need corresponding changes in SCH_LABEL_BASE::cacheShownText()
487 return EscapeString( label->GetShownText( &m_sheet, false ), CTX_NETNAME );
488 }
489
490 case SCH_SHEET_PIN_T:
491 {
492 // Sheet pins need to use their parent sheet as their starting sheet or they will resolve
493 // variables on the current sheet first
494 SCH_SHEET_PIN* sheetPin = static_cast<SCH_SHEET_PIN*>( aItem );
496
497 if( path.Last() != sheetPin->GetParent() )
498 path.push_back( sheetPin->GetParent() );
499
500 return EscapeString( sheetPin->GetShownText( &path, false ), CTX_NETNAME );
501 }
502
503 default:
504 wxFAIL_MSG( wxS( "Unhandled item type in GetNameForDriver" ) );
505 return wxEmptyString;
506 }
507}
508
509
510const wxString& CONNECTION_SUBGRAPH::GetNameForDriver( SCH_ITEM* aItem ) const
511{
512 if( aItem->HasCachedDriverName() )
513 return aItem->GetCachedDriverName();
514
515 std::lock_guard lock( m_driver_name_cache_mutex );
516 auto it = m_driver_name_cache.find( aItem );
517
518 if( it != m_driver_name_cache.end() )
519 return it->second;
520
521 return m_driver_name_cache.emplace( aItem, driverName( aItem ) ).first->second;
522}
523
524
525const std::vector<std::pair<wxString, SCH_ITEM*>>
527{
528 std::vector<std::pair<wxString, SCH_ITEM*>> foundNetclasses;
529
530 const std::unordered_set<SCH_RULE_AREA*>& ruleAreaCache = aItem->GetRuleAreaCache();
531
532 // Get netclasses on attached rule areas
533 for( SCH_RULE_AREA* ruleArea : ruleAreaCache )
534 {
535 const std::vector<std::pair<wxString, SCH_ITEM*>> ruleAreaNetclasses =
536 ruleArea->GetResolvedNetclasses( &m_sheet );
537
538 if( ruleAreaNetclasses.size() > 0 )
539 {
540 foundNetclasses.insert( foundNetclasses.end(), ruleAreaNetclasses.begin(),
541 ruleAreaNetclasses.end() );
542 }
543 }
544
545 // Get netclasses on child fields
546 aItem->RunOnChildren(
547 [&]( SCH_ITEM* aChild )
548 {
549 if( aChild->Type() == SCH_FIELD_T )
550 {
551 SCH_FIELD* field = static_cast<SCH_FIELD*>( aChild );
552
553 if( field->GetCanonicalName() == wxT( "Netclass" ) )
554 {
555 wxString netclass = field->GetShownText( &m_sheet, false );
556
557 if( netclass != wxEmptyString )
558 foundNetclasses.push_back( { netclass, aItem } );
559 }
560 }
561 },
563
564 std::sort(
565 foundNetclasses.begin(), foundNetclasses.end(),
566 []( const std::pair<wxString, SCH_ITEM*>& i1, const std::pair<wxString, SCH_ITEM*>& i2 )
567 {
568 return i1.first < i2.first;
569 } );
570
571 return foundNetclasses;
572}
573
574
576{
577 wxCHECK( m_sheet == aOther->m_sheet, /* void */ );
578
579 for( SCH_ITEM* item : aOther->m_items )
580 {
582 AddItem( item );
583 }
584
585 m_absorbed_subgraphs.insert( aOther );
586 m_absorbed_subgraphs.insert( aOther->m_absorbed_subgraphs.begin(),
587 aOther->m_absorbed_subgraphs.end() );
588
589 m_bus_neighbors.insert( aOther->m_bus_neighbors.begin(), aOther->m_bus_neighbors.end() );
590 m_bus_parents.insert( aOther->m_bus_parents.begin(), aOther->m_bus_parents.end() );
591
593
594 std::function<void( CONNECTION_SUBGRAPH* )> set_absorbed_by =
595 [ & ]( CONNECTION_SUBGRAPH *child )
596 {
597 child->m_absorbed_by = this;
598
599 for( CONNECTION_SUBGRAPH* subchild : child->m_absorbed_subgraphs )
600 set_absorbed_by( subchild );
601 };
602
603 aOther->m_absorbed = true;
604 aOther->m_dirty = false;
605 aOther->m_driver = nullptr;
606 aOther->m_driver_connection = nullptr;
607
608 set_absorbed_by( aOther );
609}
610
611
613{
614 m_items.insert( aItem );
615
616 if( aItem->Connection( &m_sheet )->IsDriver() )
617 m_drivers.insert( aItem );
618
619 if( aItem->Type() == SCH_SHEET_PIN_T )
620 m_hier_pins.insert( static_cast<SCH_SHEET_PIN*>( aItem ) );
621 else if( aItem->Type() == SCH_HIER_LABEL_T )
622 m_hier_ports.insert( static_cast<SCH_HIERLABEL*>( aItem ) );
623}
624
625
627{
629 return;
630
631 for( SCH_ITEM* item : m_items )
632 {
633 SCH_CONNECTION* item_conn = item->GetOrInitConnection( m_sheet, m_graph );
634
635 if( !item_conn )
636 continue;
637
638 if( ( m_driver_connection->IsBus() && item_conn->IsNet() ) ||
639 ( m_driver_connection->IsNet() && item_conn->IsBus() ) )
640 {
641 continue;
642 }
643
644 item_conn->Clone( *m_driver_connection );
645 item_conn->ClearDirty();
646 }
647}
648
649
651{
652 if( !aDriver )
653 return PRIORITY::NONE;
654
655 auto libSymbolRef =
656 []( const SCH_SYMBOL* symbol ) -> wxString
657 {
658 if( const std::unique_ptr<LIB_SYMBOL>& part = symbol->GetLibSymbolRef() )
659 return part->GetReferenceField().GetText();
660
661 return wxEmptyString;
662 };
663
664 switch( aDriver->Type() )
665 {
670
671 case SCH_PIN_T:
672 {
673 SCH_PIN* sch_pin = static_cast<SCH_PIN*>( aDriver );
674 const SCH_SYMBOL* sym = static_cast<SCH_SYMBOL*>( sch_pin->GetParentSymbol() );
675
676 if( sch_pin->IsGlobalPower() )
678 else if( sch_pin->IsLocalPower() )
680 else if( !sym || sym->GetExcludedFromBoard() || libSymbolRef( sym ).StartsWith( '#' ) )
681 return PRIORITY::NONE;
682 else
683 return PRIORITY::PIN;
684 }
685
686 default:
687 return PRIORITY::NONE;
688 }
689}
690
691
693{
694 std::copy( aGraph.m_items.begin(), aGraph.m_items.end(),
695 std::back_inserter( m_items ) );
696
697 for( SCH_ITEM* item : aGraph.m_items )
698 item->SetConnectionGraph( this );
699
700 std::copy( aGraph.m_subgraphs.begin(), aGraph.m_subgraphs.end(),
701 std::back_inserter( m_subgraphs ) );
702
703 for( CONNECTION_SUBGRAPH* sg : aGraph.m_subgraphs )
704 {
705 if( sg->m_driver_connection )
706 sg->m_driver_connection->SetGraph( this );
707
708 sg->m_graph = this;
709 }
710
711 std::copy( aGraph.m_driver_subgraphs.begin(), aGraph.m_driver_subgraphs.end(),
712 std::back_inserter( m_driver_subgraphs ) );
713
714 std::copy( aGraph.m_global_power_pins.begin(), aGraph.m_global_power_pins.end(),
715 std::back_inserter( m_global_power_pins ) );
716
717 for( auto& [key, value] : aGraph.m_net_name_to_subgraphs_map )
718 m_net_name_to_subgraphs_map.insert_or_assign( key, value );
719
720 for( auto& [key, value] : aGraph.m_sheet_to_subgraphs_map )
721 m_sheet_to_subgraphs_map.insert_or_assign( key, value );
722
723 for( auto& [key, value] : aGraph.m_net_name_to_code_map )
724 m_net_name_to_code_map.insert_or_assign( key, value );
725
726 for( auto& [key, value] : aGraph.m_bus_name_to_code_map )
727 m_bus_name_to_code_map.insert_or_assign( key, value );
728
729 for( auto& [key, value] : aGraph.m_net_code_to_subgraphs_map )
730 m_net_code_to_subgraphs_map.insert_or_assign( key, value );
731
732 // Union rather than replace. An incremental pass may only have rebuilt the item on some of
733 // its sheet paths, and dropping the surviving subgraphs here would orphan their references
734 // to the item so a later removal could no longer find them.
735 for( auto& [key, value] : aGraph.m_item_to_subgraph_map )
736 {
737 std::vector<CONNECTION_SUBGRAPH*>& existing = m_item_to_subgraph_map[key];
738
739 for( CONNECTION_SUBGRAPH* sg : value )
740 {
741 if( !alg::contains( existing, sg ) )
742 existing.push_back( sg );
743 }
744 }
745
746 for( auto& [key, value] : aGraph.m_local_label_cache )
747 m_local_label_cache.insert_or_assign( key, value );
748
749 for( auto& [key, value] : aGraph.m_global_label_cache )
750 m_global_label_cache.insert_or_assign( key, value );
751
752 m_last_bus_code = std::max( m_last_bus_code, aGraph.m_last_bus_code );
753 m_last_net_code = std::max( m_last_net_code, aGraph.m_last_net_code );
755
756 // Committed chains and override maps belong to the persistent schematic state, so they
757 // must travel across an incremental graph merge. Potential chains are moved alongside
758 // them to keep the merged graph self-consistent until the next RebuildNetChains() pass.
759 for( std::unique_ptr<SCH_NETCHAIN>& chain : aGraph.m_committedNetChains )
760 {
761 if( chain )
762 m_committedNetChains.push_back( std::move( chain ) );
763 }
764
765 aGraph.m_committedNetChains.clear();
766
767 for( std::unique_ptr<SCH_NETCHAIN>& chain : aGraph.m_potentialNetChains )
768 {
769 if( chain )
770 m_potentialNetChains.push_back( std::move( chain ) );
771 }
772
773 aGraph.m_potentialNetChains.clear();
774
776
777 for( auto& [key, value] : aGraph.m_netChainTerminalOverrides )
778 m_netChainTerminalOverrides.insert_or_assign( key, value );
779
780 for( auto& [key, value] : aGraph.m_netChainNetClassOverrides )
781 m_netChainNetClassOverrides.insert_or_assign( key, value );
782
783 for( auto& [key, value] : aGraph.m_netChainColorOverrides )
784 m_netChainColorOverrides.insert_or_assign( key, value );
785
786 for( auto& [key, value] : aGraph.m_netChainTerminalRefOverrides )
787 m_netChainTerminalRefOverrides.insert_or_assign( key, value );
788
789 for( auto& [key, value] : aGraph.m_netChainMemberNetOverrides )
790 m_netChainMemberNetOverrides.insert_or_assign( key, value );
791}
792
793
795{
796 wxCHECK2( aOldItem->Type() == aNewItem->Type(), return );
797
798 auto exchange = [&]( SCH_ITEM* aOld, SCH_ITEM* aNew )
799 {
800 auto it = m_item_to_subgraph_map.find( aOld );
801
802 if( it == m_item_to_subgraph_map.end() )
803 return;
804
805 std::vector<CONNECTION_SUBGRAPH*> sgs = std::move( it->second );
806
807 for( CONNECTION_SUBGRAPH* sg : sgs )
808 sg->ExchangeItem( aOld, aNew );
809
810 m_item_to_subgraph_map.erase( it );
811 m_item_to_subgraph_map.emplace( aNew, std::move( sgs ) );
812
813 for( auto it2 = m_items.begin(); it2 != m_items.end(); ++it2 )
814 {
815 if( *it2 == aOld )
816 {
817 *it2 = aNew;
818 break;
819 }
820 }
821 };
822
823 exchange( aOldItem, aNewItem );
824
825 if( aOldItem->Type() == SCH_SYMBOL_T )
826 {
827 SCH_SYMBOL* oldSymbol = static_cast<SCH_SYMBOL*>( aOldItem );
828 SCH_SYMBOL* newSymbol = static_cast<SCH_SYMBOL*>( aNewItem );
829 std::vector<SCH_PIN*> oldPins = oldSymbol->GetPins( &m_schematic->CurrentSheet() );
830 std::vector<SCH_PIN*> newPins = newSymbol->GetPins( &m_schematic->CurrentSheet() );
831
832 wxCHECK2( oldPins.size() == newPins.size(), return );
833
834 for( size_t ii = 0; ii < oldPins.size(); ii++ )
835 {
836 exchange( oldPins[ii], newPins[ii] );
837 }
838 }
839}
840
841
843{
844 for( auto& subgraph : m_subgraphs )
845 {
847 if( subgraph->m_graph == this )
848 delete subgraph;
849 }
850
851 m_items.clear();
852 m_subgraphs.clear();
853 m_driver_subgraphs.clear();
855 m_global_power_pins.clear();
856 m_bus_alias_cache.clear();
862 m_local_label_cache.clear();
863 m_global_label_cache.clear();
864 m_last_net_code = 1;
865 m_last_bus_code = 1;
867
868 // SCH_NETCHAIN::m_symbols holds non-owning SCH_SYMBOL pointers. Once the connectivity
869 // pass clears the rest of the graph the schematic items can be freed before
870 // RebuildNetChains() repopulates the chain caches, so drop the stale pointers now.
871 for( std::unique_ptr<SCH_NETCHAIN>& chain : m_committedNetChains )
872 {
873 if( chain )
874 chain->ClearSymbols();
875 }
876
877 m_potentialNetChains.clear();
878 m_netChainsBuilt = false;
879}
880
881
882void CONNECTION_GRAPH::Recalculate( const SCH_SHEET_LIST& aSheetList, bool aUnconditional,
883 std::function<void( SCH_ITEM* )>* aChangedItemHandler,
884 PROGRESS_REPORTER* aProgressReporter )
885{
886 APP_MONITOR::TRANSACTION monitorTrans( "CONNECTION_GRAPH::Recalculate", "Recalculate" );
887 PROF_TIMER recalc_time( "CONNECTION_GRAPH::Recalculate" );
888 monitorTrans.Start();
889
890 if( aUnconditional )
891 Reset();
892
893 monitorTrans.StartSpan( "updateItemConnectivity", "" );
894 PROF_TIMER update_items( "updateItemConnectivity" );
895
896 m_sheetList = aSheetList;
897 std::set<SCH_ITEM*> dirty_items;
898
899 int count = aSheetList.size() * 2;
900 int done = 0;
901
902 for( const SCH_SHEET_PATH& sheet : aSheetList )
903 {
904 if( aProgressReporter )
905 {
906 aProgressReporter->SetCurrentProgress( done++ / (double) count );
907 aProgressReporter->KeepRefreshing();
908 }
909
910 std::vector<SCH_ITEM*> items;
911
912 // Store current unit value, to replace it after calculations
913 std::vector<std::pair<SCH_SYMBOL*, int>> symbolsChanged;
914
915 for( SCH_ITEM* item : sheet.LastScreen()->Items() )
916 {
917 if( item->IsConnectable() && ( aUnconditional || item->IsConnectivityDirty() ) )
918 {
919 wxLogTrace( ConnTrace, wxT( "Adding item %s to connectivity graph update" ),
920 item->GetTypeDesc() );
921 items.push_back( item );
922 dirty_items.insert( item );
923
924 // Add any symbol dirty pins to the dirty_items list
925 if( item->Type() == SCH_SYMBOL_T )
926 {
927 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
928
929 for( SCH_PIN* pin : symbol->GetPins( &sheet ) )
930 {
931 if( pin->IsConnectivityDirty() )
932 {
933 dirty_items.insert( pin );
934 }
935 }
936 }
937 }
938 // If the symbol isn't dirty, look at the pins
939 // TODO: remove symbols from connectivity graph and only use pins
940 else if( item->Type() == SCH_SYMBOL_T )
941 {
942 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
943
944 for( SCH_PIN* pin : symbol->GetPins( &sheet ) )
945 {
946 if( pin->IsConnectivityDirty() )
947 {
948 items.push_back( pin );
949 dirty_items.insert( pin );
950 }
951 }
952 }
953 else if( item->Type() == SCH_SHEET_T )
954 {
955 SCH_SHEET* sheetItem = static_cast<SCH_SHEET*>( item );
956
957 for( SCH_SHEET_PIN* pin : sheetItem->GetPins() )
958 {
959 if( pin->IsConnectivityDirty() )
960 {
961 items.push_back( pin );
962 dirty_items.insert( pin );
963 }
964 }
965 }
966
967 // Ensure the hierarchy info stored in the SCH_SCREEN (such as symbol units) reflects
968 // the current SCH_SHEET_PATH
969 if( item->Type() == SCH_SYMBOL_T )
970 {
971 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
972 int new_unit = symbol->GetUnitSelection( &sheet );
973
974 // Store the initial unit value so we can restore it after calculations
975 if( symbol->GetUnit() != new_unit )
976 symbolsChanged.push_back( { symbol, symbol->GetUnit() } );
977
978 symbol->SetUnit( new_unit );
979 }
980 }
981
982 m_items.reserve( m_items.size() + items.size() );
983
984 updateItemConnectivity( sheet, items );
985
986 if( aProgressReporter )
987 {
988 aProgressReporter->SetCurrentProgress( done++ / count );
989 aProgressReporter->KeepRefreshing();
990 }
991
992 // UpdateDanglingState() also adds connected items for SCH_TEXT
993 sheet.LastScreen()->TestDanglingEnds( &sheet, aChangedItemHandler );
994
995 // Restore the m_unit member variables where we had to change them
996 for( const auto& [ symbol, originalUnit ] : symbolsChanged )
997 symbol->SetUnit( originalUnit );
998 }
999
1000 // Restore the dangling states of items in the current SCH_SCREEN to match the current
1001 // SCH_SHEET_PATH.
1002 SCH_SCREEN* currentScreen = m_schematic->CurrentSheet().LastScreen();
1003
1004 if( currentScreen )
1005 currentScreen->TestDanglingEnds( &m_schematic->CurrentSheet(), aChangedItemHandler );
1006
1007 for( SCH_ITEM* item : dirty_items )
1008 item->SetConnectivityDirty( false );
1009
1010
1011 monitorTrans.FinishSpan();
1012 if( wxLog::IsAllowedTraceMask( DanglingProfileMask ) )
1013 update_items.Show();
1014
1015 PROF_TIMER build_graph( "buildConnectionGraph" );
1016 monitorTrans.StartSpan( "BuildConnectionGraph", "" );
1017
1018 buildConnectionGraph( aChangedItemHandler, aUnconditional );
1019
1020 if( wxLog::IsAllowedTraceMask( DanglingProfileMask ) )
1021 build_graph.Show();
1022
1023 monitorTrans.FinishSpan();
1024
1025 recalc_time.Stop();
1026
1027 if( wxLog::IsAllowedTraceMask( DanglingProfileMask ) )
1028 recalc_time.Show();
1029
1030 monitorTrans.Finish();
1031}
1032
1033
1034std::set<std::pair<SCH_SHEET_PATH, SCH_ITEM*>> CONNECTION_GRAPH::ExtractAffectedItems(
1035 const std::set<SCH_ITEM*> &aItems )
1036{
1037 std::set<std::pair<SCH_SHEET_PATH, SCH_ITEM*>> retvals;
1038 std::set<CONNECTION_SUBGRAPH*> subgraphs;
1039
1040 auto traverse_subgraph = [&retvals, &subgraphs]( CONNECTION_SUBGRAPH* aSubgraph )
1041 {
1042 // Find the primary subgraph on this sheet
1043 while( aSubgraph->m_absorbed_by )
1044 {
1045 // Should we skip this if the absorbed by sub-graph is not this sub-grap?
1046 wxASSERT( aSubgraph->m_graph == aSubgraph->m_absorbed_by->m_graph );
1047 aSubgraph = aSubgraph->m_absorbed_by;
1048 }
1049
1050 // Find the top most connected subgraph on all sheets
1051 while( aSubgraph->m_hier_parent )
1052 {
1053 // Should we skip this if the absorbed by sub-graph is not this sub-grap?
1054 wxASSERT( aSubgraph->m_graph == aSubgraph->m_hier_parent->m_graph );
1055 aSubgraph = aSubgraph->m_hier_parent;
1056 }
1057
1058 // Recurse through all subsheets to collect connected items
1059 aSubgraph->getAllConnectedItems( retvals, subgraphs );
1060 };
1061
1062 auto extract_element = [&]( SCH_ITEM* aItem )
1063 {
1064 CONNECTION_SUBGRAPH* item_sg = GetSubgraphForItem( aItem );
1065
1066 if( !item_sg )
1067 {
1068 wxLogTrace( ConnTrace, wxT( "Item %s not found in connection graph" ),
1069 aItem->GetTypeDesc() );
1070 return;
1071 }
1072
1073 if( !item_sg->ResolveDrivers( true ) )
1074 {
1075 wxLogTrace( ConnTrace, wxT( "Item %s in subgraph %ld (%p) has no driver" ),
1076 aItem->GetTypeDesc(), item_sg->m_code, item_sg );
1077 }
1078
1079 std::vector<CONNECTION_SUBGRAPH*> sg_to_scan = GetAllSubgraphs( item_sg->GetNetName() );
1080
1081 if( sg_to_scan.empty() )
1082 {
1083 wxLogTrace( ConnTrace, wxT( "Item %s in subgraph %ld with net %s has no neighbors" ),
1084 aItem->GetTypeDesc(), item_sg->m_code, item_sg->GetNetName() );
1085 sg_to_scan.push_back( item_sg );
1086 }
1087
1088 wxLogTrace( ConnTrace,
1089 wxT( "Removing all item %s connections from subgraph %ld with net %s: Found "
1090 "%zu subgraphs" ),
1091 aItem->GetTypeDesc(), item_sg->m_code, item_sg->GetNetName(),
1092 sg_to_scan.size() );
1093
1094 for( CONNECTION_SUBGRAPH* sg : sg_to_scan )
1095 {
1096 traverse_subgraph( sg );
1097
1098 for( auto& bus_it : sg->m_bus_neighbors )
1099 {
1100 for( CONNECTION_SUBGRAPH* bus_sg : bus_it.second )
1101 traverse_subgraph( bus_sg );
1102 }
1103
1104 for( auto& bus_it : sg->m_bus_parents )
1105 {
1106 for( CONNECTION_SUBGRAPH* bus_sg : bus_it.second )
1107 traverse_subgraph( bus_sg );
1108 }
1109 }
1110
1111 std::erase( m_items, aItem );
1112 };
1113
1114 for( SCH_ITEM* item : aItems )
1115 {
1116 if( item->Type() == SCH_SHEET_T )
1117 {
1118 SCH_SHEET* sheet = static_cast<SCH_SHEET*>( item );
1119
1120 for( SCH_SHEET_PIN* pin : sheet->GetPins() )
1121 extract_element( pin );
1122 }
1123 else if ( item->Type() == SCH_SYMBOL_T )
1124 {
1125 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
1126
1127 for( SCH_PIN* pin : symbol->GetPins( &m_schematic->CurrentSheet() ) )
1128 extract_element( pin );
1129 }
1130 else
1131 {
1132 extract_element( item );
1133 }
1134 }
1135
1136 removeSubgraphs( subgraphs );
1137
1138 for( const auto& [path, item] : retvals )
1139 std::erase( m_items, item );
1140
1141 return retvals;
1142}
1143
1144
1146{
1147 auto it = m_item_to_subgraph_map.find( aItem );
1148
1149 if( it == m_item_to_subgraph_map.end() )
1150 return;
1151
1152 // The item sits in one subgraph per instantiating sheet path, and every one of them must
1153 // drop it here or a subsequent recalculation resolves drivers against freed memory
1154 for( CONNECTION_SUBGRAPH* subgraph : it->second )
1155 {
1156 while( subgraph->m_absorbed_by )
1157 subgraph = subgraph->m_absorbed_by;
1158
1159 subgraph->RemoveItem( aItem );
1160 }
1161
1162 std::erase( m_items, aItem );
1163 m_item_to_subgraph_map.erase( it );
1164}
1165
1166
1167void CONNECTION_GRAPH::removeSubgraphs( std::set<CONNECTION_SUBGRAPH*>& aSubgraphs )
1168{
1169 wxLogTrace( ConnTrace, wxT( "Removing %zu subgraphs" ), aSubgraphs.size() );
1170 std::sort( m_driver_subgraphs.begin(), m_driver_subgraphs.end() );
1171 std::sort( m_subgraphs.begin(), m_subgraphs.end() );
1172 std::set<int> codes_to_remove;
1173
1174 for( auto& el : m_sheet_to_subgraphs_map )
1175 {
1176 std::sort( el.second.begin(), el.second.end() );
1177 }
1178
1179 for( CONNECTION_SUBGRAPH* sg : aSubgraphs )
1180 {
1181 for( auto& it : sg->m_bus_neighbors )
1182 {
1183 for( CONNECTION_SUBGRAPH* neighbor : it.second )
1184 {
1185 auto& parents = neighbor->m_bus_parents[it.first];
1186
1187 for( auto test = parents.begin(); test != parents.end(); )
1188 {
1189 if( *test == sg )
1190 test = parents.erase( test );
1191 else
1192 ++test;
1193 }
1194
1195 if( parents.empty() )
1196 neighbor->m_bus_parents.erase( it.first );
1197 }
1198 }
1199
1200 for( auto& it : sg->m_bus_parents )
1201 {
1202 for( CONNECTION_SUBGRAPH* parent : it.second )
1203 {
1204 auto& neighbors = parent->m_bus_neighbors[it.first];
1205
1206 for( auto test = neighbors.begin(); test != neighbors.end(); )
1207 {
1208 if( *test == sg )
1209 test = neighbors.erase( test );
1210 else
1211 ++test;
1212 }
1213
1214 if( neighbors.empty() )
1215 parent->m_bus_neighbors.erase( it.first );
1216 }
1217 }
1218
1219 {
1220 auto it = std::lower_bound( m_driver_subgraphs.begin(), m_driver_subgraphs.end(), sg );
1221
1222 while( it != m_driver_subgraphs.end() && *it == sg )
1223 it = m_driver_subgraphs.erase( it );
1224 }
1225
1226 {
1227 auto it = std::lower_bound( m_subgraphs.begin(), m_subgraphs.end(), sg );
1228
1229 while( it != m_subgraphs.end() && *it == sg )
1230 it = m_subgraphs.erase( it );
1231 }
1232
1233 for( auto& el : m_sheet_to_subgraphs_map )
1234 {
1235 auto it = std::lower_bound( el.second.begin(), el.second.end(), sg );
1236
1237 while( it != el.second.end() && *it == sg )
1238 it = el.second.erase( it );
1239 }
1240
1241 auto remove_sg = [sg]( auto it ) -> bool
1242 {
1243 for( const CONNECTION_SUBGRAPH* test_sg : it->second )
1244 {
1245 if( sg == test_sg )
1246 return true;
1247 }
1248
1249 return false;
1250 };
1251
1252 for( auto it = m_global_label_cache.begin(); it != m_global_label_cache.end(); )
1253 {
1254 if( remove_sg( it ) )
1255 it = m_global_label_cache.erase( it );
1256 else
1257 ++it;
1258 }
1259
1260 for( auto it = m_local_label_cache.begin(); it != m_local_label_cache.end(); )
1261 {
1262 if( remove_sg( it ) )
1263 it = m_local_label_cache.erase( it );
1264 else
1265 ++it;
1266 }
1267
1268 for( auto it = m_net_code_to_subgraphs_map.begin();
1269 it != m_net_code_to_subgraphs_map.end(); )
1270 {
1271 if( remove_sg( it ) )
1272 {
1273 codes_to_remove.insert( it->first.Netcode );
1274 it = m_net_code_to_subgraphs_map.erase( it );
1275 }
1276 else
1277 {
1278 ++it;
1279 }
1280 }
1281
1282 for( auto it = m_net_name_to_subgraphs_map.begin();
1283 it != m_net_name_to_subgraphs_map.end(); )
1284 {
1285 if( remove_sg( it ) )
1286 it = m_net_name_to_subgraphs_map.erase( it );
1287 else
1288 ++it;
1289 }
1290
1291 for( auto it = m_item_to_subgraph_map.begin(); it != m_item_to_subgraph_map.end(); )
1292 {
1293 std::erase( it->second, sg );
1294
1295 if( it->second.empty() )
1296 it = m_item_to_subgraph_map.erase( it );
1297 else
1298 ++it;
1299 }
1300
1301
1302 }
1303
1304 for( auto it = m_net_name_to_code_map.begin(); it != m_net_name_to_code_map.end(); )
1305 {
1306 if( codes_to_remove.contains( it->second ) )
1307 it = m_net_name_to_code_map.erase( it );
1308 else
1309 ++it;
1310 }
1311
1312 for( auto it = m_bus_name_to_code_map.begin(); it != m_bus_name_to_code_map.end(); )
1313 {
1314 if( codes_to_remove.contains( it->second ) )
1315 it = m_bus_name_to_code_map.erase( it );
1316 else
1317 ++it;
1318 }
1319
1320 for( CONNECTION_SUBGRAPH* sg : aSubgraphs )
1321 {
1322 sg->m_code = -1;
1323 sg->m_graph = nullptr;
1324 delete sg;
1325 }
1326}
1327
1328
1330 std::map<VECTOR2I, std::vector<SCH_ITEM*>>& aConnectionMap )
1331{
1332 auto updatePin =
1333 [&]( SCH_PIN* aPin, SCH_CONNECTION* aConn )
1334 {
1335 aConn->SetType( CONNECTION_TYPE::NET );
1336 wxString name = aPin->GetDefaultNetName( aSheet );
1337 aPin->ClearConnectedItems( aSheet );
1338
1339 if( aPin->IsGlobalPower() )
1340 {
1341 aConn->SetName( name );
1342 m_global_power_pins.emplace_back( std::make_pair( aSheet, aPin ) );
1343 }
1344 };
1345
1346 std::map<wxString, std::vector<SCH_PIN*>> pinNumberMap;
1347
1348 for( SCH_PIN* pin : aSymbol->GetPins( &aSheet ) )
1349 {
1350 m_items.emplace_back( pin );
1351 SCH_CONNECTION* conn = pin->InitializeConnection( aSheet, this );
1352 updatePin( pin, conn );
1353 aConnectionMap[ pin->GetPosition() ].push_back( pin );
1354 pinNumberMap[pin->GetNumber()].emplace_back( pin );
1355 }
1356
1357 auto linkPinsInVec =
1358 [&]( const std::vector<SCH_PIN*>& aVec )
1359 {
1360 for( size_t i = 0; i < aVec.size(); ++i )
1361 {
1362 for( size_t j = i + 1; j < aVec.size(); ++j )
1363 {
1364 aVec[i]->AddConnectionTo( aSheet, aVec[j] );
1365 aVec[j]->AddConnectionTo( aSheet, aVec[i] );
1366 }
1367 }
1368 };
1369
1370 if( aSymbol->GetLibSymbolRef() )
1371 {
1373 {
1374 for( const auto& [number, group] : pinNumberMap )
1375 linkPinsInVec( group );
1376 }
1377
1378 for( const std::set<wxString>& group : aSymbol->GetLibSymbolRef()->JumperPinGroups() )
1379 {
1380 std::vector<SCH_PIN*> pins;
1381
1382 for( const wxString& pinNumber : group )
1383 {
1384 if( SCH_PIN* pin = aSymbol->GetPin( pinNumber ) )
1385 pins.emplace_back( pin );
1386 }
1387
1388 linkPinsInVec( pins );
1389 }
1390 }
1391}
1392
1393
1395{
1396 aConn->SetType( CONNECTION_TYPE::NET );
1397
1398 // because calling the first time is not thread-safe
1399 wxString name = aPin->GetDefaultNetName( aSheet );
1400 aPin->ClearConnectedItems( aSheet );
1401
1402 if( aPin->IsGlobalPower() )
1403 {
1404 aConn->SetName( name );
1405 m_global_power_pins.emplace_back( std::make_pair( aSheet, aPin ) );
1406 }
1407}
1408
1409
1411 std::map<VECTOR2I, std::vector<SCH_ITEM*>>& aConnectionMap )
1412{
1413 std::vector<VECTOR2I> points = aItem->GetConnectionPoints();
1414 aItem->ClearConnectedItems( aSheet );
1415
1416 m_items.emplace_back( aItem );
1417 SCH_CONNECTION* conn = aItem->InitializeConnection( aSheet, this );
1418
1419 switch( aItem->Type() )
1420 {
1421 case SCH_LINE_T:
1423 break;
1424
1427 static_cast<SCH_BUS_BUS_ENTRY*>( aItem )->m_connected_bus_items[0] = nullptr;
1428 static_cast<SCH_BUS_BUS_ENTRY*>( aItem )->m_connected_bus_items[1] = nullptr;
1429 break;
1430
1431 case SCH_PIN_T:
1432 if( points.empty() )
1433 points = { static_cast<SCH_PIN*>( aItem )->GetPosition() };
1434
1435 updatePinConnectivity( aSheet, static_cast<SCH_PIN*>( aItem ), conn );
1436 break;
1437
1440 static_cast<SCH_BUS_WIRE_ENTRY*>( aItem )->m_connected_bus_item = nullptr;
1441 break;
1442
1443 default: break;
1444 }
1445
1446 for( const VECTOR2I& point : points )
1447 aConnectionMap[point].push_back( aItem );
1448}
1449
1450
1452 const std::vector<SCH_ITEM*>& aItemList )
1453{
1454 wxLogTrace( wxT( "Updating connectivity for sheet %s with %zu items" ),
1455 aSheet.Last()->GetFileName(), aItemList.size() );
1456 std::map<VECTOR2I, std::vector<SCH_ITEM*>> connection_map;
1457
1458 for( SCH_ITEM* item : aItemList )
1459 {
1460 std::vector<VECTOR2I> points = item->GetConnectionPoints();
1461 item->ClearConnectedItems( aSheet );
1462 if( item->Type() == SCH_SHEET_T )
1463 {
1464 for( SCH_SHEET_PIN* pin : static_cast<SCH_SHEET*>( item )->GetPins() )
1465 {
1466 pin->InitializeConnection( aSheet, this );
1467
1468 pin->ClearConnectedItems( aSheet );
1469
1470 connection_map[ pin->GetTextPos() ].push_back( pin );
1471 m_items.emplace_back( pin );
1472 }
1473 }
1474 else if( item->Type() == SCH_SYMBOL_T )
1475 {
1476 updateSymbolConnectivity( aSheet, static_cast<SCH_SYMBOL*>( item ), connection_map );
1477 }
1478 else
1479 {
1480 updateGenericItemConnectivity( aSheet, item, connection_map );
1481
1485 if( dynamic_cast<SCH_LABEL_BASE*>( item ) )
1486 {
1487 VECTOR2I point = item->GetPosition();
1488 SCH_SCREEN* screen = aSheet.LastScreen();
1489 auto items = screen->Items().Overlapping( point );
1490 std::vector<SCH_ITEM*> overlapping_items;
1491
1492 std::copy_if( items.begin(), items.end(), std::back_inserter( overlapping_items ),
1493 [&]( SCH_ITEM* test_item )
1494 {
1495 return test_item->Type() == SCH_LINE_T
1496 && test_item->HitTest( point, -1 );
1497 } );
1498
1499 // We need at least two connnectable lines that are not the label here
1500 // Otherwise, the label will be normally assigned to one or the other
1501 if( overlapping_items.size() < 2 ) continue;
1502
1503 for( SCH_ITEM* test_item : overlapping_items )
1504 connection_map[point].push_back( test_item );
1505 }
1506
1507 // Junctions connect wires that pass through their position as midpoints.
1508 // This handles schematics where a wire was not split at a junction point,
1509 // which can happen when a wire is placed over an existing junction without
1510 // the schematic topology being updated.
1511 if( item->Type() == SCH_JUNCTION_T )
1512 {
1513 VECTOR2I point = item->GetPosition();
1514 SCH_SCREEN* screen = aSheet.LastScreen();
1515
1516 for( SCH_LINE* wire : screen->GetBusesAndWires( point, true ) )
1517 connection_map[point].push_back( wire );
1518 }
1519 }
1520 }
1521
1522 for( auto& [point, connection_vec] : connection_map )
1523 {
1524 std::sort( connection_vec.begin(), connection_vec.end() );
1525 alg::remove_duplicates( connection_vec );
1526
1527 // Pre-scan to see if we have a bus at this location
1528 SCH_LINE* busLine = aSheet.LastScreen()->GetBus( point );
1529
1530 for( SCH_ITEM* connected_item : connection_vec )
1531 {
1532 // Bus entries are special: they can have connection points in the
1533 // middle of a wire segment, because the junction algo doesn't split
1534 // the segment in two where you place a bus entry. This means that
1535 // bus entries that don't land on the end of a line segment need to
1536 // have "virtual" connection points to the segments they graphically
1537 // touch.
1538 if( connected_item->Type() == SCH_BUS_WIRE_ENTRY_T )
1539 {
1540 // If this location only has the connection point of the bus
1541 // entry itself, this means that either the bus entry is not
1542 // connected to anything graphically, or that it is connected to
1543 // a segment at some point other than at one of the endpoints.
1544 if( connection_vec.size() == 1 )
1545 {
1546 if( busLine )
1547 {
1548 auto bus_entry = static_cast<SCH_BUS_WIRE_ENTRY*>( connected_item );
1549 bus_entry->m_connected_bus_item = busLine;
1550 }
1551 }
1552 }
1553 // Bus-to-bus entries are treated just like bus wires
1554 else if( connected_item->Type() == SCH_BUS_BUS_ENTRY_T )
1555 {
1556 if( busLine )
1557 {
1558 auto bus_entry = static_cast<SCH_BUS_BUS_ENTRY*>( connected_item );
1559
1560 if( point == bus_entry->GetPosition() )
1561 bus_entry->m_connected_bus_items[0] = busLine;
1562 else
1563 bus_entry->m_connected_bus_items[1] = busLine;
1564
1565 bus_entry->AddConnectionTo( aSheet, busLine );
1566 busLine->AddConnectionTo( aSheet, bus_entry );
1567 continue;
1568 }
1569 }
1570 // Change junctions to be on bus junction layer if they are touching a bus
1571 else if( connected_item->Type() == SCH_JUNCTION_T )
1572 {
1573 connected_item->SetLayer( busLine ? LAYER_BUS_JUNCTION : LAYER_JUNCTION );
1574 }
1575
1576 for( SCH_ITEM* test_item : connection_vec )
1577 {
1578 bool bus_connection_ok = true;
1579
1580 if( test_item == connected_item )
1581 continue;
1582
1583 // Set up the link between the bus entry net and the bus
1584 if( connected_item->Type() == SCH_BUS_WIRE_ENTRY_T )
1585 {
1586 if( test_item->GetLayer() == LAYER_BUS )
1587 {
1588 auto bus_entry = static_cast<SCH_BUS_WIRE_ENTRY*>( connected_item );
1589 bus_entry->m_connected_bus_item = test_item;
1590 }
1591 }
1592
1593 // Bus entries only connect to bus lines on the end that is touching a bus line.
1594 // If the user has overlapped another net line with the endpoint of the bus entry
1595 // where the entry connects to a bus, we don't want to short-circuit it.
1596 if( connected_item->Type() == SCH_BUS_WIRE_ENTRY_T )
1597 {
1598 bus_connection_ok = !busLine || test_item->GetLayer() == LAYER_BUS;
1599 }
1600 else if( test_item->Type() == SCH_BUS_WIRE_ENTRY_T )
1601 {
1602 bus_connection_ok = !busLine || connected_item->GetLayer() == LAYER_BUS;
1603 }
1604
1605 if( connected_item->ConnectionPropagatesTo( test_item )
1606 && test_item->ConnectionPropagatesTo( connected_item )
1607 && bus_connection_ok )
1608 {
1609 connected_item->AddConnectionTo( aSheet, test_item );
1610 }
1611 }
1612
1613 // If we got this far and did not find a connected bus item for a bus entry,
1614 // we should do a manual scan in case there is a bus item on this connection
1615 // point but we didn't pick it up earlier because there is *also* a net item here.
1616 if( connected_item->Type() == SCH_BUS_WIRE_ENTRY_T )
1617 {
1618 auto bus_entry = static_cast<SCH_BUS_WIRE_ENTRY*>( connected_item );
1619
1620 if( !bus_entry->m_connected_bus_item )
1621 {
1622 SCH_SCREEN* screen = aSheet.LastScreen();
1623 SCH_LINE* bus = screen->GetBus( point );
1624
1625 if( bus )
1626 bus_entry->m_connected_bus_item = bus;
1627 }
1628 }
1629 }
1630 }
1631}
1632
1633
1635{
1636 // Recache all bus aliases for later use
1637 wxCHECK_RET( m_schematic, wxS( "Connection graph cannot be built without schematic pointer" ) );
1638
1639 m_bus_alias_cache.clear();
1640
1641 for( const std::shared_ptr<BUS_ALIAS>& alias : m_schematic->GetAllBusAliases() )
1642 {
1643 if( alias )
1644 m_bus_alias_cache[alias->GetName()] = alias;
1645 }
1646
1647 // Hash position in m_sheetList for each sheet path so that subgraphs are
1648 // created in a deterministic order matching the sheet hierarchy
1649 // https://gitlab.com/kicad/code/kicad/-/issues/24409
1650 std::unordered_map<SCH_SHEET_PATH, size_t> sheetOrder;
1651 sheetOrder.reserve( m_sheetList.size() );
1652
1653 for( size_t i = 0; i < m_sheetList.size(); ++i )
1654 sheetOrder.emplace( m_sheetList[i], i );
1655
1656 auto sheetRank =
1657 [&]( const SCH_SHEET_PATH& aSheet ) -> size_t
1658 {
1659 auto it = sheetOrder.find( aSheet );
1660
1661 return ( it == sheetOrder.end() ) ? std::numeric_limits<size_t>::max()
1662 : it->second;
1663 };
1664
1665 // Build subgraphs from items (on a per-sheet basis). Reuse the vector across
1666 // items so a flat schematic doesn't allocate per item.
1667 std::vector<std::tuple<size_t, SCH_SHEET_PATH, SCH_CONNECTION*>> ordered;
1668
1669 for( SCH_ITEM* item : m_items )
1670 {
1671 ordered.clear();
1672 ordered.reserve( item->m_connection_map.size() );
1673
1674 // Precompute sheet rank into the tuple so the comparator never re-hashes
1675 // the (vector-backed) SCH_SHEET_PATH keys.
1676 for( const auto& [sheet, connection] : item->m_connection_map )
1677 ordered.emplace_back( sheetRank( sheet ), sheet, connection );
1678
1679 std::sort( ordered.begin(), ordered.end(),
1680 []( const auto& a, const auto& b )
1681 {
1682 return std::get<0>( a ) < std::get<0>( b );
1683 } );
1684
1685 for( const auto& [rank, sheet, connection] : ordered )
1686 {
1687 if( connection->SubgraphCode() == 0 )
1688 {
1689 CONNECTION_SUBGRAPH* subgraph = new CONNECTION_SUBGRAPH( this );
1690
1691 subgraph->m_code = m_last_subgraph_code++;
1692 subgraph->m_sheet = sheet;
1693
1694 subgraph->AddItem( item );
1695
1696 connection->SetSubgraphCode( subgraph->m_code );
1697 m_item_to_subgraph_map[item].push_back( subgraph );
1698
1699 std::list<SCH_ITEM*> memberlist;
1700
1701 auto get_items =
1702 [&]( SCH_ITEM* aItem ) -> bool
1703 {
1704 SCH_CONNECTION* conn = aItem->GetOrInitConnection( sheet, this );
1705 bool unique = !( aItem->GetFlags() & CONNECTIVITY_CANDIDATE );
1706
1707 if( conn && !conn->SubgraphCode() )
1708 aItem->SetFlags( CONNECTIVITY_CANDIDATE );
1709
1710 return ( unique && conn && ( conn->SubgraphCode() == 0 ) );
1711 };
1712
1713 std::copy_if( item->ConnectedItems( sheet ).begin(),
1714 item->ConnectedItems( sheet ).end(),
1715 std::back_inserter( memberlist ), get_items );
1716
1717 for( SCH_ITEM* connected_item : memberlist )
1718 {
1719 if( connected_item->Type() == SCH_NO_CONNECT_T )
1720 subgraph->m_no_connect = connected_item;
1721
1722 SCH_CONNECTION* connected_conn = connected_item->Connection( &sheet );
1723
1724 wxCHECK2( connected_conn, continue );
1725
1726 if( connected_conn->SubgraphCode() == 0 )
1727 {
1728 connected_conn->SetSubgraphCode( subgraph->m_code );
1729 m_item_to_subgraph_map[connected_item].push_back( subgraph );
1730 subgraph->AddItem( connected_item );
1731
1732 for( SCH_ITEM* citem : connected_item->ConnectedItems( sheet ) )
1733 {
1734 if( citem->HasFlag( CONNECTIVITY_CANDIDATE ) )
1735 continue;
1736
1737 if( get_items( citem ) )
1738 memberlist.push_back( citem );
1739 }
1740 }
1741 }
1742
1743 for( SCH_ITEM* connected_item : memberlist )
1744 connected_item->ClearFlags( CONNECTIVITY_CANDIDATE );
1745
1746 subgraph->m_dirty = true;
1747 m_subgraphs.push_back( subgraph );
1748 }
1749 }
1750 }
1751}
1752
1753
1755{
1756 // Resolve drivers for subgraphs and propagate connectivity info
1757 std::vector<CONNECTION_SUBGRAPH*> dirty_graphs;
1758
1759 std::copy_if( m_subgraphs.begin(), m_subgraphs.end(), std::back_inserter( dirty_graphs ),
1760 [&] ( const CONNECTION_SUBGRAPH* candidate )
1761 {
1762 return candidate->m_dirty;
1763 } );
1764
1765 wxLogTrace( ConnTrace, wxT( "Resolving drivers for %zu subgraphs" ), dirty_graphs.size() );
1766
1767 std::vector<std::future<size_t>> returns( dirty_graphs.size() );
1768
1769 auto update_lambda =
1770 []( CONNECTION_SUBGRAPH* subgraph ) -> size_t
1771 {
1772 if( !subgraph->m_dirty )
1773 return 0;
1774
1775 // Special processing for some items
1776 for( SCH_ITEM* item : subgraph->m_items )
1777 {
1778 switch( item->Type() )
1779 {
1780 case SCH_NO_CONNECT_T:
1781 subgraph->m_no_connect = item;
1782 break;
1783
1785 subgraph->m_bus_entry = item;
1786 break;
1787
1788 case SCH_PIN_T:
1789 {
1790 auto pin = static_cast<SCH_PIN*>( item );
1791
1792 if( pin->GetType() == ELECTRICAL_PINTYPE::PT_NC )
1793 subgraph->m_no_connect = item;
1794
1795 break;
1796 }
1797
1798 default:
1799 break;
1800 }
1801 }
1802
1803 subgraph->ResolveDrivers( true );
1804 subgraph->m_dirty = false;
1805
1806 return 1;
1807 };
1808
1810
1811 auto results = tp.submit_loop( 0, dirty_graphs.size(),
1812 [&]( const int ii )
1813 {
1814 update_lambda( dirty_graphs[ii] );
1815 } );
1816 results.wait();
1817
1818 // Now discard any non-driven subgraphs from further consideration
1819
1820 std::copy_if( m_subgraphs.begin(), m_subgraphs.end(), std::back_inserter( m_driver_subgraphs ),
1821 [&] ( const CONNECTION_SUBGRAPH* candidate ) -> bool
1822 {
1823 return candidate->m_driver;
1824 } );
1825}
1826
1827
1829{
1830 // Check for subgraphs with the same net name but only weak drivers.
1831 // For example, two wires that are both connected to hierarchical
1832 // sheet pins that happen to have the same name, but are not the same.
1833
1834 for( auto&& subgraph : m_driver_subgraphs )
1835 {
1836 wxString full_name = subgraph->m_driver_connection->Name();
1837 wxString name = subgraph->m_driver_connection->Name( true );
1838 m_net_name_to_subgraphs_map[full_name].emplace_back( subgraph );
1839
1840 // For vector buses, we need to cache the prefix also, as two different instances of the
1841 // weakly driven pin may have the same prefix but different vector start and end. We need
1842 // to treat those as needing renaming also, because otherwise if they end up on a sheet with
1843 // common usage, they will be incorrectly merged.
1844 if( subgraph->m_driver_connection->Type() == CONNECTION_TYPE::BUS )
1845 {
1846 wxString prefixOnly = full_name.BeforeFirst( '[' ) + wxT( "[]" );
1847 m_net_name_to_subgraphs_map[prefixOnly].emplace_back( subgraph );
1848 }
1849
1850 subgraph->m_dirty = true;
1851
1852 if( subgraph->m_strong_driver )
1853 {
1854 SCH_ITEM* driver = subgraph->m_driver;
1855 SCH_SHEET_PATH sheet = subgraph->m_sheet;
1856
1857 switch( driver->Type() )
1858 {
1859 case SCH_LABEL_T:
1860 case SCH_HIER_LABEL_T:
1861 {
1862 m_local_label_cache[std::make_pair( sheet, name )].push_back( subgraph );
1863 break;
1864 }
1865 case SCH_GLOBAL_LABEL_T:
1866 {
1867 m_global_label_cache[name].push_back( subgraph );
1868 break;
1869 }
1870 case SCH_PIN_T:
1871 {
1872 SCH_PIN* pin = static_cast<SCH_PIN*>( driver );
1873 if( pin->IsGlobalPower() )
1874 {
1875 m_global_label_cache[name].push_back( subgraph );
1876 }
1877 else if( pin->IsLocalPower() )
1878 {
1879 m_local_label_cache[std::make_pair( sheet, name )].push_back( subgraph );
1880 }
1881 else
1882 {
1883 UNITS_PROVIDER unitsProvider( schIUScale, EDA_UNITS::MM );
1884 wxLogTrace( ConnTrace, wxS( "Unexpected normal pin %s" ),
1885 driver->GetItemDescription( &unitsProvider, true ) );
1886 }
1887
1888 break;
1889 }
1890 default:
1891 {
1892 UNITS_PROVIDER unitsProvider( schIUScale, EDA_UNITS::MM );
1893
1894 wxLogTrace( ConnTrace, wxS( "Unexpected strong driver %s" ),
1895 driver->GetItemDescription( &unitsProvider, true ) );
1896 break;
1897 }
1898 }
1899 }
1900 }
1901}
1902
1903
1905{
1906 std::vector<CONNECTION_SUBGRAPH*> new_subgraphs;
1907
1908 for( CONNECTION_SUBGRAPH* subgraph : m_driver_subgraphs )
1909 {
1910 for( SCH_ITEM* item : subgraph->GetAllBusLabels() )
1911 {
1912 SCH_LABEL_BASE* label = static_cast<SCH_LABEL_BASE*>( item );
1913
1914 SCH_CONNECTION dummy( item, subgraph->m_sheet );
1915 dummy.SetGraph( this );
1916 dummy.ConfigureFromLabel( label->GetShownText( &subgraph->m_sheet, false ) );
1917
1918 wxLogTrace( ConnTrace, wxS( "new bus label (%s)" ),
1919 label->GetShownText( &subgraph->m_sheet, false ) );
1920
1921 for( const auto& conn : dummy.Members() )
1922 {
1923 // Only create subgraphs for NET members, not nested buses
1924 if( !conn->IsNet() )
1925 continue;
1926
1927 wxString name = conn->FullLocalName();
1928
1929 CONNECTION_SUBGRAPH* new_sg = new CONNECTION_SUBGRAPH( this );
1930
1931 // This connection cannot form a part of the item because the item is not, itself
1932 // connected to this subgraph. It exists as part of a virtual item that may be
1933 // connected to other items but is not in the schematic.
1934 auto new_conn = std::make_unique<SCH_CONNECTION>( item, subgraph->m_sheet );
1935 new_conn->SetGraph( this );
1936 new_conn->SetName( name );
1937 new_conn->SetType( CONNECTION_TYPE::NET );
1938
1939 SCH_CONNECTION* new_conn_ptr = subgraph->StoreImplicitConnection( std::move( new_conn ) );
1940 int code = assignNewNetCode( *new_conn_ptr );
1941
1942 wxLogTrace( ConnTrace, wxS( "SG(%ld), Adding full local name (%s) with sg (%d) on subsheet %s" ),
1943 subgraph->m_code, name, code, subgraph->m_sheet.PathHumanReadable() );
1944
1945 new_sg->m_driver_connection = new_conn_ptr;
1946 new_sg->m_code = m_last_subgraph_code++;
1947 new_sg->m_sheet = subgraph->GetSheet();
1948 new_sg->m_is_bus_member = true;
1949 new_sg->m_strong_driver = true;
1950
1952 NET_NAME_CODE_CACHE_KEY key = { new_sg->GetNetName(), code };
1953 m_net_code_to_subgraphs_map[ key ].push_back( new_sg );
1954 m_net_name_to_subgraphs_map[ name ].push_back( new_sg );
1955 m_subgraphs.push_back( new_sg );
1956 new_subgraphs.push_back( new_sg );
1957 }
1958 }
1959 }
1960
1961 std::copy( new_subgraphs.begin(), new_subgraphs.end(),
1962 std::back_inserter( m_driver_subgraphs ) );
1963}
1964
1965
1967{
1968 // Generate subgraphs for global power pins. These will be merged with other subgraphs
1969 // on the same sheet in the next loop.
1970 // These are NOT limited to power symbols, we support legacy invisible + power-in pins
1971 // on non-power symbols.
1972
1973 // Sort power pins for deterministic processing order. This ensures that when multiple
1974 // power pins share the same net name, the same pin consistently creates the subgraph
1975 // across different ERC runs.
1976 std::sort( m_global_power_pins.begin(), m_global_power_pins.end(),
1977 []( const std::pair<SCH_SHEET_PATH, SCH_PIN*>& a,
1978 const std::pair<SCH_SHEET_PATH, SCH_PIN*>& b )
1979 {
1980 int pathCmp = a.first.Cmp( b.first );
1981
1982 if( pathCmp != 0 )
1983 return pathCmp < 0;
1984
1985 const SCH_SYMBOL* symA = static_cast<const SCH_SYMBOL*>( a.second->GetParentSymbol() );
1986 const SCH_SYMBOL* symB = static_cast<const SCH_SYMBOL*>( b.second->GetParentSymbol() );
1987
1988 wxString refA = symA ? symA->GetRef( &a.first, false ) : wxString();
1989 wxString refB = symB ? symB->GetRef( &b.first, false ) : wxString();
1990
1991 int refCmp = refA.Cmp( refB );
1992
1993 if( refCmp != 0 )
1994 return refCmp < 0;
1995
1996 return a.second->GetNumber().Cmp( b.second->GetNumber() ) < 0;
1997 } );
1998
1999 std::unordered_map<int, CONNECTION_SUBGRAPH*> global_power_pin_subgraphs;
2000
2001 for( const auto& [sheet, pin] : m_global_power_pins )
2002 {
2003 SYMBOL* libParent = pin->GetLibPin() ? pin->GetLibPin()->GetParentSymbol() : nullptr;
2004
2005 if( !pin->ConnectedItems( sheet ).empty()
2006 && ( !libParent || !libParent->IsGlobalPower() ) )
2007 {
2008 // ERC will warn about this: user has wired up an invisible pin
2009 continue;
2010 }
2011
2012 SCH_CONNECTION* connection = pin->GetOrInitConnection( sheet, this );
2013
2014 // If this pin already has a subgraph, don't need to process
2015 if( !connection || connection->SubgraphCode() > 0 )
2016 continue;
2017
2018 // Proper modern power symbols get their net name from the value field
2019 // in the symbol, but we support legacy non-power symbols with global
2020 // power connections based on invisible, power-in, pin's names.
2021 if( libParent && libParent->IsGlobalPower() )
2022 connection->SetName( pin->GetParentSymbol()->GetValue( true, &sheet, false ) );
2023 else
2024 connection->SetName( pin->GetShownName() );
2025
2026 int code = assignNewNetCode( *connection );
2027
2028 connection->SetNetCode( code );
2029
2030 CONNECTION_SUBGRAPH* subgraph;
2031 auto jj = global_power_pin_subgraphs.find( code );
2032
2033 if( jj != global_power_pin_subgraphs.end() )
2034 {
2035 subgraph = jj->second;
2036 subgraph->AddItem( pin );
2037 }
2038 else
2039 {
2040 subgraph = new CONNECTION_SUBGRAPH( this );
2041
2042 subgraph->m_code = m_last_subgraph_code++;
2043 subgraph->m_sheet = sheet;
2044
2045 subgraph->AddItem( pin );
2046 subgraph->ResolveDrivers();
2047
2048 NET_NAME_CODE_CACHE_KEY key = { subgraph->GetNetName(), code };
2049 m_net_code_to_subgraphs_map[ key ].push_back( subgraph );
2050 m_subgraphs.push_back( subgraph );
2051 m_driver_subgraphs.push_back( subgraph );
2052
2053 global_power_pin_subgraphs[code] = subgraph;
2054 }
2055
2056 connection->SetSubgraphCode( subgraph->m_code );
2057 }
2058}
2059
2060
2062{
2063 // Here we do all the local (sheet) processing of each subgraph, including assigning net
2064 // codes, merging subgraphs together that use label connections, etc.
2065
2066 // Cache remaining valid subgraphs by sheet path
2067 for( CONNECTION_SUBGRAPH* subgraph : m_driver_subgraphs )
2068 m_sheet_to_subgraphs_map[ subgraph->m_sheet ].emplace_back( subgraph );
2069
2070 std::unordered_set<CONNECTION_SUBGRAPH*> invalidated_subgraphs;
2071
2072 for( CONNECTION_SUBGRAPH* subgraph : m_driver_subgraphs )
2073 {
2074 if( subgraph->m_absorbed )
2075 continue;
2076
2077 SCH_CONNECTION* connection = subgraph->m_driver_connection;
2078 SCH_SHEET_PATH sheet = subgraph->m_sheet;
2079 wxString name = connection->Name();
2080
2081 // Test subgraphs with weak drivers for net name conflicts and fix them
2082 unsigned suffix = 1;
2083
2084 wxString base_name = connection->Name();
2085
2086 auto create_new_name =
2087 [&suffix, &base_name]( SCH_CONNECTION* aConn ) -> wxString
2088 {
2089 wxString suffixStr = std::to_wstring( suffix );
2090
2091 // For group buses with a prefix, we can add the suffix to the prefix.
2092 // If they don't have a prefix, we force the creation of a prefix so that
2093 // two buses don't get inadvertently shorted together.
2094 if( aConn->Type() == CONNECTION_TYPE::BUS_GROUP )
2095 {
2096 wxString prefix = aConn->BusPrefix();
2097
2098 if( prefix.empty() )
2099 prefix = wxT( "BUS" ); // So result will be "BUS_1{...}"
2100
2101 // Use BusPrefix length to skip past any formatting markers
2102 // in the prefix (e.g. ~{RESET}) rather than AfterFirst('{')
2103 // which would split at a formatting brace.
2104 wxString members = base_name.Mid( aConn->BusPrefix().length() );
2105
2106 wxString newName;
2107 newName << prefix << wxT( "_" ) << suffixStr << members;
2108
2109 aConn->ConfigureFromLabel( newName );
2110 }
2111 else
2112 {
2113 // Reset to the unsuffixed base so retries generate base_1, base_2, ...
2114 // instead of stacking suffixes onto the previous attempt.
2115 aConn->SetSuffix( wxString( wxT( "_" ) ) << suffixStr );
2116 }
2117
2118 suffix++;
2119 return aConn->Name();
2120 };
2121
2122 // Promote a weakly-driven sheet-pin subgraph to a strong driver so that it is considered
2123 // below for propagation/merging. A sheet pin sharing its (path-less) name with a global
2124 // label on the same sheet would then be treated as if it had a matching local label, so we
2125 // skip the promotion in that case to avoid a false merge.
2126 auto promote_sheet_pin_driver =
2127 [&]()
2128 {
2129 if( !subgraph->m_driver || subgraph->m_driver->Type() != SCH_SHEET_PIN_T )
2130 return;
2131
2132 wxString global_name = connection->Name( true );
2133 auto kk = m_net_name_to_subgraphs_map.find( global_name );
2134
2135 if( kk != m_net_name_to_subgraphs_map.end() )
2136 {
2137 for( const CONNECTION_SUBGRAPH* candidate : kk->second )
2138 {
2139 if( candidate->m_sheet == sheet )
2140 {
2141 wxLogTrace( ConnTrace,
2142 wxS( "%ld (%s) skipped for promotion due to potential conflict" ),
2143 subgraph->m_code, connection->Name() );
2144 return;
2145 }
2146 }
2147 }
2148
2149 subgraph->m_strong_driver = true;
2150 };
2151
2152 if( !subgraph->m_strong_driver )
2153 {
2154 std::vector<CONNECTION_SUBGRAPH*> vec_empty;
2155 std::vector<CONNECTION_SUBGRAPH*>* vec = &vec_empty;
2156
2157 if( m_net_name_to_subgraphs_map.count( name ) )
2158 vec = &m_net_name_to_subgraphs_map.at( name );
2159
2160 // If we are a unique bus vector, check if we aren't actually unique because of another
2161 // subgraph with a similar bus vector
2162 if( vec->size() <= 1 && subgraph->m_driver_connection->Type() == CONNECTION_TYPE::BUS )
2163 {
2164 wxString prefixOnly = name.BeforeFirst( '[' ) + wxT( "[]" );
2165
2166 if( m_net_name_to_subgraphs_map.count( prefixOnly ) )
2167 vec = &m_net_name_to_subgraphs_map.at( prefixOnly );
2168 }
2169
2170 if( vec->size() > 1 )
2171 {
2172 wxString new_name = create_new_name( connection );
2173
2174 while( m_net_name_to_subgraphs_map.contains( new_name ) )
2175 new_name = create_new_name( connection );
2176
2177 wxLogTrace( ConnTrace, wxS( "%ld (%s) is weakly driven and not unique. Changing to %s." ),
2178 subgraph->m_code, name, new_name );
2179
2180 std::erase( *vec, subgraph );
2181
2182 m_net_name_to_subgraphs_map[new_name].emplace_back( subgraph );
2183
2184 name = new_name;
2185
2186 // The renamed sheet pin still drives its own bus members through the hierarchy, so
2187 // it must be promoted for propagation to reach them (issue #21798).
2188 promote_sheet_pin_driver();
2189 }
2190 else if( subgraph->m_driver )
2191 {
2192 promote_sheet_pin_driver();
2193 }
2194 }
2195
2196 // Assign net codes
2197 if( connection->IsBus() )
2198 {
2199 int code = -1;
2200 auto it = m_bus_name_to_code_map.find( name );
2201
2202 if( it != m_bus_name_to_code_map.end() )
2203 {
2204 code = it->second;
2205 }
2206 else
2207 {
2208 code = m_last_bus_code++;
2209 m_bus_name_to_code_map[ name ] = code;
2210 }
2211
2212 connection->SetBusCode( code );
2213 assignNetCodesToBus( connection );
2214 }
2215 else
2216 {
2217 assignNewNetCode( *connection );
2218 }
2219
2220 // Reset the flag for the next loop below
2221 subgraph->m_dirty = true;
2222
2223 // Next, we merge together subgraphs that have label connections, and create
2224 // neighbor links for subgraphs that are part of a bus on the same sheet.
2225 // For merging, we consider each possible strong driver.
2226
2227 // If this subgraph doesn't have a strong driver, let's skip it, since there is no
2228 // way it will be merged with anything.
2229 if( !subgraph->m_strong_driver )
2230 continue;
2231
2232 // candidate_subgraphs will contain each valid, non-bus subgraph on the same sheet
2233 // as the subgraph we are considering that has a strong driver.
2234 // Weakly driven subgraphs are not considered since they will never be absorbed or
2235 // form neighbor links.
2236 std::vector<CONNECTION_SUBGRAPH*> candidate_subgraphs;
2237 std::copy_if( m_sheet_to_subgraphs_map[ subgraph->m_sheet ].begin(),
2238 m_sheet_to_subgraphs_map[ subgraph->m_sheet ].end(),
2239 std::back_inserter( candidate_subgraphs ),
2240 [&] ( const CONNECTION_SUBGRAPH* candidate )
2241 {
2242 return ( !candidate->m_absorbed &&
2243 candidate->m_strong_driver &&
2244 candidate != subgraph );
2245 } );
2246
2247 // This is a list of connections on the current subgraph to compare to the
2248 // drivers of each candidate subgraph. If the current subgraph is a bus,
2249 // we should consider each bus member.
2250 std::vector< std::shared_ptr<SCH_CONNECTION> > connections_to_check;
2251
2252 // Also check the main driving connection
2253 connections_to_check.push_back( std::make_shared<SCH_CONNECTION>( *connection ) );
2254
2255 auto add_connections_to_check =
2256 [&] ( CONNECTION_SUBGRAPH* aSubgraph )
2257 {
2258 for( SCH_ITEM* possible_driver : aSubgraph->m_items )
2259 {
2260 if( possible_driver == aSubgraph->m_driver )
2261 continue;
2262
2263 auto c = getDefaultConnection( possible_driver, aSubgraph );
2264
2265 if( c )
2266 {
2267 if( c->Type() != aSubgraph->m_driver_connection->Type() )
2268 continue;
2269
2270 if( c->Name( true ) == aSubgraph->m_driver_connection->Name( true ) )
2271 continue;
2272
2273 connections_to_check.push_back( c );
2274 wxLogTrace( ConnTrace, wxS( "%lu (%s): Adding secondary driver %s" ),
2275 aSubgraph->m_code,
2276 aSubgraph->m_driver_connection->Name( true ),
2277 c->Name( true ) );
2278 }
2279 }
2280 };
2281
2282 // Now add other strong drivers
2283 // The actual connection attached to these items will have been overwritten
2284 // by the chosen driver of the subgraph, so we need to create a dummy connection
2285 add_connections_to_check( subgraph );
2286
2287 std::set<SCH_CONNECTION*> checked_connections;
2288
2289 for( unsigned i = 0; i < connections_to_check.size(); i++ )
2290 {
2291 auto member = connections_to_check[i];
2292
2293 // Don't check the same connection twice
2294 if( !checked_connections.insert( member.get() ).second )
2295 continue;
2296
2297 if( member->IsBus() )
2298 {
2299 connections_to_check.insert( connections_to_check.end(),
2300 member->Members().begin(),
2301 member->Members().end() );
2302 }
2303
2304 wxString test_name = member->Name( true );
2305
2306 for( CONNECTION_SUBGRAPH* candidate : candidate_subgraphs )
2307 {
2308 if( candidate->m_absorbed || candidate == subgraph )
2309 continue;
2310
2311 bool match = false;
2312
2313 if( candidate->m_driver_connection->Name( true ) == test_name )
2314 {
2315 match = true;
2316 }
2317 else
2318 {
2319 if( !candidate->m_multiple_drivers )
2320 continue;
2321
2322 for( SCH_ITEM *driver : candidate->m_drivers )
2323 {
2324 if( driver == candidate->m_driver )
2325 continue;
2326
2327 // Sheet pins are not candidates for merging
2328 if( driver->Type() == SCH_SHEET_PIN_T )
2329 continue;
2330
2331 if( driver->Type() == SCH_PIN_T )
2332 {
2333 auto pin = static_cast<SCH_PIN*>( driver );
2334
2335 if( pin->IsPower()
2336 && pin->GetDefaultNetName( sheet ) == test_name )
2337 {
2338 match = true;
2339 break;
2340 }
2341 }
2342 else
2343 {
2344 // Should we skip this if the driver type is not one of these types?
2345 wxASSERT( driver->Type() == SCH_LABEL_T ||
2346 driver->Type() == SCH_GLOBAL_LABEL_T ||
2347 driver->Type() == SCH_HIER_LABEL_T );
2348
2349 if( subgraph->GetNameForDriver( driver ) == test_name )
2350 {
2351 match = true;
2352 break;
2353 }
2354 }
2355 }
2356 }
2357
2358 if( match )
2359 {
2360 if( connection->IsBus() && candidate->m_driver_connection->IsNet() )
2361 {
2362 wxLogTrace( ConnTrace, wxS( "%lu (%s) has bus child %lu (%s)" ),
2363 subgraph->m_code, connection->Name(),
2364 candidate->m_code, member->Name() );
2365
2366 subgraph->m_bus_neighbors[member].insert( candidate );
2367 candidate->m_bus_parents[member].insert( subgraph );
2368 }
2369 else if( ( !connection->IsBus()
2370 && !candidate->m_driver_connection->IsBus() )
2371 || connection->Type() == candidate->m_driver_connection->Type() )
2372 {
2373 wxLogTrace( ConnTrace, wxS( "%lu (%s) absorbs neighbor %lu (%s)" ),
2374 subgraph->m_code, connection->Name(),
2375 candidate->m_code, candidate->m_driver_connection->Name() );
2376
2377 // Candidate may have other non-chosen drivers we need to follow
2378 add_connections_to_check( candidate );
2379
2380 subgraph->Absorb( candidate );
2381 invalidated_subgraphs.insert( subgraph );
2382 }
2383 }
2384 }
2385 }
2386 }
2387
2388 // Update any subgraph that was invalidated above
2389 for( CONNECTION_SUBGRAPH* subgraph : invalidated_subgraphs )
2390 {
2391 if( subgraph->m_absorbed )
2392 continue;
2393
2394 if( !subgraph->ResolveDrivers() )
2395 continue;
2396
2397 if( subgraph->m_driver_connection->IsBus() )
2398 assignNetCodesToBus( subgraph->m_driver_connection );
2399 else
2400 assignNewNetCode( *subgraph->m_driver_connection );
2401
2402 wxLogTrace( ConnTrace, wxS( "Re-resolving drivers for %lu (%s)" ),
2403 subgraph->m_code, subgraph->m_driver_connection->Name() );
2404 }
2405
2406}
2407
2408
2409// TODO(JE) This won't give the same subgraph IDs (and eventually net/graph codes)
2410// to the same subgraph necessarily if it runs over and over again on the same
2411// sheet. We need:
2412//
2413// a) a cache of net/bus codes, like used before
2414// b) to persist the CONNECTION_GRAPH globally so the cache is persistent,
2415// c) some way of trying to avoid changing net names. so we should keep track
2416// of the previous driver of a net, and if it comes down to choosing between
2417// equally-prioritized drivers, choose the one that already exists as a driver
2418// on some portion of the items.
2419
2420
2421void CONNECTION_GRAPH::buildConnectionGraph( std::function<void( SCH_ITEM* )>* aChangedItemHandler,
2422 bool aUnconditional )
2423{
2424 // Recache all bus aliases for later use
2425 wxCHECK_RET( m_schematic, wxT( "Connection graph cannot be built without schematic pointer" ) );
2426
2427 m_bus_alias_cache.clear();
2428
2429 for( const std::shared_ptr<BUS_ALIAS>& alias : m_schematic->GetAllBusAliases() )
2430 {
2431 if( alias )
2432 m_bus_alias_cache[alias->GetName()] = alias;
2433 }
2434
2435 PROF_TIMER sub_graph( "buildItemSubGraphs" );
2437
2438 if( wxLog::IsAllowedTraceMask( DanglingProfileMask ) )
2439 sub_graph.Show();
2440
2445
2447
2449
2451
2453
2454 PROF_TIMER proc_sub_graph( "ProcessSubGraphs" );
2456
2457 if( wxLog::IsAllowedTraceMask( DanglingProfileMask ) )
2458 proc_sub_graph.Show();
2459
2460 // Absorbed subgraphs should no longer be considered
2461 std::erase_if( m_driver_subgraphs, [&]( const CONNECTION_SUBGRAPH* candidate ) -> bool
2462 {
2463 return candidate->m_absorbed;
2464 } );
2465
2466 // Store global subgraphs for later reference
2467 std::vector<CONNECTION_SUBGRAPH*> global_subgraphs;
2468 std::copy_if( m_driver_subgraphs.begin(), m_driver_subgraphs.end(),
2469 std::back_inserter( global_subgraphs ),
2470 [&] ( const CONNECTION_SUBGRAPH* candidate ) -> bool
2471 {
2472 return !candidate->m_local_driver;
2473 } );
2474
2475 // Recache remaining valid subgraphs by sheet path
2477
2478 for( CONNECTION_SUBGRAPH* subgraph : m_driver_subgraphs )
2479 m_sheet_to_subgraphs_map[ subgraph->m_sheet ].emplace_back( subgraph );
2480
2482
2483 auto results = tp.submit_loop( 0, m_driver_subgraphs.size(),
2484 [&]( const int ii )
2485 {
2486 m_driver_subgraphs[ii]->UpdateItemConnections();
2487 });
2488
2489 results.wait();
2490
2491 // Build equivalence classes over global subgraphs that are linked by shared
2492 // global label names. Two global subgraphs are in the same class whenever
2493 // (transitively) some subgraph has a global driver named X and another
2494 // subgraph has a global driver also named X, OR a single multi-driver
2495 // subgraph has both X and Y as global drivers.
2496 //
2497 // This is the transitive closure over the relation "shares a global name".
2498 // When users chain nets across sheets via differently-named global labels,
2499 // every subgraph reachable through any sequence of shared names must end
2500 // up on the same final net.
2501 //
2502 // The per-subgraph promote pass that follows is order-dependent and walks
2503 // candidates by their *original* driver text rather than by their already-
2504 // promoted name. As a result, when subgraph S2 promotes subgraph S1 to a
2505 // new name, and then a third subgraph S3 later renames S2 again, S1 is
2506 // left orphaned with the intermediate name. This pre-pass solves the
2507 // transitivity problem before the order-dependent loop runs (issue 23719).
2508 if( !global_subgraphs.empty() )
2509 {
2510 std::unordered_map<CONNECTION_SUBGRAPH*, CONNECTION_SUBGRAPH*> sg_root;
2511
2512 auto find_sg_root =
2514 {
2515 CONNECTION_SUBGRAPH* cur = aSg;
2516
2517 while( true )
2518 {
2519 auto it = sg_root.find( cur );
2520
2521 if( it == sg_root.end() || it->second == cur )
2522 return cur;
2523
2524 // Path compression. Hop the current node directly to its
2525 // grandparent on the way up so subsequent finds are O(1).
2526 auto parent_it = sg_root.find( it->second );
2527
2528 if( parent_it != sg_root.end() && parent_it->second != it->second )
2529 it->second = parent_it->second;
2530
2531 cur = it->second;
2532 }
2533 };
2534
2535 // Pick the subgraph whose primary driver the file-local compareDrivers helper
2536 // would rank first. Using the same helper as CONNECTION_SUBGRAPH::ResolveDrivers
2537 // guarantees both sites agree on every tie-break rule (priority, bus width,
2538 // pin power parent, sheet-pin shape, -Pad demotion, alphabetical).
2539 auto prefer_as_representative =
2540 [&]( CONNECTION_SUBGRAPH* aA, CONNECTION_SUBGRAPH* aB ) -> bool
2541 {
2544 aB->m_driver, aB->m_driver_connection,
2545 aB->m_driver_connection->Name() ) < 0;
2546 };
2547
2548 auto union_sgs =
2550 {
2551 sg_root.try_emplace( aA, aA );
2552 sg_root.try_emplace( aB, aB );
2553
2554 CONNECTION_SUBGRAPH* root_a = find_sg_root( aA );
2555 CONNECTION_SUBGRAPH* root_b = find_sg_root( aB );
2556
2557 if( root_a == root_b )
2558 return;
2559
2560 if( prefer_as_representative( root_a, root_b ) )
2561 sg_root[root_b] = root_a;
2562 else
2563 sg_root[root_a] = root_b;
2564 };
2565
2566 std::unordered_map<wxString, std::vector<CONNECTION_SUBGRAPH*>> name_to_sgs;
2567
2568 for( CONNECTION_SUBGRAPH* subgraph : global_subgraphs )
2569 {
2570 for( SCH_ITEM* driver : subgraph->m_drivers )
2571 {
2574 {
2575 continue;
2576 }
2577
2578 name_to_sgs[subgraph->GetNameForDriver( driver )].push_back( subgraph );
2579 }
2580 }
2581
2582 for( auto& [name, sgs] : name_to_sgs )
2583 {
2584 if( sgs.size() < 2 )
2585 continue;
2586
2587 for( size_t ii = 1; ii < sgs.size(); ++ii )
2588 union_sgs( sgs[0], sgs[ii] );
2589 }
2590
2591 // Every subgraph in sg_root now maps (with path compression) to the
2592 // representative of its equivalence class. Clone the representative's
2593 // connection into each member that currently differs.
2594 for( const auto& entry : sg_root )
2595 {
2596 CONNECTION_SUBGRAPH* sg = entry.first;
2597 CONNECTION_SUBGRAPH* root = find_sg_root( sg );
2598
2599 if( sg == root )
2600 continue;
2601
2602 if( sg->m_driver_connection->Name() == root->m_driver_connection->Name() )
2603 continue;
2604
2605 wxLogTrace( ConnTrace, wxS( "Global %lu (%s) canonicalized to %lu (%s)" ),
2606 sg->m_code, sg->m_driver_connection->Name(), root->m_code,
2607 root->m_driver_connection->Name() );
2608
2610 }
2611 }
2612
2613 // Next time through the subgraphs, we do some post-processing to handle things like
2614 // connecting bus members to their neighboring subgraphs, and then propagate connections
2615 // through the hierarchy
2616 for( CONNECTION_SUBGRAPH* subgraph : m_driver_subgraphs )
2617 {
2618 if( !subgraph->m_dirty )
2619 continue;
2620
2621 wxLogTrace( ConnTrace, wxS( "Processing %lu (%s) for propagation" ),
2622 subgraph->m_code, subgraph->m_driver_connection->Name() );
2623
2624 // For subgraphs that are driven by a global (power port or label) and have more
2625 // than one global driver, we need to seek out other subgraphs driven by the
2626 // same name as the non-chosen driver and update them to match the chosen one.
2627
2628 if( !subgraph->m_local_driver && subgraph->m_multiple_drivers )
2629 {
2630 for( SCH_ITEM* driver : subgraph->m_drivers )
2631 {
2632 if( driver == subgraph->m_driver )
2633 continue;
2634
2635 const wxString& secondary_name = subgraph->GetNameForDriver( driver );
2636
2637 if( secondary_name == subgraph->m_driver_connection->Name() )
2638 continue;
2639
2640 bool secondary_is_global = CONNECTION_SUBGRAPH::GetDriverPriority( driver )
2642
2643 for( CONNECTION_SUBGRAPH* candidate : global_subgraphs )
2644 {
2645 if( candidate == subgraph )
2646 continue;
2647
2648 if( !secondary_is_global && candidate->m_sheet != subgraph->m_sheet )
2649 continue;
2650
2651 for( SCH_ITEM* candidate_driver : candidate->m_drivers )
2652 {
2653 if( candidate->GetNameForDriver( candidate_driver ) == secondary_name )
2654 {
2655 wxLogTrace( ConnTrace, wxS( "Global %lu (%s) promoted to %s" ),
2656 candidate->m_code, candidate->m_driver_connection->Name(),
2657 subgraph->m_driver_connection->Name() );
2658
2659 candidate->m_driver_connection->Clone( *subgraph->m_driver_connection );
2660
2661 candidate->m_dirty = false;
2662 propagateToNeighbors( candidate, false );
2663 }
2664 }
2665 }
2666 }
2667 }
2668
2669 // This call will handle descending the hierarchy and updating child subgraphs
2670 propagateToNeighbors( subgraph, false );
2671 }
2672
2673 // After processing and allowing some to be skipped if they have hierarchical
2674 // pins connecting both up and down the hierarchy, we check to see if any of them
2675 // have not been processed. This would indicate that they do not have off-sheet connections
2676 // but we still need to handle the subgraph
2677 for( CONNECTION_SUBGRAPH* subgraph : m_driver_subgraphs )
2678 {
2679 if( subgraph->m_dirty )
2680 propagateToNeighbors( subgraph, true );
2681 }
2682
2683 // Handle buses that have been linked together somewhere by member (net) connections.
2684 // This feels a bit hacky, perhaps this algorithm should be revisited in the future.
2685
2686 // For net subgraphs that have more than one bus parent, we need to ensure that those
2687 // buses are linked together in the final netlist. The final name of each bus might not
2688 // match the local name that was used to establish the parent-child relationship, because
2689 // the bus may have been renamed by a hierarchical connection. So, for each of these cases,
2690 // we need to identify the appropriate bus members to link together (and their final names),
2691 // and then update all instances of the old name in the hierarchy.
2692 for( CONNECTION_SUBGRAPH* subgraph : m_driver_subgraphs )
2693 {
2694 // All SGs should have been processed by propagateToNeighbors above
2695 // Should we skip all of this if the subgraph is not dirty?
2696 wxASSERT_MSG( !subgraph->m_dirty,
2697 wxS( "Subgraph not processed by propagateToNeighbors!" ) );
2698
2699 if( subgraph->m_bus_parents.size() < 2 )
2700 continue;
2701
2702 SCH_CONNECTION* conn = subgraph->m_driver_connection;
2703
2704 wxLogTrace( ConnTrace, wxS( "%lu (%s) has multiple bus parents" ),
2705 subgraph->m_code, conn->Name() );
2706
2707 // Should we skip everything after this if this is not a net?
2708 wxCHECK2( conn->IsNet(), continue );
2709
2710 for( const auto& ii : subgraph->m_bus_parents )
2711 {
2712 SCH_CONNECTION* link_member = ii.first.get();
2713
2714 for( CONNECTION_SUBGRAPH* parent : ii.second )
2715 {
2716 while( parent->m_absorbed )
2717 parent = parent->m_absorbed_by;
2718
2719 SCH_CONNECTION* match = matchBusMember( parent->m_driver_connection, link_member );
2720
2721 if( !match )
2722 {
2723 wxLogTrace( ConnTrace, wxS( "Warning: could not match %s inside %lu (%s)" ),
2724 conn->Name(), parent->m_code, parent->m_driver_connection->Name() );
2725 continue;
2726 }
2727
2728 if( conn->Name() != match->Name() )
2729 {
2730 wxString old_name = match->Name();
2731
2732 wxLogTrace( ConnTrace, wxS( "Updating %lu (%s) member %s to %s" ),
2733 parent->m_code, parent->m_driver_connection->Name(), old_name, conn->Name() );
2734
2735 match->Clone( *conn );
2736
2737 auto jj = m_net_name_to_subgraphs_map.find( old_name );
2738
2739 if( jj == m_net_name_to_subgraphs_map.end() )
2740 continue;
2741
2742 // Copy the vector to avoid iterator invalidation when recaching
2743 std::vector<CONNECTION_SUBGRAPH*> old_subgraphs = jj->second;
2744
2745 for( CONNECTION_SUBGRAPH* old_sg : old_subgraphs )
2746 {
2747 while( old_sg->m_absorbed )
2748 old_sg = old_sg->m_absorbed_by;
2749
2750 wxString old_sg_name = old_sg->m_driver_connection->Name();
2751 old_sg->m_driver_connection->Clone( *conn );
2752
2753 if( old_sg_name != old_sg->m_driver_connection->Name() )
2754 recacheSubgraphName( old_sg, old_sg_name );
2755 }
2756 }
2757 }
2758 }
2759 }
2760
2761 // Phase 1: write each subgraph's items' connections. Items can be referenced from
2762 // other subgraphs (via labels), so phase 2 below has to wait for every phase 1 task
2763 // to complete before reading anything through label->Connection().
2764 auto propagateConnectionsTask =
2765 [&]( CONNECTION_SUBGRAPH* subgraph )
2766 {
2767 // Make sure weakly-driven single-pin nets get the unconnected_ prefix
2768 if( !subgraph->m_strong_driver
2769 && subgraph->m_drivers.size() == 1
2770 && subgraph->m_driver->Type() == SCH_PIN_T )
2771 {
2772 SCH_PIN* pin = static_cast<SCH_PIN*>( subgraph->m_driver );
2773 wxString name = pin->GetDefaultNetName( subgraph->m_sheet, true );
2774
2775 subgraph->m_driver_connection->ConfigureFromLabel( name );
2776 }
2777
2778 subgraph->m_dirty = false;
2779 subgraph->UpdateItemConnections();
2780 };
2781
2782 auto results1 = tp.submit_loop( 0, m_driver_subgraphs.size(),
2783 [&]( const int ii )
2784 {
2785 propagateConnectionsTask( m_driver_subgraphs[ii] );
2786 } );
2787 results1.wait();
2788
2789 // Phase 2: promote sheet-pin subgraphs to buses based on the matching child-sheet
2790 // hier label. This reads other subgraphs' connections via label->Connection() and
2791 // also writes subgraph->m_driver_connection->SetType, so it has to be serial.
2792 for( CONNECTION_SUBGRAPH* subgraph : m_driver_subgraphs )
2793 {
2794 if( subgraph->m_driver_connection->IsBus() )
2795 continue;
2796
2797 if( !subgraph->m_driver || subgraph->m_driver->Type() != SCH_SHEET_PIN_T )
2798 continue;
2799
2800 SCH_SHEET_PIN* pin = static_cast<SCH_SHEET_PIN*>( subgraph->m_driver );
2801 SCH_SHEET* sheet = pin->GetParent();
2802
2803 if( !sheet )
2804 continue;
2805
2806 wxString pinText = pin->GetShownText( false );
2807 SCH_SCREEN* screen = sheet->GetScreen();
2808
2809 for( SCH_ITEM* item : screen->Items().OfType( SCH_HIER_LABEL_T ) )
2810 {
2811 SCH_HIERLABEL* label = static_cast<SCH_HIERLABEL*>( item );
2812
2813 if( label->GetShownText( &subgraph->m_sheet, false ) == pinText )
2814 {
2815 SCH_SHEET_PATH path = subgraph->m_sheet;
2816 path.push_back( sheet );
2817
2818 SCH_CONNECTION* parent_conn = label->Connection( &path );
2819
2820 if( parent_conn && parent_conn->IsBus() )
2821 subgraph->m_driver_connection->SetType( CONNECTION_TYPE::BUS );
2822
2823 break;
2824 }
2825 }
2826 }
2827
2830
2831 for( CONNECTION_SUBGRAPH* subgraph : m_driver_subgraphs )
2832 {
2833 NET_NAME_CODE_CACHE_KEY key = { subgraph->GetNetName(),
2834 subgraph->m_driver_connection->NetCode() };
2835 m_net_code_to_subgraphs_map[ key ].push_back( subgraph );
2836
2837 m_net_name_to_subgraphs_map[subgraph->m_driver_connection->Name()].push_back( subgraph );
2838 }
2839
2840 std::shared_ptr<NET_SETTINGS>& netSettings = m_schematic->Project().GetProjectFile().m_NetSettings;
2841 std::map<wxString, std::set<wxString>> oldAssignments = netSettings->GetNetclassLabelAssignments();
2842 std::set<wxString> affectedNetclassNetAssignments;
2843
2844 netSettings->ClearNetclassLabelAssignments();
2845
2846 auto dirtySubgraphs =
2847 [&]( const std::vector<CONNECTION_SUBGRAPH*>& subgraphs )
2848 {
2849 if( aChangedItemHandler )
2850 {
2851 for( const CONNECTION_SUBGRAPH* subgraph : subgraphs )
2852 {
2853 for( SCH_ITEM* item : subgraph->m_items )
2854 (*aChangedItemHandler)( item );
2855 }
2856 }
2857 };
2858
2859 auto checkNetclassDrivers =
2860 [&]( const wxString& netName, const std::vector<CONNECTION_SUBGRAPH*>& subgraphs )
2861 {
2862 wxCHECK_RET( !subgraphs.empty(), wxS( "Invalid empty subgraph" ) );
2863
2864 std::set<wxString> netclasses;
2865
2866 // Collect all netclasses on all subgraphs for this net
2867 for( const CONNECTION_SUBGRAPH* subgraph : subgraphs )
2868 {
2869 for( SCH_ITEM* item : subgraph->m_items )
2870 {
2871 for( const auto& [name, provider] : subgraph->GetNetclassesForDriver( item ) )
2872 netclasses.insert( name );
2873 }
2874 }
2875
2876 // Append the netclasses to any included bus members
2877 for( const CONNECTION_SUBGRAPH* subgraph : subgraphs )
2878 {
2879 if( subgraph->m_driver_connection->IsBus() )
2880 {
2881 auto processBusMember = [&, this]( const SCH_CONNECTION* member )
2882 {
2883 if( !netclasses.empty() )
2884 {
2885 netSettings->AppendNetclassLabelAssignment( member->Name(), netclasses );
2886 }
2887
2888 auto ii = m_net_name_to_subgraphs_map.find( member->Name() );
2889
2890 if( oldAssignments.count( member->Name() ) )
2891 {
2892 if( oldAssignments[member->Name()] != netclasses )
2893 {
2894 affectedNetclassNetAssignments.insert( member->Name() );
2895
2896 if( ii != m_net_name_to_subgraphs_map.end() )
2897 dirtySubgraphs( ii->second );
2898 }
2899 }
2900 else if( !netclasses.empty() )
2901 {
2902 affectedNetclassNetAssignments.insert( member->Name() );
2903
2904 if( ii != m_net_name_to_subgraphs_map.end() )
2905 dirtySubgraphs( ii->second );
2906 }
2907 };
2908
2909 for( const std::shared_ptr<SCH_CONNECTION>& member : subgraph->m_driver_connection->Members() )
2910 {
2911 // Check if this member itself is a bus (which can be the case for vector buses as members
2912 // of a bus, see https://gitlab.com/kicad/code/kicad/-/issues/16545
2913 if( member->IsBus() )
2914 {
2915 for( const std::shared_ptr<SCH_CONNECTION>& nestedMember : member->Members() )
2916 processBusMember( nestedMember.get() );
2917 }
2918 else
2919 {
2920 processBusMember( member.get() );
2921 }
2922 }
2923 }
2924 }
2925
2926 // Assign the netclasses to the root netname
2927 if( !netclasses.empty() )
2928 {
2929 netSettings->AppendNetclassLabelAssignment( netName, netclasses );
2930 }
2931
2932 if( oldAssignments.count( netName ) )
2933 {
2934 if( oldAssignments[netName] != netclasses )
2935 {
2936 affectedNetclassNetAssignments.insert( netName );
2937 dirtySubgraphs( subgraphs );
2938 }
2939 }
2940 else if( !netclasses.empty() )
2941 {
2942 affectedNetclassNetAssignments.insert( netName );
2943 dirtySubgraphs( subgraphs );
2944 }
2945 };
2946
2947 // Check for netclass assignments
2948 for( const auto& [ netname, subgraphs ] : m_net_name_to_subgraphs_map )
2949 checkNetclassDrivers( netname, subgraphs );
2950
2951 if( !aUnconditional )
2952 {
2953 for( auto& [netname, netclasses] : oldAssignments )
2954 {
2955 if( netSettings->GetNetclassLabelAssignments().count( netname )
2956 || affectedNetclassNetAssignments.count( netname ) )
2957 {
2958 continue;
2959 }
2960
2961 netSettings->SetNetclassLabelAssignment( netname, netclasses );
2962 }
2963 }
2964
2966
2968}
2969
2971{
2972 static std::function<void( CONNECTION_GRAPH& )> s_hook;
2973 return s_hook;
2974}
2975
2976
2978{
2980
2981 auto getSubgraphNet = [&]( SCH_PIN* aPin ) -> wxString
2982 {
2983 if( !aPin )
2984 return wxString();
2985
2987
2988 return sg ? netChainKeyFor( sg->GetNetName(), sg->m_code ) : wxString();
2989 };
2990
2991 // Walk every 2-pin passthrough symbol on every sheet, building a flat list of bridge
2992 // edges between distinct subgraph nets.
2993
2994 result.edges.reserve( 256 );
2995
2996 for( const SCH_SHEET_PATH& sheetPath : m_sheetList )
2997 {
2998 SCH_SCREEN* sc = sheetPath.LastScreen();
2999
3000 if( !sc )
3001 continue;
3002
3003 auto findWireOnScreen = [&]( SCH_PIN* aPin, SCH_LINE*& aWire ) -> bool
3004 {
3005 const VECTOR2I p = aPin->GetPosition();
3006
3007 auto consider = [&]( SCH_ITEM* cand ) -> bool
3008 {
3009 if( cand->Type() != SCH_LINE_T )
3010 return false;
3011
3012 SCH_LINE* line = static_cast<SCH_LINE*>( cand );
3013
3014 if( line->GetLayer() != LAYER_WIRE )
3015 return false;
3016
3017 const VECTOR2I s = line->GetStartPoint();
3018 const VECTOR2I e = line->GetEndPoint();
3019
3020 if( s.y == e.y && p.y == s.y )
3021 {
3022 int minx = std::min( s.x, e.x );
3023 int maxx = std::max( s.x, e.x );
3024
3025 if( p.x >= minx && p.x <= maxx )
3026 {
3027 aWire = line;
3028 return true;
3029 }
3030 }
3031 else if( s.x == e.x && p.x == s.x )
3032 {
3033 int miny = std::min( s.y, e.y );
3034 int maxy = std::max( s.y, e.y );
3035
3036 if( p.y >= miny && p.y <= maxy )
3037 {
3038 aWire = line;
3039 return true;
3040 }
3041 }
3042
3043 return false;
3044 };
3045
3046 for( SCH_ITEM* c : sc->Items().Overlapping( SCH_LINE_T, p ) )
3047 if( consider( c ) )
3048 return true;
3049
3050 for( SCH_ITEM* c : sc->Items().OfType( SCH_LINE_T ) )
3051 if( consider( c ) )
3052 return true;
3053
3054 return false;
3055 };
3056
3057 for( SCH_ITEM* item : sc->Items().OfType( SCH_SYMBOL_T ) )
3058 {
3059 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
3060 std::vector<SCH_PIN*> pins = symbol->GetPins( &sheetPath );
3061
3062 if( pins.size() != 2 )
3063 continue;
3064
3066 continue;
3067
3068 SCH_LINE* wireA = nullptr;
3069 SCH_LINE* wireB = nullptr;
3070
3071 if( !findWireOnScreen( pins[0], wireA ) || !findWireOnScreen( pins[1], wireB ) )
3072 continue;
3073
3074 bool allow = false;
3075
3077 {
3078 allow = true;
3079 }
3080 else
3081 {
3082 if( pins[0]->IsPower() || pins[1]->IsPower() )
3083 continue;
3084
3085 VECTOR2I aS = wireA->GetStartPoint();
3086 VECTOR2I aE = wireA->GetEndPoint();
3087 VECTOR2I bS = wireB->GetStartPoint();
3088 VECTOR2I bE = wireB->GetEndPoint();
3089
3090 if( aS.x == aE.x && bS.x == bE.x && aS.x == bS.x )
3091 allow = true;
3092 else if( aS.y == aE.y && bS.y == bE.y && aS.y == bS.y )
3093 allow = true;
3094 }
3095
3096 if( !allow )
3097 continue;
3098
3099 wxString netA = getSubgraphNet( pins[0] );
3100 wxString netB = getSubgraphNet( pins[1] );
3101
3102 if( netA.IsEmpty() || netB.IsEmpty() || netA == netB )
3103 continue;
3104
3105 result.edges.push_back( { netA, netB, symbol } );
3106 }
3107 }
3108
3109 // Mark power subgraphs by walking every pin across every sheet. Any subgraph touched by a
3110 // power-class pin (or a power-symbol parent) is treated as a power node and its incident
3111 // bridge edges are excluded below.
3112
3113 std::set<long> powerSubgraphs;
3114 std::map<wxString, long> netToCode;
3115
3116 for( const SCH_SHEET_PATH& sheetPath : m_sheetList )
3117 {
3118 SCH_SCREEN* sc = sheetPath.LastScreen();
3119
3120 if( !sc )
3121 continue;
3122
3123 for( SCH_ITEM* item : sc->Items().OfType( SCH_SYMBOL_T ) )
3124 {
3125 SCH_SYMBOL* sym = static_cast<SCH_SYMBOL*>( item );
3126 std::vector<SCH_PIN*> pins = sym->GetPins( &sheetPath );
3127
3128 for( SCH_PIN* p : pins )
3129 {
3131 {
3132 netToCode[netChainKeyFor( sg->GetNetName(), sg->m_code )] = sg->m_code;
3133
3134 if( p->IsPower()
3135 || ( p->GetParentSymbol() && p->GetParentSymbol()->IsPower() ) )
3136 {
3137 powerSubgraphs.insert( sg->m_code );
3138 }
3139 }
3140 }
3141 }
3142 }
3143
3144 // Build the filtered adjacency. Edges that touch a power subgraph are dropped, and any
3145 // non-power endpoint of such a dropped edge is recorded as power-adjacent so the leaf-prune
3146 // pass below can iteratively remove power stubs.
3147
3148 std::set<wxString> powerAdjacentNets;
3149
3150 for( const BRIDGE_EDGE& be : result.edges )
3151 {
3152 long ca = -1;
3153 long cb = -1;
3154
3155 if( auto it = netToCode.find( be.a ); it != netToCode.end() )
3156 ca = it->second;
3157
3158 if( auto it = netToCode.find( be.b ); it != netToCode.end() )
3159 cb = it->second;
3160
3161 if( ca == -1 || cb == -1 )
3162 continue;
3163
3164 if( powerSubgraphs.contains( ca ) || powerSubgraphs.contains( cb ) )
3165 {
3166 if( !powerSubgraphs.contains( ca ) )
3167 powerAdjacentNets.insert( be.a );
3168
3169 if( !powerSubgraphs.contains( cb ) )
3170 powerAdjacentNets.insert( be.b );
3171
3172 continue;
3173 }
3174
3175 result.adjacency[be.a].push_back( { be.b, be.sym } );
3176 result.adjacency[be.b].push_back( { be.a, be.sym } );
3177 }
3178
3179 // Iteratively prune degree-1 power-adjacent leaves. Skip pruning entirely for very small
3180 // graphs to avoid wiping out legitimate two-net chains.
3181
3182 std::map<wxString, int> degree;
3183
3184 for( const auto& kv : result.adjacency )
3185 degree[kv.first] = static_cast<int>( kv.second.size() );
3186
3187 if( result.adjacency.size() <= 2 )
3188 powerAdjacentNets.clear();
3189
3190 if( powerAdjacentNets.size() <= 2 )
3191 powerAdjacentNets.clear();
3192
3193 std::queue<wxString> q;
3194 std::set<wxString> removed;
3195
3196 for( const auto& kv : degree )
3197 {
3198 if( kv.second <= 1 && powerAdjacentNets.contains( kv.first ) )
3199 q.push( kv.first );
3200 }
3201
3202 while( !q.empty() )
3203 {
3204 wxString n = q.front();
3205 q.pop();
3206
3207 if( removed.contains( n ) )
3208 continue;
3209
3210 removed.insert( n );
3211
3212 for( const BRIDGE_NEIGHBOR& e : result.adjacency[n] )
3213 {
3214 if( removed.contains( e.other ) )
3215 continue;
3216
3217 if( degree.count( e.other ) )
3218 {
3219 degree[e.other]--;
3220
3221 if( degree[e.other] <= 1 && powerAdjacentNets.contains( e.other ) )
3222 q.push( e.other );
3223 }
3224 }
3225 }
3226
3227 if( !removed.empty() )
3228 {
3229 std::map<wxString, std::vector<BRIDGE_NEIGHBOR>> newAdj;
3230
3231 for( const auto& kv : result.adjacency )
3232 {
3233 if( removed.contains( kv.first ) )
3234 continue;
3235
3236 for( const BRIDGE_NEIGHBOR& e : kv.second )
3237 {
3238 if( removed.contains( e.other ) )
3239 continue;
3240
3241 newAdj[kv.first].push_back( e );
3242 }
3243 }
3244
3245 result.adjacency.swap( newAdj );
3246 }
3247
3248 return result;
3249}
3250
3251
3253{
3254 // Snapshot the committed-chain count so a throw partway through the restore loop can
3255 // truncate any half-built entries instead of leaving the container partially mutated.
3256 const size_t committedSnapshot = m_committedNetChains.size();
3257 const bool builtSnapshot = m_netChainsBuilt;
3258
3259 try
3260 {
3261 wxLogTrace( traceSchNetChain, "RebuildNetChains: begin (items=%zu, schematic=%p)",
3262 m_items.size(), (void*) m_schematic );
3263 // Clear only potential net chains; leave committed net chains intact.
3264 m_potentialNetChains.clear();
3265
3266 if( !m_schematic )
3267 {
3268 wxLogTrace( traceSchNetChain, "RebuildNetChains: no schematic" );
3269 return;
3270 }
3271 std::map<wxString, SCH_NETCHAIN*> netToNetChain; // will be populated after chain extraction
3272
3273 // Collect all screens from the cached sheet list so we can operate globally rather than
3274 // only on the current sheet. (m_sheetList is populated during Recalculate()).
3275 std::vector<SCH_SCREEN*> allScreens;
3276 allScreens.reserve( m_sheetList.size() );
3277 for( const SCH_SHEET_PATH& sp : m_sheetList )
3278 {
3279 if( SCH_SCREEN* sc = sp.LastScreen() )
3280 allScreens.push_back( sc );
3281 }
3282
3283 // Clear any previous chain names on all symbols across all sheets so we can repopulate.
3284 for( SCH_SCREEN* sc : allScreens )
3285 {
3286 for( SCH_ITEM* item : sc->Items().OfType( SCH_SYMBOL_T ) )
3287 static_cast<SCH_SYMBOL*>( item )->SetNetChainName( wxEmptyString );
3288 }
3289 wxLogTrace( traceSchNetChain, "RebuildNetChains: screens=%zu (global build)", allScreens.size() );
3290 wxLogTrace( traceSchNetChain, "RebuildNetChains: debug start passes (pre-pass chains=%zu)", m_committedNetChains.size() );
3291
3292 // (Removed legacy findWire heuristic; global symbol-based connectivity no longer relies on
3293 // scanning parallel wires for 2-pin passthrough components.)
3294
3295 // Build net chains by scanning eligible 2-pin symbols on every sheet, using the original
3296 // parallel-wire passthrough heuristic. This is effectively the old pass 1 but repeated for
3297 // each screen, giving global coverage while preserving expected grouping semantics.
3298 wxLogTrace( traceSchNetChain, "RebuildNetChains: pass 1 (per-sheet 2-pin symbols)" );
3299
3300 auto getSubgraphNet = [&]( SCH_PIN* aPin ) -> wxString
3301 {
3302 if( !aPin )
3303 return wxString();
3304
3306
3307 return sg ? netChainKeyFor( sg->GetNetName(), sg->m_code ) : wxString();
3308 };
3309
3310 BRIDGE_GRAPH bridgeGraph = buildBridgeAdjacency();
3311 auto& bridgeEdges = bridgeGraph.edges;
3312 auto& adjacency = bridgeGraph.adjacency;
3313
3314 wxLogTrace( traceSchNetChain, "RebuildNetChains: bridgeEdges=%zu adjacency=%zu",
3315 bridgeEdges.size(), adjacency.size() );
3316
3317 // Targeted stub pruning: reduce any component >4 nets by removing minimal number of "stub" leaves
3318 // (degree 1 whose neighbor has degree >2). This satisfies legacy test expecting longest branch kept.
3319 {
3320 // First, discover connected components over current adjacency.
3321 wxLogTrace( traceSchNetChain, "RebuildNetChains: targeted stub pruning start (adj=%zu)", adjacency.size() );
3322 std::map<wxString,std::vector<BRIDGE_NEIGHBOR>> snapshot = adjacency; // read-only snapshot
3323 std::set<wxString> seen;
3324 std::set<wxString> globalPrune;
3325 for( const auto& kv : snapshot )
3326 {
3327 const wxString& start = kv.first;
3328 if( seen.contains( start ) ) continue;
3329 wxLogTrace( traceSchNetChain, " component BFS start '%s'", start );
3330 std::vector<wxString> comp; std::queue<wxString> q; q.push( start ); seen.insert( start );
3331 while( !q.empty() )
3332 {
3333 wxString cur = q.front(); q.pop(); comp.push_back( cur );
3334 for( const BRIDGE_NEIGHBOR& e : snapshot[cur] ) if( !seen.contains( e.other ) ) { seen.insert( e.other ); q.push( e.other ); }
3335 }
3336 wxLogTrace( traceSchNetChain, " component size=%zu", comp.size() );
3337 if( comp.size() <= 4 ) continue;
3338 std::map<wxString,int> degree;
3339 for( const wxString& n : comp ) degree[n] = (int) snapshot[n].size();
3340 std::vector<wxString> candidates;
3341 for( const wxString& n : comp )
3342 {
3343 const auto& nbrs = snapshot[n];
3344 if( nbrs.size() == 1 )
3345 {
3346 const wxString neigh = nbrs[0].other;
3347 if( degree.count( neigh ) && degree[neigh] > 2 ) candidates.push_back( n );
3348 }
3349 }
3350 wxLogTrace( traceSchNetChain, " candidates=%zu", candidates.size() );
3351 if( candidates.empty() ) continue;
3352 std::sort( candidates.begin(), candidates.end(), []( const wxString& a, const wxString& b ){ return a.CmpNoCase( b ) < 0; } );
3353 size_t needPrune = comp.size() - 4; if( needPrune > candidates.size() ) needPrune = candidates.size();
3354 wxLogTrace( traceSchNetChain, " pruning need=%zu", needPrune );
3355 for( size_t i = 0; i < needPrune; ++i ) globalPrune.insert( candidates[i] );
3356 }
3357 if( !globalPrune.empty() )
3358 {
3359 std::map<wxString,std::vector<BRIDGE_NEIGHBOR>> newAdj;
3360 for( const auto& kv2 : adjacency )
3361 {
3362 if( globalPrune.contains( kv2.first ) ) continue;
3363 for( const BRIDGE_NEIGHBOR& e : kv2.second )
3364 {
3365 if( globalPrune.contains( e.other ) ) continue;
3366 newAdj[kv2.first].push_back( e );
3367 }
3368 }
3369 adjacency.swap( newAdj );
3370 wxLogTrace( traceSchNetChain, "RebuildNetChains: pruned %zu targeted stub nets", globalPrune.size() );
3371 }
3372 }
3373
3374 // ---------- Small helpers ----------
3375 auto neighbors_of = [&]( const wxString& n ) -> const std::vector<BRIDGE_NEIGHBOR>*
3376 {
3377 if( auto it = adjacency.find(n); it != adjacency.end() ) return &it->second;
3378 return nullptr;
3379 };
3380
3381
3382 // Structural filtering already done by excluding edges; isolated power nets are implicitly ignored.
3383 m_potentialNetChains.clear();
3384
3385 // Recompute nets list after filtering
3386 std::set<wxString> netsAll;
3387 for( const auto& kv : adjacency ) netsAll.insert( kv.first );
3388
3389 // Connected component extraction over filtered adjacency (all remaining nets are non-power)
3390 std::set<wxString> visited;
3391 for( const wxString& start : netsAll )
3392 {
3393 if( visited.contains( start ) ) continue;
3394 std::queue<wxString> q; q.push( start );
3395 std::set<wxString> comp; comp.insert( start ); visited.insert( start );
3396 while( !q.empty() )
3397 {
3398 wxString cur = q.front(); q.pop();
3399 if( auto nbrs = neighbors_of( cur ) )
3400 {
3401 for( const BRIDGE_NEIGHBOR& e : *nbrs )
3402 {
3403 if( visited.contains( e.other ) ) continue;
3404 visited.insert( e.other );
3405 comp.insert( e.other );
3406 q.push( e.other );
3407 }
3408 }
3409 }
3410 if( comp.size() >= 2 )
3411 {
3412 auto sig = std::make_unique<SCH_NETCHAIN>();
3413 for( const wxString& n : comp ) sig->AddNet( n );
3414 for( const BRIDGE_EDGE& be : bridgeEdges )
3415 if( comp.contains( be.a ) && comp.contains( be.b ) && be.sym )
3416 sig->AddSymbol( be.sym );
3417 m_potentialNetChains.push_back( std::move( sig ) );
3418 }
3419 }
3420 // Build netToNetChain map for potential net chains
3421 netToNetChain.clear();
3422 for( const auto& sigUP : m_potentialNetChains )
3423 if( sigUP ) for( const wxString& n : sigUP->GetNets() ) netToNetChain[n] = sigUP.get();
3424
3425 // Debug: enumerate chains and their nets prior to label-based naming.
3426 wxLogTrace( traceSchNetChain, "RebuildNetChains: pre-label potentialNetChains=%zu", m_potentialNetChains.size() );
3427 for( const auto& sigUP : m_potentialNetChains )
3428 {
3429 if( !sigUP ) continue;
3430 wxString netsStr;
3431 int count = 0;
3432 for( const wxString& n : sigUP->GetNets() )
3433 {
3434 if( count < 32 )
3435 {
3436 netsStr += n;
3437 netsStr += wxS(" ");
3438 }
3439 else
3440 {
3441 netsStr += wxS("...");
3442 break;
3443 }
3444 ++count;
3445 }
3446 wxLogTrace( traceSchNetChain, " chain %p name='%s' nets=%zu [%s]", (void*) sigUP.get(),
3447 sigUP->GetName(), sigUP->GetNets().size(), netsStr );
3448 }
3449
3450
3451 // Names already in use by committed chains. A plain SCH_LABEL whose text matches a
3452 // committed chain's name must NOT steal that name from the committed chain; the
3453 // downstream restore pass uses these names as keys and would skip the potential
3454 // chain entirely on collision, silently losing it.
3455 std::set<wxString> committedNames;
3456
3457 for( const auto& chain : m_committedNetChains )
3458 {
3459 if( chain )
3460 committedNames.insert( chain->GetName() );
3461 }
3462
3463 for( SCH_ITEM* item : m_items )
3464 {
3465 if( item->Type() != SCH_LABEL_T )
3466 continue;
3467
3468 SCH_TEXT* label = static_cast<SCH_TEXT*>( item );
3469 wxString net;
3470
3471 if( CONNECTION_SUBGRAPH* sg = GetSubgraphForItem( item ) )
3472 net = sg->GetNetName();
3473
3474 // Defensive: guard against pathological names
3475 if( !net.IsEmpty() && net.Length() < 2048 && netToNetChain.count( net ) )
3476 {
3477 wxString name = label->GetText();
3478 if( name.Length() > 512 )
3479 name.Truncate( 512 );
3480 if( name.StartsWith( wxS( "/" ) ) )
3481 name = name.Mid( 1 );
3482
3483 // Skip if a committed chain already owns this name; let the terminal-ref /
3484 // saved-net-name restore logic below resolve the committed chain on its own.
3485 if( !committedNames.count( name ) )
3486 netToNetChain[net]->SetName( name );
3487 }
3488 }
3489
3490 int idx = 1;
3491
3492 wxLogTrace( traceSchNetChain, "RebuildNetChains: pass 3 (default naming)" );
3493 for( std::unique_ptr<SCH_NETCHAIN>& sig : m_potentialNetChains )
3494 {
3495 if( sig->GetName().IsEmpty() )
3496 {
3497 sig->SetName( wxString::Format( wxT( "NetChain%d" ), idx ) );
3498 idx++;
3499 }
3500 }
3501
3502 wxLogTrace( traceSchNetChain, "RebuildNetChains: pass 4 (terminal pins)" );
3503 for( std::unique_ptr<SCH_NETCHAIN>& sig : m_potentialNetChains )
3504 {
3505 struct PIN_INFO
3506 {
3507 SCH_PIN* pin;
3508 SCH_SYMBOL* sym;
3509 const SCH_SHEET_PATH* sheet;
3510 };
3511 std::vector<PIN_INFO> pins;
3512
3513 for( const SCH_SHEET_PATH& sheetPath : m_sheetList )
3514 {
3515 SCH_SCREEN* sc = sheetPath.LastScreen(); if( !sc ) continue;
3516 for( SCH_ITEM* item : sc->Items().OfType( SCH_SYMBOL_T ) )
3517 {
3518 SCH_SYMBOL* sym = static_cast<SCH_SYMBOL*>( item );
3519 for( SCH_PIN* p : sym->GetPins( &sheetPath ) )
3520 {
3521 wxString net = getSubgraphNet( p );
3522 if( sig->GetNets().count( net ) )
3523 pins.push_back( { p, sym, &sheetPath } );
3524 }
3525 }
3526 }
3527
3528 int64_t best = -1;
3529 KIID a, b;
3530 size_t bestI = 0, bestJ = 0;
3531
3532 for( size_t i = 0; i < pins.size(); ++i )
3533 {
3534 for( size_t j = i + 1; j < pins.size(); ++j )
3535 {
3536 VECTOR2I pa = pins[i].pin->GetPosition();
3537 VECTOR2I pb = pins[j].pin->GetPosition();
3538 int64_t dx = pa.x - pb.x;
3539 int64_t dy = pa.y - pb.y;
3540 int64_t d = dx * dx + dy * dy;
3541
3542 if( d > best )
3543 {
3544 best = d;
3545 a = pins[i].pin->m_Uuid;
3546 b = pins[j].pin->m_Uuid;
3547 bestI = i;
3548 bestJ = j;
3549 }
3550 }
3551 }
3552
3553 sig->SetTerminalPins( a, b );
3554
3555 if( best >= 0 && bestI < pins.size() && bestJ < pins.size() )
3556 {
3557 sig->SetTerminalRefs( pins[bestI].sym->GetRef( pins[bestI].sheet ), pins[bestI].pin->GetNumber(),
3558 pins[bestJ].sym->GetRef( pins[bestJ].sheet ), pins[bestJ].pin->GetNumber() );
3559 }
3560
3561 if( m_netChainTerminalOverrides.count( sig->GetName() ) )
3562 {
3563 auto ov = m_netChainTerminalOverrides[sig->GetName()];
3564 sig->SetTerminalPins( ov.first, ov.second );
3565 }
3566 }
3567
3568 wxLogTrace( traceSchNetChain, "RebuildNetChains: pass 5 (apply symbol names)" );
3569 for( auto& sigUP : m_potentialNetChains )
3570 {
3571 SCH_NETCHAIN* sig = sigUP.get();
3572 for( SCH_SYMBOL* sym : sig->GetSymbols() )
3573 {
3574 if( sym )
3575 sym->SetNetChainName( sig->GetName() );
3576 }
3577 wxString netsStr;
3578 for( const wxString& n : sig->GetNets() ) { netsStr += n + wxS(" "); }
3579 wxLogTrace( traceSchNetChain, "FinalChain %p nets(%zu): %s", (void*) sig, sig->GetNets().size(), netsStr );
3580 }
3581
3582 wxLogTrace( traceSchNetChain, "RebuildNetChains: built %zu potential net chains", m_potentialNetChains.size() );
3583
3584 // Restore committed chains from file.
3585 // Priority 1: match by terminal ref+pin (survives net renames)
3586 // Priority 2: match by saved net names (survives component renames)
3587 {
3588 std::set<wxString> alreadyCommitted;
3589
3590 for( const auto& chain : m_committedNetChains )
3591 {
3592 if( chain )
3593 alreadyCommitted.insert( chain->GetName() );
3594 }
3595
3596 // Build ref+pin → net lookup from current schematic
3597 std::map<std::pair<wxString, wxString>, wxString> refPinToNet;
3598
3599 for( const SCH_SHEET_PATH& sp : m_sheetList )
3600 {
3601 SCH_SCREEN* sc = sp.LastScreen();
3602
3603 if( !sc )
3604 continue;
3605
3606 for( SCH_ITEM* item : sc->Items().OfType( SCH_SYMBOL_T ) )
3607 {
3608 SCH_SYMBOL* sym = static_cast<SCH_SYMBOL*>( item );
3609 wxString ref = sym->GetRef( &sp );
3610
3611 for( SCH_PIN* pin : sym->GetPins( &sp ) )
3612 {
3614 {
3615 // Match potential-chain key construction so unnamed subgraphs use the
3616 // synthetic prefix instead of being skipped — without this, a chain
3617 // whose only named endpoint is at one terminal would fail strict
3618 // both-endpoint matching.
3619 refPinToNet[{ ref, pin->GetNumber() }] =
3620 netChainKeyFor( sg->GetNetName(), sg->m_code );
3621 }
3622 }
3623 }
3624 }
3625
3626 // O(1) lookup of committed chains by name so the restore passes don't linearly
3627 // scan m_committedNetChains for every override entry.
3628 std::unordered_map<wxString, SCH_NETCHAIN*> committedByName;
3629
3630 for( const auto& chain : m_committedNetChains )
3631 {
3632 if( chain )
3633 committedByName[chain->GetName()] = chain.get();
3634 }
3635
3636 // Names refreshed in pass 2a so pass 2b (manual fallback) doesn't overwrite the
3637 // potential-based payload with its broader member-net symbol collection.
3638 std::set<wxString> refreshedThisPass;
3639
3640 for( const auto& [chainName, termRefs] : m_netChainTerminalRefOverrides )
3641 {
3642 SCH_NETCHAIN* match = resolvePotentialChainByTerminals( termRefs, refPinToNet,
3643 m_potentialNetChains, chainName );
3644
3645 if( !match )
3646 continue;
3647
3648 if( alreadyCommitted.count( chainName ) )
3649 {
3650 auto it = committedByName.find( chainName );
3651
3652 if( it != committedByName.end() && it->second )
3653 {
3654 refreshCommittedChainFromPotential( it->second, *match );
3655 refreshedThisPass.insert( chainName );
3656 }
3657
3658 continue;
3659 }
3660
3661 CreateNetChainFromPotential( match, chainName );
3662 alreadyCommitted.insert( chainName );
3663 refreshedThisPass.insert( chainName );
3664 }
3665
3666 // Manual chains have no inferred potential; rebuild from the persisted
3667 // member-net list by collecting symbols whose pins land on those nets.
3668 for( const auto& [chainName, memberNets] : m_netChainMemberNetOverrides )
3669 {
3670 if( memberNets.empty() )
3671 continue;
3672
3673 // Skip chains pass 2a already refreshed; the potential's symbol set is more
3674 // precise than the broad member-net match collected here.
3675 if( alreadyCommitted.count( chainName ) && refreshedThisPass.count( chainName ) )
3676 continue;
3677
3678 auto termIt = m_netChainTerminalRefOverrides.find( chainName );
3679
3680 if( termIt == m_netChainTerminalRefOverrides.end() )
3681 continue;
3682
3683 const CHAIN_TERMINAL_REFS& termRefs = termIt->second;
3684
3685 SCH_PIN* terminalPinA = nullptr;
3686 SCH_PIN* terminalPinB = nullptr;
3687 std::set<SCH_SYMBOL*> symbols;
3688
3689 for( const SCH_SHEET_PATH& sp : m_sheetList )
3690 {
3691 SCH_SCREEN* sc = sp.LastScreen();
3692
3693 if( !sc )
3694 continue;
3695
3696 for( SCH_ITEM* item : sc->Items().OfType( SCH_SYMBOL_T ) )
3697 {
3698 SCH_SYMBOL* sym = static_cast<SCH_SYMBOL*>( item );
3699 wxString ref = sym->GetRef( &sp );
3700 bool symContributes = false;
3701
3702 for( SCH_PIN* pin : sym->GetPins( &sp ) )
3703 {
3705
3706 if( !sg )
3707 continue;
3708
3709 if( memberNets.count( sg->GetNetName() ) )
3710 symContributes = true;
3711
3712 if( ref == termRefs.first.ref && pin->GetNumber() == termRefs.first.pin )
3713 terminalPinA = pin;
3714
3715 if( ref == termRefs.second.ref && pin->GetNumber() == termRefs.second.pin )
3716 terminalPinB = pin;
3717 }
3718
3719 if( symContributes )
3720 symbols.insert( sym );
3721 }
3722 }
3723
3724 if( !terminalPinA || !terminalPinB || symbols.empty() )
3725 {
3726 wxLogTrace( traceSchNetChain,
3727 "RebuildNetChains: cannot restore manual chain '%s' "
3728 "(terminals or member nets unresolved)",
3729 chainName );
3730 continue;
3731 }
3732
3733 if( alreadyCommitted.count( chainName ) )
3734 {
3735 auto it = committedByName.find( chainName );
3736
3737 if( it != committedByName.end() && it->second )
3738 {
3739 refreshCommittedChainPayload( it->second, memberNets, symbols,
3740 terminalPinA->m_Uuid, terminalPinB->m_Uuid,
3741 termRefs.first.ref, termRefs.first.pin,
3742 termRefs.second.ref, termRefs.second.pin );
3743 }
3744
3745 continue;
3746 }
3747
3748 CreateManualNetChain( chainName, symbols, memberNets, terminalPinA->m_Uuid,
3749 terminalPinB->m_Uuid, termRefs.first.ref, termRefs.first.pin,
3750 termRefs.second.ref, termRefs.second.pin );
3751 alreadyCommitted.insert( chainName );
3752 }
3753 }
3754
3755 // Committed chain names take priority over potential chain names set by pass 5.
3756 for( const auto& chain : m_committedNetChains )
3757 {
3758 if( chain )
3759 {
3760 for( SCH_SYMBOL* sym : chain->GetSymbols() )
3761 {
3762 if( sym )
3763 sym->SetNetChainName( chain->GetName() );
3764 }
3765 }
3766 }
3767
3768 // QA fixtures install this hook to inject a throw inside the protected block and
3769 // verify the catch handler resizes m_committedNetChains and restores m_netChainsBuilt.
3770 if( auto& hook = RebuildNetChainsTestHook() )
3771 hook( *this );
3772
3773 // An empty chain list is a valid built state for chainless schematics.
3774 m_netChainsBuilt = true;
3775 }
3776 catch( const std::exception& e )
3777 {
3778 wxFAIL_MSG( wxString::Format( "RebuildNetChains threw: %s", e.what() ) );
3779 wxLogError( _( "Net chain rebuild failed: %s. The schematic may have stale chain "
3780 "data; reload to recover." ),
3781 wxString( e.what() ) );
3782 m_potentialNetChains.clear();
3783
3784 if( m_committedNetChains.size() > committedSnapshot )
3785 m_committedNetChains.resize( committedSnapshot );
3786
3787 m_netChainsBuilt = builtSnapshot;
3788 return;
3789 }
3790 catch( ... )
3791 {
3792 wxFAIL_MSG( "RebuildNetChains threw an unknown exception" );
3793 wxLogError( _( "Net chain rebuild failed with an unknown error. The schematic may "
3794 "have stale chain data; reload to recover." ) );
3795 m_potentialNetChains.clear();
3796
3797 if( m_committedNetChains.size() > committedSnapshot )
3798 m_committedNetChains.resize( committedSnapshot );
3799
3800 m_netChainsBuilt = builtSnapshot;
3801 return;
3802 }
3803}
3804
3806 const CHAIN_TERMINAL_REFS& aTermRefs,
3807 const std::map<std::pair<wxString, wxString>, wxString>& aRefPinToNet,
3808 const std::vector<std::unique_ptr<SCH_NETCHAIN>>& aPotentials,
3809 const wxString& aChainName )
3810{
3811 auto itFrom = aRefPinToNet.find( { aTermRefs.first.ref, aTermRefs.first.pin } );
3812 auto itTo = aRefPinToNet.find( { aTermRefs.second.ref, aTermRefs.second.pin } );
3813
3814 if( itFrom == aRefPinToNet.end() || itTo == aRefPinToNet.end() )
3815 {
3816 wxLogTrace( traceSchNetChain,
3817 "RebuildNetChains: cannot restore chain '%s' (terminal %s.%s/%s.%s unresolved)",
3818 aChainName, aTermRefs.first.ref, aTermRefs.first.pin,
3819 aTermRefs.second.ref, aTermRefs.second.pin );
3820 return nullptr;
3821 }
3822
3823 for( const auto& pot : aPotentials )
3824 {
3825 if( pot && pot->GetNets().count( itFrom->second ) && pot->GetNets().count( itTo->second ) )
3826 return pot.get();
3827 }
3828
3829 wxLogTrace( traceSchNetChain,
3830 "RebuildNetChains: no potential chain spans both terminals of '%s' (%s/%s)",
3831 aChainName, itFrom->second, itTo->second );
3832 return nullptr;
3833}
3834
3835
3837{
3838 if( !aPinA || !aPinB )
3839 return nullptr;
3840
3841 wxString netA;
3842 wxString netB;
3843
3844 if( CONNECTION_SUBGRAPH* sgA = GetSubgraphForItem( aPinA ) )
3845 netA = netChainKeyFor( sgA->GetNetName(), sgA->m_code );
3846
3847 if( CONNECTION_SUBGRAPH* sgB = GetSubgraphForItem( aPinB ) )
3848 netB = netChainKeyFor( sgB->GetNetName(), sgB->m_code );
3849
3850 if( netA.IsEmpty() || netB.IsEmpty() )
3851 return nullptr;
3852
3853 for( const auto& sigUP : m_potentialNetChains )
3854 {
3855 if( sigUP && sigUP->GetNets().contains( netA ) && sigUP->GetNets().contains( netB ) )
3856 return sigUP.get();
3857 }
3858
3859 return nullptr;
3860}
3861
3863{
3864 if( aName.IsEmpty() )
3865 return false;
3866
3867 auto it = std::find_if( m_committedNetChains.begin(), m_committedNetChains.end(),
3868 [&]( const std::unique_ptr<SCH_NETCHAIN>& aChain )
3869 {
3870 return aChain && aChain->GetName() == aName;
3871 } );
3872
3873 if( it == m_committedNetChains.end() )
3874 return false;
3875
3876 // Drop the chain marker from every member symbol so a future
3877 // RebuildNetChains() doesn't re-promote them under the same name.
3878 for( SCH_SYMBOL* sym : (*it)->GetSymbols() )
3879 {
3880 if( sym )
3881 sym->SetNetChainName( wxEmptyString );
3882 }
3883
3884 m_committedNetChains.erase( it );
3885
3886 // Drop orphaned overrides keyed on this name.
3887 m_netChainNetClassOverrides.erase( aName );
3888 m_netChainColorOverrides.erase( aName );
3889 m_netChainTerminalRefOverrides.erase( aName );
3890 m_netChainTerminalOverrides.erase( aName );
3891 m_netChainMemberNetOverrides.erase( aName );
3892
3893 return true;
3894}
3895
3896
3897bool CONNECTION_GRAPH::RenameCommittedNetChain( const wxString& aOld, const wxString& aNew )
3898{
3899 if( aOld.IsEmpty() || aNew.IsEmpty() || aOld == aNew )
3900 return false;
3901
3902 auto findByName = [&]( const wxString& aName ) -> SCH_NETCHAIN*
3903 {
3904 for( const std::unique_ptr<SCH_NETCHAIN>& chain : m_committedNetChains )
3905 {
3906 if( chain && chain->GetName() == aName )
3907 return chain.get();
3908 }
3909
3910 return nullptr;
3911 };
3912
3913 SCH_NETCHAIN* existing = findByName( aOld );
3914
3915 if( !existing )
3916 return false;
3917
3918 // Reject collisions: if some other committed chain already owns aNew, don't
3919 // silently merge them.
3920 if( findByName( aNew ) )
3921 return false;
3922
3923 existing->SetName( aNew );
3924
3925 for( SCH_SYMBOL* sym : existing->GetSymbols() )
3926 {
3927 if( sym )
3928 sym->SetNetChainName( aNew );
3929 }
3930
3931 rekeyOverrideMaps( aOld, aNew );
3932
3933 return true;
3934}
3935
3936
3937void CONNECTION_GRAPH::rekeyOverrideMaps( const wxString& aOld, const wxString& aNew )
3938{
3939 if( aOld == aNew )
3940 return;
3941
3942 auto rekey = [&]( auto& aMap )
3943 {
3944 auto it = aMap.find( aOld );
3945
3946 if( it != aMap.end() )
3947 {
3948 auto val = std::move( it->second );
3949 aMap.erase( it );
3950 aMap[aNew] = std::move( val );
3951 }
3952 };
3953
3955 rekey( m_netChainColorOverrides );
3959}
3960
3961
3963 const std::set<wxString>& aNets,
3964 const std::set<SCH_SYMBOL*>& aSymbols,
3965 const KIID& aTerminalPinA,
3966 const KIID& aTerminalPinB,
3967 const wxString& aRefA,
3968 const wxString& aPinNumA,
3969 const wxString& aRefB,
3970 const wxString& aPinNumB )
3971{
3972 if( !aTarget )
3973 return;
3974
3975 std::set<wxString> filtered;
3976
3977 for( const wxString& net : aNets )
3978 {
3979 if( !net.IsEmpty() )
3980 filtered.insert( net );
3981 }
3982
3983 aTarget->ReplaceNets( filtered );
3984
3985 aTarget->ClearSymbols();
3986
3987 for( SCH_SYMBOL* sym : aSymbols )
3988 aTarget->AddSymbol( sym );
3989
3990 // Honor an explicit terminal-pin override (set via ReplaceNetChainTerminalPin) over the
3991 // topology-derived defaults; otherwise an unconditional Recalculate would silently revert
3992 // user retargeting of the chain's terminal endpoints.
3993 auto termOverride = m_netChainTerminalOverrides.find( aTarget->GetName() );
3994
3995 if( termOverride != m_netChainTerminalOverrides.end() )
3996 aTarget->SetTerminalPins( termOverride->second.first, termOverride->second.second );
3997 else
3998 aTarget->SetTerminalPins( aTerminalPinA, aTerminalPinB );
3999
4000 aTarget->SetTerminalRefs( aRefA, aPinNumA, aRefB, aPinNumB );
4001
4002 for( SCH_SYMBOL* sym : aTarget->GetSymbols() )
4003 sym->SetNetChainName( aTarget->GetName() );
4004}
4005
4006
4008 const SCH_NETCHAIN& aSource )
4009{
4010 refreshCommittedChainPayload( aTarget, aSource.GetNets(), aSource.GetSymbols(),
4011 aSource.GetTerminalPinA(), aSource.GetTerminalPinB(),
4012 aSource.GetTerminalRef( 0 ), aSource.GetTerminalPinNum( 0 ),
4013 aSource.GetTerminalRef( 1 ), aSource.GetTerminalPinNum( 1 ) );
4014}
4015
4016
4018{
4019 if( !aPotential )
4020 return nullptr;
4021 auto sig = std::make_unique<SCH_NETCHAIN>();
4022 for( const wxString& n : aPotential->GetNets() )
4023 sig->AddNet( n );
4024 for( SCH_SYMBOL* sym : aPotential->GetSymbols() )
4025 sig->AddSymbol( sym );
4026 sig->SetName( aName );
4027 sig->SetTerminalPins( aPotential->GetTerminalPinA(), aPotential->GetTerminalPinB() );
4028 sig->SetTerminalRefs( aPotential->GetTerminalRef( 0 ), aPotential->GetTerminalPinNum( 0 ),
4029 aPotential->GetTerminalRef( 1 ), aPotential->GetTerminalPinNum( 1 ) );
4030
4031 // Apply any parsed netclass override for this chain name.
4032 auto ncIt = m_netChainNetClassOverrides.find( aName );
4033
4034 if( ncIt != m_netChainNetClassOverrides.end() )
4035 sig->SetNetClass( ncIt->second );
4036
4037 // Apply any parsed colour override for this chain name.
4038 auto colIt = m_netChainColorOverrides.find( aName );
4039
4040 if( colIt != m_netChainColorOverrides.end() )
4041 sig->SetColor( colIt->second );
4042
4043 // Apply name to symbols now
4044 for( SCH_SYMBOL* sym : sig->GetSymbols() )
4045 sym->SetNetChainName( sig->GetName() );
4046
4047 // Register terminal refs in the override map so a subsequent unconditional Recalculate
4048 // (which calls Reset() and clears the chain's symbol list) can find this chain in the
4049 // restore pass and refresh it in place. Runtime-created chains otherwise live only in
4050 // m_committedNetChains and would be missed by the override-driven restore loop.
4051 CHAIN_TERMINAL_REFS termRefs{
4052 { aPotential->GetTerminalRef( 0 ), aPotential->GetTerminalPinNum( 0 ) },
4053 { aPotential->GetTerminalRef( 1 ), aPotential->GetTerminalPinNum( 1 ) }
4054 };
4055 m_netChainTerminalRefOverrides[aName] = termRefs;
4056
4057 // Mirror the persisted-format member-net override so pass 2b has a fallback if the
4058 // schematic topology shifts and the inferred potential no longer resolves. Synthetic
4059 // and empty entries are excluded to match the save path's filter in the s-expr writer.
4060 std::set<wxString> persistableNets;
4061
4062 for( const wxString& net : sig->GetNets() )
4063 {
4064 if( net.IsEmpty() )
4065 continue;
4066
4067 if( net.StartsWith( SCH_NETCHAIN::SYNTHETIC_NET_PREFIX ) )
4068 continue;
4069
4070 persistableNets.insert( net );
4071 }
4072
4073 if( !persistableNets.empty() )
4074 m_netChainMemberNetOverrides[aName] = std::move( persistableNets );
4075 else
4076 m_netChainMemberNetOverrides.erase( aName );
4077
4078 SCH_NETCHAIN* raw = sig.get();
4079 m_committedNetChains.push_back( std::move( sig ) ); // committed from potential net chain
4080 return raw;
4081}
4082
4083
4085 const std::set<SCH_SYMBOL*>& aSymbols,
4086 const std::set<wxString>& aNets,
4087 const KIID& aTerminalPinA,
4088 const KIID& aTerminalPinB,
4089 const wxString& aRefA,
4090 const wxString& aPinNumA,
4091 const wxString& aRefB,
4092 const wxString& aPinNumB )
4093{
4094 if( !SCH_NETCHAIN::IsValidName( aName ) )
4095 return nullptr;
4096
4097 if( GetNetChainByName( aName ) )
4098 return nullptr;
4099
4100 // GetNetChainForNet returns the first match, so dual ownership of any net would
4101 // make resolution depend on iteration order.
4102 for( const wxString& net : aNets )
4103 {
4104 if( net.IsEmpty() )
4105 continue;
4106
4107 if( GetNetChainForNet( net ) )
4108 return nullptr;
4109 }
4110
4111 auto sig = std::make_unique<SCH_NETCHAIN>();
4112 sig->SetName( aName );
4113
4114 for( const wxString& net : aNets )
4115 {
4116 if( net.IsEmpty() )
4117 continue;
4118
4119 sig->AddNet( net );
4120 }
4121
4122 for( SCH_SYMBOL* sym : aSymbols )
4123 sig->AddSymbol( sym );
4124
4125 sig->SetTerminalPins( aTerminalPinA, aTerminalPinB );
4126 sig->SetTerminalRefs( aRefA, aPinNumA, aRefB, aPinNumB );
4127
4128 auto ncIt = m_netChainNetClassOverrides.find( aName );
4129
4130 if( ncIt != m_netChainNetClassOverrides.end() )
4131 sig->SetNetClass( ncIt->second );
4132
4133 auto colIt = m_netChainColorOverrides.find( aName );
4134
4135 if( colIt != m_netChainColorOverrides.end() )
4136 sig->SetColor( colIt->second );
4137
4138 for( SCH_SYMBOL* sym : sig->GetSymbols() )
4139 sym->SetNetChainName( sig->GetName() );
4140
4141 // Register the override-map entries that the rebuild restore pass needs to refresh this
4142 // manual chain after a future unconditional Recalculate. Without this the chain is only
4143 // known to m_committedNetChains, and the restore pass cannot rebuild its derived view.
4144 CHAIN_TERMINAL_REFS termRefs{ { aRefA, aPinNumA }, { aRefB, aPinNumB } };
4145 m_netChainTerminalRefOverrides[aName] = termRefs;
4146 m_netChainMemberNetOverrides[aName] = sig->GetNets();
4147
4148 SCH_NETCHAIN* raw = sig.get();
4149 m_committedNetChains.push_back( std::move( sig ) );
4150 return raw;
4151}
4152
4153
4155{
4156 wxLogTrace( traceSchNetChain, "CONNECTION_GRAPH::GetNetChainForNet(%s)", aNet );
4157 for( std::unique_ptr<SCH_NETCHAIN>& sig : m_committedNetChains )
4158 {
4159 if( !sig )
4160 continue;
4161
4162 if( sig->GetNets().count( aNet ) )
4163 {
4164 wxLogTrace( traceSchNetChain, "GetNetChainForNet: found chain '%s'", sig->GetName() );
4165 return sig.get();
4166 }
4167 }
4168
4169 wxLogTrace( traceSchNetChain, "GetNetChainForNet: no chain found" );
4170 return nullptr;
4171}
4172
4173
4175{
4176 if( !m_schematic )
4177 return;
4178
4179 std::shared_ptr<NET_SETTINGS> netSettings = m_schematic->Project().GetProjectFile().NetSettings();
4180
4181 if( !netSettings )
4182 return;
4183
4184 bool anyOverride = std::any_of( m_committedNetChains.begin(), m_committedNetChains.end(),
4185 []( const std::unique_ptr<SCH_NETCHAIN>& aChain )
4186 {
4187 return aChain && !aChain->GetNetClass().IsEmpty();
4188 } );
4189
4190 // The common no-chain path must not wipe the effective-netclass cache on every connectivity
4191 // rebuild. Only rebuild when a chain carries an override or a prior pass left stale entries.
4192 if( !anyOverride && !netSettings->HasChainPatternAssignments( NET_CHAIN_SOURCE::SCHEMATIC ) )
4193 return;
4194
4195 netSettings->ClearChainPatternAssignments( NET_CHAIN_SOURCE::SCHEMATIC );
4196
4197 for( const std::unique_ptr<SCH_NETCHAIN>& chain : m_committedNetChains )
4198 {
4199 if( !chain )
4200 continue;
4201
4202 const wxString& netclass = chain->GetNetClass();
4203
4204 if( netclass.IsEmpty() || !netSettings->HasNetclass( netclass ) )
4205 continue;
4206
4207 for( const wxString& net : chain->GetNets() )
4208 {
4209 // Synthetic per-run keys embed a subgraph code and never match a resolved net name.
4210 if( net.StartsWith( SCH_NETCHAIN::SYNTHETIC_NET_PREFIX ) )
4211 continue;
4212
4213 netSettings->SetChainPatternAssignment( NET_CHAIN_SOURCE::SCHEMATIC, net, netclass );
4214 }
4215 }
4216}
4217
4218
4220{
4221 wxLogTrace( traceSchNetChain, "CONNECTION_GRAPH::GetNetChainByName(%s)", aName );
4222 for( std::unique_ptr<SCH_NETCHAIN>& sig : m_committedNetChains )
4223 {
4224 if( sig->GetName() == aName )
4225 {
4226 wxLogTrace( traceSchNetChain, "GetNetChainByName: found" );
4227 return sig.get();
4228 }
4229 }
4230
4231 wxLogTrace( traceSchNetChain, "GetNetChainByName: not found" );
4232 return nullptr;
4233}
4234
4235
4236void CONNECTION_GRAPH::ReplaceNetChainTerminalPin( const wxString& aNetChain, const KIID& aPrev,
4237 const KIID& aNew )
4238{
4239 wxLogTrace( traceSchNetChain, "ReplaceNetChainTerminalPin: chain='%s' prev=%s new=%s",
4240 aNetChain, aPrev.AsString(), aNew.AsString() );
4241 if( SCH_NETCHAIN* sig = GetNetChainByName( aNetChain ) )
4242 {
4243 sig->ReplaceTerminalPin( aPrev, aNew );
4244 m_netChainTerminalOverrides[aNetChain] = std::make_pair( sig->GetTerminalPinA(),
4245 sig->GetTerminalPinB() );
4246 wxLogTrace( traceSchNetChain, "ReplaceNetChainTerminalPin: updated overrides to (%s,%s)",
4247 sig->GetTerminalPinA().AsString(), sig->GetTerminalPinB().AsString() );
4248 }
4249}
4250
4251
4253 std::pair<KIID, KIID>>& aOverrides )
4254{
4255 m_netChainTerminalOverrides = aOverrides;
4256 wxLogTrace( traceSchNetChain, "SetNetChainTerminalOverrides: count=%zu",
4258}
4259
4260
4261int CONNECTION_GRAPH::getOrCreateNetCode( const wxString& aNetName )
4262{
4263 int code;
4264
4265 auto it = m_net_name_to_code_map.find( aNetName );
4266
4267 if( it == m_net_name_to_code_map.end() )
4268 {
4269 code = m_last_net_code++;
4270 m_net_name_to_code_map[ aNetName ] = code;
4271 }
4272 else
4273 {
4274 code = it->second;
4275 }
4276
4277 return code;
4278}
4279
4280
4282{
4283 int code = getOrCreateNetCode( aConnection.Name() );
4284
4285 aConnection.SetNetCode( code );
4286
4287 return code;
4288}
4289
4290
4292{
4293 std::vector<std::shared_ptr<SCH_CONNECTION>> connections_to_check( aConnection->Members() );
4294
4295 for( unsigned i = 0; i < connections_to_check.size(); i++ )
4296 {
4297 const std::shared_ptr<SCH_CONNECTION>& member = connections_to_check[i];
4298
4299 if( member->IsBus() )
4300 {
4301 connections_to_check.insert( connections_to_check.end(),
4302 member->Members().begin(),
4303 member->Members().end() );
4304 continue;
4305 }
4306
4307 assignNewNetCode( *member );
4308 }
4309}
4310
4311
4313{
4314 SCH_CONNECTION* conn = aSubgraph->m_driver_connection;
4315 std::vector<CONNECTION_SUBGRAPH*> search_list;
4316 std::unordered_set<CONNECTION_SUBGRAPH*> visited;
4317 std::unordered_set<SCH_CONNECTION*> stale_bus_members;
4318
4319 auto visit =[&]( CONNECTION_SUBGRAPH* aParent )
4320 {
4321 for( SCH_SHEET_PIN* pin : aParent->m_hier_pins )
4322 {
4323 SCH_SHEET_PATH path = aParent->m_sheet;
4324 path.push_back( pin->GetParent() );
4325
4326 auto it = m_sheet_to_subgraphs_map.find( path );
4327
4328 if( it == m_sheet_to_subgraphs_map.end() )
4329 continue;
4330
4331 for( CONNECTION_SUBGRAPH* candidate : it->second )
4332 {
4333 if( !candidate->m_strong_driver
4334 || candidate->m_hier_ports.empty()
4335 || visited.contains( candidate ) )
4336 {
4337 continue;
4338 }
4339
4340 for( SCH_HIERLABEL* label : candidate->m_hier_ports )
4341 {
4342 if( candidate->GetNameForDriver( label ) == aParent->GetNameForDriver( pin ) )
4343 {
4344 wxLogTrace( ConnTrace, wxS( "%lu: found child %lu (%s)" ), aParent->m_code,
4345 candidate->m_code, candidate->m_driver_connection->Name() );
4346
4347 candidate->m_hier_parent = aParent;
4348 aParent->m_hier_children.insert( candidate );
4349
4350 // Should we skip adding the candidate to the list if the parent and candidate subgraphs
4351 // are not the same?
4352 wxASSERT( candidate->m_graph == aParent->m_graph );
4353
4354 search_list.push_back( candidate );
4355 break;
4356 }
4357 }
4358 }
4359 }
4360
4361 for( SCH_HIERLABEL* label : aParent->m_hier_ports )
4362 {
4363 SCH_SHEET_PATH path = aParent->m_sheet;
4364 path.pop_back();
4365
4366 auto it = m_sheet_to_subgraphs_map.find( path );
4367
4368 if( it == m_sheet_to_subgraphs_map.end() )
4369 continue;
4370
4371 for( CONNECTION_SUBGRAPH* candidate : it->second )
4372 {
4373 if( candidate->m_hier_pins.empty()
4374 || visited.contains( candidate )
4375 || candidate->m_driver_connection->Type() != aParent->m_driver_connection->Type() )
4376 {
4377 continue;
4378 }
4379
4380 const KIID& last_parent_uuid = aParent->m_sheet.Last()->m_Uuid;
4381
4382 for( SCH_SHEET_PIN* pin : candidate->m_hier_pins )
4383 {
4384 // If the last sheet UUIDs won't match, no need to check the full path
4385 if( pin->GetParent()->m_Uuid != last_parent_uuid )
4386 continue;
4387
4388 SCH_SHEET_PATH pin_path = path;
4389 pin_path.push_back( pin->GetParent() );
4390
4391 if( pin_path != aParent->m_sheet )
4392 continue;
4393
4394 if( aParent->GetNameForDriver( label ) == candidate->GetNameForDriver( pin ) )
4395 {
4396 wxLogTrace( ConnTrace, wxS( "%lu: found additional parent %lu (%s)" ),
4397 aParent->m_code, candidate->m_code, candidate->m_driver_connection->Name() );
4398
4399 aParent->m_hier_children.insert( candidate );
4400 search_list.push_back( candidate );
4401 break;
4402 }
4403 }
4404 }
4405 }
4406 };
4407
4408 auto propagate_bus_neighbors = [&]( CONNECTION_SUBGRAPH* aParentGraph )
4409 {
4410 // Sort bus neighbors by name to ensure deterministic processing order.
4411 // When multiple bus members (e.g., A0, A1, A2, A3) all connect to the same
4412 // shorted net in a child sheet, the first one processed "wins" and sets
4413 // the net name. Sorting ensures the alphabetically-first name is chosen.
4414 std::vector<std::shared_ptr<SCH_CONNECTION>> sortedMembers;
4415
4416 for( const auto& kv : aParentGraph->m_bus_neighbors )
4417 sortedMembers.push_back( kv.first );
4418
4419 std::sort( sortedMembers.begin(), sortedMembers.end(),
4420 []( const std::shared_ptr<SCH_CONNECTION>& a,
4421 const std::shared_ptr<SCH_CONNECTION>& b )
4422 {
4423 return a->Name() < b->Name();
4424 } );
4425
4426 for( const std::shared_ptr<SCH_CONNECTION>& member_conn : sortedMembers )
4427 {
4428 const auto& kv_it = aParentGraph->m_bus_neighbors.find( member_conn );
4429
4430 if( kv_it == aParentGraph->m_bus_neighbors.end() )
4431 continue;
4432
4433 for( CONNECTION_SUBGRAPH* neighbor : kv_it->second )
4434 {
4435 // May have been absorbed but won't have been deleted
4436 while( neighbor->m_absorbed )
4437 neighbor = neighbor->m_absorbed_by;
4438
4439 SCH_CONNECTION* parent = aParentGraph->m_driver_connection;
4440
4441 // Now member may be out of date, since we just cloned the
4442 // connection from higher up in the hierarchy. We need to
4443 // figure out what the actual new connection is.
4444 SCH_CONNECTION* member = matchBusMember( parent, member_conn.get() );
4445
4446 if( !member )
4447 {
4448 // Try harder: we might match on a secondary driver
4449 for( CONNECTION_SUBGRAPH* sg : kv_it->second )
4450 {
4451 if( sg->m_multiple_drivers )
4452 {
4453 SCH_SHEET_PATH sheet = sg->m_sheet;
4454
4455 for( SCH_ITEM* driver : sg->m_drivers )
4456 {
4457 auto c = getDefaultConnection( driver, sg );
4458 member = matchBusMember( parent, c.get() );
4459
4460 if( member )
4461 break;
4462 }
4463 }
4464
4465 if( member )
4466 break;
4467 }
4468 }
4469
4470 // This is bad, probably an ERC error
4471 if( !member )
4472 {
4473 wxLogTrace( ConnTrace, wxS( "Could not match bus member %s in %s" ),
4474 member_conn->Name(), parent->Name() );
4475 continue;
4476 }
4477
4478 SCH_CONNECTION* neighbor_conn = neighbor->m_driver_connection;
4479
4480 wxCHECK2( neighbor_conn, continue );
4481
4482 wxString neighbor_name = neighbor_conn->Name();
4483
4484 // Matching name: no update needed
4485 if( neighbor_name == member->Name() )
4486 continue;
4487
4488 // Was this neighbor already updated from a different sheet? Don't rename it again,
4489 // unless this same parent bus updated it and the bus member name has since changed
4490 // (which can happen when a bus member is renamed via stale member update, issue #18299).
4491 if( neighbor_conn->Sheet() != neighbor->m_sheet )
4492 {
4493 // If the neighbor's connection sheet doesn't match this parent bus's sheet,
4494 // it was updated by a different bus entirely. Don't override.
4495 if( neighbor_conn->Sheet() != parent->Sheet() )
4496 continue;
4497
4498 // If the neighbor's connection sheet matches this parent bus's sheet but
4499 // the names differ, check if the neighbor's current name still matches
4500 // a member of this bus. If it does, the neighbor was updated by a different
4501 // member of this same bus and we should preserve that (determinism).
4502 // If it doesn't match any member, the bus member was renamed and we should
4503 // update. We compare by name rather than VectorIndex because non-bus
4504 // connections (e.g., "GND" from power pin propagation) have a default
4505 // VectorIndex of 0 that falsely matches the first bus member.
4506 bool alreadyUpdatedByBusMember = false;
4507
4508 for( const auto& m : parent->Members() )
4509 {
4510 if( m->Name() == neighbor_name )
4511 {
4512 alreadyUpdatedByBusMember = true;
4513 break;
4514 }
4515 }
4516
4517 if( alreadyUpdatedByBusMember )
4518 continue;
4519 }
4520
4521 // Safety check against infinite recursion
4522 wxCHECK2_MSG( neighbor_conn->IsNet(), continue,
4523 wxS( "\"" ) + neighbor_name + wxS( "\" is not a net." ) );
4524
4525 wxLogTrace( ConnTrace, wxS( "%lu (%s) connected to bus member %s (local %s)" ),
4526 neighbor->m_code, neighbor_name, member->Name(), member->LocalName() );
4527
4528 // Take whichever name is higher priority
4531 {
4532 member->Clone( *neighbor_conn );
4533 stale_bus_members.insert( member );
4534 }
4535 else
4536 {
4537 neighbor_conn->Clone( *member );
4538
4539 recacheSubgraphName( neighbor, neighbor_name );
4540
4541 // Recurse onto this neighbor in case it needs to re-propagate
4542 neighbor->m_dirty = true;
4543 propagateToNeighbors( neighbor, aForce );
4544
4545 // After hierarchy propagation, the neighbor's connection may have been
4546 // updated to a higher-priority driver (e.g., a power symbol discovered
4547 // through hierarchical sheet pins). If so, update the bus member to match.
4548 // This ensures that net names propagate correctly through bus connections
4549 // that span hierarchical boundaries (issue #18119).
4550 if( neighbor_conn->Name() != member->Name() )
4551 {
4552 member->Clone( *neighbor_conn );
4553 stale_bus_members.insert( member );
4554 }
4555 }
4556 }
4557 }
4558 };
4559
4560 // If we are a bus, we must propagate to local neighbors and then the hierarchy
4561 if( conn->IsBus() )
4562 propagate_bus_neighbors( aSubgraph );
4563
4564 // If we have both ports and pins, skip processing as we'll be visited by a parent or child.
4565 // If we only have one or the other, process (we can either go bottom-up or top-down depending
4566 // on which subgraph comes up first)
4567 if( !aForce && !aSubgraph->m_hier_ports.empty() && !aSubgraph->m_hier_pins.empty() )
4568 {
4569 wxLogTrace( ConnTrace, wxS( "%lu (%s) has both hier ports and pins; deferring processing" ),
4570 aSubgraph->m_code, conn->Name() );
4571 return;
4572 }
4573 else if( aSubgraph->m_hier_ports.empty() && aSubgraph->m_hier_pins.empty() )
4574 {
4575 wxLogTrace( ConnTrace, wxS( "%lu (%s) has no hier pins or ports on sheet %s; marking clean" ),
4576 aSubgraph->m_code, conn->Name(), aSubgraph->m_sheet.PathHumanReadable() );
4577 aSubgraph->m_dirty = false;
4578 return;
4579 }
4580
4581 visited.insert( aSubgraph );
4582
4583 wxLogTrace( ConnTrace, wxS( "Propagating %lu (%s) to subsheets" ),
4584 aSubgraph->m_code, aSubgraph->m_driver_connection->Name() );
4585
4586 visit( aSubgraph );
4587
4588 for( unsigned i = 0; i < search_list.size(); i++ )
4589 {
4590 auto child = search_list[i];
4591
4592 if( visited.insert( child ).second )
4593 visit( child );
4594
4595 child->m_dirty = false;
4596 }
4597
4598 // Now, find the best driver for this chain of subgraphs
4599 CONNECTION_SUBGRAPH* bestDriver = aSubgraph;
4601 bool bestIsStrong = ( highest >= CONNECTION_SUBGRAPH::PRIORITY::HIER_LABEL );
4602 wxString bestName = aSubgraph->m_driver_connection->Name();
4603
4604 // Check if a subsheet has a higher-priority connection to the same net
4606 {
4607 for( CONNECTION_SUBGRAPH* subgraph : visited )
4608 {
4609 if( subgraph == aSubgraph )
4610 continue;
4611
4613 CONNECTION_SUBGRAPH::GetDriverPriority( subgraph->m_driver );
4614
4615 bool candidateStrong = ( priority >= CONNECTION_SUBGRAPH::PRIORITY::HIER_LABEL );
4616 wxString candidateName = subgraph->m_driver_connection->Name();
4617 bool shorterPath = subgraph->m_sheet.size() < bestDriver->m_sheet.size();
4618 bool asGoodPath = subgraph->m_sheet.size() <= bestDriver->m_sheet.size();
4619
4620 // Pick a better driving subgraph if it:
4621 // a) has a power pin or global driver
4622 // b) is a strong driver and we're a weak driver
4623 // c) is a higher priority strong driver
4624 // d) matches our priority, is a strong driver, and has a shorter path
4625 // e) matches our strength and is at least as short, and is alphabetically lower
4626
4628 ( !bestIsStrong && candidateStrong ) ||
4629 ( priority > highest && candidateStrong ) ||
4630 ( priority == highest && candidateStrong && shorterPath ) ||
4631 ( ( bestIsStrong == candidateStrong ) && asGoodPath && ( priority == highest ) &&
4632 ( candidateName < bestName ) ) )
4633 {
4634 bestDriver = subgraph;
4635 highest = priority;
4636 bestIsStrong = candidateStrong;
4637 bestName = candidateName;
4638 }
4639 }
4640 }
4641
4642 if( bestDriver != aSubgraph )
4643 {
4644 wxLogTrace( ConnTrace, wxS( "%lu (%s) overridden by new driver %lu (%s)" ),
4645 aSubgraph->m_code, aSubgraph->m_driver_connection->Name(), bestDriver->m_code,
4646 bestDriver->m_driver_connection->Name() );
4647 }
4648
4649 conn = bestDriver->m_driver_connection;
4650
4651 for( CONNECTION_SUBGRAPH* subgraph : visited )
4652 {
4653 wxString old_name = subgraph->m_driver_connection->Name();
4654
4655 subgraph->m_driver_connection->Clone( *conn );
4656
4657 if( old_name != conn->Name() )
4658 recacheSubgraphName( subgraph, old_name );
4659
4660 if( conn->IsBus() )
4661 propagate_bus_neighbors( subgraph );
4662 }
4663
4664 // Somewhere along the way, a bus member may have been upgraded to a global or power label.
4665 // Because this can happen anywhere, we need a second pass to update all instances of that bus
4666 // member to have the correct connection info
4667 if( conn->IsBus() && !stale_bus_members.empty() )
4668 {
4669 std::unordered_set<SCH_CONNECTION*> cached_members = stale_bus_members;
4670
4671 for( SCH_CONNECTION* stale_member : cached_members )
4672 {
4673 for( CONNECTION_SUBGRAPH* subgraph : visited )
4674 {
4675 SCH_CONNECTION* member = matchBusMember( subgraph->m_driver_connection, stale_member );
4676
4677 if( !member )
4678 {
4679 wxLogTrace( ConnTrace, wxS( "WARNING: failed to match stale member %s in %s." ),
4680 stale_member->Name(), subgraph->m_driver_connection->Name() );
4681 continue;
4682 }
4683
4684 wxLogTrace( ConnTrace, wxS( "Updating %lu (%s) member %s to %s" ), subgraph->m_code,
4685 subgraph->m_driver_connection->Name(), member->LocalName(), stale_member->Name() );
4686
4687 member->Clone( *stale_member );
4688
4689 propagate_bus_neighbors( subgraph );
4690 }
4691 }
4692 }
4693
4694 aSubgraph->m_dirty = false;
4695}
4696
4697
4698std::shared_ptr<SCH_CONNECTION> CONNECTION_GRAPH::getDefaultConnection( SCH_ITEM* aItem,
4699 CONNECTION_SUBGRAPH* aSubgraph )
4700{
4701 std::shared_ptr<SCH_CONNECTION> c = std::shared_ptr<SCH_CONNECTION>( nullptr );
4702
4703 switch( aItem->Type() )
4704 {
4705 case SCH_PIN_T:
4706 if( static_cast<SCH_PIN*>( aItem )->IsPower() )
4707 c = std::make_shared<SCH_CONNECTION>( aItem, aSubgraph->m_sheet );
4708
4709 break;
4710
4711 case SCH_GLOBAL_LABEL_T:
4712 case SCH_HIER_LABEL_T:
4713 case SCH_LABEL_T:
4714 c = std::make_shared<SCH_CONNECTION>( aItem, aSubgraph->m_sheet );
4715 break;
4716
4717 default:
4718 break;
4719 }
4720
4721 if( c )
4722 {
4723 c->SetGraph( this );
4724 c->ConfigureFromLabel( aSubgraph->GetNameForDriver( aItem ) );
4725 }
4726
4727 return c;
4728}
4729
4730
4732 SCH_CONNECTION* aSearch )
4733{
4734 if( !aBusConnection->IsBus() )
4735 return nullptr;
4736
4737 SCH_CONNECTION* match = nullptr;
4738
4739 if( aBusConnection->Type() == CONNECTION_TYPE::BUS )
4740 {
4741 // Vector bus: compare against index, because we allow the name
4742 // to be different
4743
4744 for( const std::shared_ptr<SCH_CONNECTION>& bus_member : aBusConnection->Members() )
4745 {
4746 if( bus_member->VectorIndex() == aSearch->VectorIndex() )
4747 {
4748 match = bus_member.get();
4749 break;
4750 }
4751 }
4752 }
4753 else
4754 {
4755 // Group bus
4756 for( const std::shared_ptr<SCH_CONNECTION>& c : aBusConnection->Members() )
4757 {
4758 // Vector inside group: compare names, because for bus groups
4759 // we expect the naming to be consistent across all usages
4760 // TODO(JE) explain this in the docs
4761 if( c->Type() == CONNECTION_TYPE::BUS )
4762 {
4763 for( const std::shared_ptr<SCH_CONNECTION>& bus_member : c->Members() )
4764 {
4765 if( bus_member->LocalName() == aSearch->LocalName() )
4766 {
4767 match = bus_member.get();
4768 break;
4769 }
4770 }
4771 }
4772 else if( c->LocalName() == aSearch->LocalName() )
4773 {
4774 match = c.get();
4775 break;
4776 }
4777 }
4778
4779 if( !match && aSearch->VectorIndex() >= 0 )
4780 {
4781 int flatIdx = 0;
4782
4783 for( const std::shared_ptr<SCH_CONNECTION>& c : aBusConnection->Members() )
4784 {
4785 if( c->Type() == CONNECTION_TYPE::BUS )
4786 {
4787 for( const std::shared_ptr<SCH_CONNECTION>& bus_member : c->Members() )
4788 {
4789 if( flatIdx == aSearch->VectorIndex() )
4790 {
4791 match = bus_member.get();
4792 break;
4793 }
4794
4795 flatIdx++;
4796 }
4797 }
4798 else
4799 {
4800 if( flatIdx == aSearch->VectorIndex() )
4801 {
4802 match = c.get();
4803 break;
4804 }
4805
4806 flatIdx++;
4807 }
4808
4809 if( match )
4810 break;
4811 }
4812 }
4813 }
4814
4815 return match;
4816}
4817
4818
4819void CONNECTION_GRAPH::recacheSubgraphName( CONNECTION_SUBGRAPH* aSubgraph, const wxString& aOldName )
4820{
4821 auto it = m_net_name_to_subgraphs_map.find( aOldName );
4822
4823 if( it != m_net_name_to_subgraphs_map.end() )
4824 {
4825 std::vector<CONNECTION_SUBGRAPH*>& vec = it->second;
4826 std::erase( vec, aSubgraph );
4827 }
4828
4829 wxLogTrace( ConnTrace, wxS( "recacheSubgraphName: %s => %s" ), aOldName,
4830 aSubgraph->m_driver_connection->Name() );
4831
4832 m_net_name_to_subgraphs_map[aSubgraph->m_driver_connection->Name()].push_back( aSubgraph );
4833}
4834
4835
4836std::shared_ptr<BUS_ALIAS> CONNECTION_GRAPH::GetBusAlias( const wxString& aName )
4837{
4838 auto it = m_bus_alias_cache.find( aName );
4839
4840 return it != m_bus_alias_cache.end() ? it->second : nullptr;
4841}
4842
4843
4844std::vector<const CONNECTION_SUBGRAPH*> CONNECTION_GRAPH::GetBusesNeedingMigration()
4845{
4846 std::vector<const CONNECTION_SUBGRAPH*> ret;
4847
4848 for( CONNECTION_SUBGRAPH* subgraph : m_subgraphs )
4849 {
4850 // Graph is supposed to be up-to-date before calling this
4851 // Should we continue if the subgraph is not up to date?
4852 wxASSERT( !subgraph->m_dirty );
4853
4854 if( !subgraph->m_driver )
4855 continue;
4856
4857 SCH_SHEET_PATH* sheet = &subgraph->m_sheet;
4858 SCH_CONNECTION* connection = subgraph->m_driver->Connection( sheet );
4859
4860 if( !connection->IsBus() )
4861 continue;
4862
4863 auto labels = subgraph->GetVectorBusLabels();
4864
4865 if( labels.size() > 1 )
4866 {
4867 bool different = false;
4868 wxString first = static_cast<SCH_TEXT*>( labels.at( 0 ) )->GetShownText( sheet, false );
4869
4870 for( unsigned i = 1; i < labels.size(); ++i )
4871 {
4872 if( static_cast<SCH_TEXT*>( labels.at( i ) )->GetShownText( sheet, false ) != first )
4873 {
4874 different = true;
4875 break;
4876 }
4877 }
4878
4879 if( !different )
4880 continue;
4881
4882 wxLogTrace( ConnTrace, wxS( "SG %ld (%s) has multiple bus labels" ), subgraph->m_code,
4883 connection->Name() );
4884
4885 ret.push_back( subgraph );
4886 }
4887 }
4888
4889 return ret;
4890}
4891
4892
4894{
4895 wxString retval = aSubGraph->GetNetName();
4896 bool found = false;
4897
4898 // This is a hacky way to find the true subgraph net name (why do we not store it?)
4899 // TODO: Remove once the actual netname of the subgraph is stored with the subgraph
4900
4901 for( auto it = m_net_name_to_subgraphs_map.begin();
4902 it != m_net_name_to_subgraphs_map.end() && !found; ++it )
4903 {
4904 for( CONNECTION_SUBGRAPH* graph : it->second )
4905 {
4906 if( graph == aSubGraph )
4907 {
4908 retval = it->first;
4909 found = true;
4910 break;
4911 }
4912 }
4913 }
4914
4915 return retval;
4916}
4917
4918
4920 const SCH_SHEET_PATH& aPath )
4921{
4922 auto it = m_net_name_to_subgraphs_map.find( aNetName );
4923
4924 if( it == m_net_name_to_subgraphs_map.end() )
4925 return nullptr;
4926
4927 for( CONNECTION_SUBGRAPH* sg : it->second )
4928 {
4929 // Cache is supposed to be valid by now
4930 // Should we continue if the cache is not valid?
4931 wxASSERT( sg && !sg->m_absorbed && sg->m_driver_connection );
4932
4933 if( sg->m_sheet == aPath && sg->m_driver_connection->Name() == aNetName )
4934 return sg;
4935 }
4936
4937 return nullptr;
4938}
4939
4940
4942{
4943 auto it = m_net_name_to_subgraphs_map.find( aNetName );
4944
4945 if( it == m_net_name_to_subgraphs_map.end() )
4946 return nullptr;
4947
4948 // Should this return a nullptr if the map entry is empty?
4949 wxASSERT( !it->second.empty() );
4950
4951 return it->second[0];
4952}
4953
4954
4956{
4957 auto it = m_item_to_subgraph_map.find( aItem );
4958
4959 // Callers expect a single subgraph even for items registered on several sheet paths, so
4960 // hand back the most recently registered one
4961 CONNECTION_SUBGRAPH* ret = ( it != m_item_to_subgraph_map.end() && !it->second.empty() )
4962 ? it->second.back()
4963 : nullptr;
4964
4965 while( ret && ret->m_absorbed )
4966 ret = ret->m_absorbed_by;
4967
4968 return ret;
4969}
4970
4971
4972const std::vector<CONNECTION_SUBGRAPH*>&
4973CONNECTION_GRAPH::GetAllSubgraphs( const wxString& aNetName ) const
4974{
4975 static const std::vector<CONNECTION_SUBGRAPH*> subgraphs;
4976
4977 auto it = m_net_name_to_subgraphs_map.find( aNetName );
4978
4979 if( it == m_net_name_to_subgraphs_map.end() )
4980 return subgraphs;
4981
4982 return it->second;
4983}
4984
4985
4986std::vector<wxString> CONNECTION_GRAPH::GetEquivalentBusNames( const wxString& aBusName ) const
4987{
4988 std::vector<wxString> equivalents;
4989
4990 // Split off the sheet-path prefix. A literal '/' is always the hierarchy separator here, since
4991 // slashes in member names are escaped as "{slash}". Re-attached to results so they match the
4992 // net-name map keys.
4993 wxString path;
4994 wxString group = aBusName;
4995 size_t lastSlash = aBusName.find_last_of( '/' );
4996
4997 if( lastSlash != wxString::npos )
4998 {
4999 path = aBusName.Left( lastSlash + 1 );
5000 group = aBusName.Mid( lastSlash + 1 );
5001 }
5002
5003 wxString prefix;
5004 std::vector<wxString> members;
5005
5006 if( !NET_SETTINGS::ParseBusGroup( UnescapeString( group ), &prefix, &members ) )
5007 return equivalents;
5008
5009 // A named-group prefix ("BUS{A B}") renames the members, so it is not aliasable.
5010 if( !prefix.IsEmpty() )
5011 return equivalents;
5012
5013 // ParseBusGroup escapes spaces as "\ " and leaves net-name escapes in place; BUS_ALIAS stores
5014 // members verbatim. Undo both so the two compare in the same form.
5015 for( wxString& member : members )
5016 {
5017 member.Replace( wxT( "\\ " ), wxT( " " ) );
5018 member = UnescapeString( member );
5019 }
5020
5021 // A single-member name may itself be an alias ("{MIXED_BUS}"); expand it and don't re-emit it.
5022 wxString selfAlias;
5023
5024 if( members.size() == 1 )
5025 {
5026 auto aliasIt = m_bus_alias_cache.find( members[0] );
5027
5028 if( aliasIt != m_bus_alias_cache.end() )
5029 {
5030 selfAlias = members[0];
5031 members = aliasIt->second->Members();
5032 }
5033 }
5034
5035 // Re-escape members back to net-name form so the label matches the connection-graph keys.
5036 wxString expandedLabel = path + wxT( "{" );
5037
5038 for( size_t i = 0; i < members.size(); ++i )
5039 {
5040 if( i > 0 )
5041 expandedLabel += wxT( " " );
5042
5043 wxString escaped = EscapeString( members[i], CTX_NETNAME );
5044 escaped.Replace( wxT( " " ), wxT( "\\ " ) );
5045 expandedLabel += escaped;
5046 }
5047
5048 expandedLabel += wxT( "}" );
5049
5050 if( expandedLabel != aBusName )
5051 equivalents.push_back( expandedLabel );
5052
5053 // Match aliases by member set; bus connectivity is order-independent, so compare as multisets.
5054 std::multiset<wxString> memberSet( members.begin(), members.end() );
5055
5056 for( const auto& [aliasName, alias] : m_bus_alias_cache )
5057 {
5058 if( aliasName == selfAlias || alias->Members().size() != members.size() )
5059 continue;
5060
5061 std::multiset<wxString> aliasMembers( alias->Members().begin(), alias->Members().end() );
5062
5063 if( memberSet == aliasMembers )
5064 equivalents.push_back( path + wxT( "{" ) + aliasName + wxT( "}" ) );
5065 }
5066
5067 return equivalents;
5068}
5069
5070
5072{
5073 int error_count = 0;
5074
5075 wxCHECK_MSG( m_schematic, true, wxS( "Null m_schematic in CONNECTION_GRAPH::RunERC" ) );
5076
5077 ERC_SETTINGS& settings = m_schematic->ErcSettings();
5078
5079 // We don't want to run many ERC checks more than once on a given screen even though it may
5080 // represent multiple sheets with multiple subgraphs. We can tell these apart by drivers.
5081 std::set<SCH_ITEM*> seenDriverInstances;
5082
5083 for( CONNECTION_SUBGRAPH* subgraph : m_subgraphs )
5084 {
5085 // There shouldn't be any null sub-graph pointers.
5086 wxCHECK2( subgraph, continue );
5087
5088 // Graph is supposed to be up-to-date before calling RunERC()
5089 // Should we continue if the subgraph is not up to date?
5090 wxASSERT( !subgraph->m_dirty );
5091
5092 if( subgraph->m_absorbed )
5093 continue;
5094
5095 if( seenDriverInstances.count( subgraph->m_driver ) )
5096 continue;
5097
5098 if( subgraph->m_driver )
5099 seenDriverInstances.insert( subgraph->m_driver );
5100
5111 if( settings.IsTestEnabled( ERCE_DRIVER_CONFLICT ) )
5112 {
5113 if( !ercCheckMultipleDrivers( subgraph ) )
5114 error_count++;
5115 }
5116
5117 subgraph->ResolveDrivers( false );
5118
5119 if( settings.IsTestEnabled( ERCE_BUS_TO_NET_CONFLICT ) )
5120 {
5121 if( !ercCheckBusToNetConflicts( subgraph ) )
5122 error_count++;
5123 }
5124
5125 if( settings.IsTestEnabled( ERCE_BUS_ENTRY_CONFLICT ) )
5126 {
5127 if( !ercCheckBusToBusEntryConflicts( subgraph ) )
5128 error_count++;
5129 }
5130
5131 if( settings.IsTestEnabled( ERCE_BUS_TO_BUS_CONFLICT ) )
5132 {
5133 if( !ercCheckBusToBusConflicts( subgraph ) )
5134 error_count++;
5135 }
5136
5137 if( settings.IsTestEnabled( ERCE_WIRE_DANGLING ) )
5138 {
5139 if( !ercCheckFloatingWires( subgraph ) )
5140 error_count++;
5141 }
5142
5144 {
5145 if( !ercCheckDanglingWireEndpoints( subgraph ) )
5146 error_count++;
5147 }
5148
5151 || settings.IsTestEnabled( ERCE_PIN_NOT_CONNECTED ) )
5152 {
5153 if( !ercCheckNoConnects( subgraph ) )
5154 error_count++;
5155 }
5156
5158 || settings.IsTestEnabled( ERCE_LABEL_SINGLE_PIN ) )
5159 {
5160 if( !ercCheckLabels( subgraph ) )
5161 error_count++;
5162 }
5163 }
5164
5165 if( settings.IsTestEnabled( ERCE_LABEL_NOT_CONNECTED ) )
5166 {
5167 error_count += ercCheckDirectiveLabels();
5168 }
5169
5170 // Hierarchical sheet checking is done at the schematic level
5172 || settings.IsTestEnabled( ERCE_PIN_NOT_CONNECTED ) )
5173 {
5174 error_count += ercCheckHierSheets();
5175 }
5176
5177 if( settings.IsTestEnabled( ERCE_SINGLE_GLOBAL_LABEL ) )
5178 {
5179 error_count += ercCheckSingleGlobalLabel();
5180 }
5181
5182 return error_count;
5183}
5184
5185
5187{
5188 wxCHECK( aSubgraph, false );
5189
5190 if( aSubgraph->m_multiple_drivers )
5191 {
5192 for( SCH_ITEM* driver : aSubgraph->m_drivers )
5193 {
5194 if( driver == aSubgraph->m_driver )
5195 continue;
5196
5197 if( driver->Type() == SCH_GLOBAL_LABEL_T
5198 || driver->Type() == SCH_HIER_LABEL_T
5199 || driver->Type() == SCH_LABEL_T
5200 || ( driver->Type() == SCH_PIN_T && static_cast<SCH_PIN*>( driver )->IsPower() ) )
5201 {
5202 const wxString& primaryName = aSubgraph->GetNameForDriver( aSubgraph->m_driver );
5203 const wxString& secondaryName = aSubgraph->GetNameForDriver( driver );
5204
5205 if( primaryName == secondaryName )
5206 continue;
5207
5208 wxString msg = wxString::Format( _( "Both %s and %s are attached to the same "
5209 "items; %s will be used in the netlist" ),
5210 primaryName, secondaryName, primaryName );
5211
5212 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_DRIVER_CONFLICT );
5213 ercItem->SetItems( aSubgraph->m_driver, driver );
5214 ercItem->SetSheetSpecificPath( aSubgraph->GetSheet() );
5215 ercItem->SetItemsSheetPaths( aSubgraph->GetSheet(), aSubgraph->m_sheet );
5216 ercItem->SetErrorMessage( msg );
5217
5218 SCH_MARKER* marker = new SCH_MARKER( std::move( ercItem ), driver->GetPosition() );
5219 aSubgraph->m_sheet.LastScreen()->Append( marker );
5220
5221 return false;
5222 }
5223 }
5224 }
5225
5226 return true;
5227}
5228
5229
5231{
5232 const SCH_SHEET_PATH& sheet = aSubgraph->m_sheet;
5233 SCH_SCREEN* screen = sheet.LastScreen();
5234
5235 SCH_ITEM* net_item = nullptr;
5236 SCH_ITEM* bus_item = nullptr;
5237 SCH_CONNECTION conn( this );
5238
5239 for( SCH_ITEM* item : aSubgraph->m_items )
5240 {
5241 switch( item->Type() )
5242 {
5243 case SCH_LINE_T:
5244 {
5245 if( item->GetLayer() == LAYER_BUS )
5246 bus_item = ( !bus_item ) ? item : bus_item;
5247 else
5248 net_item = ( !net_item ) ? item : net_item;
5249
5250 break;
5251 }
5252
5253 case SCH_LABEL_T:
5254 case SCH_GLOBAL_LABEL_T:
5255 case SCH_SHEET_PIN_T:
5256 case SCH_HIER_LABEL_T:
5257 {
5258 SCH_TEXT* text = static_cast<SCH_TEXT*>( item );
5259 conn.ConfigureFromLabel( EscapeString( text->GetShownText( &sheet, false ), CTX_NETNAME ) );
5260
5261 if( conn.IsBus() )
5262 bus_item = ( !bus_item ) ? item : bus_item;
5263 else
5264 net_item = ( !net_item ) ? item : net_item;
5265
5266 break;
5267 }
5268
5269 default:
5270 break;
5271 }
5272 }
5273
5274 if( net_item && bus_item )
5275 {
5276 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_BUS_TO_NET_CONFLICT );
5277 ercItem->SetSheetSpecificPath( sheet );
5278 ercItem->SetItems( net_item, bus_item );
5279
5280 SCH_MARKER* marker = new SCH_MARKER( std::move( ercItem ), net_item->GetPosition() );
5281 screen->Append( marker );
5282
5283 return false;
5284 }
5285
5286 return true;
5287}
5288
5289
5291{
5292 const SCH_SHEET_PATH& sheet = aSubgraph->m_sheet;
5293 SCH_SCREEN* screen = sheet.LastScreen();
5294
5295 SCH_ITEM* label = nullptr;
5296 SCH_ITEM* port = nullptr;
5297
5298 for( SCH_ITEM* item : aSubgraph->m_items )
5299 {
5300 switch( item->Type() )
5301 {
5302 case SCH_TEXT_T:
5303 case SCH_GLOBAL_LABEL_T:
5304 if( !label && item->Connection( &sheet )->IsBus() )
5305 label = item;
5306 break;
5307
5308 case SCH_SHEET_PIN_T:
5309 case SCH_HIER_LABEL_T:
5310 if( !port && item->Connection( &sheet )->IsBus() )
5311 port = item;
5312 break;
5313
5314 default:
5315 break;
5316 }
5317 }
5318
5319 if( label && port )
5320 {
5321 bool match = false;
5322
5323 for( const auto& member : label->Connection( &sheet )->Members() )
5324 {
5325 for( const auto& test : port->Connection( &sheet )->Members() )
5326 {
5327 if( test != member && member->Name() == test->Name() )
5328 {
5329 match = true;
5330 break;
5331 }
5332 }
5333
5334 if( match )
5335 break;
5336 }
5337
5338 if( !match )
5339 {
5340 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_BUS_TO_BUS_CONFLICT );
5341 ercItem->SetSheetSpecificPath( sheet );
5342 ercItem->SetItems( label, port );
5343
5344 SCH_MARKER* marker = new SCH_MARKER( std::move( ercItem ), label->GetPosition() );
5345 screen->Append( marker );
5346
5347 return false;
5348 }
5349 }
5350
5351 return true;
5352}
5353
5354
5356{
5357 bool conflict = false;
5358 const SCH_SHEET_PATH& sheet = aSubgraph->m_sheet;
5359 SCH_SCREEN* screen = sheet.LastScreen();
5360
5361 SCH_BUS_WIRE_ENTRY* bus_entry = nullptr;
5362 SCH_ITEM* bus_wire = nullptr;
5363 wxString bus_name;
5364
5365 if( !aSubgraph->m_driver_connection )
5366 {
5367 // Incomplete bus entry. Let the unconnected tests handle it.
5368 return true;
5369 }
5370
5371 for( SCH_ITEM* item : aSubgraph->m_items )
5372 {
5373 switch( item->Type() )
5374 {
5376 if( !bus_entry )
5377 bus_entry = static_cast<SCH_BUS_WIRE_ENTRY*>( item );
5378
5379 break;
5380
5381 default:
5382 break;
5383 }
5384 }
5385
5386 if( bus_entry && bus_entry->m_connected_bus_item )
5387 {
5388 bus_wire = bus_entry->m_connected_bus_item;
5389
5390 // Should we continue if the type is not a line?
5391 wxASSERT( bus_wire->Type() == SCH_LINE_T );
5392
5393 // In some cases, the connection list (SCH_CONNECTION*) can be null.
5394 // Skip null connections.
5395 if( bus_entry->Connection( &sheet )
5396 && bus_wire->Type() == SCH_LINE_T
5397 && bus_wire->Connection( &sheet ) )
5398 {
5399 conflict = true; // Assume a conflict; we'll reset if we find it's OK
5400
5401 bus_name = bus_wire->Connection( &sheet )->Name();
5402
5403 std::set<wxString> test_names;
5404 test_names.insert( bus_entry->Connection( &sheet )->FullLocalName() );
5405
5406 wxString baseName = sheet.PathHumanReadable();
5407
5408 for( SCH_ITEM* driver : aSubgraph->m_drivers )
5409 test_names.insert( baseName + aSubgraph->GetNameForDriver( driver ) );
5410
5411 for( const auto& member : bus_wire->Connection( &sheet )->Members() )
5412 {
5413 if( member->Type() == CONNECTION_TYPE::BUS )
5414 {
5415 for( const auto& sub_member : member->Members() )
5416 {
5417 if( test_names.count( sub_member->FullLocalName() ) )
5418 conflict = false;
5419 }
5420 }
5421 else if( test_names.count( member->FullLocalName() ) )
5422 {
5423 conflict = false;
5424 }
5425 }
5426 }
5427 }
5428
5429 // Don't report warnings if this bus member has been overridden by a higher priority power pin
5430 // or global label
5431 if( conflict && CONNECTION_SUBGRAPH::GetDriverPriority( aSubgraph->m_driver )
5433 {
5434 conflict = false;
5435 }
5436
5437 if( conflict )
5438 {
5439 wxString netName = aSubgraph->m_driver_connection->Name();
5440 wxString msg = wxString::Format( _( "Net %s is graphically connected to bus %s but is not a"
5441 " member of that bus" ),
5442 UnescapeString( netName ),
5443 UnescapeString( bus_name ) );
5444 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_BUS_ENTRY_CONFLICT );
5445 ercItem->SetSheetSpecificPath( sheet );
5446 ercItem->SetItems( bus_entry, bus_wire );
5447 ercItem->SetErrorMessage( msg );
5448
5449 SCH_MARKER* marker = new SCH_MARKER( std::move( ercItem ), bus_entry->GetPosition() );
5450 screen->Append( marker );
5451
5452 return false;
5453 }
5454
5455 return true;
5456}
5457
5458
5460{
5461 ERC_SETTINGS& settings = m_schematic->ErcSettings();
5462 const SCH_SHEET_PATH& sheet = aSubgraph->m_sheet;
5463 SCH_SCREEN* screen = sheet.LastScreen();
5464 bool ok = true;
5465 SCH_PIN* pin = nullptr;
5466
5467 std::set<SCH_PIN*> unique_pins;
5468 std::set<SCH_LABEL_BASE*> unique_labels;
5469
5470 wxString netName = GetResolvedSubgraphName( aSubgraph );
5471
5472 auto process_subgraph = [&]( const CONNECTION_SUBGRAPH* aProcessGraph )
5473 {
5474 // Any subgraph that contains a no-connect should not
5475 // more than one pin (which would indicate it is connected
5476 for( SCH_ITEM* item : aProcessGraph->m_items )
5477 {
5478 switch( item->Type() )
5479 {
5480 case SCH_PIN_T:
5481 {
5482 SCH_PIN* test_pin = static_cast<SCH_PIN*>( item );
5483
5484 // Only link NC to pin on the current subgraph being checked
5485 if( aProcessGraph == aSubgraph )
5486 pin = test_pin;
5487
5488 if( std::none_of( unique_pins.begin(), unique_pins.end(),
5489 [test_pin]( SCH_PIN* aPin )
5490 {
5491 return test_pin->IsStacked( aPin );
5492 }
5493 ))
5494 {
5495 unique_pins.insert( test_pin );
5496 }
5497
5498 break;
5499 }
5500
5501 case SCH_LABEL_T:
5502 case SCH_GLOBAL_LABEL_T:
5503 case SCH_HIER_LABEL_T:
5504 unique_labels.insert( static_cast<SCH_LABEL_BASE*>( item ) );
5506 default:
5507 break;
5508 }
5509 }
5510 };
5511
5512 auto it = m_net_name_to_subgraphs_map.find( netName );
5513
5514 if( it != m_net_name_to_subgraphs_map.end() )
5515 {
5516 for( const CONNECTION_SUBGRAPH* subgraph : it->second )
5517 {
5518 process_subgraph( subgraph );
5519 }
5520 }
5521 else
5522 {
5523 process_subgraph( aSubgraph );
5524 }
5525
5526 if( aSubgraph->m_no_connect != nullptr )
5527 {
5528 // If this subgraph reaches the rest of the schematic only through a hier
5529 // sheet pin (parent side) or hier label (inner side), and contains no real
5530 // connection points of its own, suppress the warning. The user's intent
5531 // is to mark the hier link as unconnected -- whether the no-connect sits
5532 // on the pin or at the end of a short wire stub.
5533 if( !aSubgraph->m_hier_pins.empty() || !aSubgraph->m_hier_ports.empty() )
5534 {
5535 bool clean = true;
5536
5537 for( SCH_ITEM* item : aSubgraph->m_items )
5538 {
5539 switch( item->Type() )
5540 {
5541 case SCH_PIN_T:
5542 case SCH_LABEL_T:
5543 case SCH_GLOBAL_LABEL_T:
5544 case SCH_DIRECTIVE_LABEL_T: clean = false; break;
5545 default: break;
5546 }
5547
5548 if( !clean )
5549 break;
5550 }
5551
5552 if( clean )
5553 return true;
5554 }
5555
5556 // Special case: If the subgraph being checked consists of only a hier port/pin and
5557 // a no-connect, we don't issue a "no-connect connected" warning just because
5558 // connections exist on the sheet on the other side of the link.
5559 VECTOR2I noConnectPos = aSubgraph->m_no_connect->GetPosition();
5560
5561 for( SCH_SHEET_PIN* hierPin : aSubgraph->m_hier_pins )
5562 {
5563 if( hierPin->GetPosition() == noConnectPos )
5564 return true;
5565 }
5566
5567 for( SCH_HIERLABEL* hierLabel : aSubgraph->m_hier_ports )
5568 {
5569 if( hierLabel->GetPosition() == noConnectPos )
5570 return true;
5571 }
5572
5573 for( SCH_ITEM* item : screen->Items().Overlapping( SCH_SYMBOL_T, noConnectPos ) )
5574 {
5575 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
5576
5577 const SCH_PIN* test_pin = symbol->GetPin( noConnectPos );
5578
5579 if( test_pin && test_pin->GetType() == ELECTRICAL_PINTYPE::PT_NC )
5580 return true;
5581 }
5582
5583 if( unique_pins.size() > 1 && settings.IsTestEnabled( ERCE_NOCONNECT_CONNECTED ) )
5584 {
5585 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_NOCONNECT_CONNECTED );
5586 ercItem->SetSheetSpecificPath( sheet );
5587 ercItem->SetItemsSheetPaths( sheet );
5588
5589 VECTOR2I pos;
5590
5591 if( pin )
5592 {
5593 ercItem->SetItems( pin, aSubgraph->m_no_connect );
5594 pos = pin->GetPosition();
5595 }
5596 else
5597 {
5598 ercItem->SetItems( aSubgraph->m_no_connect );
5599 pos = aSubgraph->m_no_connect->GetPosition();
5600 }
5601
5602 SCH_MARKER* marker = new SCH_MARKER( std::move( ercItem ), pos );
5603 screen->Append( marker );
5604
5605 ok = false;
5606 }
5607
5608 if( unique_pins.empty() && unique_labels.empty() &&
5610 {
5611 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_NOCONNECT_NOT_CONNECTED );
5612 ercItem->SetItems( aSubgraph->m_no_connect );
5613 ercItem->SetSheetSpecificPath( sheet );
5614 ercItem->SetItemsSheetPaths( sheet );
5615
5616 SCH_MARKER* marker = new SCH_MARKER( std::move( ercItem ), aSubgraph->m_no_connect->GetPosition() );
5617 screen->Append( marker );
5618
5619 ok = false;
5620 }
5621 }
5622 else
5623 {
5624 bool has_other_connections = false;
5625 std::vector<SCH_PIN*> pins;
5626
5627 // Any subgraph that lacks a no-connect and contains a pin should also
5628 // contain at least one other potential driver
5629
5630 for( SCH_ITEM* item : aSubgraph->m_items )
5631 {
5632 switch( item->Type() )
5633 {
5634 case SCH_PIN_T:
5635 {
5636 SCH_PIN* test_pin = static_cast<SCH_PIN*>( item );
5637
5638 // Stacked pins do not count as other connections but non-stacked pins do
5639 if( !has_other_connections && !pins.empty()
5640 && !test_pin->GetParentSymbol()->IsPower() )
5641 {
5642 for( SCH_PIN* other_pin : pins )
5643 {
5644 if( !test_pin->IsStacked( other_pin ) )
5645 {
5646 has_other_connections = true;
5647 break;
5648 }
5649 }
5650 }
5651
5652 pins.emplace_back( static_cast<SCH_PIN*>( item ) );
5653
5654 break;
5655 }
5656
5657 default:
5658 if( aSubgraph->GetDriverPriority( item ) != CONNECTION_SUBGRAPH::PRIORITY::NONE )
5659 has_other_connections = true;
5660
5661 break;
5662 }
5663 }
5664
5665 // For many checks, we can just use the first pin
5666 pin = pins.empty() ? nullptr : pins[0];
5667
5668 // But if there is a power pin, it might be connected elsewhere
5669 for( SCH_PIN* test_pin : pins )
5670 {
5671 // Prefer the pin is part of a real component rather than some stray power symbol
5672 // Or else we may fail walking connected components to a power symbol pin since we
5673 // reject starting at a power symbol
5674 if( test_pin->GetType() == ELECTRICAL_PINTYPE::PT_POWER_IN && !test_pin->IsPower() )
5675 {
5676 pin = test_pin;
5677 break;
5678 }
5679 }
5680
5681 // Check if power input pins connect to anything else via net name,
5682 // but not for power symbols (with visible or legacy invisible pins).
5683 // We want to throw unconnected errors for power symbols even if they are connected to other
5684 // net items by name, because usually failing to connect them graphically is a mistake
5685 SYMBOL* pinLibParent = ( pin && pin->GetLibPin() )
5686 ? pin->GetLibPin()->GetParentSymbol() : nullptr;
5687
5688 if( pin && !has_other_connections
5689 && !pin->IsPower()
5690 && ( !pinLibParent || !pinLibParent->IsPower() ) )
5691 {
5692 wxString name = pin->Connection( &sheet )->Name();
5693 wxString local_name = pin->Connection( &sheet )->Name( true );
5694
5695 if( m_global_label_cache.count( name )
5696 || m_local_label_cache.count( std::make_pair( sheet, local_name ) ) )
5697 {
5698 has_other_connections = true;
5699 }
5700 }
5701
5702 // Only one pin, and it's not a no-connect pin
5703 if( pin && !has_other_connections
5704 && pin->GetType() != ELECTRICAL_PINTYPE::PT_NC
5705 && pin->GetType() != ELECTRICAL_PINTYPE::PT_NIC
5706 && settings.IsTestEnabled( ERCE_PIN_NOT_CONNECTED ) )
5707 {
5708 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_PIN_NOT_CONNECTED );
5709 ercItem->SetSheetSpecificPath( sheet );
5710 ercItem->SetItemsSheetPaths( sheet );
5711 ercItem->SetItems( pin );
5712
5713 SCH_MARKER* marker = new SCH_MARKER( std::move( ercItem ), pin->GetPosition() );
5714 screen->Append( marker );
5715
5716 ok = false;
5717 }
5718
5719 // If there are multiple pins in this SG, they might be indirectly connected (by netname)
5720 // rather than directly connected (by wires). We want to flag dangling pins even if they
5721 // join nets with another pin, as it's often a mistake
5722 if( pins.size() > 1 )
5723 {
5724 for( SCH_PIN* testPin : pins )
5725 {
5726 // We only apply this test to power symbols, because other symbols have
5727 // pins that are meant to be dangling, but the power symbols have pins
5728 // that are *not* meant to be dangling.
5729 SYMBOL* testLibParent = testPin->GetLibPin()
5730 ? testPin->GetLibPin()->GetParentSymbol()
5731 : nullptr;
5732
5733 if( testLibParent && testLibParent->IsPower()
5734 && testPin->ConnectedItems( sheet ).empty()
5735 && settings.IsTestEnabled( ERCE_PIN_NOT_CONNECTED ) )
5736 {
5737 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_PIN_NOT_CONNECTED );
5738 ercItem->SetSheetSpecificPath( sheet );
5739 ercItem->SetItemsSheetPaths( sheet );
5740 ercItem->SetItems( testPin );
5741
5742 SCH_MARKER* marker = new SCH_MARKER( std::move( ercItem ), testPin->GetPosition() );
5743 screen->Append( marker );
5744
5745 ok = false;
5746 }
5747 }
5748 }
5749 }
5750
5751 return ok;
5752}
5753
5754
5756{
5757 int err_count = 0;
5758 const SCH_SHEET_PATH& sheet = aSubgraph->m_sheet;
5759
5760 for( SCH_ITEM* item : aSubgraph->m_items )
5761 {
5762 if( item->GetLayer() != LAYER_WIRE )
5763 continue;
5764
5765 if( item->Type() == SCH_LINE_T )
5766 {
5767 SCH_LINE* line = static_cast<SCH_LINE*>( item );
5768
5769 if( line->IsGraphicLine() )
5770 continue;
5771
5772 auto report_error = [&]( VECTOR2I& location )
5773 {
5774 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_UNCONNECTED_WIRE_ENDPOINT );
5775
5776 ercItem->SetItems( line );
5777 ercItem->SetSheetSpecificPath( sheet );
5778 ercItem->SetErrorMessage( _( "Unconnected wire endpoint" ) );
5779
5780 SCH_MARKER* marker = new SCH_MARKER( std::move( ercItem ), location );
5781 sheet.LastScreen()->Append( marker );
5782
5783 err_count++;
5784 };
5785
5786 if( line->IsStartDangling() )
5787 report_error( line->GetConnectionPoints()[0] );
5788
5789 if( line->IsEndDangling() )
5790 report_error( line->GetConnectionPoints()[1] );
5791 }
5792 else if( item->Type() == SCH_BUS_WIRE_ENTRY_T )
5793 {
5794 SCH_BUS_WIRE_ENTRY* entry = static_cast<SCH_BUS_WIRE_ENTRY*>( item );
5795
5796 auto report_error = [&]( VECTOR2I& location )
5797 {
5798 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_UNCONNECTED_WIRE_ENDPOINT );
5799
5800 ercItem->SetItems( entry );
5801 ercItem->SetSheetSpecificPath( sheet );
5802 ercItem->SetErrorMessage( _( "Unconnected wire to bus entry" ) );
5803
5804 SCH_MARKER* marker = new SCH_MARKER( std::move( ercItem ), location );
5805 sheet.LastScreen()->Append( marker );
5806
5807 err_count++;
5808 };
5809
5810 if( entry->IsStartDangling() )
5811 report_error( entry->GetConnectionPoints()[0] );
5812
5813 if( entry->IsEndDangling() )
5814 report_error( entry->GetConnectionPoints()[1] );
5815 }
5816
5817 }
5818
5819 return err_count > 0;
5820}
5821
5822
5824{
5825 if( aSubgraph->m_driver )
5826 return true;
5827
5828 const SCH_SHEET_PATH& sheet = aSubgraph->m_sheet;
5829 std::vector<SCH_ITEM*> wires;
5830
5831 // We've gotten this far, so we know we have no valid driver. All we need to do is check
5832 // for a wire that we can place the error on.
5833 for( SCH_ITEM* item : aSubgraph->m_items )
5834 {
5835 if( item->Type() == SCH_LINE_T && item->GetLayer() == LAYER_WIRE )
5836 wires.emplace_back( item );
5837 else if( item->Type() == SCH_BUS_WIRE_ENTRY_T )
5838 wires.emplace_back( item );
5839 }
5840
5841 if( !wires.empty() )
5842 {
5843 SCH_SCREEN* screen = aSubgraph->m_sheet.LastScreen();
5844
5845 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_WIRE_DANGLING );
5846 ercItem->SetSheetSpecificPath( sheet );
5847 ercItem->SetItems( wires[0],
5848 wires.size() > 1 ? wires[1] : nullptr,
5849 wires.size() > 2 ? wires[2] : nullptr,
5850 wires.size() > 3 ? wires[3] : nullptr );
5851
5852 SCH_MARKER* marker = new SCH_MARKER( std::move( ercItem ), wires[0]->GetPosition() );
5853 screen->Append( marker );
5854
5855 return false;
5856 }
5857
5858 return true;
5859}
5860
5861
5862void CONNECTION_GRAPH::collectBusMemberSiblings( const CONNECTION_SUBGRAPH* aBusParent, const wxString& aMemberName,
5863 std::unordered_set<const CONNECTION_SUBGRAPH*>& aOut ) const
5864{
5865 auto busBucket = m_net_name_to_subgraphs_map.find( aBusParent->m_driver_connection->Name() );
5866
5867 if( busBucket == m_net_name_to_subgraphs_map.end() )
5868 return;
5869
5870 for( const CONNECTION_SUBGRAPH* siblingBus : busBucket->second )
5871 {
5872 for( const auto& [sibMemberConn, sibMembers] : siblingBus->m_bus_neighbors )
5873 {
5874 if( sibMemberConn->Name() != aMemberName )
5875 continue;
5876
5877 for( const CONNECTION_SUBGRAPH* sibling : sibMembers )
5878 aOut.insert( sibling );
5879 }
5880 }
5881}
5882
5883
5885{
5886 // Label connection rules:
5887 // Any label without a no-connect needs to have at least 2 pins, otherwise it is invalid
5888 // Local labels are flagged if they don't connect to any pins and don't have a no-connect
5889 // Global labels are flagged if they appear only once, don't connect to any local labels,
5890 // and don't have a no-connect marker
5891
5892 if( !aSubgraph->m_driver_connection )
5893 return true;
5894
5895 // Buses are excluded from this test: many users create buses with only a single instance
5896 // and it's not really a problem as long as the nets in the bus pass ERC
5897 if( aSubgraph->m_driver_connection->IsBus() )
5898 return true;
5899
5900 const SCH_SHEET_PATH& sheet = aSubgraph->m_sheet;
5901 ERC_SETTINGS& settings = m_schematic->ErcSettings();
5902 bool ok = true;
5903 size_t pinCount = 0;
5904 bool has_nc = !!aSubgraph->m_no_connect;
5905
5906 std::map<KICAD_T, std::vector<SCH_TEXT*>> label_map;
5907
5908
5909 auto hasPins =
5910 []( const CONNECTION_SUBGRAPH* aLocSubgraph ) -> size_t
5911 {
5912 return std::count_if( aLocSubgraph->m_items.begin(), aLocSubgraph->m_items.end(),
5913 []( const SCH_ITEM* item )
5914 {
5915 return item->Type() == SCH_PIN_T;
5916 } );
5917 };
5918
5919 auto reportError =
5920 [&]( SCH_TEXT* aText, int errCode )
5921 {
5922 if( settings.IsTestEnabled( errCode ) )
5923 {
5924 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( errCode );
5925 ercItem->SetSheetSpecificPath( sheet );
5926 ercItem->SetItems( aText );
5927
5928 SCH_MARKER* marker = new SCH_MARKER( std::move( ercItem ), aText->GetPosition() );
5929 aSubgraph->m_sheet.LastScreen()->Append( marker );
5930 }
5931 };
5932
5933 pinCount = hasPins( aSubgraph );
5934
5935 for( SCH_ITEM* item : aSubgraph->m_items )
5936 {
5937 switch( item->Type() )
5938 {
5939 case SCH_LABEL_T:
5940 case SCH_GLOBAL_LABEL_T:
5941 case SCH_HIER_LABEL_T:
5942 {
5943 SCH_TEXT* text = static_cast<SCH_TEXT*>( item );
5944
5945 label_map[item->Type()].push_back( text );
5946
5947 // Below, we'll create an ERC if the whole subgraph is unconnected. But, additionally,
5948 // we want to error if an individual label in the subgraph is floating, even if it's
5949 // connected to other valid things by way of another label on the same sheet.
5950 if( text->IsDangling() )
5951 {
5952 reportError( text, ERCE_LABEL_NOT_CONNECTED );
5953 return false;
5954 }
5955
5956 break;
5957 }
5958
5959 default:
5960 break;
5961 }
5962 }
5963
5964 if( label_map.empty() )
5965 return true;
5966
5967 // Walk m_bus_parents once. Bus parents may carry a no-connect that suppresses
5968 // an unconnected-label error, and they're how we reach bus members on other
5969 // sheets that share this net.
5970 std::unordered_set<const CONNECTION_SUBGRAPH*> busMemberSiblings;
5971
5972 for( auto& [memberConn, busParents] : aSubgraph->m_bus_parents )
5973 {
5974 wxString memberName = memberConn->Name();
5975
5976 for( CONNECTION_SUBGRAPH* busParent : busParents )
5977 {
5978 if( busParent->m_no_connect )
5979 has_nc = true;
5980
5981 for( CONNECTION_SUBGRAPH* hp = busParent->m_hier_parent; hp; hp = hp->m_hier_parent )
5982 {
5983 if( hp->m_no_connect )
5984 has_nc = true;
5985 }
5986
5987 collectBusMemberSiblings( busParent, memberName, busMemberSiblings );
5988 }
5989 }
5990
5991 wxString netName = GetResolvedSubgraphName( aSubgraph );
5992
5993 wxCHECK_MSG( m_schematic, true, wxS( "Null m_schematic in CONNECTION_GRAPH::ercCheckLabels" ) );
5994
5995 // Labels that have multiple pins connected are not dangling (may be used for naming segments)
5996 // so leave them without errors here
5997 if( pinCount > 1 )
5998 return true;
5999
6000 for( auto& [type, label_vec] : label_map )
6001 {
6002 for( SCH_TEXT* text : label_vec )
6003 {
6004 size_t allPins = pinCount;
6005 size_t localPins = pinCount;
6006 bool hasLocalHierarchy = false;
6007
6008 if( !aSubgraph->m_hier_pins.empty() || !aSubgraph->m_hier_ports.empty() )
6009 {
6010 // A label bridging multiple hierarchical connections
6011 // (e.g., connecting sheet pins from different sub-sheet
6012 // instances) is serving a valid routing purpose even
6013 // without local component pins.
6014 std::set<wxString> uniquePortNames;
6015 for( SCH_HIERLABEL* port : aSubgraph->m_hier_ports )
6016 uniquePortNames.insert( aSubgraph->GetNameForDriver( port ) );
6017
6018 if( aSubgraph->m_hier_pins.size() + uniquePortNames.size() > 1 )
6019 {
6020 hasLocalHierarchy = true;
6021 }
6022
6023 // Also check bus parents for bus-based hierarchical
6024 // routing on the same sheet.
6025 for( auto& [connection, busParents] : aSubgraph->m_bus_parents )
6026 {
6027 for( const CONNECTION_SUBGRAPH* busParent : busParents )
6028 {
6029 if( busParent->m_sheet == sheet
6030 && ( !busParent->m_hier_pins.empty()
6031 || !busParent->m_hier_ports.empty() ) )
6032 {
6033 hasLocalHierarchy = true;
6034 break;
6035 }
6036 }
6037
6038 if( hasLocalHierarchy )
6039 break;
6040 }
6041 }
6042
6043 std::unordered_set<const CONNECTION_SUBGRAPH*> creditedNeighbors;
6044 creditedNeighbors.insert( aSubgraph );
6045
6046 auto creditNeighbor = [&]( const CONNECTION_SUBGRAPH* neighbor )
6047 {
6048 if( !creditedNeighbors.insert( neighbor ).second )
6049 return;
6050
6051 if( neighbor->m_no_connect )
6052 has_nc = true;
6053
6054 size_t neighborPins = hasPins( neighbor );
6055 allPins += neighborPins;
6056
6057 if( neighbor->m_sheet == sheet )
6058 {
6059 localPins += neighborPins;
6060
6061 if( !neighbor->m_hier_pins.empty() || !neighbor->m_hier_ports.empty() )
6062 {
6063 hasLocalHierarchy = true;
6064 }
6065 }
6066 };
6067
6068 auto it = m_net_name_to_subgraphs_map.find( netName );
6069
6070 if( it != m_net_name_to_subgraphs_map.end() )
6071 {
6072 for( const CONNECTION_SUBGRAPH* neighbor : it->second )
6073 creditNeighbor( neighbor );
6074 }
6075
6076 for( const CONNECTION_SUBGRAPH* sibling : busMemberSiblings )
6077 creditNeighbor( sibling );
6078
6079 if( allPins == 1 && !has_nc )
6080 {
6081 reportError( text, ERCE_LABEL_SINGLE_PIN );
6082 ok = false;
6083 }
6084
6085 // A local label that connects to other subgraphs with
6086 // hierarchical connections on the same sheet (through bus
6087 // parents or net-name neighbors) is routing aggregated nets and should
6088 // not be flagged even without local component pins.
6089 if( allPins == 0
6090 || ( type == SCH_LABEL_T && localPins == 0 && allPins > 1
6091 && !has_nc && !hasLocalHierarchy ) )
6092 {
6093 reportError( text, ERCE_LABEL_NOT_CONNECTED );
6094 ok = false;
6095 }
6096 }
6097 }
6098
6099 return ok;
6100}
6101
6102
6104{
6105 int errors = 0;
6106
6107 std::map<wxString, std::tuple<int, const SCH_ITEM*, SCH_SHEET_PATH>> labelData;
6108
6109 for( const SCH_SHEET_PATH& sheet : m_sheetList )
6110 {
6111 for( SCH_ITEM* item : sheet.LastScreen()->Items().OfType( SCH_GLOBAL_LABEL_T ) )
6112 {
6113 SCH_TEXT* labelText = static_cast<SCH_TEXT*>( item );
6114 wxString resolvedLabelText =
6115 EscapeString( labelText->GetShownText( &sheet, false ), CTX_NETNAME );
6116
6117 if( labelData.find( resolvedLabelText ) == labelData.end() )
6118 {
6119 labelData[resolvedLabelText] = { 1, item, sheet };
6120 }
6121 else
6122 {
6123 std::get<0>( labelData[resolvedLabelText] ) += 1;
6124 std::get<1>( labelData[resolvedLabelText] ) = nullptr;
6125 std::get<2>( labelData[resolvedLabelText] ) = sheet;
6126 }
6127 }
6128 }
6129
6130 for( const auto& label : labelData )
6131 {
6132 if( std::get<0>( label.second ) == 1 )
6133 {
6134 const SCH_SHEET_PATH& sheet = std::get<2>( label.second );
6135 const SCH_ITEM* item = std::get<1>( label.second );
6136
6137 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_SINGLE_GLOBAL_LABEL );
6138 ercItem->SetItems( std::get<1>( label.second ) );
6139 ercItem->SetSheetSpecificPath( sheet );
6140 ercItem->SetItemsSheetPaths( sheet );
6141
6142 SCH_MARKER* marker = new SCH_MARKER( std::move( ercItem ), item->GetPosition() );
6143 sheet.LastScreen()->Append( marker );
6144
6145 errors++;
6146 }
6147 }
6148
6149 return errors;
6150}
6151
6152
6154{
6155 int error_count = 0;
6156
6157 for( const SCH_SHEET_PATH& sheet : m_sheetList )
6158 {
6159 for( SCH_ITEM* item : sheet.LastScreen()->Items().OfType( SCH_DIRECTIVE_LABEL_T ) )
6160 {
6161 SCH_LABEL* label = static_cast<SCH_LABEL*>( item );
6162
6163 if( label->IsDangling() )
6164 {
6165 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_LABEL_NOT_CONNECTED );
6166 SCH_TEXT* text = static_cast<SCH_TEXT*>( item );
6167 ercItem->SetSheetSpecificPath( sheet );
6168 ercItem->SetItems( text );
6169
6170 SCH_MARKER* marker = new SCH_MARKER( std::move( ercItem ), text->GetPosition() );
6171 sheet.LastScreen()->Append( marker );
6172 error_count++;
6173 }
6174 }
6175 }
6176
6177 return error_count;
6178}
6179
6180
6182{
6183 wxString msg;
6184 int errors = 0;
6185
6186 ERC_SETTINGS& settings = m_schematic->ErcSettings();
6187
6188 for( const SCH_SHEET_PATH& sheet : m_sheetList )
6189 {
6190 // Hierarchical labels in the top-level sheets cannot be connected to anything.
6191 if( sheet.Last()->IsTopLevelSheet() )
6192 {
6193 for( const SCH_ITEM* item : sheet.LastScreen()->Items().OfType( SCH_HIER_LABEL_T ) )
6194 {
6195 const SCH_HIERLABEL* label = static_cast<const SCH_HIERLABEL*>( item );
6196
6197 wxCHECK2( label, continue );
6198
6199 msg.Printf( _( "Hierarchical label '%s' in root sheet cannot be connected to non-existent "
6200 "parent sheet" ),
6201 label->GetShownText( &sheet, true ) );
6202 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_PIN_NOT_CONNECTED );
6203 ercItem->SetItems( item );
6204 ercItem->SetErrorMessage( msg );
6205
6206 SCH_MARKER* marker = new SCH_MARKER( std::move( ercItem ), item->GetPosition() );
6207 sheet.LastScreen()->Append( marker );
6208
6209 errors++;
6210 }
6211 }
6212
6213 for( SCH_ITEM* item : sheet.LastScreen()->Items().OfType( SCH_SHEET_T ) )
6214 {
6215 SCH_SHEET* parentSheet = static_cast<SCH_SHEET*>( item );
6216 SCH_SHEET_PATH parentSheetPath = sheet;
6217
6218 parentSheetPath.push_back( parentSheet );
6219
6220 std::map<wxString, SCH_SHEET_PIN*> pins;
6221 std::map<wxString, SCH_HIERLABEL*> labels;
6222
6223 for( SCH_SHEET_PIN* pin : parentSheet->GetPins() )
6224 {
6225 if( settings.IsTestEnabled( ERCE_HIERACHICAL_LABEL ) )
6226 pins[ pin->GetShownText( &parentSheetPath, false ) ] = pin;
6227
6228 if( pin->IsDangling() && settings.IsTestEnabled( ERCE_PIN_NOT_CONNECTED ) )
6229 {
6230 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_PIN_NOT_CONNECTED );
6231 ercItem->SetItems( pin );
6232 ercItem->SetSheetSpecificPath( sheet );
6233 ercItem->SetItemsSheetPaths( sheet );
6234
6235 SCH_MARKER* marker = new SCH_MARKER( std::move( ercItem ), pin->GetPosition() );
6236 sheet.LastScreen()->Append( marker );
6237
6238 errors++;
6239 }
6240 }
6241
6242 if( settings.IsTestEnabled( ERCE_HIERACHICAL_LABEL ) )
6243 {
6244 std::set<wxString> matchedPins;
6245
6246 for( SCH_ITEM* subItem : parentSheet->GetScreen()->Items() )
6247 {
6248 if( subItem->Type() == SCH_HIER_LABEL_T )
6249 {
6250 SCH_HIERLABEL* label = static_cast<SCH_HIERLABEL*>( subItem );
6251 wxString labelText = label->GetShownText( &parentSheetPath, false );
6252
6253 if( !pins.contains( labelText ) )
6254 labels[ labelText ] = label;
6255 else
6256 matchedPins.insert( labelText );
6257 }
6258 }
6259
6260 for( const wxString& matched : matchedPins )
6261 pins.erase( matched );
6262
6263 for( const auto& [name, pin] : pins )
6264 {
6265 msg.Printf( _( "Sheet pin %s has no matching hierarchical label inside the sheet" ),
6266 UnescapeString( name ) );
6267
6268 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_HIERACHICAL_LABEL );
6269 ercItem->SetItems( pin );
6270 ercItem->SetErrorMessage( msg );
6271 ercItem->SetSheetSpecificPath( sheet );
6272 ercItem->SetItemsSheetPaths( sheet );
6273
6274 SCH_MARKER* marker = new SCH_MARKER( std::move( ercItem ), pin->GetPosition() );
6275 sheet.LastScreen()->Append( marker );
6276
6277 errors++;
6278 }
6279
6280 for( const auto& [name, label] : labels )
6281 {
6282 msg.Printf( _( "Hierarchical label %s has no matching sheet pin in the parent sheet" ),
6283 UnescapeString( name ) );
6284
6285 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_HIERACHICAL_LABEL );
6286 ercItem->SetItems( label );
6287 ercItem->SetErrorMessage( msg );
6288 ercItem->SetSheetSpecificPath( parentSheetPath );
6289 ercItem->SetItemsSheetPaths( parentSheetPath );
6290
6291 SCH_MARKER* marker = new SCH_MARKER( std::move( ercItem ), label->GetPosition() );
6292 parentSheet->GetScreen()->Append( marker );
6293
6294 errors++;
6295 }
6296 }
6297 }
6298 }
6299
6300 return errors;
6301}
const char * name
constexpr EDA_IU_SCALE schIUScale
Definition base_units.h:123
This represents a sentry transaction which is used for time-performance metrics You start a transacti...
Definition app_monitor.h:60
void StartSpan(const std::string &aOperation, const std::string &aDescription)
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 ...
int RunERC()
Run electrical rule checks on the connectivity graph.
bool ercCheckBusToBusConflicts(const CONNECTION_SUBGRAPH *aSubgraph)
Check one subgraph for conflicting connections between two bus items.
void processSubGraphs()
Process all subgraphs to assign netcodes and merge subgraphs based on labels.
void refreshCommittedChainFromPotential(SCH_NETCHAIN *aTarget, const SCH_NETCHAIN &aSource)
Thin forwarder over refreshCommittedChainPayload that pulls payload fields from an inferred potential...
std::map< wxString, wxString > m_netChainNetClassOverrides
std::pair< CHAIN_TERMINAL_REF, CHAIN_TERMINAL_REF > CHAIN_TERMINAL_REFS
SCH_NETCHAIN * GetNetChainByName(const wxString &aName)
bool ercCheckLabels(const CONNECTION_SUBGRAPH *aSubgraph)
Check one subgraph for proper connection of labels.
std::unordered_map< SCH_ITEM *, std::vector< CONNECTION_SUBGRAPH * > > m_item_to_subgraph_map
Every subgraph referencing the item, one per instantiating sheet path for items on shared screens.
void RemoveItem(SCH_ITEM *aItem)
std::map< wxString, COLOR4D > m_netChainColorOverrides
void collectAllDriverValues()
Map the driver values for each subgraph.
CONNECTION_SUBGRAPH * FindSubgraphByName(const wxString &aNetName, const SCH_SHEET_PATH &aPath)
Return the subgraph for a given net name on a given sheet.
int ercCheckDirectiveLabels()
Check directive labels should be connected to something.
void recacheSubgraphName(CONNECTION_SUBGRAPH *aSubgraph, const wxString &aOldName)
static SCH_CONNECTION * matchBusMember(SCH_CONNECTION *aBusConnection, SCH_CONNECTION *aSearch)
Search for a matching bus member inside a bus connection.
std::unordered_map< wxString, std::shared_ptr< BUS_ALIAS > > m_bus_alias_cache
SCHEMATIC * m_schematic
The schematic this graph represents.
void updateGenericItemConnectivity(const SCH_SHEET_PATH &aSheet, SCH_ITEM *aItem, std::map< VECTOR2I, std::vector< SCH_ITEM * > > &aConnectionMap)
Update the connectivity of items that are not pins or symbols.
std::vector< std::unique_ptr< SCH_NETCHAIN > > m_committedNetChains
std::unordered_map< SCH_SHEET_PATH, std::vector< CONNECTION_SUBGRAPH * > > m_sheet_to_subgraphs_map
Cache to lookup subgraphs in m_driver_subgraphs by sheet path.
void updateSymbolConnectivity(const SCH_SHEET_PATH &aSheet, SCH_SYMBOL *aSymbol, std::map< VECTOR2I, std::vector< SCH_ITEM * > > &aConnectionMap)
Update the connectivity of a symbol and its pins.
CONNECTION_SUBGRAPH * FindFirstSubgraphByName(const wxString &aNetName)
Retrieve a subgraph for the given net name, if one exists.
void propagateToNeighbors(CONNECTION_SUBGRAPH *aSubgraph, bool aForce)
Update all neighbors of a subgraph with this one's connectivity info.
void buildItemSubGraphs()
Generate individual item subgraphs on a per-sheet basis.
SCH_NETCHAIN * GetNetChainForNet(const wxString &aNet)
const std::vector< CONNECTION_SUBGRAPH * > & GetAllSubgraphs(const wxString &aNetName) const
bool ercCheckMultipleDrivers(const CONNECTION_SUBGRAPH *aSubgraph)
If the subgraph has multiple drivers of equal priority that are graphically connected,...
SCH_SHEET_LIST m_sheetList
All the sheets in the schematic (as long as we don't have partial updates).
void generateGlobalPowerPinSubGraphs()
Iterate through the global power pins to collect the global labels as drivers.
std::map< wxString, CHAIN_TERMINAL_REFS > m_netChainTerminalRefOverrides
BRIDGE_GRAPH buildBridgeAdjacency()
Build the bridge graph used for net-chain discovery.
SCH_NETCHAIN * CreateNetChainFromPotential(SCH_NETCHAIN *aPotential, const wxString &aName)
Promote a potential net chain to an actual user net chain with the provided name.
std::unordered_map< wxString, int > m_net_name_to_code_map
int ercCheckSingleGlobalLabel()
Check that a global label is instantiated more that once across the schematic hierarchy.
int ercCheckHierSheets()
Check that a hierarchical sheet has at least one matching label inside the sheet for each port on the...
bool ercCheckBusToNetConflicts(const CONNECTION_SUBGRAPH *aSubgraph)
Check one subgraph for conflicting connections between net and bus labels.
std::shared_ptr< SCH_CONNECTION > getDefaultConnection(SCH_ITEM *aItem, CONNECTION_SUBGRAPH *aSubgraph)
Build a new default connection for the given item based on its properties.
static std::function< void(CONNECTION_GRAPH &)> & RebuildNetChainsTestHook()
Test-only hook fired inside RebuildNetChains() after the restore passes have finished but before the ...
bool RenameCommittedNetChain(const wxString &aOld, const wxString &aNew)
Rename a committed net chain.
std::vector< const CONNECTION_SUBGRAPH * > GetBusesNeedingMigration()
Determine which subgraphs have more than one conflicting bus label.
void Recalculate(const SCH_SHEET_LIST &aSheetList, bool aUnconditional=false, std::function< void(SCH_ITEM *)> *aChangedItemHandler=nullptr, PROGRESS_REPORTER *aProgressReporter=nullptr)
Update the connection graph for the given list of sheets.
int assignNewNetCode(SCH_CONNECTION &aConnection)
Helper to assign a new net code to a connection.
std::map< std::pair< SCH_SHEET_PATH, wxString >, std::vector< const CONNECTION_SUBGRAPH * > > m_local_label_cache
int getOrCreateNetCode(const wxString &aNetName)
void collectBusMemberSiblings(const CONNECTION_SUBGRAPH *aBusParent, const wxString &aMemberName, std::unordered_set< const CONNECTION_SUBGRAPH * > &aOut) const
Find bus members on other sheets that share aBusParent's bus and member name.
bool ercCheckDanglingWireEndpoints(const CONNECTION_SUBGRAPH *aSubgraph)
Check one subgraph for dangling wire endpoints.
void refreshCommittedChainPayload(SCH_NETCHAIN *aTarget, const std::set< wxString > &aNets, const std::set< class SCH_SYMBOL * > &aSymbols, const KIID &aTerminalPinA, const KIID &aTerminalPinB, const wxString &aRefA, const wxString &aPinNumA, const wxString &aRefB, const wxString &aPinNumB)
Replace the derived-view payload on aTarget with explicitly supplied member nets, symbols,...
void assignNetCodesToBus(SCH_CONNECTION *aConnection)
Ensure all members of the bus connection have a valid net code assigned.
std::unordered_map< wxString, int > m_bus_name_to_code_map
std::unordered_map< wxString, std::vector< const CONNECTION_SUBGRAPH * > > m_global_label_cache
std::vector< CONNECTION_SUBGRAPH * > m_subgraphs
The owner of all CONNECTION_SUBGRAPH objects.
std::vector< std::pair< SCH_SHEET_PATH, SCH_PIN * > > m_global_power_pins
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.
CONNECTION_GRAPH(SCHEMATIC *aSchematic=nullptr)
bool ercCheckNoConnects(const CONNECTION_SUBGRAPH *aSubgraph)
Check one subgraph for proper presence or absence of no-connect symbols.
size_t hasPins(const CONNECTION_SUBGRAPH *aLocSubgraph)
Get the number of pins in a given subgraph.
std::vector< SCH_ITEM * > m_items
All connectable items in the schematic.
void SetNetChainTerminalOverrides(const std::map< wxString, std::pair< KIID, KIID > > &aOverrides)
std::vector< std::unique_ptr< SCH_NETCHAIN > > m_potentialNetChains
last built potential (uncommitted) net chains
std::unordered_map< wxString, std::vector< CONNECTION_SUBGRAPH * > > m_net_name_to_subgraphs_map
std::shared_ptr< BUS_ALIAS > GetBusAlias(const wxString &aName)
Return a bus alias pointer for the given name if it exists (from cache)
void removeSubgraphs(std::set< CONNECTION_SUBGRAPH * > &aSubgraphs)
Remove references to the given subgraphs from all structures in the connection graph.
SCH_NETCHAIN * FindPotentialNetChainBetweenPins(SCH_PIN *aPinA, SCH_PIN *aPinB)
Locate a potential net chain that contains both pins (by subgraph net membership).
std::set< std::pair< SCH_SHEET_PATH, SCH_ITEM * > > ExtractAffectedItems(const std::set< SCH_ITEM * > &aItems)
For a set of items, this will remove the connected items and their associated data including subgraph...
wxString GetResolvedSubgraphName(const CONNECTION_SUBGRAPH *aSubGraph) const
Return the fully-resolved netname for a given subgraph.
bool ercCheckBusToBusEntryConflicts(const CONNECTION_SUBGRAPH *aSubgraph)
Check one subgraph for conflicting bus entry to bus connections.
void ReplaceNetChainTerminalPin(const wxString &aNetChain, const KIID &aPrev, const KIID &aNew)
std::vector< CONNECTION_SUBGRAPH * > m_driver_subgraphs
Cache of a subset of m_subgraphs.
std::vector< wxString > GetEquivalentBusNames(const wxString &aBusName) const
Map a bus group name between its alias and expanded forms ({MIXED_BUS} <-> {FOO BAR HAM EGGS}...
void ExchangeItem(SCH_ITEM *aOldItem, SCH_ITEM *aNewItem)
Replace all references to #aOldItem with #aNewItem in the graph.
NET_MAP m_net_code_to_subgraphs_map
bool ercCheckFloatingWires(const CONNECTION_SUBGRAPH *aSubgraph)
Check one subgraph for floating wires.
static wxString MakeNetChainKey(const wxString &aRawNetName, long aSubgraphCode)
Map a subgraph's raw net name and code to the stable key used as a SCH_NETCHAIN member.
void ApplyNetChainNetclasses()
Mirror each committed net chain's netclass override into the project NET_SETTINGS as a chain-derived ...
void rekeyOverrideMaps(const wxString &aOld, const wxString &aNew)
Move every net-chain override map entry keyed by aOld to aNew.
void buildConnectionGraph(std::function< void(SCH_ITEM *)> *aChangedItemHandler, bool aUnconditional)
Generate the connection graph (after all item connectivity has been updated).
void Merge(CONNECTION_GRAPH &aGraph)
Combine the input graph contents into the current graph.
void updatePinConnectivity(const SCH_SHEET_PATH &aSheet, SCH_PIN *aPin, SCH_CONNECTION *aConnection)
Update the connectivity of a pin and its connections.
void resolveAllDrivers()
Find all subgraphs in the connection graph and calls ResolveDrivers() in parallel.
void updateItemConnectivity(const SCH_SHEET_PATH &aSheet, const std::vector< SCH_ITEM * > &aItemList)
Update the graphical connectivity between items (i.e.
CONNECTION_SUBGRAPH * GetSubgraphForItem(SCH_ITEM *aItem) const
void generateBusAliasMembers()
Iterate through labels to create placeholders for bus elements.
bool DeleteCommittedNetChain(const wxString &aName)
Delete a committed net chain by name.
std::map< wxString, std::set< wxString > > m_netChainMemberNetOverrides
std::map< wxString, std::pair< KIID, KIID > > m_netChainTerminalOverrides
A subgraph is a set of items that are electrically connected on a single sheet.
wxString driverName(SCH_ITEM *aItem) const
bool m_strong_driver
True if the driver is "strong": a label or power object.
SCH_ITEM * m_no_connect
No-connect item in graph, if any.
std::set< CONNECTION_SUBGRAPH * > m_absorbed_subgraphs
Set of subgraphs that have been absorbed by this subgraph.
static PRIORITY GetDriverPriority(SCH_ITEM *aDriver)
Return the priority (higher is more important) of a candidate driver.
std::mutex m_driver_name_cache_mutex
A cache of escaped netnames from schematic items.
SCH_SHEET_PATH m_sheet
On which logical sheet is the subgraph contained.
void UpdateItemConnections()
Update all items to match the driver connection.
std::set< SCH_SHEET_PIN * > m_hier_pins
Cache for lookup of any hierarchical (sheet) pins on this subgraph (for referring down).
std::unordered_map< std::shared_ptr< SCH_CONNECTION >, std::unordered_set< CONNECTION_SUBGRAPH * > > m_bus_neighbors
If a subgraph is a bus, this map contains links between the bus members and any local sheet neighbors...
CONNECTION_GRAPH * m_graph
std::vector< SCH_ITEM * > GetAllBusLabels() const
Return all the all bus labels attached to this subgraph (if any).
std::unordered_map< SCH_ITEM *, wxString > m_driver_name_cache
const wxString & GetNameForDriver(SCH_ITEM *aItem) const
Return the candidate net name for a driver.
wxString GetNetName() const
Return the fully-qualified net name for this subgraph (if one exists)
std::vector< SCH_ITEM * > GetVectorBusLabels() const
Return all the vector-based bus labels attached to this subgraph (if any).
const SCH_SHEET_PATH & GetSheet() const
bool m_multiple_drivers
True if this subgraph contains more than one driver that should be shorted together in the netlist.
bool ResolveDrivers(bool aCheckMultipleDrivers=false)
Determine which potential driver should drive the subgraph.
std::set< SCH_ITEM * > m_drivers
bool m_absorbed
True if this subgraph has been absorbed into another. No pointers here are safe if so!
SCH_CONNECTION * m_driver_connection
Cache for driver connection.
CONNECTION_SUBGRAPH * m_absorbed_by
If this subgraph is absorbed, points to the absorbing (and valid) subgraph.
std::unordered_set< CONNECTION_SUBGRAPH * > m_hier_children
If not null, this indicates the subgraph(s) on a lower level sheet that are linked to this one.
void AddItem(SCH_ITEM *aItem)
Add a new item to the subgraph.
const std::vector< std::pair< wxString, SCH_ITEM * > > GetNetclassesForDriver(SCH_ITEM *aItem) const
Return the resolved netclasses for the item, and the source item providing the netclass.
void Absorb(CONNECTION_SUBGRAPH *aOther)
Combine another subgraph on the same sheet into this one.
std::set< SCH_ITEM * > m_items
Contents of the subgraph.
std::unordered_map< std::shared_ptr< SCH_CONNECTION >, std::unordered_set< CONNECTION_SUBGRAPH * > > m_bus_parents
If this is a net, this vector contains links to any same-sheet buses that contain it.
SCH_ITEM * m_driver
Fully-resolved driver for the subgraph (might not exist in this subgraph).
CONNECTION_SUBGRAPH(CONNECTION_GRAPH *aGraph)
bool m_is_bus_member
True if the subgraph is not actually part of a net.
void ExchangeItem(SCH_ITEM *aOldItem, SCH_ITEM *aNewItem)
Replaces all references to #aOldItem with #aNewItem in the subgraph.
CONNECTION_SUBGRAPH * m_hier_parent
If not null, this indicates the subgraph on a higher level sheet that is linked to this one.
void RemoveItem(SCH_ITEM *aItem)
bool m_local_driver
True if the driver is a local (i.e. non-global) type.
std::set< SCH_HIERLABEL * > m_hier_ports
Cache for lookup of any hierarchical ports on this subgraph (for referring up).
void getAllConnectedItems(std::set< std::pair< SCH_SHEET_PATH, SCH_ITEM * > > &aItems, std::set< CONNECTION_SUBGRAPH * > &aSubgraphs)
Find all items in the subgraph as well as child subgraphs recursively.
virtual VECTOR2I GetPosition() const
Definition eda_item.h:282
virtual wxString GetItemDescription(UNITS_PROVIDER *aUnitsProvider, bool aFull) const
Return a user-visible description string of this item.
Definition eda_item.cpp:169
const KIID m_Uuid
Definition eda_item.h:531
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:108
virtual const wxString & GetText() const
Return the string associated with the text object.
Definition eda_text.h:110
EE_TYPE Overlapping(const BOX2I &aRect) const
Definition sch_rtree.h:226
EE_TYPE OfType(KICAD_T aType) const
Definition sch_rtree.h:221
static std::shared_ptr< ERC_ITEM > Create(int aErrorCode)
Constructs an ERC_ITEM for the given error code.
Definition erc_item.cpp:305
Container for ERC settings.
bool IsTestEnabled(int aErrorCode) const
Definition kiid.h:46
wxString AsString() const
Definition kiid.cpp:264
bool GetDuplicatePinNumbersAreJumpers() const
Definition lib_symbol.h:850
std::vector< std::set< wxString > > & JumperPinGroups()
Each jumper pin group is a set of pin numbers that should be treated as internally connected.
Definition lib_symbol.h:857
static bool ParseBusGroup(const wxString &aGroup, wxString *name, std::vector< wxString > *aMemberList)
Parse a bus group label into the name and a list of components.
A small class to help profiling.
Definition profile.h:46
void Show(std::ostream &aStream=std::cerr)
Print the elapsed time (in a suitable unit) to a stream.
Definition profile.h:103
void Stop()
Save the time when this function was called, and set the counter stane to stop.
Definition profile.h:86
A progress reporter interface for use in multi-threaded environments.
virtual bool KeepRefreshing(bool aWait=false)=0
Update the UI (if any).
virtual void SetCurrentProgress(double aProgress)=0
Set the progress value to aProgress (0..1).
Class for a bus to bus entry.
SCH_ITEM * m_connected_bus_items[2]
Pointer to the bus items (usually bus wires) connected to this bus-bus entry (either or both may be n...
bool IsStartDangling() const
VECTOR2I GetPosition() const override
bool IsEndDangling() const
std::vector< VECTOR2I > GetConnectionPoints() const override
Add all the connection points for this item to aPoints.
Class for a wire to bus entry.
SCH_ITEM * m_connected_bus_item
Pointer to the bus item (usually a bus wire) connected to this bus-wire entry, if it is connected to ...
Each graphical item can have a SCH_CONNECTION describing its logical connection (to a bus or net).
wxString FullLocalName() const
void ConfigureFromLabel(const wxString &aLabel)
Configures the connection given a label.
bool IsNet() const
void SetSubgraphCode(int aCode)
void SetBusCode(int aCode)
void SetName(const wxString &aName)
SCH_SHEET_PATH Sheet() const
CONNECTION_TYPE Type() const
int SubgraphCode() const
void SetNetCode(int aCode)
SCH_ITEM * m_driver
The SCH_ITEM that drives this connection's net.
bool IsDriver() const
Checks if the SCH_ITEM this connection is attached to can drive connections Drivers can be labels,...
void SetType(CONNECTION_TYPE aType)
wxString LocalName() const
wxString Name(bool aIgnoreSheet=false) const
bool IsSubsetOf(SCH_CONNECTION *aOther) const
Returns true if this connection is contained within aOther (but not the same as aOther)
void SetDriver(SCH_ITEM *aItem)
bool IsBus() const
void Clone(const SCH_CONNECTION &aOther)
Copies connectivity information (but not parent) from another connection.
void SetGraph(CONNECTION_GRAPH *aGraph)
const std::vector< std::shared_ptr< SCH_CONNECTION > > & Members() const
long VectorIndex() const
wxString GetCanonicalName() const
Get a non-language-specific name for a field which can be used for storage, variable look-up,...
wxString GetShownText(const SCH_SHEET_PATH *aPath, bool aAllowExtraText, int aDepth=0, const wxString &aVariantName=wxEmptyString) const
Base class for any item which can be embedded within the SCHEMATIC container class,...
Definition sch_item.h:162
void ClearConnectedItems(const SCH_SHEET_PATH &aPath)
Clear all connections to this item.
Definition sch_item.cpp:549
virtual void RunOnChildren(const std::function< void(SCH_ITEM *)> &aFunction, RECURSE_MODE aMode)
Definition sch_item.h:628
const SYMBOL * GetParentSymbol() const
Definition sch_item.cpp:274
virtual const wxString & GetCachedDriverName() const
Definition sch_item.cpp:619
const std::unordered_set< SCH_RULE_AREA * > & GetRuleAreaCache() const
Get the cache of rule areas enclosing this item.
Definition sch_item.h:681
SCH_CONNECTION * InitializeConnection(const SCH_SHEET_PATH &aPath, CONNECTION_GRAPH *aGraph)
Create a new connection object associated with this object.
Definition sch_item.cpp:580
void AddConnectionTo(const SCH_SHEET_PATH &aPath, SCH_ITEM *aItem)
Add a connection link between this item and another.
Definition sch_item.cpp:564
int GetUnit() const
Definition sch_item.h:233
const std::vector< SCH_ITEM * > & ConnectedItems(const SCH_SHEET_PATH &aPath)
Retrieve the set of items connected to this item on the given sheet.
Definition sch_item.cpp:558
SCH_LAYER_ID GetLayer() const
Return the layer this item is on.
Definition sch_item.h:338
void SetConnectionGraph(CONNECTION_GRAPH *aGraph)
Update the connection graph for all connections in this item.
Definition sch_item.cpp:511
virtual void SetUnit(int aUnit)
Definition sch_item.h:232
virtual bool HasCachedDriverName() const
Definition sch_item.h:613
SCH_CONNECTION * GetOrInitConnection(const SCH_SHEET_PATH &aPath, CONNECTION_GRAPH *aGraph)
Definition sch_item.cpp:604
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:487
virtual std::vector< VECTOR2I > GetConnectionPoints() const
Add all the connection points for this item to aPoints.
Definition sch_item.h:539
wxString GetShownText(const SCH_SHEET_PATH *aPath, bool aAllowExtraText, int aDepth=0) const override
bool IsDangling() const override
Definition sch_label.h:335
LABEL_FLAG_SHAPE GetShape() const
Definition sch_label.h:178
Segment description base class to describe items which have 2 end points (track, wire,...
Definition sch_line.h:38
std::vector< VECTOR2I > GetConnectionPoints() const override
Add all the connection points for this item to aPoints.
Definition sch_line.cpp:755
bool IsStartDangling() const
Definition sch_line.h:299
VECTOR2I GetEndPoint() const
Definition sch_line.h:144
VECTOR2I GetStartPoint() const
Definition sch_line.h:135
bool IsEndDangling() const
Definition sch_line.h:300
bool IsGraphicLine() const
Return if the line is a graphic (non electrical line)
A net chain is a collection of nets that are connected together through passive components.
const KIID & GetTerminalPinB() const
const std::set< wxString > & GetNets() const
void AddSymbol(class SCH_SYMBOL *aSymbol)
const wxString & GetTerminalRef(int aIdx) const
const wxString & GetName() const
static constexpr char SYNTHETIC_NET_PREFIX[]
Prefix used when synthesising net names for unnamed subgraphs.
static bool IsValidName(const wxString &aName)
void SetTerminalPins(const KIID &aPinA, const KIID &aPinB)
void ClearSymbols()
void ReplaceNets(const std::set< wxString > &aNew)
const std::set< class SCH_SYMBOL * > & GetSymbols() const
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)
bool IsGlobalPower() const
Return whether this pin forms a global power connection: i.e., is part of a power symbol and of type ...
Definition sch_pin.cpp:453
bool IsLocalPower() const
Local power pin is the same except that it is sheet-local and it does not support the legacy hidden p...
Definition sch_pin.cpp:472
SCH_PIN * GetLibPin() const
Definition sch_pin.h:95
VECTOR2I GetPosition() const override
Definition sch_pin.cpp:350
bool IsStacked(const SCH_PIN *aPin) const
Definition sch_pin.cpp:575
wxString GetDefaultNetName(const SCH_SHEET_PATH &aPath, bool aForceNoConnect=false)
Definition sch_pin.cpp:1651
bool IsPower() const
Check if the pin is either a global or local power pin.
Definition sch_pin.cpp:479
ELECTRICAL_PINTYPE GetType() const
Definition sch_pin.cpp:407
void Append(SCH_ITEM *aItem, bool aUpdateLibSymbol=true)
void TestDanglingEnds(const SCH_SHEET_PATH *aPath=nullptr, std::function< void(SCH_ITEM *)> *aChangedHandler=nullptr) const
Test all of the connectable objects in the schematic for unused connection points.
std::vector< SCH_LINE * > GetBusesAndWires(const VECTOR2I &aPosition, bool aIgnoreEndpoints=false) const
Return buses and wires passing through aPosition.
EE_RTREE & Items()
Get the full RTree, usually for iterating.
Definition sch_screen.h:115
SCH_LINE * GetBus(const VECTOR2I &aPosition, int aAccuracy=0, SCH_LINE_TEST_T aSearchType=ENTIRE_LENGTH_T) const
Definition sch_screen.h:448
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...
const SCH_SHEET * GetSheet(unsigned aIndex) const
SCH_SCREEN * LastScreen()
wxString PathHumanReadable(bool aUseShortRootName=true, bool aStripTrailingSeparator=false, bool aEscapeSheetNames=false) const
Return the sheet path in a human readable form made from the sheet names.
SCH_SHEET * Last() const
Return a pointer to the last SCH_SHEET of the list.
void push_back(SCH_SHEET *aSheet)
Forwarded method from std::vector.
size_t size() const
Forwarded method from std::vector.
Define a sheet pin (label) used in sheets to create hierarchical schematics.
SCH_SHEET * GetParent() const
Get the parent sheet object of this sheet pin.
Sheet symbol placed in a schematic, and is the entry point for a sub schematic.
Definition sch_sheet.h:44
wxString GetFileName() const
Return the filename corresponding to this sheet.
Definition sch_sheet.h:370
SCH_SCREEN * GetScreen() const
Definition sch_sheet.h:139
std::vector< SCH_SHEET_PIN * > & GetPins()
Definition sch_sheet.h:227
Schematic symbol object.
Definition sch_symbol.h:69
PASSTHROUGH_MODE GetPassthroughMode() const
Definition sch_symbol.h:874
bool IsInNetlist() const
std::vector< const SCH_PIN * > GetPins(const SCH_SHEET_PATH *aSheet) const
Retrieve a list of the SCH_PINs for the given sheet path.
bool GetExcludedFromBoard(const SCH_SHEET_PATH *aInstance=nullptr, const wxString &aVariantName=wxEmptyString) const override
int GetUnitSelection(const SCH_SHEET_PATH *aSheet) const
Return the instance-specific unit selection for the given sheet path.
SCH_PIN * GetPin(const wxString &number) const
Find a symbol pin by number.
std::unique_ptr< LIB_SYMBOL > & GetLibSymbolRef()
Definition sch_symbol.h:177
const wxString GetRef(const SCH_SHEET_PATH *aSheet, bool aIncludeUnit=false) const override
VECTOR2I GetPosition() const override
Definition sch_text.h:146
virtual wxString GetShownText(const SCH_SHEET_PATH *aPath, bool aAllowExtraText, int aDepth=0) const
Definition sch_text.cpp:362
A base class for LIB_SYMBOL and SCH_SYMBOL.
Definition symbol.h:59
virtual bool IsGlobalPower() const =0
virtual bool IsLocalPower() const =0
virtual bool IsPower() const =0
The common library.
static int compareDrivers(SCH_ITEM *aA, SCH_CONNECTION *aAConn, const wxString &aAName, SCH_ITEM *aB, SCH_CONNECTION *aBConn, const wxString &aBName)
Unified driver ranking used by CONNECTION_SUBGRAPH::ResolveDrivers (within a single subgraph) and by ...
static wxString netChainKeyFor(const wxString &aRawNetName, long aSubgraphCode)
#define _(s)
@ NO_RECURSE
Definition eda_item.h:50
#define CONNECTIVITY_CANDIDATE
flag indicating that the structure is connected for connectivity
@ ERCE_DRIVER_CONFLICT
Conflicting drivers (labels, etc) on a subgraph.
@ ERCE_UNCONNECTED_WIRE_ENDPOINT
A label is connected to more than one wire.
@ ERCE_LABEL_NOT_CONNECTED
Label not connected to any pins.
@ ERCE_BUS_TO_BUS_CONFLICT
A connection between bus objects doesn't share at least one net.
@ ERCE_LABEL_SINGLE_PIN
A label is connected only to a single pin.
@ ERCE_BUS_ENTRY_CONFLICT
A wire connected to a bus doesn't match the bus.
@ ERCE_BUS_TO_NET_CONFLICT
A bus wire is graphically connected to a net port/pin (or vice versa).
@ ERCE_NOCONNECT_NOT_CONNECTED
A no connect symbol is not connected to anything.
@ ERCE_PIN_NOT_CONNECTED
Pin not connected and not no connect symbol.
@ ERCE_NOCONNECT_CONNECTED
A no connect symbol is connected to more than 1 pin.
@ ERCE_HIERACHICAL_LABEL
Mismatch between hierarchical labels and pins sheets.
@ ERCE_WIRE_DANGLING
Some wires are not connected to anything else.
@ ERCE_SINGLE_GLOBAL_LABEL
A label only exists once in the schematic.
static const wxChar DanglingProfileMask[]
Flag to enable connectivity profiling.
const wxChar *const traceSchNetChain
Flag to enable tracing of schematic net chain rebuild and ERC cross-chain checks.
static const wxChar ConnTrace[]
Flag to enable connectivity tracing.
@ LAYER_WIRE
Definition layer_ids.h:458
@ LAYER_BUS
Definition layer_ids.h:459
@ LAYER_JUNCTION
Definition layer_ids.h:460
@ LAYER_BUS_JUNCTION
Definition layer_ids.h:504
#define KI_FALLTHROUGH
The KI_FALLTHROUGH macro is to be used when switch statement cases should purposely fallthrough from ...
Definition macros.h:79
void remove_duplicates(_Container &__c)
Deletes all duplicate values from __c.
Definition kicad_algo.h:157
bool contains(const _Container &__container, _Value __value)
Returns true if the container contains the given value.
Definition kicad_algo.h:96
@ PT_NC
not connected (must be left open)
Definition pin_type.h:46
@ PT_NIC
not internally connected (may be connected to anything)
Definition pin_type.h:40
@ PT_POWER_IN
power input (GND, VCC for ICs). Must be connected to a power output.
Definition pin_type.h:42
CONNECTION_TYPE
@ BUS
This item represents a bus vector.
@ NET
This item represents a net.
@ BUS_GROUP
This item represents a bus group.
@ L_OUTPUT
Definition sch_label.h:99
Definition of the SCH_SHEET_PATH and SCH_SHEET_LIST classes for Eeschema.
std::vector< FAB_LAYER_COLOR > dummy
wxString UnescapeString(const wxString &aSource)
wxString EscapeString(const wxString &aSource, ESCAPE_CONTEXT aContext)
The Escape/Unescape routines use HTML-entity-reference-style encoding to handle characters which are:...
@ CTX_NETNAME
std::vector< BRIDGE_EDGE > edges
std::map< wxString, std::vector< BRIDGE_NEIGHBOR > > adjacency
std::string path
KIBIS_COMPONENT * comp
KIBIS_PIN * pin
KIBIS_PIN * pinA
const SHAPE_LINE_CHAIN chain
VECTOR2I location
wxString result
Test unit parsing edge cases and error handling.
thread_pool & GetKiCadThreadPool()
Get a reference to the current thread pool.
static thread_pool * tp
BS::priority_thread_pool thread_pool
Definition thread_pool.h:27
wxLogTrace helper definitions.
#define kv
@ SCH_LINE_T
Definition typeinfo.h:160
@ SCH_NO_CONNECT_T
Definition typeinfo.h:157
@ SCH_SYMBOL_T
Definition typeinfo.h:169
@ SCH_FIELD_T
Definition typeinfo.h:147
@ SCH_DIRECTIVE_LABEL_T
Definition typeinfo.h:168
@ SCH_LABEL_T
Definition typeinfo.h:164
@ SCH_SHEET_T
Definition typeinfo.h:172
@ SCH_HIER_LABEL_T
Definition typeinfo.h:166
@ SCH_BUS_BUS_ENTRY_T
Definition typeinfo.h:159
@ SCH_SHEET_PIN_T
Definition typeinfo.h:171
@ SCH_TEXT_T
Definition typeinfo.h:148
@ SCH_BUS_WIRE_ENTRY_T
Definition typeinfo.h:158
@ SCH_GLOBAL_LABEL_T
Definition typeinfo.h:165
@ SCH_JUNCTION_T
Definition typeinfo.h:156
@ SCH_PIN_T
Definition typeinfo.h:150
Functions to provide common constants and other functions to assist in making a consistent UI.
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683