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