KiCad PCB EDA Suite
Loading...
Searching...
No Matches
connection_graph.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2018 CERN
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * @author Jon Evans <[email protected]>
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License
11 * as published by the Free Software Foundation; either version 2
12 * of the License, or (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 */
22
23#include <algorithm>
24#include <list>
25#include <functional>
26#include <future>
27#include <map>
28#include <ranges>
29#include <set>
30#include <tuple>
31#include <unordered_map>
32#include <unordered_set>
33#include <vector>
34
35#include <app_monitor.h>
36#include <core/profile.h>
37#include <core/kicad_algo.h>
38#include <common.h>
39#include <erc/erc.h>
40#include <pin_type.h>
41#include <sch_bus_entry.h>
42#include <sch_symbol.h>
43#include <sch_edit_frame.h>
44#include <sch_line.h>
45#include <sch_marker.h>
46#include <sch_pin.h>
47#include <sch_rule_area.h>
48#include <trace_helpers.h>
49#include <wx/log.h>
50#include <sch_netchain.h>
51#include <sch_label.h>
52#include <sch_sheet.h>
53#include <sch_sheet_path.h>
54#include <sch_sheet_pin.h>
55#include <sch_text.h>
56#include <schematic.h>
57#include <symbol.h>
58#include <connection_graph.h>
61#include <widgets/ui_common.h>
62#include <string_utils.h>
63#include <thread_pool.h>
64#include <wx/log.h>
65
67#include <advanced_config.h> // for realtime connectivity switch in release builds
68
69
74static const wxChar DanglingProfileMask[] = wxT( "CONN_PROFILE" );
75
76
81static const wxChar ConnTrace[] = wxT( "CONN" );
82
83
85{
86 // Item cleanup must stop calling into this graph before teardown starts.
87 m_lifetime.reset();
88 // Ensure destruction happens in a translation unit that includes full SCH_NETCHAIN
89 // definition to avoid incomplete type issues with std::unique_ptr<SCH_NETCHAIN>.
90 Reset();
91}
92
93
95{
96 m_items.erase( aItem );
97 m_drivers.erase( aItem );
98
99 if( aItem == m_driver )
100 {
101 m_driver = nullptr;
102 m_driver_connection = nullptr;
103 }
104
105 if( aItem->Type() == SCH_SHEET_PIN_T )
106 m_hier_pins.erase( static_cast<SCH_SHEET_PIN*>( aItem ) );
107
108 if( aItem->Type() == SCH_HIER_LABEL_T )
109 m_hier_ports.erase( static_cast<SCH_HIERLABEL*>( aItem ) );
110}
111
112
114{
115 m_items.erase( aOldItem );
116 m_items.insert( aNewItem );
117
118 m_drivers.erase( aOldItem );
119 m_drivers.insert( aNewItem );
120
121 if( aOldItem == m_driver )
122 {
123 m_driver = aNewItem;
125 }
126
127 SCH_CONNECTION* old_conn = aOldItem->Connection( &m_sheet );
128 SCH_CONNECTION* new_conn = aNewItem->GetOrInitConnection( m_sheet, m_graph );
129
130 if( old_conn && new_conn )
131 {
132 new_conn->Clone( *old_conn );
133
134 if( old_conn->IsDriver() )
135 new_conn->SetDriver( aNewItem );
136
137 new_conn->ClearDirty();
138 }
139
140 if( aOldItem->Type() == SCH_SHEET_PIN_T )
141 {
142 m_hier_pins.erase( static_cast<SCH_SHEET_PIN*>( aOldItem ) );
143 m_hier_pins.insert( static_cast<SCH_SHEET_PIN*>( aNewItem ) );
144 }
145
146 if( aOldItem->Type() == SCH_HIER_LABEL_T )
147 {
148 m_hier_ports.erase( static_cast<SCH_HIERLABEL*>( aOldItem ) );
149 m_hier_ports.insert( static_cast<SCH_HIERLABEL*>( aNewItem ) );
150 }
151}
152
153
154using DRIVER_IDENTITY = std::tuple<KIID, wxString, int, VECTOR2I>;
155using SUBGRAPH_IDENTITY = std::pair<KIID_PATH, DRIVER_IDENTITY>;
156
157
159{
160 KIID owner = aDriver->m_Uuid;
161 wxString number;
162
163 if( aDriver->Type() == SCH_PIN_T )
164 {
165 SCH_PIN* pin = static_cast<SCH_PIN*>( aDriver );
166 owner = pin->GetParentSymbol()->m_Uuid;
167 number = pin->GetNumber();
168 }
169
170 return { owner, number, aDriver->GetUnit(), aDriver->GetPosition() };
171}
172
173
193static int compareDrivers( SCH_ITEM* aA, SCH_CONNECTION* aAConn, const wxString& aAName,
194 SCH_ITEM* aB, SCH_CONNECTION* aBConn, const wxString& aBName )
195{
198
199 if( pa != pb )
200 return pa > pb ? -1 : 1;
201
202 if( aAConn->IsBus() && aBConn->IsBus() )
203 {
204 bool a_in_b = aAConn->IsSubsetOf( aBConn );
205 bool b_in_a = aBConn->IsSubsetOf( aAConn );
206
207 if( b_in_a && !a_in_b )
208 return -1;
209
210 if( a_in_b && !b_in_a )
211 return 1;
212 }
213
214 if( aA->Type() == SCH_PIN_T && aB->Type() == SCH_PIN_T )
215 {
216 SCH_PIN* pinA = static_cast<SCH_PIN*>( aA );
217 SCH_PIN* pinB = static_cast<SCH_PIN*>( aB );
218
219 SYMBOL* parentA = pinA->GetLibPin() ? pinA->GetLibPin()->GetParentSymbol() : nullptr;
220 SYMBOL* parentB = pinB->GetLibPin() ? pinB->GetLibPin()->GetParentSymbol() : nullptr;
221
222 bool aGlobal = parentA && parentA->IsGlobalPower();
223 bool bGlobal = parentB && parentB->IsGlobalPower();
224
225 if( aGlobal != bGlobal )
226 return aGlobal ? -1 : 1;
227
228 bool aLocal = parentA && parentA->IsLocalPower();
229 bool bLocal = parentB && parentB->IsLocalPower();
230
231 if( aLocal != bLocal )
232 return aLocal ? -1 : 1;
233 }
234
235 if( aA->Type() == SCH_SHEET_PIN_T && aB->Type() == SCH_SHEET_PIN_T )
236 {
237 SCH_SHEET_PIN* sheetPinA = static_cast<SCH_SHEET_PIN*>( aA );
238 SCH_SHEET_PIN* sheetPinB = static_cast<SCH_SHEET_PIN*>( aB );
239
240 if( sheetPinA->GetShape() != sheetPinB->GetShape() )
241 {
242 if( sheetPinA->GetShape() == LABEL_FLAG_SHAPE::L_OUTPUT )
243 return -1;
244
245 if( sheetPinB->GetShape() == LABEL_FLAG_SHAPE::L_OUTPUT )
246 return 1;
247 }
248 }
249
250 bool aLowQuality = aAName.Contains( wxS( "-Pad" ) );
251 bool bLowQuality = aBName.Contains( wxS( "-Pad" ) );
252
253 if( aLowQuality != bLowQuality )
254 return aLowQuality ? 1 : -1;
255
256 if( aAName < aBName )
257 return -1;
258
259 if( aBName < aAName )
260 return 1;
261
262 return 0;
263}
264
265
266bool CONNECTION_SUBGRAPH::ResolveDrivers( bool aCheckMultipleDrivers )
267{
268 std::lock_guard lock( m_driver_mutex );
269
270 // Collect candidate drivers of highest priority in a simple vector which will be
271 // sorted later. Using a vector makes the ranking logic explicit and easier to
272 // maintain than relying on the ordering semantics of std::set.
273 PRIORITY highest_priority = PRIORITY::INVALID;
274 std::vector<SCH_ITEM*> candidates;
275 std::set<SCH_ITEM*> strong_drivers;
276
277 m_driver = nullptr;
278
279 // Hierarchical labels are lower priority than local labels here,
280 // because on the first pass we want local labels to drive subgraphs
281 // so that we can identify same-sheet neighbors and link them together.
282 // Hierarchical labels will end up overriding the final net name if
283 // a higher-level sheet has a different name during the hierarchical
284 // pass.
285
286 for( SCH_ITEM* item : m_drivers )
287 {
288 PRIORITY item_priority = GetDriverPriority( item );
289
290 if( item_priority == PRIORITY::PIN )
291 {
292 SCH_PIN* pin = static_cast<SCH_PIN*>( item );
293
294 if( !static_cast<SCH_SYMBOL*>( pin->GetParentSymbol() )->IsInNetlist() )
295 continue;
296 }
297
298 if( item_priority >= PRIORITY::HIER_LABEL )
299 strong_drivers.insert( item );
300
301 if( item_priority > highest_priority )
302 {
303 candidates.clear();
304 candidates.push_back( item );
305 highest_priority = item_priority;
306 }
307 else if( !candidates.empty() && ( item_priority == highest_priority ) )
308 {
309 candidates.push_back( item );
310 }
311 }
312
313 if( highest_priority >= PRIORITY::HIER_LABEL )
314 m_strong_driver = true;
315
316 // Power pins are 5, global labels are 6
317 m_local_driver = ( highest_priority < PRIORITY::GLOBAL_POWER_PIN );
318
319 if( !candidates.empty() )
320 {
321 // Use the same driver priorities as the global-label transitive-closure pre-pass.
322 // Resolve equal candidates by persistent identity for stable weak-net suffixes.
323 auto candidate_cmp = [&]( SCH_ITEM* a, SCH_ITEM* b )
324 {
325 int priority = compareDrivers( a, a->Connection( &m_sheet ), GetNameForDriver( a ),
326 b, b->Connection( &m_sheet ), GetNameForDriver( b ) );
327
328 // Equal-name pins may belong to different units; their owner identity also orders later suffixes.
329 return priority != 0 ? priority < 0 : stableDriverIdentity( a ) < stableDriverIdentity( b );
330 };
331
332 std::sort( candidates.begin(), candidates.end(), candidate_cmp );
333
334 m_driver = candidates.front();
335 }
336
337 if( strong_drivers.size() > 1 )
338 m_multiple_drivers = true;
339
340 // Drop weak drivers
341 if( m_strong_driver )
342 {
343 m_drivers.clear();
344 m_drivers.insert( strong_drivers.begin(), strong_drivers.end() );
345 }
346
347 // Cache driver connection
348 if( m_driver )
349 {
350 m_driver_connection = m_driver->Connection( &m_sheet );
351 m_driver_connection->ConfigureFromLabel( GetNameForDriver( m_driver ) );
352 m_driver_connection->SetDriver( m_driver );
353 m_driver_connection->ClearDirty();
354 }
355 else if( !m_is_bus_member )
356 {
357 m_driver_connection = nullptr;
358 }
359
360 return ( m_driver != nullptr );
361}
362
363
365 SCH_ITEM*>>& aItems,
366 std::set<CONNECTION_SUBGRAPH*>& aSubgraphs )
367{
368 CONNECTION_SUBGRAPH* sg = this;
369
370 while( sg->m_absorbed_by )
371 {
372 wxCHECK2( sg->m_graph == sg->m_absorbed_by->m_graph, continue );
373 sg = sg->m_absorbed_by;
374 }
375
376 // If we are unable to insert the subgraph into the set, then we have already
377 // visited it and don't need to add it again.
378 if( aSubgraphs.insert( sg ).second == false )
379 return;
380
381 aSubgraphs.insert( sg->m_absorbed_subgraphs.begin(), sg->m_absorbed_subgraphs.end() );
382
383 for( SCH_ITEM* item : sg->m_items )
384 aItems.emplace( m_sheet, item );
385
386 for( CONNECTION_SUBGRAPH* child_sg : sg->m_hier_children )
387 child_sg->getAllConnectedItems( aItems, aSubgraphs );
388}
389
390
392{
393 if( !m_driver || m_dirty )
394 return "";
395
396 if( !m_driver->Connection( &m_sheet ) )
397 {
398#ifdef CONNECTIVITY_DEBUG
399 wxASSERT_MSG( false, wxS( "Tried to get the net name of an item with no connection" ) );
400#endif
401
402 return "";
403 }
404
405 return m_driver->Connection( &m_sheet )->Name();
406}
407
408
409std::vector<SCH_ITEM*> CONNECTION_SUBGRAPH::GetAllBusLabels() const
410{
411 std::vector<SCH_ITEM*> labels;
412
413 for( SCH_ITEM* item : m_drivers )
414 {
415 switch( item->Type() )
416 {
417 case SCH_LABEL_T:
419 case SCH_HIER_LABEL_T:
420 {
421 CONNECTION_TYPE type = item->Connection( &m_sheet )->Type();
422
423 // Only consider bus vectors
424 if( type == CONNECTION_TYPE::BUS || type == CONNECTION_TYPE::BUS_GROUP )
425 labels.push_back( item );
426
427 break;
428 }
429
430 default:
431 break;
432 }
433 }
434
435 return labels;
436}
437
438
439std::vector<SCH_ITEM*> CONNECTION_SUBGRAPH::GetVectorBusLabels() const
440{
441 std::vector<SCH_ITEM*> labels;
442
443 for( SCH_ITEM* item : m_drivers )
444 {
445 switch( item->Type() )
446 {
447 case SCH_LABEL_T:
449 case SCH_HIER_LABEL_T:
450 {
451 SCH_CONNECTION* label_conn = item->Connection( &m_sheet );
452
453 // Only consider bus vectors
454 if( label_conn->Type() == CONNECTION_TYPE::BUS )
455 labels.push_back( item );
456
457 break;
458 }
459
460 default:
461 break;
462 }
463 }
464
465 return labels;
466}
467
468
470{
471 switch( aItem->Type() )
472 {
473 case SCH_PIN_T:
474 {
475 SCH_PIN* pin = static_cast<SCH_PIN*>( aItem );
476 bool forceNoConnect = m_no_connect != nullptr;
477
478 return pin->GetDefaultNetName( m_sheet, forceNoConnect );
479 }
480
481 case SCH_LABEL_T:
483 case SCH_HIER_LABEL_T:
484 {
485 SCH_LABEL_BASE* label = static_cast<SCH_LABEL_BASE*>( aItem );
486
487 // NB: any changes here will need corresponding changes in SCH_LABEL_BASE::cacheShownText()
489 }
490
491 case SCH_SHEET_PIN_T:
492 {
493 // Sheet pins need to use their parent sheet as their starting sheet or they will resolve
494 // variables on the current sheet first
495 SCH_SHEET_PIN* sheetPin = static_cast<SCH_SHEET_PIN*>( aItem );
497
498 if( path.Last() != sheetPin->GetParent() )
499 path.push_back( sheetPin->GetParent() );
500
501 return EscapeString( sheetPin->GetShownText( &path, FOR_NETNAME ), CTX_NETNAME );
502 }
503
504 default:
505 wxFAIL_MSG( wxS( "Unhandled item type in GetNameForDriver" ) );
506 return wxEmptyString;
507 }
508}
509
510
511const wxString& CONNECTION_SUBGRAPH::GetNameForDriver( SCH_ITEM* aItem ) const
512{
513 if( aItem->HasCachedDriverName() )
514 return aItem->GetCachedDriverName();
515
516 std::lock_guard lock( m_driver_name_cache_mutex );
517 auto it = m_driver_name_cache.find( aItem );
518
519 if( it != m_driver_name_cache.end() )
520 return it->second;
521
522 return m_driver_name_cache.emplace( aItem, driverName( aItem ) ).first->second;
523}
524
525
526const std::vector<std::pair<wxString, SCH_ITEM*>>
528{
529 std::vector<std::pair<wxString, SCH_ITEM*>> foundNetclasses;
530
531 const std::unordered_set<SCH_RULE_AREA*>& ruleAreaCache = aItem->GetRuleAreaCache();
532
533 // Get netclasses on attached rule areas
534 for( SCH_RULE_AREA* ruleArea : ruleAreaCache )
535 {
536 const std::vector<std::pair<wxString, SCH_ITEM*>> ruleAreaNetclasses =
537 ruleArea->GetResolvedNetclasses( &m_sheet );
538
539 if( ruleAreaNetclasses.size() > 0 )
540 {
541 foundNetclasses.insert( foundNetclasses.end(), ruleAreaNetclasses.begin(),
542 ruleAreaNetclasses.end() );
543 }
544 }
545
546 // Get netclasses on child fields
547 aItem->RunOnChildren(
548 [&]( SCH_ITEM* aChild )
549 {
550 if( aChild->Type() == SCH_FIELD_T )
551 {
552 SCH_FIELD* field = static_cast<SCH_FIELD*>( aChild );
553
554 if( field->GetUntranslatedName() == wxT( "Netclass" ) )
555 {
556 wxString netclass = field->GetShownText( &m_sheet, FOR_NETNAME );
557
558 if( netclass != wxEmptyString )
559 foundNetclasses.push_back( { netclass, aItem } );
560 }
561 }
562 },
564
565 std::sort(
566 foundNetclasses.begin(), foundNetclasses.end(),
567 []( const std::pair<wxString, SCH_ITEM*>& i1, const std::pair<wxString, SCH_ITEM*>& i2 )
568 {
569 return i1.first < i2.first;
570 } );
571
572 return foundNetclasses;
573}
574
575
577{
578 wxCHECK( m_sheet == aOther->m_sheet, /* void */ );
579
580 for( SCH_ITEM* item : aOther->m_items )
581 {
583 AddItem( item );
584 }
585
586 m_absorbed_subgraphs.insert( aOther );
587 m_absorbed_subgraphs.insert( aOther->m_absorbed_subgraphs.begin(),
588 aOther->m_absorbed_subgraphs.end() );
589
590 m_bus_neighbors.insert( aOther->m_bus_neighbors.begin(), aOther->m_bus_neighbors.end() );
591 m_bus_parents.insert( aOther->m_bus_parents.begin(), aOther->m_bus_parents.end() );
592
594
595 std::function<void( CONNECTION_SUBGRAPH* )> set_absorbed_by =
596 [ & ]( CONNECTION_SUBGRAPH *child )
597 {
598 child->m_absorbed_by = this;
599
600 for( CONNECTION_SUBGRAPH* subchild : child->m_absorbed_subgraphs )
601 set_absorbed_by( subchild );
602 };
603
604 aOther->m_absorbed = true;
605 aOther->m_dirty = false;
606 aOther->m_driver = nullptr;
607 aOther->m_driver_connection = nullptr;
608
609 set_absorbed_by( aOther );
610}
611
612
614{
615 m_items.insert( aItem );
616
617 if( aItem->Connection( &m_sheet )->IsDriver() )
618 m_drivers.insert( aItem );
619
620 if( aItem->Type() == SCH_SHEET_PIN_T )
621 m_hier_pins.insert( static_cast<SCH_SHEET_PIN*>( aItem ) );
622 else if( aItem->Type() == SCH_HIER_LABEL_T )
623 m_hier_ports.insert( static_cast<SCH_HIERLABEL*>( aItem ) );
624}
625
626
628{
630 return;
631
632 for( SCH_ITEM* item : m_items )
633 {
634 SCH_CONNECTION* item_conn = item->GetOrInitConnection( m_sheet, m_graph );
635
636 if( !item_conn )
637 continue;
638
639 if( ( m_driver_connection->IsBus() && item_conn->IsNet() ) ||
640 ( m_driver_connection->IsNet() && item_conn->IsBus() ) )
641 {
642 continue;
643 }
644
645 item_conn->Clone( *m_driver_connection );
646 item_conn->ClearDirty();
647 }
648}
649
650
652{
653 if( !aDriver )
654 return PRIORITY::NONE;
655
656 auto libSymbolRef =
657 []( const SCH_SYMBOL* symbol ) -> wxString
658 {
659 if( const std::unique_ptr<LIB_SYMBOL>& part = symbol->GetLibSymbolRef() )
660 return part->GetReferenceField().GetText();
661
662 return wxEmptyString;
663 };
664
665 switch( aDriver->Type() )
666 {
671
672 case SCH_PIN_T:
673 {
674 SCH_PIN* sch_pin = static_cast<SCH_PIN*>( aDriver );
675 const SCH_SYMBOL* sym = static_cast<SCH_SYMBOL*>( sch_pin->GetParentSymbol() );
676
677 if( sch_pin->IsGlobalPower() )
679 else if( sch_pin->IsLocalPower() )
681 else if( !sym || sym->GetExcludedFromBoard() || libSymbolRef( sym ).StartsWith( '#' ) )
682 return PRIORITY::NONE;
683 else
684 return PRIORITY::PIN;
685 }
686
687 default:
688 return PRIORITY::NONE;
689 }
690}
691
692
694{
695 std::copy( aGraph.m_items.begin(), aGraph.m_items.end(),
696 std::back_inserter( m_items ) );
697
698 for( SCH_ITEM* item : aGraph.m_items )
699 {
700 item->SetConnectionGraph( this );
702 }
703
704 std::copy( aGraph.m_subgraphs.begin(), aGraph.m_subgraphs.end(),
705 std::back_inserter( m_subgraphs ) );
706
707 for( CONNECTION_SUBGRAPH* sg : aGraph.m_subgraphs )
708 {
709 if( sg->m_driver_connection )
710 sg->m_driver_connection->SetGraph( this );
711
712 sg->m_graph = this;
713 }
714
715 std::copy( aGraph.m_driver_subgraphs.begin(), aGraph.m_driver_subgraphs.end(),
716 std::back_inserter( m_driver_subgraphs ) );
717
718 std::copy( aGraph.m_global_power_pins.begin(), aGraph.m_global_power_pins.end(),
719 std::back_inserter( m_global_power_pins ) );
720
721 for( auto& [key, value] : aGraph.m_net_name_to_subgraphs_map )
722 m_net_name_to_subgraphs_map.insert_or_assign( key, value );
723
724 for( auto& [key, value] : aGraph.m_sheet_to_subgraphs_map )
725 m_sheet_to_subgraphs_map.insert_or_assign( key, value );
726
727 for( auto& [key, value] : aGraph.m_net_name_to_code_map )
728 m_net_name_to_code_map.insert_or_assign( key, value );
729
730 for( auto& [key, value] : aGraph.m_bus_name_to_code_map )
731 m_bus_name_to_code_map.insert_or_assign( key, value );
732
733 for( auto& [key, value] : aGraph.m_net_code_to_subgraphs_map )
734 m_net_code_to_subgraphs_map.insert_or_assign( key, value );
735
736 // Union rather than replace. An incremental pass may only have rebuilt the item on some of
737 // its sheet paths, and dropping the surviving subgraphs here would orphan their references
738 // to the item so a later removal could no longer find them.
739 for( auto& [key, value] : aGraph.m_item_to_subgraph_map )
740 {
741 key->registerConnectivityOwner( m_lifetime );
742 std::vector<CONNECTION_SUBGRAPH*>& existing = m_item_to_subgraph_map[key];
743
744 for( CONNECTION_SUBGRAPH* sg : value )
745 {
746 if( !alg::contains( existing, sg ) )
747 existing.push_back( sg );
748 }
749 }
750
751 for( auto& [key, value] : aGraph.m_local_label_cache )
752 m_local_label_cache.insert_or_assign( key, value );
753
754 for( auto& [key, value] : aGraph.m_global_label_cache )
755 m_global_label_cache.insert_or_assign( key, value );
756
757 m_last_bus_code = std::max( m_last_bus_code, aGraph.m_last_bus_code );
758 m_last_net_code = std::max( m_last_net_code, aGraph.m_last_net_code );
760
761 m_netChains->Merge( *aGraph.m_netChains );
762 m_netChains->ApplyNetChainNetclasses();
763}
764
765
767{
768 wxCHECK2( aOldItem->Type() == aNewItem->Type(), return );
769
770 auto exchange = [&]( SCH_ITEM* aOld, SCH_ITEM* aNew )
771 {
772 auto it = m_item_to_subgraph_map.find( aOld );
773
774 if( it == m_item_to_subgraph_map.end() )
775 return;
776
777 std::vector<CONNECTION_SUBGRAPH*> sgs = std::move( it->second );
778
779 for( CONNECTION_SUBGRAPH* sg : sgs )
780 sg->ExchangeItem( aOld, aNew );
781
782 m_item_to_subgraph_map.erase( it );
783 m_item_to_subgraph_map.emplace( aNew, std::move( sgs ) );
784 aNew->registerConnectivityOwner( m_lifetime );
785
786 for( auto it2 = m_items.begin(); it2 != m_items.end(); ++it2 )
787 {
788 if( *it2 == aOld )
789 {
790 *it2 = aNew;
791 break;
792 }
793 }
794 };
795
796 exchange( aOldItem, aNewItem );
797
798 if( aOldItem->Type() == SCH_SYMBOL_T )
799 {
800 SCH_SYMBOL* oldSymbol = static_cast<SCH_SYMBOL*>( aOldItem );
801 SCH_SYMBOL* newSymbol = static_cast<SCH_SYMBOL*>( aNewItem );
802 std::vector<SCH_PIN*> oldPins = oldSymbol->GetPins( &m_schematic->CurrentSheet() );
803 std::vector<SCH_PIN*> newPins = newSymbol->GetPins( &m_schematic->CurrentSheet() );
804
805 wxCHECK2( oldPins.size() == newPins.size(), return );
806
807 for( size_t ii = 0; ii < oldPins.size(); ii++ )
808 {
809 exchange( oldPins[ii], newPins[ii] );
810 }
811 }
812}
813
814
816{
817 for( auto& subgraph : m_subgraphs )
818 {
820 if( subgraph->m_graph == this )
821 delete subgraph;
822 }
823
824 m_items.clear();
825 m_subgraphs.clear();
826 m_driver_subgraphs.clear();
828 m_global_power_pins.clear();
829 m_bus_alias_cache.clear();
835 m_local_label_cache.clear();
836 m_global_label_cache.clear();
837 m_last_net_code = 1;
838 m_last_bus_code = 1;
840
841 m_netChains->ClearDerived();
842}
843
844
845void CONNECTION_GRAPH::Recalculate( const SCH_SHEET_LIST& aSheetList, bool aUnconditional,
846 std::function<void( SCH_ITEM* )>* aChangedItemHandler,
847 PROGRESS_REPORTER* aProgressReporter )
848{
849 APP_MONITOR::TRANSACTION monitorTrans( "CONNECTION_GRAPH::Recalculate", "Recalculate" );
850 PROF_TIMER recalc_time( "CONNECTION_GRAPH::Recalculate" );
851 monitorTrans.Start();
852
853 if( aUnconditional )
854 Reset();
855
856 monitorTrans.StartSpan( "updateItemConnectivity", "" );
857 PROF_TIMER update_items( "updateItemConnectivity" );
858
859 m_sheetList = aSheetList;
860 std::set<SCH_ITEM*> dirty_items;
861
862 int count = aSheetList.size() * 2;
863 int done = 0;
864
865 for( const SCH_SHEET_PATH& sheet : aSheetList )
866 {
867 if( aProgressReporter )
868 {
869 aProgressReporter->SetCurrentProgress( done++ / (double) count );
870 aProgressReporter->KeepRefreshing();
871 }
872
873 std::vector<SCH_ITEM*> items;
874
875 // Store current unit value, to replace it after calculations
876 std::vector<std::pair<SCH_SYMBOL*, int>> symbolsChanged;
877
878 for( SCH_ITEM* item : sheet.LastScreen()->Items() )
879 {
880 if( item->IsConnectable() && ( aUnconditional || item->IsConnectivityDirty() ) )
881 {
882 wxLogTrace( ConnTrace, wxT( "Adding item %s to connectivity graph update" ),
883 item->GetTypeDesc() );
884 items.push_back( item );
885 dirty_items.insert( item );
886
887 // Add any symbol dirty pins to the dirty_items list
888 if( item->Type() == SCH_SYMBOL_T )
889 {
890 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
891
892 for( SCH_PIN* pin : symbol->GetPins( &sheet ) )
893 {
894 if( pin->IsConnectivityDirty() )
895 {
896 dirty_items.insert( pin );
897 }
898 }
899 }
900 // updateItemConnectivity() rebuilds the sheet's pins too, so clear their dirty
901 // flags or the painter keeps ignoring their connections
902 else if( item->Type() == SCH_SHEET_T )
903 {
904 SCH_SHEET* sheetItem = static_cast<SCH_SHEET*>( item );
905
906 for( SCH_SHEET_PIN* pin : sheetItem->GetPins() )
907 {
908 if( pin->IsConnectivityDirty() )
909 {
910 dirty_items.insert( pin );
911 }
912 }
913 }
914 }
915 // If the symbol isn't dirty, look at the pins
916 // TODO: remove symbols from connectivity graph and only use pins
917 else if( item->Type() == SCH_SYMBOL_T )
918 {
919 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
920
921 for( SCH_PIN* pin : symbol->GetPins( &sheet ) )
922 {
923 if( pin->IsConnectivityDirty() )
924 {
925 items.push_back( pin );
926 dirty_items.insert( pin );
927 }
928 }
929 }
930 else if( item->Type() == SCH_SHEET_T )
931 {
932 SCH_SHEET* sheetItem = static_cast<SCH_SHEET*>( item );
933
934 for( SCH_SHEET_PIN* pin : sheetItem->GetPins() )
935 {
936 if( pin->IsConnectivityDirty() )
937 {
938 items.push_back( pin );
939 dirty_items.insert( pin );
940 }
941 }
942 }
943
944 // Ensure the hierarchy info stored in the SCH_SCREEN (such as symbol units) reflects
945 // the current SCH_SHEET_PATH
946 if( item->Type() == SCH_SYMBOL_T )
947 {
948 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
949 int new_unit = symbol->GetUnitSelection( &sheet );
950
951 // Store the initial unit value so we can restore it after calculations
952 if( symbol->GetUnit() != new_unit )
953 symbolsChanged.push_back( { symbol, symbol->GetUnit() } );
954
955 symbol->SetUnit( new_unit );
956 }
957 }
958
959 m_items.reserve( m_items.size() + items.size() );
960
961 updateItemConnectivity( sheet, items );
962
963 if( aProgressReporter )
964 {
965 aProgressReporter->SetCurrentProgress( done++ / count );
966 aProgressReporter->KeepRefreshing();
967 }
968
969 // UpdateDanglingState() also adds connected items for SCH_TEXT
970 sheet.LastScreen()->TestDanglingEnds( &sheet, aChangedItemHandler );
971
972 // Restore the m_unit member variables where we had to change them
973 for( const auto& [ symbol, originalUnit ] : symbolsChanged )
974 symbol->SetUnit( originalUnit );
975 }
976
977 // Restore the dangling states of items in the current SCH_SCREEN to match the current
978 // SCH_SHEET_PATH.
979 SCH_SCREEN* currentScreen = m_schematic->CurrentSheet().LastScreen();
980
981 if( currentScreen )
982 currentScreen->TestDanglingEnds( &m_schematic->CurrentSheet(), aChangedItemHandler );
983
984 for( SCH_ITEM* item : dirty_items )
985 item->SetConnectivityDirty( false );
986
987
988 monitorTrans.FinishSpan();
989 if( wxLog::IsAllowedTraceMask( DanglingProfileMask ) )
990 update_items.Show();
991
992 PROF_TIMER build_graph( "buildConnectionGraph" );
993 monitorTrans.StartSpan( "BuildConnectionGraph", "" );
994
995 buildConnectionGraph( aChangedItemHandler, aUnconditional );
996
997 if( wxLog::IsAllowedTraceMask( DanglingProfileMask ) )
998 build_graph.Show();
999
1000 monitorTrans.FinishSpan();
1001
1002 recalc_time.Stop();
1003
1004 if( wxLog::IsAllowedTraceMask( DanglingProfileMask ) )
1005 recalc_time.Show();
1006
1007 monitorTrans.Finish();
1008}
1009
1010
1011std::set<std::pair<SCH_SHEET_PATH, SCH_ITEM*>> CONNECTION_GRAPH::ExtractAffectedItems(
1012 const std::set<SCH_ITEM*> &aItems )
1013{
1014 std::set<std::pair<SCH_SHEET_PATH, SCH_ITEM*>> retvals;
1015 std::set<CONNECTION_SUBGRAPH*> subgraphs;
1016
1017 auto traverse_subgraph = [&retvals, &subgraphs]( CONNECTION_SUBGRAPH* aSubgraph )
1018 {
1019 // Find the primary subgraph on this sheet
1020 while( aSubgraph->m_absorbed_by )
1021 {
1022 // Should we skip this if the absorbed by sub-graph is not this sub-grap?
1023 wxASSERT( aSubgraph->m_graph == aSubgraph->m_absorbed_by->m_graph );
1024 aSubgraph = aSubgraph->m_absorbed_by;
1025 }
1026
1027 // Find the top most connected subgraph on all sheets
1028 while( aSubgraph->m_hier_parent )
1029 {
1030 // Should we skip this if the absorbed by sub-graph is not this sub-grap?
1031 wxASSERT( aSubgraph->m_graph == aSubgraph->m_hier_parent->m_graph );
1032 aSubgraph = aSubgraph->m_hier_parent;
1033 }
1034
1035 // Recurse through all subsheets to collect connected items
1036 aSubgraph->getAllConnectedItems( retvals, subgraphs );
1037 };
1038
1039 auto scan_subgraphs = [&traverse_subgraph]( const std::vector<CONNECTION_SUBGRAPH*>& aScanList )
1040 {
1041 for( CONNECTION_SUBGRAPH* sg : aScanList )
1042 {
1043 traverse_subgraph( sg );
1044
1045 for( auto& bus_it : sg->m_bus_neighbors )
1046 {
1047 for( CONNECTION_SUBGRAPH* bus_sg : bus_it.second )
1048 traverse_subgraph( bus_sg );
1049 }
1050
1051 for( auto& bus_it : sg->m_bus_parents )
1052 {
1053 for( CONNECTION_SUBGRAPH* bus_sg : bus_it.second )
1054 traverse_subgraph( bus_sg );
1055 }
1056 }
1057 };
1058
1059 auto extract_element = [&]( SCH_ITEM* aItem )
1060 {
1061 CONNECTION_SUBGRAPH* item_sg = GetSubgraphForItem( aItem );
1062
1063 if( !item_sg )
1064 {
1065 wxLogTrace( ConnTrace, wxT( "Item %s not found in connection graph" ),
1066 aItem->GetTypeDesc() );
1067
1068 // A label names the net it joins, so a freshly placed one sits in no subgraph yet but
1069 // still has to drag that net's other subgraphs into the rebuild
1070 if( aItem->HasCachedDriverName() )
1071 scan_subgraphs( GetAllSubgraphs( aItem->GetCachedDriverName() ) );
1072
1073 return;
1074 }
1075
1076 if( !item_sg->ResolveDrivers( true ) )
1077 {
1078 wxLogTrace( ConnTrace, wxT( "Item %s in subgraph %ld (%p) has no driver" ),
1079 aItem->GetTypeDesc(), item_sg->m_code, item_sg );
1080 }
1081
1082 std::vector<CONNECTION_SUBGRAPH*> sg_to_scan = GetAllSubgraphs( item_sg->GetNetName() );
1083
1084 if( sg_to_scan.empty() )
1085 {
1086 wxLogTrace( ConnTrace, wxT( "Item %s in subgraph %ld with net %s has no neighbors" ),
1087 aItem->GetTypeDesc(), item_sg->m_code, item_sg->GetNetName() );
1088 sg_to_scan.push_back( item_sg );
1089 }
1090
1091 wxLogTrace( ConnTrace,
1092 wxT( "Removing all item %s connections from subgraph %ld with net %s: Found "
1093 "%zu subgraphs" ),
1094 aItem->GetTypeDesc(), item_sg->m_code, item_sg->GetNetName(),
1095 sg_to_scan.size() );
1096
1097 scan_subgraphs( sg_to_scan );
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 std::erase( m_items, aItem );
1136
1137 auto it = m_item_to_subgraph_map.find( aItem );
1138
1139 if( it == m_item_to_subgraph_map.end() )
1140 return;
1141
1142 // The item sits in one subgraph per instantiating sheet path, and every one of them must
1143 // drop it here or a subsequent recalculation resolves drivers against freed memory
1144 for( CONNECTION_SUBGRAPH* subgraph : it->second )
1145 {
1146 while( subgraph->m_absorbed_by )
1147 subgraph = subgraph->m_absorbed_by;
1148
1149 subgraph->RemoveItem( aItem );
1150 }
1151
1152 m_item_to_subgraph_map.erase( it );
1153}
1154
1155
1156void CONNECTION_GRAPH::removeSubgraphs( std::set<CONNECTION_SUBGRAPH*>& aSubgraphs )
1157{
1158 wxLogTrace( ConnTrace, wxT( "Removing %zu subgraphs" ), aSubgraphs.size() );
1159 std::sort( m_driver_subgraphs.begin(), m_driver_subgraphs.end() );
1160 std::sort( m_subgraphs.begin(), m_subgraphs.end() );
1161 std::set<int> codes_to_remove;
1162
1163 for( auto& el : m_sheet_to_subgraphs_map )
1164 {
1165 std::sort( el.second.begin(), el.second.end() );
1166 }
1167
1168 for( CONNECTION_SUBGRAPH* sg : aSubgraphs )
1169 {
1170 for( auto& it : sg->m_bus_neighbors )
1171 {
1172 for( CONNECTION_SUBGRAPH* neighbor : it.second )
1173 {
1174 auto& parents = neighbor->m_bus_parents[it.first];
1175
1176 for( auto test = parents.begin(); test != parents.end(); )
1177 {
1178 if( *test == sg )
1179 test = parents.erase( test );
1180 else
1181 ++test;
1182 }
1183
1184 if( parents.empty() )
1185 neighbor->m_bus_parents.erase( it.first );
1186 }
1187 }
1188
1189 for( auto& it : sg->m_bus_parents )
1190 {
1191 for( CONNECTION_SUBGRAPH* parent : it.second )
1192 {
1193 auto& neighbors = parent->m_bus_neighbors[it.first];
1194
1195 for( auto test = neighbors.begin(); test != neighbors.end(); )
1196 {
1197 if( *test == sg )
1198 test = neighbors.erase( test );
1199 else
1200 ++test;
1201 }
1202
1203 if( neighbors.empty() )
1204 parent->m_bus_neighbors.erase( it.first );
1205 }
1206 }
1207
1208 {
1209 auto it = std::lower_bound( m_driver_subgraphs.begin(), m_driver_subgraphs.end(), sg );
1210
1211 while( it != m_driver_subgraphs.end() && *it == sg )
1212 it = m_driver_subgraphs.erase( it );
1213 }
1214
1215 {
1216 auto it = std::lower_bound( m_subgraphs.begin(), m_subgraphs.end(), sg );
1217
1218 while( it != m_subgraphs.end() && *it == sg )
1219 it = m_subgraphs.erase( it );
1220 }
1221
1222 for( auto& el : m_sheet_to_subgraphs_map )
1223 {
1224 auto it = std::lower_bound( el.second.begin(), el.second.end(), sg );
1225
1226 while( it != el.second.end() && *it == sg )
1227 it = el.second.erase( it );
1228 }
1229
1230 auto remove_sg = [sg]( auto it ) -> bool
1231 {
1232 for( const CONNECTION_SUBGRAPH* test_sg : it->second )
1233 {
1234 if( sg == test_sg )
1235 return true;
1236 }
1237
1238 return false;
1239 };
1240
1241 for( auto it = m_global_label_cache.begin(); it != m_global_label_cache.end(); )
1242 {
1243 if( remove_sg( it ) )
1244 it = m_global_label_cache.erase( it );
1245 else
1246 ++it;
1247 }
1248
1249 for( auto it = m_local_label_cache.begin(); it != m_local_label_cache.end(); )
1250 {
1251 if( remove_sg( it ) )
1252 it = m_local_label_cache.erase( it );
1253 else
1254 ++it;
1255 }
1256
1257 for( auto it = m_net_code_to_subgraphs_map.begin();
1258 it != m_net_code_to_subgraphs_map.end(); )
1259 {
1260 if( remove_sg( it ) )
1261 {
1262 codes_to_remove.insert( it->first.Netcode );
1263 it = m_net_code_to_subgraphs_map.erase( it );
1264 }
1265 else
1266 {
1267 ++it;
1268 }
1269 }
1270
1271 for( auto it = m_net_name_to_subgraphs_map.begin();
1272 it != m_net_name_to_subgraphs_map.end(); )
1273 {
1274 if( remove_sg( it ) )
1275 it = m_net_name_to_subgraphs_map.erase( it );
1276 else
1277 ++it;
1278 }
1279
1280 for( auto it = m_item_to_subgraph_map.begin(); it != m_item_to_subgraph_map.end(); )
1281 {
1282 std::erase( it->second, sg );
1283
1284 if( it->second.empty() )
1285 it = m_item_to_subgraph_map.erase( it );
1286 else
1287 ++it;
1288 }
1289
1290
1291 }
1292
1293 for( auto it = m_net_name_to_code_map.begin(); it != m_net_name_to_code_map.end(); )
1294 {
1295 if( codes_to_remove.contains( it->second ) )
1296 it = m_net_name_to_code_map.erase( it );
1297 else
1298 ++it;
1299 }
1300
1301 for( auto it = m_bus_name_to_code_map.begin(); it != m_bus_name_to_code_map.end(); )
1302 {
1303 if( codes_to_remove.contains( it->second ) )
1304 it = m_bus_name_to_code_map.erase( it );
1305 else
1306 ++it;
1307 }
1308
1309 for( CONNECTION_SUBGRAPH* sg : aSubgraphs )
1310 {
1311 sg->m_code = -1;
1312 sg->m_graph = nullptr;
1313 delete sg;
1314 }
1315}
1316
1317
1319 std::map<VECTOR2I, std::vector<SCH_ITEM*>>& aConnectionMap )
1320{
1321 auto updatePin =
1322 [&]( SCH_PIN* aPin, SCH_CONNECTION* aConn )
1323 {
1324 aConn->SetType( CONNECTION_TYPE::NET );
1325 wxString name = aPin->GetDefaultNetName( aSheet );
1326 aPin->ClearConnectedItems( aSheet );
1327
1328 if( aPin->IsGlobalPower() )
1329 {
1330 aConn->SetName( name );
1331 m_global_power_pins.emplace_back( std::make_pair( aSheet, aPin ) );
1332 }
1333 };
1334
1335 std::map<wxString, std::vector<SCH_PIN*>> pinNumberMap;
1336
1337 for( SCH_PIN* pin : aSymbol->GetPins( &aSheet ) )
1338 {
1339 m_items.emplace_back( pin );
1340 SCH_CONNECTION* conn = pin->InitializeConnection( aSheet, this );
1341 updatePin( pin, conn );
1342 aConnectionMap[ pin->GetPosition() ].push_back( pin );
1343 pinNumberMap[pin->GetNumber()].emplace_back( pin );
1344 }
1345
1346 auto linkPinsInVec =
1347 [&]( const std::vector<SCH_PIN*>& aVec )
1348 {
1349 for( size_t i = 0; i < aVec.size(); ++i )
1350 {
1351 for( size_t j = i + 1; j < aVec.size(); ++j )
1352 {
1353 aVec[i]->AddConnectionTo( aSheet, aVec[j] );
1354 aVec[j]->AddConnectionTo( aSheet, aVec[i] );
1355 }
1356 }
1357 };
1358
1359 if( aSymbol->GetLibSymbolRef() )
1360 {
1362 {
1363 for( const auto& [number, group] : pinNumberMap )
1364 linkPinsInVec( group );
1365 }
1366
1367 for( const std::set<wxString>& group : aSymbol->GetLibSymbolRef()->JumperPinGroups() )
1368 {
1369 std::vector<SCH_PIN*> pins;
1370
1371 for( const wxString& pinNumber : group )
1372 {
1373 SCH_PIN* found = aSymbol->GetPin( pinNumber );
1374
1375 if( !found )
1376 {
1377 // A group member can name one contact of a stacked pin like [A1,A12].
1378 for( SCH_PIN* pin : aSymbol->GetPins( &aSheet ) )
1379 {
1380 if( alg::contains( pin->GetStackedPinNumbers(), pinNumber ) )
1381 {
1382 found = pin;
1383 break;
1384 }
1385 }
1386 }
1387
1388 // Several members can name contacts of the same pin, which must be linked once.
1389 if( found && !alg::contains( pins, found ) )
1390 pins.emplace_back( found );
1391 }
1392
1393 linkPinsInVec( pins );
1394 }
1395 }
1396}
1397
1398
1400{
1401 aConn->SetType( CONNECTION_TYPE::NET );
1402
1403 // because calling the first time is not thread-safe
1404 wxString name = aPin->GetDefaultNetName( aSheet );
1405 aPin->ClearConnectedItems( aSheet );
1406
1407 if( aPin->IsGlobalPower() )
1408 {
1409 aConn->SetName( name );
1410 m_global_power_pins.emplace_back( std::make_pair( aSheet, aPin ) );
1411 }
1412}
1413
1414
1416 std::map<VECTOR2I, std::vector<SCH_ITEM*>>& aConnectionMap )
1417{
1418 std::vector<VECTOR2I> points = aItem->GetConnectionPoints();
1419 aItem->ClearConnectedItems( aSheet );
1420
1421 m_items.emplace_back( aItem );
1422 SCH_CONNECTION* conn = aItem->InitializeConnection( aSheet, this );
1423
1424 switch( aItem->Type() )
1425 {
1426 case SCH_LINE_T:
1428 break;
1429
1432 static_cast<SCH_BUS_BUS_ENTRY*>( aItem )->m_connected_bus_items[0] = nullptr;
1433 static_cast<SCH_BUS_BUS_ENTRY*>( aItem )->m_connected_bus_items[1] = nullptr;
1434 break;
1435
1436 case SCH_PIN_T:
1437 if( points.empty() )
1438 points = { static_cast<SCH_PIN*>( aItem )->GetPosition() };
1439
1440 updatePinConnectivity( aSheet, static_cast<SCH_PIN*>( aItem ), conn );
1441 break;
1442
1445 static_cast<SCH_BUS_WIRE_ENTRY*>( aItem )->m_connected_bus_item = nullptr;
1446 break;
1447
1448 default: break;
1449 }
1450
1451 for( const VECTOR2I& point : points )
1452 aConnectionMap[point].push_back( aItem );
1453}
1454
1455
1457 const std::vector<SCH_ITEM*>& aItemList )
1458{
1459 wxLogTrace( wxT( "Updating connectivity for sheet %s with %zu items" ),
1460 aSheet.Last()->GetFileName(), aItemList.size() );
1461 std::map<VECTOR2I, std::vector<SCH_ITEM*>> connection_map;
1462
1463 for( SCH_ITEM* item : aItemList )
1464 {
1465 std::vector<VECTOR2I> points = item->GetConnectionPoints();
1466 item->ClearConnectedItems( aSheet );
1467 if( item->Type() == SCH_SHEET_T )
1468 {
1469 for( SCH_SHEET_PIN* pin : static_cast<SCH_SHEET*>( item )->GetPins() )
1470 {
1471 pin->InitializeConnection( aSheet, this );
1472
1473 pin->ClearConnectedItems( aSheet );
1474
1475 connection_map[ pin->GetTextPos() ].push_back( pin );
1476 m_items.emplace_back( pin );
1477 }
1478 }
1479 else if( item->Type() == SCH_SYMBOL_T )
1480 {
1481 updateSymbolConnectivity( aSheet, static_cast<SCH_SYMBOL*>( item ), connection_map );
1482 }
1483 else
1484 {
1485 updateGenericItemConnectivity( aSheet, item, connection_map );
1486
1490 if( dynamic_cast<SCH_LABEL_BASE*>( item ) )
1491 {
1492 VECTOR2I point = item->GetPosition();
1493 SCH_SCREEN* screen = aSheet.LastScreen();
1494 auto items = screen->Items().Overlapping( point );
1495 std::vector<SCH_ITEM*> overlapping_items;
1496
1497 std::copy_if( items.begin(), items.end(), std::back_inserter( overlapping_items ),
1498 [&]( SCH_ITEM* test_item )
1499 {
1500 return test_item->Type() == SCH_LINE_T
1501 && test_item->HitTest( point, -1 );
1502 } );
1503
1504 // We need at least two connnectable lines that are not the label here
1505 // Otherwise, the label will be normally assigned to one or the other
1506 if( overlapping_items.size() < 2 ) continue;
1507
1508 for( SCH_ITEM* test_item : overlapping_items )
1509 connection_map[point].push_back( test_item );
1510 }
1511
1512 // Junctions connect wires that pass through their position as midpoints.
1513 // This handles schematics where a wire was not split at a junction point,
1514 // which can happen when a wire is placed over an existing junction without
1515 // the schematic topology being updated.
1516 if( item->Type() == SCH_JUNCTION_T )
1517 {
1518 VECTOR2I point = item->GetPosition();
1519 SCH_SCREEN* screen = aSheet.LastScreen();
1520
1521 for( SCH_LINE* wire : screen->GetBusesAndWires( point, true ) )
1522 connection_map[point].push_back( wire );
1523 }
1524 }
1525 }
1526
1527 for( auto& [point, connection_vec] : connection_map )
1528 {
1529 std::sort( connection_vec.begin(), connection_vec.end() );
1530 alg::remove_duplicates( connection_vec );
1531
1532 // Pre-scan to see if we have a bus at this location
1533 SCH_LINE* busLine = aSheet.LastScreen()->GetBus( point );
1534
1535 for( SCH_ITEM* connected_item : connection_vec )
1536 {
1537 // Bus entries are special: they can have connection points in the
1538 // middle of a wire segment, because the junction algo doesn't split
1539 // the segment in two where you place a bus entry. This means that
1540 // bus entries that don't land on the end of a line segment need to
1541 // have "virtual" connection points to the segments they graphically
1542 // touch.
1543 if( connected_item->Type() == SCH_BUS_WIRE_ENTRY_T )
1544 {
1545 // If this location only has the connection point of the bus
1546 // entry itself, this means that either the bus entry is not
1547 // connected to anything graphically, or that it is connected to
1548 // a segment at some point other than at one of the endpoints.
1549 if( connection_vec.size() == 1 )
1550 {
1551 if( busLine )
1552 {
1553 auto bus_entry = static_cast<SCH_BUS_WIRE_ENTRY*>( connected_item );
1554 bus_entry->m_connected_bus_item = busLine;
1555 }
1556 }
1557 }
1558 // Bus-to-bus entries are treated just like bus wires
1559 else if( connected_item->Type() == SCH_BUS_BUS_ENTRY_T )
1560 {
1561 if( busLine )
1562 {
1563 auto bus_entry = static_cast<SCH_BUS_BUS_ENTRY*>( connected_item );
1564
1565 if( point == bus_entry->GetPosition() )
1566 bus_entry->m_connected_bus_items[0] = busLine;
1567 else
1568 bus_entry->m_connected_bus_items[1] = busLine;
1569
1570 bus_entry->AddConnectionTo( aSheet, busLine );
1571 busLine->AddConnectionTo( aSheet, bus_entry );
1572 continue;
1573 }
1574 }
1575 // Change junctions to be on bus junction layer if they are touching a bus
1576 else if( connected_item->Type() == SCH_JUNCTION_T )
1577 {
1578 connected_item->SetLayer( busLine ? LAYER_BUS_JUNCTION : LAYER_JUNCTION );
1579 }
1580
1581 for( SCH_ITEM* test_item : connection_vec )
1582 {
1583 bool bus_connection_ok = true;
1584
1585 if( test_item == connected_item )
1586 continue;
1587
1588 // Set up the link between the bus entry net and the bus
1589 if( connected_item->Type() == SCH_BUS_WIRE_ENTRY_T )
1590 {
1591 if( test_item->GetLayer() == LAYER_BUS )
1592 {
1593 auto bus_entry = static_cast<SCH_BUS_WIRE_ENTRY*>( connected_item );
1594 bus_entry->m_connected_bus_item = test_item;
1595 }
1596 }
1597
1598 // Bus entries only connect to bus lines on the end that is touching a bus line.
1599 // If the user has overlapped another net line with the endpoint of the bus entry
1600 // where the entry connects to a bus, we don't want to short-circuit it.
1601 if( connected_item->Type() == SCH_BUS_WIRE_ENTRY_T )
1602 {
1603 bus_connection_ok = !busLine || test_item->GetLayer() == LAYER_BUS;
1604 }
1605 else if( test_item->Type() == SCH_BUS_WIRE_ENTRY_T )
1606 {
1607 bus_connection_ok = !busLine || connected_item->GetLayer() == LAYER_BUS;
1608 }
1609
1610 if( connected_item->ConnectionPropagatesTo( test_item )
1611 && test_item->ConnectionPropagatesTo( connected_item )
1612 && bus_connection_ok )
1613 {
1614 connected_item->AddConnectionTo( aSheet, test_item );
1615 }
1616 }
1617
1618 // If we got this far and did not find a connected bus item for a bus entry,
1619 // we should do a manual scan in case there is a bus item on this connection
1620 // point but we didn't pick it up earlier because there is *also* a net item here.
1621 if( connected_item->Type() == SCH_BUS_WIRE_ENTRY_T )
1622 {
1623 auto bus_entry = static_cast<SCH_BUS_WIRE_ENTRY*>( connected_item );
1624
1625 if( !bus_entry->m_connected_bus_item )
1626 {
1627 SCH_SCREEN* screen = aSheet.LastScreen();
1628 SCH_LINE* bus = screen->GetBus( point );
1629
1630 if( bus )
1631 bus_entry->m_connected_bus_item = bus;
1632 }
1633 }
1634 }
1635 }
1636}
1637
1638
1640{
1641 // Recache all bus aliases for later use
1642 wxCHECK_RET( m_schematic, wxS( "Connection graph cannot be built without schematic pointer" ) );
1643
1644 m_bus_alias_cache.clear();
1645
1646 for( const std::shared_ptr<BUS_ALIAS>& alias : m_schematic->GetAllBusAliases() )
1647 {
1648 if( alias )
1649 m_bus_alias_cache[alias->GetName()] = alias;
1650 }
1651
1652 // Hash position in m_sheetList for each sheet path so that subgraphs are
1653 // created in a deterministic order matching the sheet hierarchy
1654 // https://gitlab.com/kicad/code/kicad/-/issues/24409
1655 std::unordered_map<SCH_SHEET_PATH, size_t> sheetOrder;
1656 sheetOrder.reserve( m_sheetList.size() );
1657
1658 for( size_t i = 0; i < m_sheetList.size(); ++i )
1659 sheetOrder.emplace( m_sheetList[i], i );
1660
1661 auto sheetRank =
1662 [&]( const SCH_SHEET_PATH& aSheet ) -> size_t
1663 {
1664 auto it = sheetOrder.find( aSheet );
1665
1666 return ( it == sheetOrder.end() ) ? std::numeric_limits<size_t>::max()
1667 : it->second;
1668 };
1669
1670 // Build subgraphs from items (on a per-sheet basis). Reuse the vector across
1671 // items so a flat schematic doesn't allocate per item.
1672 std::vector<std::tuple<size_t, SCH_SHEET_PATH, SCH_CONNECTION*>> ordered;
1673
1674 for( SCH_ITEM* item : m_items )
1675 {
1676 ordered.clear();
1677 ordered.reserve( item->m_connection_map.size() );
1678
1679 // Precompute sheet rank into the tuple so the comparator never re-hashes
1680 // the (vector-backed) SCH_SHEET_PATH keys.
1681 for( const auto& [sheet, connection] : item->m_connection_map )
1682 ordered.emplace_back( sheetRank( sheet ), sheet, connection );
1683
1684 std::sort( ordered.begin(), ordered.end(),
1685 []( const auto& a, const auto& b )
1686 {
1687 return std::get<0>( a ) < std::get<0>( b );
1688 } );
1689
1690 for( const auto& [rank, sheet, connection] : ordered )
1691 {
1692 if( connection->SubgraphCode() == 0 )
1693 {
1694 CONNECTION_SUBGRAPH* subgraph = new CONNECTION_SUBGRAPH( this );
1695
1696 subgraph->m_code = m_last_subgraph_code++;
1697 subgraph->m_sheet = sheet;
1698
1699 subgraph->AddItem( item );
1700
1701 connection->SetSubgraphCode( subgraph->m_code );
1702 m_item_to_subgraph_map[item].push_back( subgraph );
1703 item->registerConnectivityOwner( m_lifetime );
1704
1705 std::list<SCH_ITEM*> memberlist;
1706
1707 auto get_items =
1708 [&]( SCH_ITEM* aItem ) -> bool
1709 {
1710 SCH_CONNECTION* conn = aItem->GetOrInitConnection( sheet, this );
1711 bool unique = !( aItem->GetFlags() & CONNECTIVITY_CANDIDATE );
1712
1713 if( conn && !conn->SubgraphCode() )
1714 aItem->SetFlags( CONNECTIVITY_CANDIDATE );
1715
1716 return ( unique && conn && ( conn->SubgraphCode() == 0 ) );
1717 };
1718
1719 std::copy_if( item->ConnectedItems( sheet ).begin(),
1720 item->ConnectedItems( sheet ).end(),
1721 std::back_inserter( memberlist ), get_items );
1722
1723 for( SCH_ITEM* connected_item : memberlist )
1724 {
1725 if( connected_item->Type() == SCH_NO_CONNECT_T )
1726 subgraph->m_no_connect = connected_item;
1727
1728 SCH_CONNECTION* connected_conn = connected_item->Connection( &sheet );
1729
1730 wxCHECK2( connected_conn, continue );
1731
1732 if( connected_conn->SubgraphCode() == 0 )
1733 {
1734 connected_conn->SetSubgraphCode( subgraph->m_code );
1735 m_item_to_subgraph_map[connected_item].push_back( subgraph );
1736 connected_item->registerConnectivityOwner( m_lifetime );
1737 subgraph->AddItem( connected_item );
1738
1739 for( SCH_ITEM* citem : connected_item->ConnectedItems( sheet ) )
1740 {
1741 if( citem->HasFlag( CONNECTIVITY_CANDIDATE ) )
1742 continue;
1743
1744 if( get_items( citem ) )
1745 memberlist.push_back( citem );
1746 }
1747 }
1748 }
1749
1750 for( SCH_ITEM* connected_item : memberlist )
1751 connected_item->ClearFlags( CONNECTIVITY_CANDIDATE );
1752
1753 subgraph->m_dirty = true;
1754 m_subgraphs.push_back( subgraph );
1755 }
1756 }
1757 }
1758}
1759
1760
1762{
1763 // Resolve drivers for subgraphs and propagate connectivity info
1764 std::vector<CONNECTION_SUBGRAPH*> dirty_graphs;
1765
1766 std::copy_if( m_subgraphs.begin(), m_subgraphs.end(), std::back_inserter( dirty_graphs ),
1767 [&] ( const CONNECTION_SUBGRAPH* candidate )
1768 {
1769 return candidate->m_dirty;
1770 } );
1771
1772 wxLogTrace( ConnTrace, wxT( "Resolving drivers for %zu subgraphs" ), dirty_graphs.size() );
1773
1774 std::vector<std::future<size_t>> returns( dirty_graphs.size() );
1775
1776 auto update_lambda =
1777 []( CONNECTION_SUBGRAPH* subgraph ) -> size_t
1778 {
1779 if( !subgraph->m_dirty )
1780 return 0;
1781
1782 // Special processing for some items
1783 for( SCH_ITEM* item : subgraph->m_items )
1784 {
1785 switch( item->Type() )
1786 {
1787 case SCH_NO_CONNECT_T:
1788 subgraph->m_no_connect = item;
1789 break;
1790
1792 subgraph->m_bus_entry = item;
1793 break;
1794
1795 case SCH_PIN_T:
1796 {
1797 auto pin = static_cast<SCH_PIN*>( item );
1798
1799 if( pin->GetType() == ELECTRICAL_PINTYPE::PT_NC )
1800 subgraph->m_no_connect = item;
1801
1802 break;
1803 }
1804
1805 default:
1806 break;
1807 }
1808 }
1809
1810 subgraph->ResolveDrivers( true );
1811 subgraph->m_dirty = false;
1812
1813 return 1;
1814 };
1815
1817
1818 auto results = tp.submit_loop( 0, dirty_graphs.size(),
1819 [&]( const int ii )
1820 {
1821 update_lambda( dirty_graphs[ii] );
1822 } );
1823 results.wait();
1824
1825 // Now discard any non-driven subgraphs from further consideration
1826
1827 std::copy_if( m_subgraphs.begin(), m_subgraphs.end(), std::back_inserter( m_driver_subgraphs ),
1828 [&] ( const CONNECTION_SUBGRAPH* candidate ) -> bool
1829 {
1830 return candidate->m_driver;
1831 } );
1832}
1833
1834
1836{
1837 // Check for subgraphs with the same net name but only weak drivers.
1838 // For example, two wires that are both connected to hierarchical
1839 // sheet pins that happen to have the same name, but are not the same.
1840
1841 for( auto&& subgraph : m_driver_subgraphs )
1842 {
1843 wxString full_name = subgraph->m_driver_connection->Name();
1844 wxString name = subgraph->m_driver_connection->Name( true );
1845 m_net_name_to_subgraphs_map[full_name].emplace_back( subgraph );
1846
1847 // For vector buses, we need to cache the prefix also, as two different instances of the
1848 // weakly driven pin may have the same prefix but different vector start and end. We need
1849 // to treat those as needing renaming also, because otherwise if they end up on a sheet with
1850 // common usage, they will be incorrectly merged.
1851 if( subgraph->m_driver_connection->Type() == CONNECTION_TYPE::BUS )
1852 {
1853 wxString prefixOnly = full_name.BeforeFirst( '[' ) + wxT( "[]" );
1854 m_net_name_to_subgraphs_map[prefixOnly].emplace_back( subgraph );
1855 }
1856
1857 subgraph->m_dirty = true;
1858
1859 if( subgraph->m_strong_driver )
1860 {
1861 SCH_ITEM* driver = subgraph->m_driver;
1862 SCH_SHEET_PATH sheet = subgraph->m_sheet;
1863
1864 switch( driver->Type() )
1865 {
1866 case SCH_LABEL_T:
1867 case SCH_HIER_LABEL_T:
1868 {
1869 m_local_label_cache[std::make_pair( sheet, name )].push_back( subgraph );
1870 break;
1871 }
1872 case SCH_GLOBAL_LABEL_T:
1873 {
1874 m_global_label_cache[name].push_back( subgraph );
1875 break;
1876 }
1877 case SCH_PIN_T:
1878 {
1879 SCH_PIN* pin = static_cast<SCH_PIN*>( driver );
1880 if( pin->IsGlobalPower() )
1881 {
1882 m_global_label_cache[name].push_back( subgraph );
1883 }
1884 else if( pin->IsLocalPower() )
1885 {
1886 m_local_label_cache[std::make_pair( sheet, name )].push_back( subgraph );
1887 }
1888 else
1889 {
1890 UNITS_PROVIDER unitsProvider( schIUScale, EDA_UNITS::MM );
1891 wxLogTrace( ConnTrace, wxS( "Unexpected normal pin %s" ),
1892 driver->GetItemDescription( &unitsProvider, true ) );
1893 }
1894
1895 break;
1896 }
1897 default:
1898 {
1899 UNITS_PROVIDER unitsProvider( schIUScale, EDA_UNITS::MM );
1900
1901 wxLogTrace( ConnTrace, wxS( "Unexpected strong driver %s" ),
1902 driver->GetItemDescription( &unitsProvider, true ) );
1903 break;
1904 }
1905 }
1906 }
1907 }
1908}
1909
1910
1912{
1913 std::vector<CONNECTION_SUBGRAPH*> new_subgraphs;
1914
1915 for( CONNECTION_SUBGRAPH* subgraph : m_driver_subgraphs )
1916 {
1917 for( SCH_ITEM* item : subgraph->GetAllBusLabels() )
1918 {
1919 SCH_LABEL_BASE* label = static_cast<SCH_LABEL_BASE*>( item );
1920
1921 SCH_CONNECTION dummy( item, subgraph->m_sheet );
1922 dummy.SetGraph( this );
1923 dummy.ConfigureFromLabel( label->GetShownText( &subgraph->m_sheet, FOR_NETNAME ) );
1924
1925 wxLogTrace( ConnTrace, wxS( "new bus label (%s)" ),
1926 label->GetShownText( &subgraph->m_sheet, FOR_NETNAME ) );
1927
1928 for( const auto& conn : dummy.Members() )
1929 {
1930 // Only create subgraphs for NET members, not nested buses
1931 if( !conn->IsNet() )
1932 continue;
1933
1934 wxString name = conn->FullLocalName();
1935
1936 CONNECTION_SUBGRAPH* new_sg = new CONNECTION_SUBGRAPH( this );
1937
1938 // This connection cannot form a part of the item because the item is not, itself
1939 // connected to this subgraph. It exists as part of a virtual item that may be
1940 // connected to other items but is not in the schematic.
1941 auto new_conn = std::make_unique<SCH_CONNECTION>( item, subgraph->m_sheet );
1942 new_conn->SetGraph( this );
1943 new_conn->SetName( name );
1944 new_conn->SetType( CONNECTION_TYPE::NET );
1945
1946 SCH_CONNECTION* new_conn_ptr = subgraph->StoreImplicitConnection( std::move( new_conn ) );
1947 int code = assignNewNetCode( *new_conn_ptr );
1948
1949 wxLogTrace( ConnTrace, wxS( "SG(%ld), Adding full local name (%s) with sg (%d) on subsheet %s" ),
1950 subgraph->m_code, name, code, subgraph->m_sheet.PathHumanReadable() );
1951
1952 new_sg->m_driver_connection = new_conn_ptr;
1953 new_sg->m_code = m_last_subgraph_code++;
1954 new_sg->m_sheet = subgraph->GetSheet();
1955 new_sg->m_is_bus_member = true;
1956 new_sg->m_strong_driver = true;
1957
1959 NET_NAME_CODE_CACHE_KEY key = { new_sg->GetNetName(), code };
1960 m_net_code_to_subgraphs_map[ key ].push_back( new_sg );
1961 m_net_name_to_subgraphs_map[ name ].push_back( new_sg );
1962 m_subgraphs.push_back( new_sg );
1963 new_subgraphs.push_back( new_sg );
1964 }
1965 }
1966 }
1967
1968 std::copy( new_subgraphs.begin(), new_subgraphs.end(),
1969 std::back_inserter( m_driver_subgraphs ) );
1970}
1971
1972
1974{
1975 // Generate subgraphs for global power pins. These will be merged with other subgraphs
1976 // on the same sheet in the next loop.
1977 // These are NOT limited to power symbols, we support legacy invisible + power-in pins
1978 // on non-power symbols.
1979
1980 // Sort power pins for deterministic processing order. This ensures that when multiple
1981 // power pins share the same net name, the same pin consistently creates the subgraph
1982 // across different ERC runs.
1983 std::sort( m_global_power_pins.begin(), m_global_power_pins.end(),
1984 []( const std::pair<SCH_SHEET_PATH, SCH_PIN*>& a,
1985 const std::pair<SCH_SHEET_PATH, SCH_PIN*>& b )
1986 {
1987 int pathCmp = a.first.Cmp( b.first );
1988
1989 if( pathCmp != 0 )
1990 return pathCmp < 0;
1991
1992 const SCH_SYMBOL* symA = static_cast<const SCH_SYMBOL*>( a.second->GetParentSymbol() );
1993 const SCH_SYMBOL* symB = static_cast<const SCH_SYMBOL*>( b.second->GetParentSymbol() );
1994
1995 wxString refA = symA ? symA->GetRef( &a.first, false ) : wxString();
1996 wxString refB = symB ? symB->GetRef( &b.first, false ) : wxString();
1997
1998 int refCmp = refA.Cmp( refB );
1999
2000 if( refCmp != 0 )
2001 return refCmp < 0;
2002
2003 return a.second->GetNumber().Cmp( b.second->GetNumber() ) < 0;
2004 } );
2005
2006 std::unordered_map<int, CONNECTION_SUBGRAPH*> global_power_pin_subgraphs;
2007
2008 for( const auto& [sheet, pin] : m_global_power_pins )
2009 {
2010 SYMBOL* libParent = pin->GetLibPin() ? pin->GetLibPin()->GetParentSymbol() : nullptr;
2011
2012 if( !pin->ConnectedItems( sheet ).empty()
2013 && ( !libParent || !libParent->IsGlobalPower() ) )
2014 {
2015 // ERC will warn about this: user has wired up an invisible pin
2016 continue;
2017 }
2018
2019 SCH_CONNECTION* connection = pin->GetOrInitConnection( sheet, this );
2020
2021 // If this pin already has a subgraph, don't need to process
2022 if( !connection || connection->SubgraphCode() > 0 )
2023 continue;
2024
2025 // Proper modern power symbols get their net name from the value field
2026 // in the symbol, but we support legacy non-power symbols with global
2027 // power connections based on invisible, power-in, pin's names.
2028 if( libParent && libParent->IsGlobalPower() )
2029 connection->SetName( pin->GetParentSymbol()->GetValue( &sheet, FOR_NETNAME ) );
2030 else
2031 connection->SetName( pin->GetShownName() );
2032
2033 int code = assignNewNetCode( *connection );
2034
2035 connection->SetNetCode( code );
2036
2037 CONNECTION_SUBGRAPH* subgraph;
2038 auto jj = global_power_pin_subgraphs.find( code );
2039
2040 if( jj != global_power_pin_subgraphs.end() )
2041 {
2042 subgraph = jj->second;
2043 subgraph->AddItem( pin );
2044 }
2045 else
2046 {
2047 subgraph = new CONNECTION_SUBGRAPH( this );
2048
2049 subgraph->m_code = m_last_subgraph_code++;
2050 subgraph->m_sheet = sheet;
2051
2052 subgraph->AddItem( pin );
2053 subgraph->ResolveDrivers();
2054
2055 NET_NAME_CODE_CACHE_KEY key = { subgraph->GetNetName(), code };
2056 m_net_code_to_subgraphs_map[ key ].push_back( subgraph );
2057 m_subgraphs.push_back( subgraph );
2058 m_driver_subgraphs.push_back( subgraph );
2059
2060 global_power_pin_subgraphs[code] = subgraph;
2061 }
2062
2063 connection->SetSubgraphCode( subgraph->m_code );
2064 }
2065}
2066
2067
2069{
2070 // Here we do all the local (sheet) processing of each subgraph, including assigning net
2071 // codes, merging subgraphs together that use label connections, etc.
2072
2073 std::unordered_map<wxString, std::vector<size_t>> weakConflicts;
2074
2075 for( size_t i = 0; i < m_driver_subgraphs.size(); ++i )
2076 {
2078 SCH_CONNECTION* connection = subgraph->m_driver_connection;
2079
2080 if( !subgraph->m_absorbed && !subgraph->m_strong_driver && connection->IsNet() )
2081 {
2082 const wxString name = connection->Name();
2083 auto peers = m_net_name_to_subgraphs_map.find( name );
2084
2085 if( peers != m_net_name_to_subgraphs_map.end() && peers->second.size() > 1 )
2086 weakConflicts[name].push_back( i );
2087 }
2088 }
2089
2090 // Spatial-index traversal changes after reload. Only reorder competing weak drivers:
2091 // their processing order decides which physical net receives each numeric suffix.
2092 for( const auto& [name, positions] : weakConflicts )
2093 {
2094 if( positions.size() < 2 )
2095 continue;
2096
2097 // Each identity allocates a sheet path and a pin number, so build them once per
2098 // subgraph rather than twice per comparison.
2099 std::vector<std::pair<SUBGRAPH_IDENTITY, CONNECTION_SUBGRAPH*>> ordered;
2100 ordered.reserve( positions.size() );
2101
2102 for( size_t position : positions )
2103 {
2104 CONNECTION_SUBGRAPH* subgraph = m_driver_subgraphs[position];
2105 ordered.emplace_back( SUBGRAPH_IDENTITY{ subgraph->m_sheet.Path(),
2106 stableDriverIdentity( subgraph->m_driver ) },
2107 subgraph );
2108 }
2109
2110 std::sort( ordered.begin(), ordered.end(),
2111 []( const auto& left, const auto& right )
2112 {
2113 return left.first < right.first;
2114 } );
2115
2116 for( size_t i = 0; i < positions.size(); ++i )
2117 m_driver_subgraphs[positions[i]] = ordered[i].second;
2118 }
2119
2120 // Cache remaining valid subgraphs by sheet path
2121 for( CONNECTION_SUBGRAPH* subgraph : m_driver_subgraphs )
2122 m_sheet_to_subgraphs_map[ subgraph->m_sheet ].emplace_back( subgraph );
2123
2124 std::unordered_set<CONNECTION_SUBGRAPH*> invalidated_subgraphs;
2125
2126 for( CONNECTION_SUBGRAPH* subgraph : m_driver_subgraphs )
2127 {
2128 if( subgraph->m_absorbed )
2129 continue;
2130
2131 SCH_CONNECTION* connection = subgraph->m_driver_connection;
2132 SCH_SHEET_PATH sheet = subgraph->m_sheet;
2133 wxString name = connection->Name();
2134
2135 // Test subgraphs with weak drivers for net name conflicts and fix them
2136 unsigned suffix = 1;
2137
2138 wxString base_name = connection->Name();
2139
2140 auto create_new_name =
2141 [&suffix, &base_name]( SCH_CONNECTION* aConn ) -> wxString
2142 {
2143 wxString suffixStr = std::to_wstring( suffix );
2144
2145 // For group buses with a prefix, we can add the suffix to the prefix.
2146 // If they don't have a prefix, we force the creation of a prefix so that
2147 // two buses don't get inadvertently shorted together.
2148 if( aConn->Type() == CONNECTION_TYPE::BUS_GROUP )
2149 {
2150 wxString prefix = aConn->BusPrefix();
2151
2152 if( prefix.empty() )
2153 prefix = wxT( "BUS" ); // So result will be "BUS_1{...}"
2154
2155 // Use BusPrefix length to skip past any formatting markers
2156 // in the prefix (e.g. ~{RESET}) rather than AfterFirst('{')
2157 // which would split at a formatting brace.
2158 wxString members = base_name.Mid( aConn->BusPrefix().length() );
2159
2160 wxString newName;
2161 newName << prefix << wxT( "_" ) << suffixStr << members;
2162
2163 aConn->ConfigureFromLabel( newName );
2164 }
2165 else
2166 {
2167 // Reset to the unsuffixed base so retries generate base_1, base_2, ...
2168 // instead of stacking suffixes onto the previous attempt.
2169 aConn->SetSuffix( wxString( wxT( "_" ) ) << suffixStr );
2170 }
2171
2172 suffix++;
2173 return aConn->Name();
2174 };
2175
2176 // Promote a weakly-driven sheet-pin subgraph to a strong driver so that it is considered
2177 // below for propagation/merging. A sheet pin sharing its (path-less) name with a global
2178 // label on the same sheet would then be treated as if it had a matching local label, so we
2179 // skip the promotion in that case to avoid a false merge.
2180 auto promote_sheet_pin_driver =
2181 [&]()
2182 {
2183 if( !subgraph->m_driver || subgraph->m_driver->Type() != SCH_SHEET_PIN_T )
2184 return;
2185
2186 wxString global_name = connection->Name( true );
2187 auto kk = m_net_name_to_subgraphs_map.find( global_name );
2188
2189 if( kk != m_net_name_to_subgraphs_map.end() )
2190 {
2191 for( const CONNECTION_SUBGRAPH* candidate : kk->second )
2192 {
2193 if( candidate->m_sheet == sheet )
2194 {
2195 wxLogTrace( ConnTrace,
2196 wxS( "%ld (%s) skipped for promotion due to potential conflict" ),
2197 subgraph->m_code, connection->Name() );
2198 return;
2199 }
2200 }
2201 }
2202
2203 subgraph->m_strong_driver = true;
2204 };
2205
2206 if( !subgraph->m_strong_driver )
2207 {
2208 std::vector<CONNECTION_SUBGRAPH*> vec_empty;
2209 std::vector<CONNECTION_SUBGRAPH*>* vec = &vec_empty;
2210
2211 if( m_net_name_to_subgraphs_map.count( name ) )
2212 vec = &m_net_name_to_subgraphs_map.at( name );
2213
2214 // If we are a unique bus vector, check if we aren't actually unique because of another
2215 // subgraph with a similar bus vector
2216 if( vec->size() <= 1 && subgraph->m_driver_connection->Type() == CONNECTION_TYPE::BUS )
2217 {
2218 wxString prefixOnly = name.BeforeFirst( '[' ) + wxT( "[]" );
2219
2220 if( m_net_name_to_subgraphs_map.count( prefixOnly ) )
2221 vec = &m_net_name_to_subgraphs_map.at( prefixOnly );
2222 }
2223
2224 if( vec->size() > 1 )
2225 {
2226 wxString new_name = create_new_name( connection );
2227
2228 while( m_net_name_to_subgraphs_map.contains( new_name ) )
2229 new_name = create_new_name( connection );
2230
2231 wxLogTrace( ConnTrace, wxS( "%ld (%s) is weakly driven and not unique. Changing to %s." ),
2232 subgraph->m_code, name, new_name );
2233
2234 std::erase( *vec, subgraph );
2235
2236 m_net_name_to_subgraphs_map[new_name].emplace_back( subgraph );
2237
2238 name = new_name;
2239
2240 // The renamed sheet pin still drives its own bus members through the hierarchy, so
2241 // it must be promoted for propagation to reach them (issue #21798).
2242 promote_sheet_pin_driver();
2243 }
2244 else if( subgraph->m_driver )
2245 {
2246 promote_sheet_pin_driver();
2247 }
2248 }
2249
2250 // Assign net codes
2251 if( connection->IsBus() )
2252 {
2253 int code = -1;
2254 auto it = m_bus_name_to_code_map.find( name );
2255
2256 if( it != m_bus_name_to_code_map.end() )
2257 {
2258 code = it->second;
2259 }
2260 else
2261 {
2262 code = m_last_bus_code++;
2263 m_bus_name_to_code_map[ name ] = code;
2264 }
2265
2266 connection->SetBusCode( code );
2267 assignNetCodesToBus( connection );
2268 }
2269 else
2270 {
2271 assignNewNetCode( *connection );
2272 }
2273
2274 // Reset the flag for the next loop below
2275 subgraph->m_dirty = true;
2276
2277 // Next, we merge together subgraphs that have label connections, and create
2278 // neighbor links for subgraphs that are part of a bus on the same sheet.
2279 // For merging, we consider each possible strong driver.
2280
2281 // If this subgraph doesn't have a strong driver, let's skip it, since there is no
2282 // way it will be merged with anything.
2283 if( !subgraph->m_strong_driver )
2284 continue;
2285
2286 // candidate_subgraphs will contain each valid, non-bus subgraph on the same sheet
2287 // as the subgraph we are considering that has a strong driver.
2288 // Weakly driven subgraphs are not considered since they will never be absorbed or
2289 // form neighbor links.
2290 std::vector<CONNECTION_SUBGRAPH*> candidate_subgraphs;
2291 std::copy_if( m_sheet_to_subgraphs_map[ subgraph->m_sheet ].begin(),
2292 m_sheet_to_subgraphs_map[ subgraph->m_sheet ].end(),
2293 std::back_inserter( candidate_subgraphs ),
2294 [&] ( const CONNECTION_SUBGRAPH* candidate )
2295 {
2296 return ( !candidate->m_absorbed &&
2297 candidate->m_strong_driver &&
2298 candidate != subgraph );
2299 } );
2300
2301 // This is a list of connections on the current subgraph to compare to the
2302 // drivers of each candidate subgraph. If the current subgraph is a bus,
2303 // we should consider each bus member.
2304 std::vector< std::shared_ptr<SCH_CONNECTION> > connections_to_check;
2305
2306 // Also check the main driving connection
2307 connections_to_check.push_back( std::make_shared<SCH_CONNECTION>( *connection ) );
2308
2309 auto add_connections_to_check =
2310 [&] ( CONNECTION_SUBGRAPH* aSubgraph )
2311 {
2312 for( SCH_ITEM* possible_driver : aSubgraph->m_items )
2313 {
2314 if( possible_driver == aSubgraph->m_driver )
2315 continue;
2316
2317 auto c = getDefaultConnection( possible_driver, aSubgraph );
2318
2319 if( c )
2320 {
2321 if( c->Type() != aSubgraph->m_driver_connection->Type() )
2322 continue;
2323
2324 if( c->Name( true ) == aSubgraph->m_driver_connection->Name( true ) )
2325 continue;
2326
2327 connections_to_check.push_back( c );
2328 wxLogTrace( ConnTrace, wxS( "%lu (%s): Adding secondary driver %s" ),
2329 aSubgraph->m_code,
2330 aSubgraph->m_driver_connection->Name( true ),
2331 c->Name( true ) );
2332 }
2333 }
2334 };
2335
2336 // Now add other strong drivers
2337 // The actual connection attached to these items will have been overwritten
2338 // by the chosen driver of the subgraph, so we need to create a dummy connection
2339 add_connections_to_check( subgraph );
2340
2341 std::set<SCH_CONNECTION*> checked_connections;
2342
2343 for( unsigned i = 0; i < connections_to_check.size(); i++ )
2344 {
2345 auto member = connections_to_check[i];
2346
2347 // Don't check the same connection twice
2348 if( !checked_connections.insert( member.get() ).second )
2349 continue;
2350
2351 if( member->IsBus() )
2352 {
2353 connections_to_check.insert( connections_to_check.end(),
2354 member->Members().begin(),
2355 member->Members().end() );
2356 }
2357
2358 wxString test_name = member->Name( true );
2359
2360 for( CONNECTION_SUBGRAPH* candidate : candidate_subgraphs )
2361 {
2362 if( candidate->m_absorbed || candidate == subgraph )
2363 continue;
2364
2365 bool match = false;
2366
2367 if( candidate->m_driver_connection->Name( true ) == test_name )
2368 {
2369 match = true;
2370 }
2371 else
2372 {
2373 if( !candidate->m_multiple_drivers )
2374 continue;
2375
2376 for( SCH_ITEM *driver : candidate->m_drivers )
2377 {
2378 if( driver == candidate->m_driver )
2379 continue;
2380
2381 // Sheet pins are not candidates for merging
2382 if( driver->Type() == SCH_SHEET_PIN_T )
2383 continue;
2384
2385 if( driver->Type() == SCH_PIN_T )
2386 {
2387 auto pin = static_cast<SCH_PIN*>( driver );
2388
2389 if( pin->IsPower()
2390 && pin->GetDefaultNetName( sheet ) == test_name )
2391 {
2392 match = true;
2393 break;
2394 }
2395 }
2396 else
2397 {
2398 // Should we skip this if the driver type is not one of these types?
2399 wxASSERT( driver->Type() == SCH_LABEL_T ||
2400 driver->Type() == SCH_GLOBAL_LABEL_T ||
2401 driver->Type() == SCH_HIER_LABEL_T );
2402
2403 if( subgraph->GetNameForDriver( driver ) == test_name )
2404 {
2405 match = true;
2406 break;
2407 }
2408 }
2409 }
2410 }
2411
2412 if( match )
2413 {
2414 if( connection->IsBus() && candidate->m_driver_connection->IsNet() )
2415 {
2416 wxLogTrace( ConnTrace, wxS( "%lu (%s) has bus child %lu (%s)" ),
2417 subgraph->m_code, connection->Name(),
2418 candidate->m_code, member->Name() );
2419
2420 subgraph->m_bus_neighbors[member].insert( candidate );
2421 candidate->m_bus_parents[member].insert( subgraph );
2422 }
2423 else if( ( !connection->IsBus()
2424 && !candidate->m_driver_connection->IsBus() )
2425 || connection->Type() == candidate->m_driver_connection->Type() )
2426 {
2427 wxLogTrace( ConnTrace, wxS( "%lu (%s) absorbs neighbor %lu (%s)" ),
2428 subgraph->m_code, connection->Name(),
2429 candidate->m_code, candidate->m_driver_connection->Name() );
2430
2431 // Candidate may have other non-chosen drivers we need to follow
2432 add_connections_to_check( candidate );
2433
2434 subgraph->Absorb( candidate );
2435 invalidated_subgraphs.insert( subgraph );
2436 }
2437 }
2438 }
2439 }
2440 }
2441
2442 // Update any subgraph that was invalidated above
2443 for( CONNECTION_SUBGRAPH* subgraph : invalidated_subgraphs )
2444 {
2445 if( subgraph->m_absorbed )
2446 continue;
2447
2448 if( !subgraph->ResolveDrivers() )
2449 continue;
2450
2451 if( subgraph->m_driver_connection->IsBus() )
2452 assignNetCodesToBus( subgraph->m_driver_connection );
2453 else
2454 assignNewNetCode( *subgraph->m_driver_connection );
2455
2456 wxLogTrace( ConnTrace, wxS( "Re-resolving drivers for %lu (%s)" ),
2457 subgraph->m_code, subgraph->m_driver_connection->Name() );
2458 }
2459
2460}
2461
2462
2463// TODO(JE) This won't give the same subgraph IDs (and eventually net/graph codes)
2464// to the same subgraph necessarily if it runs over and over again on the same
2465// sheet. We need:
2466//
2467// a) a cache of net/bus codes, like used before
2468// b) to persist the CONNECTION_GRAPH globally so the cache is persistent,
2469// c) some way of trying to avoid changing net names. so we should keep track
2470// of the previous driver of a net, and if it comes down to choosing between
2471// equally-prioritized drivers, choose the one that already exists as a driver
2472// on some portion of the items.
2473
2474
2475void CONNECTION_GRAPH::buildConnectionGraph( std::function<void( SCH_ITEM* )>* aChangedItemHandler,
2476 bool aUnconditional )
2477{
2478 // Recache all bus aliases for later use
2479 wxCHECK_RET( m_schematic, wxT( "Connection graph cannot be built without schematic pointer" ) );
2480
2481 m_bus_alias_cache.clear();
2482
2483 for( const std::shared_ptr<BUS_ALIAS>& alias : m_schematic->GetAllBusAliases() )
2484 {
2485 if( alias )
2486 m_bus_alias_cache[alias->GetName()] = alias;
2487 }
2488
2489 PROF_TIMER sub_graph( "buildItemSubGraphs" );
2491
2492 if( wxLog::IsAllowedTraceMask( DanglingProfileMask ) )
2493 sub_graph.Show();
2494
2499
2501
2503
2505
2507
2508 PROF_TIMER proc_sub_graph( "ProcessSubGraphs" );
2510
2511 if( wxLog::IsAllowedTraceMask( DanglingProfileMask ) )
2512 proc_sub_graph.Show();
2513
2514 // Absorbed subgraphs should no longer be considered
2515 std::erase_if( m_driver_subgraphs, [&]( const CONNECTION_SUBGRAPH* candidate ) -> bool
2516 {
2517 return candidate->m_absorbed;
2518 } );
2519
2520 // Store global subgraphs for later reference
2521 std::vector<CONNECTION_SUBGRAPH*> global_subgraphs;
2522 std::copy_if( m_driver_subgraphs.begin(), m_driver_subgraphs.end(),
2523 std::back_inserter( global_subgraphs ),
2524 [&] ( const CONNECTION_SUBGRAPH* candidate ) -> bool
2525 {
2526 return !candidate->m_local_driver;
2527 } );
2528
2529 // Recache remaining valid subgraphs by sheet path
2531
2532 for( CONNECTION_SUBGRAPH* subgraph : m_driver_subgraphs )
2533 m_sheet_to_subgraphs_map[ subgraph->m_sheet ].emplace_back( subgraph );
2534
2536
2537 auto results = tp.submit_loop( 0, m_driver_subgraphs.size(),
2538 [&]( const int ii )
2539 {
2540 m_driver_subgraphs[ii]->UpdateItemConnections();
2541 });
2542
2543 results.wait();
2544
2545 // Build equivalence classes over global subgraphs that are linked by shared
2546 // global label names. Two global subgraphs are in the same class whenever
2547 // (transitively) some subgraph has a global driver named X and another
2548 // subgraph has a global driver also named X, OR a single multi-driver
2549 // subgraph has both X and Y as global drivers.
2550 //
2551 // This is the transitive closure over the relation "shares a global name".
2552 // When users chain nets across sheets via differently-named global labels,
2553 // every subgraph reachable through any sequence of shared names must end
2554 // up on the same final net.
2555 //
2556 // The per-subgraph promote pass that follows is order-dependent and walks
2557 // candidates by their *original* driver text rather than by their already-
2558 // promoted name. As a result, when subgraph S2 promotes subgraph S1 to a
2559 // new name, and then a third subgraph S3 later renames S2 again, S1 is
2560 // left orphaned with the intermediate name. This pre-pass solves the
2561 // transitivity problem before the order-dependent loop runs (issue 23719).
2562 if( !global_subgraphs.empty() )
2563 {
2564 std::unordered_map<CONNECTION_SUBGRAPH*, CONNECTION_SUBGRAPH*> sg_root;
2565
2566 auto find_sg_root =
2568 {
2569 CONNECTION_SUBGRAPH* cur = aSg;
2570
2571 while( true )
2572 {
2573 auto it = sg_root.find( cur );
2574
2575 if( it == sg_root.end() || it->second == cur )
2576 return cur;
2577
2578 // Path compression. Hop the current node directly to its
2579 // grandparent on the way up so subsequent finds are O(1).
2580 auto parent_it = sg_root.find( it->second );
2581
2582 if( parent_it != sg_root.end() && parent_it->second != it->second )
2583 it->second = parent_it->second;
2584
2585 cur = it->second;
2586 }
2587 };
2588
2589 // Pick the subgraph whose primary driver the file-local compareDrivers helper
2590 // would rank first. Using the same helper as CONNECTION_SUBGRAPH::ResolveDrivers
2591 // guarantees both sites agree on every tie-break rule (priority, bus width,
2592 // pin power parent, sheet-pin shape, -Pad demotion, alphabetical).
2593 auto prefer_as_representative =
2594 [&]( CONNECTION_SUBGRAPH* aA, CONNECTION_SUBGRAPH* aB ) -> bool
2595 {
2598 aB->m_driver, aB->m_driver_connection,
2599 aB->m_driver_connection->Name() ) < 0;
2600 };
2601
2602 auto union_sgs =
2604 {
2605 sg_root.try_emplace( aA, aA );
2606 sg_root.try_emplace( aB, aB );
2607
2608 CONNECTION_SUBGRAPH* root_a = find_sg_root( aA );
2609 CONNECTION_SUBGRAPH* root_b = find_sg_root( aB );
2610
2611 if( root_a == root_b )
2612 return;
2613
2614 if( prefer_as_representative( root_a, root_b ) )
2615 sg_root[root_b] = root_a;
2616 else
2617 sg_root[root_a] = root_b;
2618 };
2619
2620 std::unordered_map<wxString, std::vector<CONNECTION_SUBGRAPH*>> name_to_sgs;
2621
2622 for( CONNECTION_SUBGRAPH* subgraph : global_subgraphs )
2623 {
2624 for( SCH_ITEM* driver : subgraph->m_drivers )
2625 {
2628 {
2629 continue;
2630 }
2631
2632 name_to_sgs[subgraph->GetNameForDriver( driver )].push_back( subgraph );
2633 }
2634 }
2635
2636 for( auto& [name, sgs] : name_to_sgs )
2637 {
2638 if( sgs.size() < 2 )
2639 continue;
2640
2641 for( size_t ii = 1; ii < sgs.size(); ++ii )
2642 union_sgs( sgs[0], sgs[ii] );
2643 }
2644
2645 // Every subgraph in sg_root now maps (with path compression) to the
2646 // representative of its equivalence class. Clone the representative's
2647 // connection into each member that currently differs.
2648 for( const auto& entry : sg_root )
2649 {
2650 CONNECTION_SUBGRAPH* sg = entry.first;
2651 CONNECTION_SUBGRAPH* root = find_sg_root( sg );
2652
2653 if( sg == root )
2654 continue;
2655
2656 if( sg->m_driver_connection->Name() == root->m_driver_connection->Name() )
2657 continue;
2658
2659 wxLogTrace( ConnTrace, wxS( "Global %lu (%s) canonicalized to %lu (%s)" ),
2660 sg->m_code, sg->m_driver_connection->Name(), root->m_code,
2661 root->m_driver_connection->Name() );
2662
2664 }
2665 }
2666
2667 // Next time through the subgraphs, we do some post-processing to handle things like
2668 // connecting bus members to their neighboring subgraphs, and then propagate connections
2669 // through the hierarchy
2670 for( CONNECTION_SUBGRAPH* subgraph : m_driver_subgraphs )
2671 {
2672 if( !subgraph->m_dirty )
2673 continue;
2674
2675 wxLogTrace( ConnTrace, wxS( "Processing %lu (%s) for propagation" ),
2676 subgraph->m_code, subgraph->m_driver_connection->Name() );
2677
2678 // For subgraphs that are driven by a global (power port or label) and have more
2679 // than one global driver, we need to seek out other subgraphs driven by the
2680 // same name as the non-chosen driver and update them to match the chosen one.
2681
2682 if( !subgraph->m_local_driver && subgraph->m_multiple_drivers )
2683 {
2684 for( SCH_ITEM* driver : subgraph->m_drivers )
2685 {
2686 if( driver == subgraph->m_driver )
2687 continue;
2688
2689 const wxString& secondary_name = subgraph->GetNameForDriver( driver );
2690
2691 if( secondary_name == subgraph->m_driver_connection->Name() )
2692 continue;
2693
2694 bool secondary_is_global = CONNECTION_SUBGRAPH::GetDriverPriority( driver )
2696
2697 for( CONNECTION_SUBGRAPH* candidate : global_subgraphs )
2698 {
2699 if( candidate == subgraph )
2700 continue;
2701
2702 if( !secondary_is_global && candidate->m_sheet != subgraph->m_sheet )
2703 continue;
2704
2705 for( SCH_ITEM* candidate_driver : candidate->m_drivers )
2706 {
2707 if( candidate->GetNameForDriver( candidate_driver ) == secondary_name )
2708 {
2709 wxLogTrace( ConnTrace, wxS( "Global %lu (%s) promoted to %s" ),
2710 candidate->m_code, candidate->m_driver_connection->Name(),
2711 subgraph->m_driver_connection->Name() );
2712
2713 candidate->m_driver_connection->Clone( *subgraph->m_driver_connection );
2714
2715 candidate->m_dirty = false;
2716 propagateToNeighbors( candidate, false );
2717 }
2718 }
2719 }
2720 }
2721 }
2722
2723 // This call will handle descending the hierarchy and updating child subgraphs
2724 propagateToNeighbors( subgraph, false );
2725 }
2726
2727 // After processing and allowing some to be skipped if they have hierarchical
2728 // pins connecting both up and down the hierarchy, we check to see if any of them
2729 // have not been processed. This would indicate that they do not have off-sheet connections
2730 // but we still need to handle the subgraph
2731 for( CONNECTION_SUBGRAPH* subgraph : m_driver_subgraphs )
2732 {
2733 if( subgraph->m_dirty )
2734 propagateToNeighbors( subgraph, true );
2735 }
2736
2737 // Handle buses that have been linked together somewhere by member (net) connections.
2738 // This feels a bit hacky, perhaps this algorithm should be revisited in the future.
2739
2740 // For net subgraphs that have more than one bus parent, we need to ensure that those
2741 // buses are linked together in the final netlist. The final name of each bus might not
2742 // match the local name that was used to establish the parent-child relationship, because
2743 // the bus may have been renamed by a hierarchical connection. So, for each of these cases,
2744 // we need to identify the appropriate bus members to link together (and their final names),
2745 // and then update all instances of the old name in the hierarchy.
2746 for( CONNECTION_SUBGRAPH* subgraph : m_driver_subgraphs )
2747 {
2748 // All SGs should have been processed by propagateToNeighbors above
2749 // Should we skip all of this if the subgraph is not dirty?
2750 wxASSERT_MSG( !subgraph->m_dirty,
2751 wxS( "Subgraph not processed by propagateToNeighbors!" ) );
2752
2753 if( subgraph->m_bus_parents.size() < 2 )
2754 continue;
2755
2756 SCH_CONNECTION* conn = subgraph->m_driver_connection;
2757
2758 wxLogTrace( ConnTrace, wxS( "%lu (%s) has multiple bus parents" ),
2759 subgraph->m_code, conn->Name() );
2760
2761 // Should we skip everything after this if this is not a net?
2762 wxCHECK2( conn->IsNet(), continue );
2763
2764 for( const auto& ii : subgraph->m_bus_parents )
2765 {
2766 SCH_CONNECTION* link_member = ii.first.get();
2767
2768 for( CONNECTION_SUBGRAPH* parent : ii.second )
2769 {
2770 while( parent->m_absorbed )
2771 parent = parent->m_absorbed_by;
2772
2773 SCH_CONNECTION* match = matchBusMember( parent->m_driver_connection, link_member );
2774
2775 if( !match )
2776 {
2777 wxLogTrace( ConnTrace, wxS( "Warning: could not match %s inside %lu (%s)" ),
2778 conn->Name(), parent->m_code, parent->m_driver_connection->Name() );
2779 continue;
2780 }
2781
2782 if( conn->Name() != match->Name() )
2783 {
2784 wxString old_name = match->Name();
2785
2786 wxLogTrace( ConnTrace, wxS( "Updating %lu (%s) member %s to %s" ),
2787 parent->m_code, parent->m_driver_connection->Name(), old_name, conn->Name() );
2788
2789 match->Clone( *conn );
2790
2791 auto jj = m_net_name_to_subgraphs_map.find( old_name );
2792
2793 if( jj == m_net_name_to_subgraphs_map.end() )
2794 continue;
2795
2796 // Copy the vector to avoid iterator invalidation when recaching
2797 std::vector<CONNECTION_SUBGRAPH*> old_subgraphs = jj->second;
2798
2799 for( CONNECTION_SUBGRAPH* old_sg : old_subgraphs )
2800 {
2801 while( old_sg->m_absorbed )
2802 old_sg = old_sg->m_absorbed_by;
2803
2804 wxString old_sg_name = old_sg->m_driver_connection->Name();
2805 old_sg->m_driver_connection->Clone( *conn );
2806
2807 if( old_sg_name != old_sg->m_driver_connection->Name() )
2808 recacheSubgraphName( old_sg, old_sg_name );
2809 }
2810 }
2811 }
2812 }
2813 }
2814
2815 // Phase 1: write each subgraph's items' connections. Items can be referenced from
2816 // other subgraphs (via labels), so phase 2 below has to wait for every phase 1 task
2817 // to complete before reading anything through label->Connection().
2818 auto propagateConnectionsTask =
2819 [&]( CONNECTION_SUBGRAPH* subgraph )
2820 {
2821 // Make sure weakly-driven single-pin nets get the unconnected_ prefix
2822 if( !subgraph->m_strong_driver
2823 && subgraph->m_drivers.size() == 1
2824 && subgraph->m_driver->Type() == SCH_PIN_T )
2825 {
2826 SCH_PIN* pin = static_cast<SCH_PIN*>( subgraph->m_driver );
2827 wxString name = pin->GetDefaultNetName( subgraph->m_sheet, true );
2828
2829 subgraph->m_driver_connection->ConfigureFromLabel( name );
2830 }
2831
2832 subgraph->m_dirty = false;
2833 subgraph->UpdateItemConnections();
2834 };
2835
2836 auto results1 = tp.submit_loop( 0, m_driver_subgraphs.size(),
2837 [&]( const int ii )
2838 {
2839 propagateConnectionsTask( m_driver_subgraphs[ii] );
2840 } );
2841 results1.wait();
2842
2843 // Phase 2: promote sheet-pin subgraphs to buses based on the matching child-sheet
2844 // hier label. This reads other subgraphs' connections via label->Connection() and
2845 // also writes subgraph->m_driver_connection->SetType, so it has to be serial.
2846 for( CONNECTION_SUBGRAPH* subgraph : m_driver_subgraphs )
2847 {
2848 if( subgraph->m_driver_connection->IsBus() )
2849 continue;
2850
2851 if( !subgraph->m_driver || subgraph->m_driver->Type() != SCH_SHEET_PIN_T )
2852 continue;
2853
2854 SCH_SHEET_PIN* pin = static_cast<SCH_SHEET_PIN*>( subgraph->m_driver );
2855 SCH_SHEET* sheet = pin->GetParent();
2856
2857 if( !sheet )
2858 continue;
2859
2860 wxString pinText = pin->GetShownText( FOR_NETNAME );
2861 SCH_SCREEN* screen = sheet->GetScreen();
2862
2863 for( SCH_ITEM* item : screen->Items().OfType( SCH_HIER_LABEL_T ) )
2864 {
2865 SCH_HIERLABEL* label = static_cast<SCH_HIERLABEL*>( item );
2866
2867 if( label->GetShownText( &subgraph->m_sheet, FOR_NETNAME ) == pinText )
2868 {
2869 SCH_SHEET_PATH path = subgraph->m_sheet;
2870 path.push_back( sheet );
2871
2872 SCH_CONNECTION* parent_conn = label->Connection( &path );
2873
2874 if( parent_conn && parent_conn->IsBus() )
2875 subgraph->m_driver_connection->SetType( CONNECTION_TYPE::BUS );
2876
2877 break;
2878 }
2879 }
2880 }
2881
2884
2885 for( CONNECTION_SUBGRAPH* subgraph : m_driver_subgraphs )
2886 {
2887 NET_NAME_CODE_CACHE_KEY key = { subgraph->GetNetName(),
2888 subgraph->m_driver_connection->NetCode() };
2889 m_net_code_to_subgraphs_map[ key ].push_back( subgraph );
2890
2891 m_net_name_to_subgraphs_map[subgraph->m_driver_connection->Name()].push_back( subgraph );
2892 }
2893
2894 std::shared_ptr<NET_SETTINGS>& netSettings = m_schematic->Project().GetProjectFile().m_NetSettings;
2895 std::map<wxString, std::set<wxString>> oldAssignments = netSettings->GetNetclassLabelAssignments();
2896 std::set<wxString> affectedNetclassNetAssignments;
2897
2898 netSettings->ClearNetclassLabelAssignments();
2899
2900 auto dirtySubgraphs =
2901 [&]( const std::vector<CONNECTION_SUBGRAPH*>& subgraphs )
2902 {
2903 if( aChangedItemHandler )
2904 {
2905 for( const CONNECTION_SUBGRAPH* subgraph : subgraphs )
2906 {
2907 for( SCH_ITEM* item : subgraph->m_items )
2908 (*aChangedItemHandler)( item );
2909 }
2910 }
2911 };
2912
2913 auto checkNetclassDrivers =
2914 [&]( const wxString& netName, const std::vector<CONNECTION_SUBGRAPH*>& subgraphs )
2915 {
2916 wxCHECK_RET( !subgraphs.empty(), wxS( "Invalid empty subgraph" ) );
2917
2918 std::set<wxString> netclasses;
2919
2920 // Collect all netclasses on all subgraphs for this net
2921 for( const CONNECTION_SUBGRAPH* subgraph : subgraphs )
2922 {
2923 for( SCH_ITEM* item : subgraph->m_items )
2924 {
2925 for( const auto& [name, provider] : subgraph->GetNetclassesForDriver( item ) )
2926 netclasses.insert( name );
2927 }
2928 }
2929
2930 // Append the netclasses to any included bus members
2931 for( const CONNECTION_SUBGRAPH* subgraph : subgraphs )
2932 {
2933 if( subgraph->m_driver_connection->IsBus() )
2934 {
2935 auto processBusMember = [&, this]( const SCH_CONNECTION* member )
2936 {
2937 if( !netclasses.empty() )
2938 {
2939 netSettings->AppendNetclassLabelAssignment( member->Name(), netclasses );
2940 }
2941
2942 auto ii = m_net_name_to_subgraphs_map.find( member->Name() );
2943
2944 if( oldAssignments.count( member->Name() ) )
2945 {
2946 if( oldAssignments[member->Name()] != netclasses )
2947 {
2948 affectedNetclassNetAssignments.insert( member->Name() );
2949
2950 if( ii != m_net_name_to_subgraphs_map.end() )
2951 dirtySubgraphs( ii->second );
2952 }
2953 }
2954 else if( !netclasses.empty() )
2955 {
2956 affectedNetclassNetAssignments.insert( member->Name() );
2957
2958 if( ii != m_net_name_to_subgraphs_map.end() )
2959 dirtySubgraphs( ii->second );
2960 }
2961 };
2962
2963 for( const std::shared_ptr<SCH_CONNECTION>& member : subgraph->m_driver_connection->Members() )
2964 {
2965 // Check if this member itself is a bus (which can be the case for vector buses as members
2966 // of a bus, see https://gitlab.com/kicad/code/kicad/-/issues/16545
2967 if( member->IsBus() )
2968 {
2969 for( const std::shared_ptr<SCH_CONNECTION>& nestedMember : member->Members() )
2970 processBusMember( nestedMember.get() );
2971 }
2972 else
2973 {
2974 processBusMember( member.get() );
2975 }
2976 }
2977 }
2978 }
2979
2980 // Assign the netclasses to the root netname
2981 if( !netclasses.empty() )
2982 {
2983 netSettings->AppendNetclassLabelAssignment( netName, netclasses );
2984 }
2985
2986 if( oldAssignments.count( netName ) )
2987 {
2988 if( oldAssignments[netName] != netclasses )
2989 {
2990 affectedNetclassNetAssignments.insert( netName );
2991 dirtySubgraphs( subgraphs );
2992 }
2993 }
2994 else if( !netclasses.empty() )
2995 {
2996 affectedNetclassNetAssignments.insert( netName );
2997 dirtySubgraphs( subgraphs );
2998 }
2999 };
3000
3001 // Check for netclass assignments
3002 for( const auto& [ netname, subgraphs ] : m_net_name_to_subgraphs_map )
3003 checkNetclassDrivers( netname, subgraphs );
3004
3005 if( !aUnconditional )
3006 {
3007 for( auto& [netname, netclasses] : oldAssignments )
3008 {
3009 if( netSettings->GetNetclassLabelAssignments().count( netname )
3010 || affectedNetclassNetAssignments.count( netname ) )
3011 {
3012 continue;
3013 }
3014
3015 netSettings->SetNetclassLabelAssignment( netname, netclasses );
3016 }
3017 }
3018
3020
3022}
3023
3025{
3026 static std::function<void( SCH_CONNECTIVITY::NETCHAIN_MANAGER& )> s_hook;
3027 return s_hook;
3028}
3029
3030
3032{
3033 if( !m_schematic )
3034 return;
3035
3037 connectivity.sheets.reserve( m_sheetList.size() );
3038 std::unordered_map<SCH_SHEET_PATH, SCH_CONNECTIVITY::NETCHAIN_INPUT::SHEET*> sheets;
3039
3040 for( const SCH_SHEET_PATH& path : m_sheetList )
3041 {
3042 connectivity.sheets.emplace_back( path, &connectivity.storage );
3043 sheets.emplace( path, &connectivity.sheets.back() );
3044 }
3045
3046 for( const auto& [item, subgraphs] : m_item_to_subgraph_map )
3047 {
3048 if( item->Type() != SCH_PIN_T && item->Type() != SCH_LABEL_T )
3049 continue;
3050
3051 for( CONNECTION_SUBGRAPH* subgraph : subgraphs )
3052 {
3053 if( !subgraph )
3054 continue;
3055
3056 const auto sheet = sheets.find( subgraph->GetSheet() );
3057
3058 if( sheet == sheets.end() )
3059 continue;
3060
3061 while( subgraph && subgraph->m_absorbed )
3062 subgraph = subgraph->m_absorbed_by;
3063
3064 if( subgraph )
3065 {
3066 const wxString name = subgraph->GetNetName();
3067 sheet->second->nets.insert_or_assign(
3069 name, SCH_NETCHAIN::MakeKey( name, subgraph->m_code ) } );
3070 }
3071 }
3072 }
3073
3074 m_netChains->Rebuild( connectivity, RebuildNetChainsTestHook() );
3075}
3076
3077
3079 const CHAIN_TERMINAL_REFS& aTermRefs,
3080 const std::map<std::pair<wxString, wxString>, wxString>& aRefPinToNet,
3081 const std::vector<std::unique_ptr<SCH_NETCHAIN>>& aPotentials,
3082 const wxString& aChainName )
3083{
3085 aTermRefs, aRefPinToNet, aPotentials, aChainName );
3086}
3087
3089{
3090 return m_netChains->GetNetChainForNet( aNet );
3091}
3092
3094{
3095 return m_netChains->GetNetChainByName( aName );
3096}
3097
3099{
3100 return m_netChains->DeleteCommittedNetChain( aName );
3101}
3102
3103bool CONNECTION_GRAPH::RenameCommittedNetChain( const wxString& aOld, const wxString& aNew )
3104{
3105 return m_netChains->RenameCommittedNetChain( aOld, aNew );
3106}
3107
3108
3109
3111{
3112 m_netChains->ApplyNetChainNetclasses();
3113}
3114
3116{
3117 return m_netChains->CreateNetChainFromPotential( aPotential, aName );
3118}
3119
3121 const std::set<SCH_SYMBOL*>& aSymbols, const std::set<wxString>& aNets,
3122 const KIID& aTerminalPinA, const KIID& aTerminalPinB,
3123 const wxString& aRefA, const wxString& aPinNumA,
3124 const wxString& aRefB, const wxString& aPinNumB )
3125{
3126 return m_netChains->CreateManualNetChain( aName, aSymbols, aNets, aTerminalPinA, aTerminalPinB,
3127 aRefA, aPinNumA, aRefB, aPinNumB );
3128}
3129
3130
3131int CONNECTION_GRAPH::getOrCreateNetCode( const wxString& aNetName )
3132{
3133 int code;
3134
3135 auto it = m_net_name_to_code_map.find( aNetName );
3136
3137 if( it == m_net_name_to_code_map.end() )
3138 {
3139 code = m_last_net_code++;
3140 m_net_name_to_code_map[ aNetName ] = code;
3141 }
3142 else
3143 {
3144 code = it->second;
3145 }
3146
3147 return code;
3148}
3149
3150
3152{
3153 int code = getOrCreateNetCode( aConnection.Name() );
3154
3155 aConnection.SetNetCode( code );
3156
3157 return code;
3158}
3159
3160
3162{
3163 std::vector<std::shared_ptr<SCH_CONNECTION>> connections_to_check( aConnection->Members() );
3164
3165 for( unsigned i = 0; i < connections_to_check.size(); i++ )
3166 {
3167 const std::shared_ptr<SCH_CONNECTION>& member = connections_to_check[i];
3168
3169 if( member->IsBus() )
3170 {
3171 connections_to_check.insert( connections_to_check.end(),
3172 member->Members().begin(),
3173 member->Members().end() );
3174 continue;
3175 }
3176
3177 assignNewNetCode( *member );
3178 }
3179}
3180
3181
3183{
3184 SCH_CONNECTION* conn = aSubgraph->m_driver_connection;
3185 std::vector<CONNECTION_SUBGRAPH*> search_list;
3186 std::unordered_set<CONNECTION_SUBGRAPH*> visited;
3187 std::unordered_set<SCH_CONNECTION*> stale_bus_members;
3188
3189 auto visit =[&]( CONNECTION_SUBGRAPH* aParent )
3190 {
3191 for( SCH_SHEET_PIN* pin : aParent->m_hier_pins )
3192 {
3193 SCH_SHEET_PATH path = aParent->m_sheet;
3194 path.push_back( pin->GetParent() );
3195
3196 auto it = m_sheet_to_subgraphs_map.find( path );
3197
3198 if( it == m_sheet_to_subgraphs_map.end() )
3199 continue;
3200
3201 for( CONNECTION_SUBGRAPH* candidate : it->second )
3202 {
3203 if( !candidate->m_strong_driver
3204 || candidate->m_hier_ports.empty()
3205 || visited.contains( candidate ) )
3206 {
3207 continue;
3208 }
3209
3210 for( SCH_HIERLABEL* label : candidate->m_hier_ports )
3211 {
3212 if( candidate->GetNameForDriver( label ) == aParent->GetNameForDriver( pin ) )
3213 {
3214 wxLogTrace( ConnTrace, wxS( "%lu: found child %lu (%s)" ), aParent->m_code,
3215 candidate->m_code, candidate->m_driver_connection->Name() );
3216
3217 candidate->m_hier_parent = aParent;
3218 aParent->m_hier_children.insert( candidate );
3219
3220 // Should we skip adding the candidate to the list if the parent and candidate subgraphs
3221 // are not the same?
3222 wxASSERT( candidate->m_graph == aParent->m_graph );
3223
3224 search_list.push_back( candidate );
3225 break;
3226 }
3227 }
3228 }
3229 }
3230
3231 for( SCH_HIERLABEL* label : aParent->m_hier_ports )
3232 {
3233 SCH_SHEET_PATH path = aParent->m_sheet;
3234 path.pop_back();
3235
3236 auto it = m_sheet_to_subgraphs_map.find( path );
3237
3238 if( it == m_sheet_to_subgraphs_map.end() )
3239 continue;
3240
3241 for( CONNECTION_SUBGRAPH* candidate : it->second )
3242 {
3243 if( candidate->m_hier_pins.empty()
3244 || visited.contains( candidate )
3245 || candidate->m_driver_connection->Type() != aParent->m_driver_connection->Type() )
3246 {
3247 continue;
3248 }
3249
3250 const KIID& last_parent_uuid = aParent->m_sheet.Last()->m_Uuid;
3251
3252 for( SCH_SHEET_PIN* pin : candidate->m_hier_pins )
3253 {
3254 // If the last sheet UUIDs won't match, no need to check the full path
3255 if( pin->GetParent()->m_Uuid != last_parent_uuid )
3256 continue;
3257
3258 SCH_SHEET_PATH pin_path = path;
3259 pin_path.push_back( pin->GetParent() );
3260
3261 if( pin_path != aParent->m_sheet )
3262 continue;
3263
3264 if( aParent->GetNameForDriver( label ) == candidate->GetNameForDriver( pin ) )
3265 {
3266 wxLogTrace( ConnTrace, wxS( "%lu: found additional parent %lu (%s)" ),
3267 aParent->m_code, candidate->m_code, candidate->m_driver_connection->Name() );
3268
3269 aParent->m_hier_children.insert( candidate );
3270 search_list.push_back( candidate );
3271 break;
3272 }
3273 }
3274 }
3275 }
3276 };
3277
3278 auto propagate_bus_neighbors = [&]( CONNECTION_SUBGRAPH* aParentGraph )
3279 {
3280 // Sort bus neighbors by name to ensure deterministic processing order.
3281 // When multiple bus members (e.g., A0, A1, A2, A3) all connect to the same
3282 // shorted net in a child sheet, the first one processed "wins" and sets
3283 // the net name. Sorting ensures the alphabetically-first name is chosen.
3284 std::vector<std::shared_ptr<SCH_CONNECTION>> sortedMembers;
3285
3286 for( const auto& kv : aParentGraph->m_bus_neighbors )
3287 sortedMembers.push_back( kv.first );
3288
3289 std::sort( sortedMembers.begin(), sortedMembers.end(),
3290 []( const std::shared_ptr<SCH_CONNECTION>& a,
3291 const std::shared_ptr<SCH_CONNECTION>& b )
3292 {
3293 return a->Name() < b->Name();
3294 } );
3295
3296 for( const std::shared_ptr<SCH_CONNECTION>& member_conn : sortedMembers )
3297 {
3298 const auto& kv_it = aParentGraph->m_bus_neighbors.find( member_conn );
3299
3300 if( kv_it == aParentGraph->m_bus_neighbors.end() )
3301 continue;
3302
3303 for( CONNECTION_SUBGRAPH* neighbor : kv_it->second )
3304 {
3305 // May have been absorbed but won't have been deleted
3306 while( neighbor->m_absorbed )
3307 neighbor = neighbor->m_absorbed_by;
3308
3309 SCH_CONNECTION* parent = aParentGraph->m_driver_connection;
3310
3311 // Now member may be out of date, since we just cloned the
3312 // connection from higher up in the hierarchy. We need to
3313 // figure out what the actual new connection is.
3314 SCH_CONNECTION* member = matchBusMember( parent, member_conn.get() );
3315
3316 if( !member )
3317 {
3318 // Try harder: we might match on a secondary driver
3319 for( CONNECTION_SUBGRAPH* sg : kv_it->second )
3320 {
3321 if( sg->m_multiple_drivers )
3322 {
3323 SCH_SHEET_PATH sheet = sg->m_sheet;
3324
3325 for( SCH_ITEM* driver : sg->m_drivers )
3326 {
3327 auto c = getDefaultConnection( driver, sg );
3328 member = matchBusMember( parent, c.get() );
3329
3330 if( member )
3331 break;
3332 }
3333 }
3334
3335 if( member )
3336 break;
3337 }
3338 }
3339
3340 // This is bad, probably an ERC error
3341 if( !member )
3342 {
3343 wxLogTrace( ConnTrace, wxS( "Could not match bus member %s in %s" ),
3344 member_conn->Name(), parent->Name() );
3345 continue;
3346 }
3347
3348 SCH_CONNECTION* neighbor_conn = neighbor->m_driver_connection;
3349
3350 wxCHECK2( neighbor_conn, continue );
3351
3352 wxString neighbor_name = neighbor_conn->Name();
3353
3354 // Matching name: no update needed
3355 if( neighbor_name == member->Name() )
3356 continue;
3357
3358 // Was this neighbor already updated from a different sheet? Don't rename it again,
3359 // unless this same parent bus updated it and the bus member name has since changed
3360 // (which can happen when a bus member is renamed via stale member update, issue #18299).
3361 if( neighbor_conn->Sheet() != neighbor->m_sheet )
3362 {
3363 // If the neighbor's connection sheet doesn't match this parent bus's sheet,
3364 // it was updated by a different bus entirely. Don't override.
3365 if( neighbor_conn->Sheet() != parent->Sheet() )
3366 continue;
3367
3368 // If the neighbor's connection sheet matches this parent bus's sheet but
3369 // the names differ, check if the neighbor's current name still matches
3370 // a member of this bus. If it does, the neighbor was updated by a different
3371 // member of this same bus and we should preserve that (determinism).
3372 // If it doesn't match any member, the bus member was renamed and we should
3373 // update. We compare by name rather than VectorIndex because non-bus
3374 // connections (e.g., "GND" from power pin propagation) have a default
3375 // VectorIndex of 0 that falsely matches the first bus member.
3376 bool alreadyUpdatedByBusMember = false;
3377
3378 for( const auto& m : parent->Members() )
3379 {
3380 if( m->Name() == neighbor_name )
3381 {
3382 alreadyUpdatedByBusMember = true;
3383 break;
3384 }
3385 }
3386
3387 if( alreadyUpdatedByBusMember )
3388 continue;
3389 }
3390
3391 // Safety check against infinite recursion
3392 wxCHECK2_MSG( neighbor_conn->IsNet(), continue,
3393 wxS( "\"" ) + neighbor_name + wxS( "\" is not a net." ) );
3394
3395 wxLogTrace( ConnTrace, wxS( "%lu (%s) connected to bus member %s (local %s)" ),
3396 neighbor->m_code, neighbor_name, member->Name(), member->LocalName() );
3397
3398 // Take whichever name is higher priority
3401 {
3402 member->Clone( *neighbor_conn );
3403 stale_bus_members.insert( member );
3404 }
3405 else
3406 {
3407 neighbor_conn->Clone( *member );
3408
3409 recacheSubgraphName( neighbor, neighbor_name );
3410
3411 // Recurse onto this neighbor in case it needs to re-propagate
3412 neighbor->m_dirty = true;
3413 propagateToNeighbors( neighbor, aForce );
3414
3415 // After hierarchy propagation, the neighbor's connection may have been
3416 // updated to a higher-priority driver (e.g., a power symbol discovered
3417 // through hierarchical sheet pins). If so, update the bus member to match.
3418 // This ensures that net names propagate correctly through bus connections
3419 // that span hierarchical boundaries (issue #18119).
3420 if( neighbor_conn->Name() != member->Name() )
3421 {
3422 member->Clone( *neighbor_conn );
3423 stale_bus_members.insert( member );
3424 }
3425 }
3426 }
3427 }
3428 };
3429
3430 // If we are a bus, we must propagate to local neighbors and then the hierarchy
3431 if( conn->IsBus() )
3432 propagate_bus_neighbors( aSubgraph );
3433
3434 // If we have both ports and pins, skip processing as we'll be visited by a parent or child.
3435 // If we only have one or the other, process (we can either go bottom-up or top-down depending
3436 // on which subgraph comes up first)
3437 if( !aForce && !aSubgraph->m_hier_ports.empty() && !aSubgraph->m_hier_pins.empty() )
3438 {
3439 wxLogTrace( ConnTrace, wxS( "%lu (%s) has both hier ports and pins; deferring processing" ),
3440 aSubgraph->m_code, conn->Name() );
3441 return;
3442 }
3443 else if( aSubgraph->m_hier_ports.empty() && aSubgraph->m_hier_pins.empty() )
3444 {
3445 wxLogTrace( ConnTrace, wxS( "%lu (%s) has no hier pins or ports on sheet %s; marking clean" ),
3446 aSubgraph->m_code, conn->Name(), aSubgraph->m_sheet.PathHumanReadable() );
3447 aSubgraph->m_dirty = false;
3448 return;
3449 }
3450
3451 visited.insert( aSubgraph );
3452
3453 wxLogTrace( ConnTrace, wxS( "Propagating %lu (%s) to subsheets" ),
3454 aSubgraph->m_code, aSubgraph->m_driver_connection->Name() );
3455
3456 visit( aSubgraph );
3457
3458 for( unsigned i = 0; i < search_list.size(); i++ )
3459 {
3460 auto child = search_list[i];
3461
3462 if( visited.insert( child ).second )
3463 visit( child );
3464
3465 child->m_dirty = false;
3466 }
3467
3468 // Now, find the best driver for this chain of subgraphs
3469 CONNECTION_SUBGRAPH* bestDriver = aSubgraph;
3471 bool bestIsStrong = ( highest >= CONNECTION_SUBGRAPH::PRIORITY::HIER_LABEL );
3472 wxString bestName = aSubgraph->m_driver_connection->Name();
3473
3474 // Check if a subsheet has a higher-priority connection to the same net
3476 {
3477 for( CONNECTION_SUBGRAPH* subgraph : visited )
3478 {
3479 if( subgraph == aSubgraph )
3480 continue;
3481
3483 CONNECTION_SUBGRAPH::GetDriverPriority( subgraph->m_driver );
3484
3485 bool candidateStrong = ( priority >= CONNECTION_SUBGRAPH::PRIORITY::HIER_LABEL );
3486 wxString candidateName = subgraph->m_driver_connection->Name();
3487 bool shorterPath = subgraph->m_sheet.size() < bestDriver->m_sheet.size();
3488 bool asGoodPath = subgraph->m_sheet.size() <= bestDriver->m_sheet.size();
3489
3490 // Pick a better driving subgraph if it:
3491 // a) is a strong driver and we're a weak driver
3492 // b) is a higher priority strong driver
3493 // c) matches our priority, is a strong driver, and has a shorter path
3494 // d) matches our strength and is at least as short, and is alphabetically lower
3495
3496 if( ( !bestIsStrong && candidateStrong ) ||
3497 ( priority > highest && candidateStrong ) ||
3498 ( priority == highest && candidateStrong && shorterPath ) ||
3499 ( ( bestIsStrong == candidateStrong ) && asGoodPath && ( priority == highest ) &&
3500 ( candidateName < bestName ) ) )
3501 {
3502 bestDriver = subgraph;
3503 highest = priority;
3504 bestIsStrong = candidateStrong;
3505 bestName = candidateName;
3506 }
3507 }
3508 }
3509
3510 if( bestDriver != aSubgraph )
3511 {
3512 wxLogTrace( ConnTrace, wxS( "%lu (%s) overridden by new driver %lu (%s)" ),
3513 aSubgraph->m_code, aSubgraph->m_driver_connection->Name(), bestDriver->m_code,
3514 bestDriver->m_driver_connection->Name() );
3515 }
3516
3517 conn = bestDriver->m_driver_connection;
3518
3519 for( CONNECTION_SUBGRAPH* subgraph : visited )
3520 {
3521 wxString old_name = subgraph->m_driver_connection->Name();
3522
3523 subgraph->m_driver_connection->Clone( *conn );
3524
3525 if( old_name != conn->Name() )
3526 recacheSubgraphName( subgraph, old_name );
3527
3528 if( conn->IsBus() )
3529 propagate_bus_neighbors( subgraph );
3530 }
3531
3532 // Somewhere along the way, a bus member may have been upgraded to a global or power label.
3533 // Because this can happen anywhere, we need a second pass to update all instances of that bus
3534 // member to have the correct connection info
3535 if( conn->IsBus() && !stale_bus_members.empty() )
3536 {
3537 std::unordered_set<SCH_CONNECTION*> cached_members = stale_bus_members;
3538
3539 for( SCH_CONNECTION* stale_member : cached_members )
3540 {
3541 for( CONNECTION_SUBGRAPH* subgraph : visited )
3542 {
3543 SCH_CONNECTION* member = matchBusMember( subgraph->m_driver_connection, stale_member );
3544
3545 if( !member )
3546 {
3547 wxLogTrace( ConnTrace, wxS( "WARNING: failed to match stale member %s in %s." ),
3548 stale_member->Name(), subgraph->m_driver_connection->Name() );
3549 continue;
3550 }
3551
3552 wxLogTrace( ConnTrace, wxS( "Updating %lu (%s) member %s to %s" ), subgraph->m_code,
3553 subgraph->m_driver_connection->Name(), member->LocalName(), stale_member->Name() );
3554
3555 member->Clone( *stale_member );
3556
3557 propagate_bus_neighbors( subgraph );
3558 }
3559 }
3560 }
3561
3562 aSubgraph->m_dirty = false;
3563}
3564
3565
3566std::shared_ptr<SCH_CONNECTION> CONNECTION_GRAPH::getDefaultConnection( SCH_ITEM* aItem,
3567 CONNECTION_SUBGRAPH* aSubgraph )
3568{
3569 std::shared_ptr<SCH_CONNECTION> c = std::shared_ptr<SCH_CONNECTION>( nullptr );
3570
3571 switch( aItem->Type() )
3572 {
3573 case SCH_PIN_T:
3574 if( static_cast<SCH_PIN*>( aItem )->IsPower() )
3575 c = std::make_shared<SCH_CONNECTION>( aItem, aSubgraph->m_sheet );
3576
3577 break;
3578
3579 case SCH_GLOBAL_LABEL_T:
3580 case SCH_HIER_LABEL_T:
3581 case SCH_LABEL_T:
3582 c = std::make_shared<SCH_CONNECTION>( aItem, aSubgraph->m_sheet );
3583 break;
3584
3585 default:
3586 break;
3587 }
3588
3589 if( c )
3590 {
3591 c->SetGraph( this );
3592 c->ConfigureFromLabel( aSubgraph->GetNameForDriver( aItem ) );
3593 }
3594
3595 return c;
3596}
3597
3598
3600 SCH_CONNECTION* aSearch )
3601{
3602 if( !aBusConnection->IsBus() )
3603 return nullptr;
3604
3605 SCH_CONNECTION* match = nullptr;
3606
3607 if( aBusConnection->Type() == CONNECTION_TYPE::BUS )
3608 {
3609 // Vector bus: compare against index, because we allow the name
3610 // to be different
3611
3612 for( const std::shared_ptr<SCH_CONNECTION>& bus_member : aBusConnection->Members() )
3613 {
3614 if( bus_member->VectorIndex() == aSearch->VectorIndex() )
3615 {
3616 match = bus_member.get();
3617 break;
3618 }
3619 }
3620 }
3621 else
3622 {
3623 // Group bus
3624 for( const std::shared_ptr<SCH_CONNECTION>& c : aBusConnection->Members() )
3625 {
3626 // Vector inside group: compare names, because for bus groups
3627 // we expect the naming to be consistent across all usages
3628 // TODO(JE) explain this in the docs
3629 if( c->Type() == CONNECTION_TYPE::BUS )
3630 {
3631 for( const std::shared_ptr<SCH_CONNECTION>& bus_member : c->Members() )
3632 {
3633 if( bus_member->LocalName() == aSearch->LocalName() )
3634 {
3635 match = bus_member.get();
3636 break;
3637 }
3638 }
3639 }
3640 else if( c->LocalName() == aSearch->LocalName() )
3641 {
3642 match = c.get();
3643 break;
3644 }
3645 }
3646
3647 if( !match && aSearch->VectorIndex() >= 0 )
3648 {
3649 int flatIdx = 0;
3650
3651 for( const std::shared_ptr<SCH_CONNECTION>& c : aBusConnection->Members() )
3652 {
3653 if( c->Type() == CONNECTION_TYPE::BUS )
3654 {
3655 for( const std::shared_ptr<SCH_CONNECTION>& bus_member : c->Members() )
3656 {
3657 if( flatIdx == aSearch->VectorIndex() )
3658 {
3659 match = bus_member.get();
3660 break;
3661 }
3662
3663 flatIdx++;
3664 }
3665 }
3666 else
3667 {
3668 if( flatIdx == aSearch->VectorIndex() )
3669 {
3670 match = c.get();
3671 break;
3672 }
3673
3674 flatIdx++;
3675 }
3676
3677 if( match )
3678 break;
3679 }
3680 }
3681 }
3682
3683 return match;
3684}
3685
3686
3687void CONNECTION_GRAPH::recacheSubgraphName( CONNECTION_SUBGRAPH* aSubgraph, const wxString& aOldName )
3688{
3689 auto it = m_net_name_to_subgraphs_map.find( aOldName );
3690
3691 if( it != m_net_name_to_subgraphs_map.end() )
3692 {
3693 std::vector<CONNECTION_SUBGRAPH*>& vec = it->second;
3694 std::erase( vec, aSubgraph );
3695 }
3696
3697 wxLogTrace( ConnTrace, wxS( "recacheSubgraphName: %s => %s" ), aOldName,
3698 aSubgraph->m_driver_connection->Name() );
3699
3700 m_net_name_to_subgraphs_map[aSubgraph->m_driver_connection->Name()].push_back( aSubgraph );
3701}
3702
3703
3704std::shared_ptr<BUS_ALIAS> CONNECTION_GRAPH::GetBusAlias( const wxString& aName )
3705{
3706 auto it = m_bus_alias_cache.find( aName );
3707
3708 return it != m_bus_alias_cache.end() ? it->second : nullptr;
3709}
3710
3711
3712std::vector<const CONNECTION_SUBGRAPH*> CONNECTION_GRAPH::GetBusesNeedingMigration()
3713{
3714 std::vector<const CONNECTION_SUBGRAPH*> ret;
3715
3716 for( CONNECTION_SUBGRAPH* subgraph : m_subgraphs )
3717 {
3718 // Graph is supposed to be up-to-date before calling this
3719 // Should we continue if the subgraph is not up to date?
3720 wxASSERT( !subgraph->m_dirty );
3721
3722 if( !subgraph->m_driver )
3723 continue;
3724
3725 SCH_SHEET_PATH* sheet = &subgraph->m_sheet;
3726 SCH_CONNECTION* connection = subgraph->m_driver->Connection( sheet );
3727
3728 if( !connection->IsBus() )
3729 continue;
3730
3731 auto labels = subgraph->GetVectorBusLabels();
3732
3733 if( labels.size() > 1 )
3734 {
3735 bool different = false;
3736 wxString first = static_cast<SCH_TEXT*>( labels.at( 0 ) )->GetShownText( sheet, FOR_NETNAME );
3737
3738 for( unsigned i = 1; i < labels.size(); ++i )
3739 {
3740 if( static_cast<SCH_TEXT*>( labels.at( i ) )->GetShownText( sheet, FOR_NETNAME ) != first )
3741 {
3742 different = true;
3743 break;
3744 }
3745 }
3746
3747 if( !different )
3748 continue;
3749
3750 wxLogTrace( ConnTrace, wxS( "SG %ld (%s) has multiple bus labels" ), subgraph->m_code,
3751 connection->Name() );
3752
3753 ret.push_back( subgraph );
3754 }
3755 }
3756
3757 return ret;
3758}
3759
3760
3762{
3763 wxString retval = aSubGraph->GetNetName();
3764 bool found = false;
3765
3766 // This is a hacky way to find the true subgraph net name (why do we not store it?)
3767 // TODO: Remove once the actual netname of the subgraph is stored with the subgraph
3768
3769 for( auto it = m_net_name_to_subgraphs_map.begin();
3770 it != m_net_name_to_subgraphs_map.end() && !found; ++it )
3771 {
3772 for( CONNECTION_SUBGRAPH* graph : it->second )
3773 {
3774 if( graph == aSubGraph )
3775 {
3776 retval = it->first;
3777 found = true;
3778 break;
3779 }
3780 }
3781 }
3782
3783 return retval;
3784}
3785
3786
3788{
3789 auto it = m_net_name_to_subgraphs_map.find( aNetName );
3790
3791 if( it == m_net_name_to_subgraphs_map.end() )
3792 return nullptr;
3793
3794 // Should this return a nullptr if the map entry is empty?
3795 wxASSERT( !it->second.empty() );
3796
3797 return it->second[0];
3798}
3799
3800
3802{
3803 auto it = m_item_to_subgraph_map.find( aItem );
3804
3805 // Callers expect a single subgraph even for items registered on several sheet paths, so
3806 // hand back the most recently registered one
3807 CONNECTION_SUBGRAPH* ret = ( it != m_item_to_subgraph_map.end() && !it->second.empty() )
3808 ? it->second.back()
3809 : nullptr;
3810
3811 while( ret && ret->m_absorbed )
3812 ret = ret->m_absorbed_by;
3813
3814 return ret;
3815}
3816
3817
3818const std::vector<CONNECTION_SUBGRAPH*>&
3819CONNECTION_GRAPH::GetAllSubgraphs( const wxString& aNetName ) const
3820{
3821 static const std::vector<CONNECTION_SUBGRAPH*> subgraphs;
3822
3823 auto it = m_net_name_to_subgraphs_map.find( aNetName );
3824
3825 if( it == m_net_name_to_subgraphs_map.end() )
3826 return subgraphs;
3827
3828 return it->second;
3829}
3830
3831
3832std::vector<wxString> CONNECTION_GRAPH::GetEquivalentBusNames( const wxString& aBusName ) const
3833{
3834 std::vector<wxString> equivalents;
3835
3836 // Split off the sheet-path prefix. A literal '/' is always the hierarchy separator here, since
3837 // slashes in member names are escaped as "{slash}". Re-attached to results so they match the
3838 // net-name map keys.
3839 wxString path;
3840 wxString group = aBusName;
3841 size_t lastSlash = aBusName.find_last_of( '/' );
3842
3843 if( lastSlash != wxString::npos )
3844 {
3845 path = aBusName.Left( lastSlash + 1 );
3846 group = aBusName.Mid( lastSlash + 1 );
3847 }
3848
3849 wxString prefix;
3850 std::vector<wxString> members;
3851
3852 if( !NET_SETTINGS::ParseBusGroup( UnescapeString( group ), &prefix, &members ) )
3853 return equivalents;
3854
3855 // A named-group prefix ("BUS{A B}") renames the members, so it is not aliasable.
3856 if( !prefix.IsEmpty() )
3857 return equivalents;
3858
3859 // ParseBusGroup escapes spaces as "\ " and leaves net-name escapes in place; BUS_ALIAS stores
3860 // members verbatim. Undo both so the two compare in the same form.
3861 for( wxString& member : members )
3862 {
3863 member.Replace( wxT( "\\ " ), wxT( " " ) );
3864 member = UnescapeString( member );
3865 }
3866
3867 // A single-member name may itself be an alias ("{MIXED_BUS}"); expand it and don't re-emit it.
3868 wxString selfAlias;
3869
3870 if( members.size() == 1 )
3871 {
3872 auto aliasIt = m_bus_alias_cache.find( members[0] );
3873
3874 if( aliasIt != m_bus_alias_cache.end() )
3875 {
3876 selfAlias = members[0];
3877 members = aliasIt->second->Members();
3878 }
3879 }
3880
3881 // Re-escape members back to net-name form so the label matches the connection-graph keys.
3882 wxString expandedLabel = path + wxT( "{" );
3883
3884 for( size_t i = 0; i < members.size(); ++i )
3885 {
3886 if( i > 0 )
3887 expandedLabel += wxT( " " );
3888
3889 wxString escaped = EscapeString( members[i], CTX_NETNAME );
3890 escaped.Replace( wxT( " " ), wxT( "\\ " ) );
3891 expandedLabel += escaped;
3892 }
3893
3894 expandedLabel += wxT( "}" );
3895
3896 if( expandedLabel != aBusName )
3897 equivalents.push_back( expandedLabel );
3898
3899 // Match aliases by member set; bus connectivity is order-independent, so compare as multisets.
3900 std::multiset<wxString> memberSet( members.begin(), members.end() );
3901
3902 for( const auto& [aliasName, alias] : m_bus_alias_cache )
3903 {
3904 if( aliasName == selfAlias || alias->Members().size() != members.size() )
3905 continue;
3906
3907 std::multiset<wxString> aliasMembers( alias->Members().begin(), alias->Members().end() );
3908
3909 if( memberSet == aliasMembers )
3910 equivalents.push_back( path + wxT( "{" ) + aliasName + wxT( "}" ) );
3911 }
3912
3913 return equivalents;
3914}
3915
3916
3918{
3919 int error_count = 0;
3920
3921 wxCHECK_MSG( m_schematic, 0, wxS( "Null m_schematic in CONNECTION_GRAPH::RunERC" ) );
3922
3923 ERC_SETTINGS& settings = m_schematic->ErcSettings();
3924
3925 // We don't want to run many ERC checks more than once on a given screen even though it may
3926 // represent multiple sheets with multiple subgraphs. We can tell these apart by drivers.
3927 std::set<SCH_ITEM*> seenDriverInstances;
3928
3929 for( CONNECTION_SUBGRAPH* subgraph : m_subgraphs )
3930 {
3931 // There shouldn't be any null sub-graph pointers.
3932 wxCHECK2( subgraph, continue );
3933
3934 // Graph is supposed to be up-to-date before calling RunERC()
3935 // Should we continue if the subgraph is not up to date?
3936 wxASSERT( !subgraph->m_dirty );
3937
3938 if( subgraph->m_absorbed )
3939 continue;
3940
3941 if( seenDriverInstances.count( subgraph->m_driver ) )
3942 continue;
3943
3944 if( subgraph->m_driver )
3945 seenDriverInstances.insert( subgraph->m_driver );
3946
3957 if( settings.IsTestEnabled( ERCE_DRIVER_CONFLICT ) )
3958 {
3959 if( !ercCheckMultipleDrivers( subgraph ) )
3960 error_count++;
3961 }
3962
3963 subgraph->ResolveDrivers( false );
3964
3965 if( settings.IsTestEnabled( ERCE_BUS_TO_NET_CONFLICT ) )
3966 {
3967 if( !ercCheckBusToNetConflicts( subgraph ) )
3968 error_count++;
3969 }
3970
3971 if( settings.IsTestEnabled( ERCE_BUS_ENTRY_CONFLICT ) )
3972 {
3973 if( !ercCheckBusToBusEntryConflicts( subgraph ) )
3974 error_count++;
3975 }
3976
3977 if( settings.IsTestEnabled( ERCE_BUS_TO_BUS_CONFLICT ) )
3978 {
3979 if( !ercCheckBusToBusConflicts( subgraph ) )
3980 error_count++;
3981 }
3982
3983 if( settings.IsTestEnabled( ERCE_WIRE_DANGLING ) )
3984 {
3985 if( !ercCheckFloatingWires( subgraph ) )
3986 error_count++;
3987 }
3988
3990 {
3991 if( !ercCheckDanglingWireEndpoints( subgraph ) )
3992 error_count++;
3993 }
3994
3997 || settings.IsTestEnabled( ERCE_PIN_NOT_CONNECTED ) )
3998 {
3999 if( !ercCheckNoConnects( subgraph ) )
4000 error_count++;
4001 }
4002
4004 || settings.IsTestEnabled( ERCE_LABEL_SINGLE_PIN ) )
4005 {
4006 if( !ercCheckLabels( subgraph ) )
4007 error_count++;
4008 }
4009 }
4010
4011 if( settings.IsTestEnabled( ERCE_LABEL_NOT_CONNECTED ) )
4012 {
4013 error_count += ercCheckDirectiveLabels();
4014 }
4015
4016 // Hierarchical sheet checking is done at the schematic level
4018 || settings.IsTestEnabled( ERCE_PIN_NOT_CONNECTED ) )
4019 {
4020 error_count += ercCheckHierSheets();
4021 }
4022
4023 if( settings.IsTestEnabled( ERCE_SINGLE_GLOBAL_LABEL ) )
4024 {
4025 error_count += ercCheckSingleGlobalLabel();
4026 }
4027
4028 return error_count;
4029}
4030
4031
4033{
4034 wxCHECK( aSubgraph, false );
4035
4036 if( aSubgraph->m_multiple_drivers )
4037 {
4038 for( SCH_ITEM* driver : aSubgraph->m_drivers )
4039 {
4040 if( driver == aSubgraph->m_driver )
4041 continue;
4042
4043 if( driver->Type() == SCH_GLOBAL_LABEL_T
4044 || driver->Type() == SCH_HIER_LABEL_T
4045 || driver->Type() == SCH_LABEL_T
4046 || ( driver->Type() == SCH_PIN_T && static_cast<SCH_PIN*>( driver )->IsPower() ) )
4047 {
4048 const wxString& primaryName = aSubgraph->GetNameForDriver( aSubgraph->m_driver );
4049 const wxString& secondaryName = aSubgraph->GetNameForDriver( driver );
4050
4051 if( primaryName == secondaryName )
4052 continue;
4053
4054 wxString msg = wxString::Format( _( "Both %s and %s are attached to the same "
4055 "items; %s will be used in the netlist" ),
4056 primaryName, secondaryName, primaryName );
4057
4058 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_DRIVER_CONFLICT );
4059 ercItem->SetItems( aSubgraph->m_driver, driver );
4060 ercItem->SetSheetSpecificPath( aSubgraph->GetSheet() );
4061 ercItem->SetItemsSheetPaths( aSubgraph->GetSheet(), aSubgraph->m_sheet );
4062 ercItem->SetErrorMessage( msg );
4063
4064 SCH_MARKER* marker = new SCH_MARKER( std::move( ercItem ), driver->GetPosition() );
4065 aSubgraph->m_sheet.LastScreen()->Append( marker );
4066
4067 return false;
4068 }
4069 }
4070 }
4071
4072 return true;
4073}
4074
4075
4077{
4078 const SCH_SHEET_PATH& sheet = aSubgraph->m_sheet;
4079 SCH_SCREEN* screen = sheet.LastScreen();
4080
4081 SCH_ITEM* net_item = nullptr;
4082 SCH_ITEM* bus_item = nullptr;
4083 SCH_CONNECTION conn( this );
4084
4085 for( SCH_ITEM* item : aSubgraph->m_items )
4086 {
4087 switch( item->Type() )
4088 {
4089 case SCH_LINE_T:
4090 {
4091 if( item->GetLayer() == LAYER_BUS )
4092 bus_item = ( !bus_item ) ? item : bus_item;
4093 else
4094 net_item = ( !net_item ) ? item : net_item;
4095
4096 break;
4097 }
4098
4099 case SCH_LABEL_T:
4100 case SCH_GLOBAL_LABEL_T:
4101 case SCH_SHEET_PIN_T:
4102 case SCH_HIER_LABEL_T:
4103 {
4104 SCH_TEXT* text = static_cast<SCH_TEXT*>( item );
4105 conn.ConfigureFromLabel( EscapeString( text->GetShownText( &sheet, FOR_NETNAME ), CTX_NETNAME ) );
4106
4107 if( conn.IsBus() )
4108 bus_item = ( !bus_item ) ? item : bus_item;
4109 else
4110 net_item = ( !net_item ) ? item : net_item;
4111
4112 break;
4113 }
4114
4115 default:
4116 break;
4117 }
4118 }
4119
4120 if( net_item && bus_item )
4121 {
4122 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_BUS_TO_NET_CONFLICT );
4123 ercItem->SetSheetSpecificPath( sheet );
4124 ercItem->SetItems( net_item, bus_item );
4125
4126 SCH_MARKER* marker = new SCH_MARKER( std::move( ercItem ), net_item->GetPosition() );
4127 screen->Append( marker );
4128
4129 return false;
4130 }
4131
4132 return true;
4133}
4134
4135
4137{
4138 const SCH_SHEET_PATH& sheet = aSubgraph->m_sheet;
4139 SCH_SCREEN* screen = sheet.LastScreen();
4140
4141 SCH_ITEM* label = nullptr;
4142 SCH_ITEM* port = nullptr;
4143
4144 for( SCH_ITEM* item : aSubgraph->m_items )
4145 {
4146 switch( item->Type() )
4147 {
4148 case SCH_TEXT_T:
4149 case SCH_GLOBAL_LABEL_T:
4150 if( !label && item->Connection( &sheet )->IsBus() )
4151 label = item;
4152 break;
4153
4154 case SCH_SHEET_PIN_T:
4155 case SCH_HIER_LABEL_T:
4156 if( !port && item->Connection( &sheet )->IsBus() )
4157 port = item;
4158 break;
4159
4160 default:
4161 break;
4162 }
4163 }
4164
4165 if( label && port )
4166 {
4167 bool match = false;
4168
4169 for( const auto& member : label->Connection( &sheet )->Members() )
4170 {
4171 for( const auto& test : port->Connection( &sheet )->Members() )
4172 {
4173 if( test != member && member->Name() == test->Name() )
4174 {
4175 match = true;
4176 break;
4177 }
4178 }
4179
4180 if( match )
4181 break;
4182 }
4183
4184 if( !match )
4185 {
4186 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_BUS_TO_BUS_CONFLICT );
4187 ercItem->SetSheetSpecificPath( sheet );
4188 ercItem->SetItems( label, port );
4189
4190 SCH_MARKER* marker = new SCH_MARKER( std::move( ercItem ), label->GetPosition() );
4191 screen->Append( marker );
4192
4193 return false;
4194 }
4195 }
4196
4197 return true;
4198}
4199
4200
4202{
4203 bool conflict = false;
4204 const SCH_SHEET_PATH& sheet = aSubgraph->m_sheet;
4205 SCH_SCREEN* screen = sheet.LastScreen();
4206
4207 SCH_BUS_WIRE_ENTRY* bus_entry = nullptr;
4208 SCH_ITEM* bus_wire = nullptr;
4209 wxString bus_name;
4210
4211 if( !aSubgraph->m_driver_connection )
4212 {
4213 // Incomplete bus entry. Let the unconnected tests handle it.
4214 return true;
4215 }
4216
4217 for( SCH_ITEM* item : aSubgraph->m_items )
4218 {
4219 switch( item->Type() )
4220 {
4222 if( !bus_entry )
4223 bus_entry = static_cast<SCH_BUS_WIRE_ENTRY*>( item );
4224
4225 break;
4226
4227 default:
4228 break;
4229 }
4230 }
4231
4232 if( bus_entry && bus_entry->m_connected_bus_item )
4233 {
4234 bus_wire = bus_entry->m_connected_bus_item;
4235
4236 // Should we continue if the type is not a line?
4237 wxASSERT( bus_wire->Type() == SCH_LINE_T );
4238
4239 // In some cases, the connection list (SCH_CONNECTION*) can be null.
4240 // Skip null connections.
4241 if( bus_entry->Connection( &sheet )
4242 && bus_wire->Type() == SCH_LINE_T
4243 && bus_wire->Connection( &sheet ) )
4244 {
4245 conflict = true; // Assume a conflict; we'll reset if we find it's OK
4246
4247 bus_name = bus_wire->Connection( &sheet )->Name();
4248
4249 std::set<wxString> test_names;
4250 test_names.insert( bus_entry->Connection( &sheet )->FullLocalName() );
4251
4252 wxString baseName = sheet.PathHumanReadable();
4253
4254 for( SCH_ITEM* driver : aSubgraph->m_drivers )
4255 test_names.insert( baseName + aSubgraph->GetNameForDriver( driver ) );
4256
4257 for( const auto& member : bus_wire->Connection( &sheet )->Members() )
4258 {
4259 if( member->Type() == CONNECTION_TYPE::BUS )
4260 {
4261 for( const auto& sub_member : member->Members() )
4262 {
4263 if( test_names.count( sub_member->FullLocalName() ) )
4264 conflict = false;
4265 }
4266 }
4267 else if( test_names.count( member->FullLocalName() ) )
4268 {
4269 conflict = false;
4270 }
4271 }
4272 }
4273 }
4274
4275 // Don't report warnings if this bus member has been overridden by a higher priority power pin
4276 // or global label
4277 if( conflict && CONNECTION_SUBGRAPH::GetDriverPriority( aSubgraph->m_driver )
4279 {
4280 conflict = false;
4281 }
4282
4283 if( conflict )
4284 {
4285 wxString netName = aSubgraph->m_driver_connection->Name();
4286 wxString msg = wxString::Format( _( "Net %s is graphically connected to bus %s but is not a"
4287 " member of that bus" ),
4288 UnescapeString( netName ),
4289 UnescapeString( bus_name ) );
4290 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_BUS_ENTRY_CONFLICT );
4291 ercItem->SetSheetSpecificPath( sheet );
4292 ercItem->SetItems( bus_entry, bus_wire );
4293 ercItem->SetErrorMessage( msg );
4294
4295 SCH_MARKER* marker = new SCH_MARKER( std::move( ercItem ), bus_entry->GetPosition() );
4296 screen->Append( marker );
4297
4298 return false;
4299 }
4300
4301 return true;
4302}
4303
4304
4306{
4307 ERC_SETTINGS& settings = m_schematic->ErcSettings();
4308 const SCH_SHEET_PATH& sheet = aSubgraph->m_sheet;
4309 SCH_SCREEN* screen = sheet.LastScreen();
4310 bool ok = true;
4311 SCH_PIN* pin = nullptr;
4312
4313 std::set<SCH_PIN*> unique_pins;
4314 std::set<SCH_LABEL_BASE*> unique_labels;
4315
4316 wxString netName = GetResolvedSubgraphName( aSubgraph );
4317
4318 auto process_subgraph = [&]( const CONNECTION_SUBGRAPH* aProcessGraph )
4319 {
4320 // Any subgraph that contains a no-connect should not
4321 // more than one pin (which would indicate it is connected
4322 for( SCH_ITEM* item : aProcessGraph->m_items )
4323 {
4324 switch( item->Type() )
4325 {
4326 case SCH_PIN_T:
4327 {
4328 SCH_PIN* test_pin = static_cast<SCH_PIN*>( item );
4329
4330 // Only link NC to pin on the current subgraph being checked
4331 if( aProcessGraph == aSubgraph )
4332 pin = test_pin;
4333
4334 if( std::none_of( unique_pins.begin(), unique_pins.end(),
4335 [test_pin]( SCH_PIN* aPin )
4336 {
4337 return test_pin->IsStacked( aPin );
4338 }
4339 ))
4340 {
4341 unique_pins.insert( test_pin );
4342 }
4343
4344 break;
4345 }
4346
4347 case SCH_LABEL_T:
4348 case SCH_GLOBAL_LABEL_T:
4349 case SCH_HIER_LABEL_T:
4350 unique_labels.insert( static_cast<SCH_LABEL_BASE*>( item ) );
4352 default:
4353 break;
4354 }
4355 }
4356 };
4357
4358 auto it = m_net_name_to_subgraphs_map.find( netName );
4359
4360 if( it != m_net_name_to_subgraphs_map.end() )
4361 {
4362 for( const CONNECTION_SUBGRAPH* subgraph : it->second )
4363 {
4364 process_subgraph( subgraph );
4365 }
4366 }
4367 else
4368 {
4369 process_subgraph( aSubgraph );
4370 }
4371
4372 if( aSubgraph->m_no_connect != nullptr )
4373 {
4374 // If this subgraph reaches the rest of the schematic only through a hier
4375 // sheet pin (parent side) or hier label (inner side), and contains no real
4376 // connection points of its own, suppress the warning. The user's intent
4377 // is to mark the hier link as unconnected -- whether the no-connect sits
4378 // on the pin or at the end of a short wire stub.
4379 if( !aSubgraph->m_hier_pins.empty() || !aSubgraph->m_hier_ports.empty() )
4380 {
4381 bool clean = true;
4382
4383 for( SCH_ITEM* item : aSubgraph->m_items )
4384 {
4385 switch( item->Type() )
4386 {
4387 case SCH_PIN_T:
4388 case SCH_LABEL_T:
4389 case SCH_GLOBAL_LABEL_T:
4390 case SCH_DIRECTIVE_LABEL_T: clean = false; break;
4391 default: break;
4392 }
4393
4394 if( !clean )
4395 break;
4396 }
4397
4398 if( clean )
4399 return true;
4400 }
4401
4402 // Special case: If the subgraph being checked consists of only a hier port/pin and
4403 // a no-connect, we don't issue a "no-connect connected" warning just because
4404 // connections exist on the sheet on the other side of the link.
4405 VECTOR2I noConnectPos = aSubgraph->m_no_connect->GetPosition();
4406
4407 for( SCH_SHEET_PIN* hierPin : aSubgraph->m_hier_pins )
4408 {
4409 if( hierPin->GetPosition() == noConnectPos )
4410 return true;
4411 }
4412
4413 for( SCH_HIERLABEL* hierLabel : aSubgraph->m_hier_ports )
4414 {
4415 if( hierLabel->GetPosition() == noConnectPos )
4416 return true;
4417 }
4418
4419 for( SCH_ITEM* item : screen->Items().Overlapping( SCH_SYMBOL_T, noConnectPos ) )
4420 {
4421 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
4422
4423 const SCH_PIN* test_pin = symbol->GetPin( noConnectPos );
4424
4425 if( test_pin && test_pin->GetType() == ELECTRICAL_PINTYPE::PT_NC )
4426 return true;
4427 }
4428
4429 if( unique_pins.size() > 1 && settings.IsTestEnabled( ERCE_NOCONNECT_CONNECTED ) )
4430 {
4431 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_NOCONNECT_CONNECTED );
4432 ercItem->SetSheetSpecificPath( sheet );
4433 ercItem->SetItemsSheetPaths( sheet );
4434
4435 VECTOR2I pos;
4436
4437 if( pin )
4438 {
4439 ercItem->SetItems( pin, aSubgraph->m_no_connect );
4440 pos = pin->GetPosition();
4441 }
4442 else
4443 {
4444 ercItem->SetItems( aSubgraph->m_no_connect );
4445 pos = aSubgraph->m_no_connect->GetPosition();
4446 }
4447
4448 SCH_MARKER* marker = new SCH_MARKER( std::move( ercItem ), pos );
4449 screen->Append( marker );
4450
4451 ok = false;
4452 }
4453
4454 if( unique_pins.empty() && unique_labels.empty() &&
4456 {
4457 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_NOCONNECT_NOT_CONNECTED );
4458 ercItem->SetItems( aSubgraph->m_no_connect );
4459 ercItem->SetSheetSpecificPath( sheet );
4460 ercItem->SetItemsSheetPaths( sheet );
4461
4462 SCH_MARKER* marker = new SCH_MARKER( std::move( ercItem ), aSubgraph->m_no_connect->GetPosition() );
4463 screen->Append( marker );
4464
4465 ok = false;
4466 }
4467 }
4468 else
4469 {
4470 bool has_other_connections = false;
4471 std::vector<SCH_PIN*> pins;
4472
4473 // Any subgraph that lacks a no-connect and contains a pin should also
4474 // contain at least one other potential driver
4475
4476 for( SCH_ITEM* item : aSubgraph->m_items )
4477 {
4478 switch( item->Type() )
4479 {
4480 case SCH_PIN_T:
4481 {
4482 SCH_PIN* test_pin = static_cast<SCH_PIN*>( item );
4483
4484 // Stacked pins do not count as other connections but non-stacked pins do
4485 if( !has_other_connections && !pins.empty()
4486 && !test_pin->GetParentSymbol()->IsPower() )
4487 {
4488 for( SCH_PIN* other_pin : pins )
4489 {
4490 if( !test_pin->IsStacked( other_pin ) )
4491 {
4492 has_other_connections = true;
4493 break;
4494 }
4495 }
4496 }
4497
4498 pins.emplace_back( static_cast<SCH_PIN*>( item ) );
4499
4500 break;
4501 }
4502
4503 default:
4504 if( aSubgraph->GetDriverPriority( item ) != CONNECTION_SUBGRAPH::PRIORITY::NONE )
4505 has_other_connections = true;
4506
4507 break;
4508 }
4509 }
4510
4511 // For many checks, we can just use the first pin
4512 pin = pins.empty() ? nullptr : pins[0];
4513
4514 // But if there is a power pin, it might be connected elsewhere
4515 for( SCH_PIN* test_pin : pins )
4516 {
4517 // Prefer the pin is part of a real component rather than some stray power symbol
4518 // Or else we may fail walking connected components to a power symbol pin since we
4519 // reject starting at a power symbol
4520 if( test_pin->GetType() == ELECTRICAL_PINTYPE::PT_POWER_IN && !test_pin->IsPower() )
4521 {
4522 pin = test_pin;
4523 break;
4524 }
4525 }
4526
4527 // Check if power input pins connect to anything else via net name,
4528 // but not for power symbols (with visible or legacy invisible pins).
4529 // We want to throw unconnected errors for power symbols even if they are connected to other
4530 // net items by name, because usually failing to connect them graphically is a mistake
4531 SYMBOL* pinLibParent = ( pin && pin->GetLibPin() )
4532 ? pin->GetLibPin()->GetParentSymbol() : nullptr;
4533
4534 if( pin && !has_other_connections
4535 && !pin->IsPower()
4536 && ( !pinLibParent || !pinLibParent->IsPower() ) )
4537 {
4538 wxString name = pin->Connection( &sheet )->Name();
4539 wxString local_name = pin->Connection( &sheet )->Name( true );
4540
4541 if( m_global_label_cache.count( name )
4542 || m_local_label_cache.count( std::make_pair( sheet, local_name ) ) )
4543 {
4544 has_other_connections = true;
4545 }
4546 }
4547
4548 // Only one pin, and it's not a no-connect pin
4549 if( pin && !has_other_connections
4550 && pin->GetType() != ELECTRICAL_PINTYPE::PT_NC
4551 && pin->GetType() != ELECTRICAL_PINTYPE::PT_NIC
4552 && settings.IsTestEnabled( ERCE_PIN_NOT_CONNECTED ) )
4553 {
4554 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_PIN_NOT_CONNECTED );
4555 ercItem->SetSheetSpecificPath( sheet );
4556 ercItem->SetItemsSheetPaths( sheet );
4557 ercItem->SetItems( pin );
4558
4559 SCH_MARKER* marker = new SCH_MARKER( std::move( ercItem ), pin->GetPosition() );
4560 screen->Append( marker );
4561
4562 ok = false;
4563 }
4564
4565 // If there are multiple pins in this SG, they might be indirectly connected (by netname)
4566 // rather than directly connected (by wires). We want to flag dangling pins even if they
4567 // join nets with another pin, as it's often a mistake
4568 if( pins.size() > 1 )
4569 {
4570 for( SCH_PIN* testPin : pins )
4571 {
4572 // We only apply this test to power symbols, because other symbols have
4573 // pins that are meant to be dangling, but the power symbols have pins
4574 // that are *not* meant to be dangling.
4575 SYMBOL* testLibParent = testPin->GetLibPin()
4576 ? testPin->GetLibPin()->GetParentSymbol()
4577 : nullptr;
4578
4579 if( testLibParent && testLibParent->IsPower()
4580 && testPin->ConnectedItems( sheet ).empty()
4581 && settings.IsTestEnabled( ERCE_PIN_NOT_CONNECTED ) )
4582 {
4583 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_PIN_NOT_CONNECTED );
4584 ercItem->SetSheetSpecificPath( sheet );
4585 ercItem->SetItemsSheetPaths( sheet );
4586 ercItem->SetItems( testPin );
4587
4588 SCH_MARKER* marker = new SCH_MARKER( std::move( ercItem ), testPin->GetPosition() );
4589 screen->Append( marker );
4590
4591 ok = false;
4592 }
4593 }
4594 }
4595 }
4596
4597 return ok;
4598}
4599
4600
4602{
4603 int err_count = 0;
4604 const SCH_SHEET_PATH& sheet = aSubgraph->m_sheet;
4605
4606 for( SCH_ITEM* item : aSubgraph->m_items )
4607 {
4608 if( item->GetLayer() != LAYER_WIRE )
4609 continue;
4610
4611 if( item->Type() == SCH_LINE_T )
4612 {
4613 SCH_LINE* line = static_cast<SCH_LINE*>( item );
4614
4615 if( line->IsGraphicLine() )
4616 continue;
4617
4618 auto report_error = [&]( VECTOR2I& location )
4619 {
4620 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_UNCONNECTED_WIRE_ENDPOINT );
4621
4622 ercItem->SetItems( line );
4623 ercItem->SetSheetSpecificPath( sheet );
4624 ercItem->SetErrorMessage( _( "Unconnected wire endpoint" ) );
4625
4626 SCH_MARKER* marker = new SCH_MARKER( std::move( ercItem ), location );
4627 sheet.LastScreen()->Append( marker );
4628
4629 err_count++;
4630 };
4631
4632 if( line->IsStartDangling() )
4633 report_error( line->GetConnectionPoints()[0] );
4634
4635 if( line->IsEndDangling() )
4636 report_error( line->GetConnectionPoints()[1] );
4637 }
4638 else if( item->Type() == SCH_BUS_WIRE_ENTRY_T )
4639 {
4640 SCH_BUS_WIRE_ENTRY* entry = static_cast<SCH_BUS_WIRE_ENTRY*>( item );
4641
4642 auto report_error = [&]( VECTOR2I& location )
4643 {
4644 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_UNCONNECTED_WIRE_ENDPOINT );
4645
4646 ercItem->SetItems( entry );
4647 ercItem->SetSheetSpecificPath( sheet );
4648 ercItem->SetErrorMessage( _( "Unconnected wire to bus entry" ) );
4649
4650 SCH_MARKER* marker = new SCH_MARKER( std::move( ercItem ), location );
4651 sheet.LastScreen()->Append( marker );
4652
4653 err_count++;
4654 };
4655
4656 if( entry->IsStartDangling() )
4657 report_error( entry->GetConnectionPoints()[0] );
4658
4659 if( entry->IsEndDangling() )
4660 report_error( entry->GetConnectionPoints()[1] );
4661 }
4662
4663 }
4664
4665 return err_count > 0;
4666}
4667
4668
4670{
4671 if( aSubgraph->m_driver )
4672 return true;
4673
4674 const SCH_SHEET_PATH& sheet = aSubgraph->m_sheet;
4675 std::vector<SCH_ITEM*> wires;
4676
4677 // We've gotten this far, so we know we have no valid driver. All we need to do is check
4678 // for a wire that we can place the error on.
4679 for( SCH_ITEM* item : aSubgraph->m_items )
4680 {
4681 if( item->Type() == SCH_LINE_T && item->GetLayer() == LAYER_WIRE )
4682 wires.emplace_back( item );
4683 else if( item->Type() == SCH_BUS_WIRE_ENTRY_T )
4684 wires.emplace_back( item );
4685 }
4686
4687 if( !wires.empty() )
4688 {
4689 SCH_SCREEN* screen = aSubgraph->m_sheet.LastScreen();
4690
4691 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_WIRE_DANGLING );
4692 ercItem->SetSheetSpecificPath( sheet );
4693 ercItem->SetItems( wires[0],
4694 wires.size() > 1 ? wires[1] : nullptr,
4695 wires.size() > 2 ? wires[2] : nullptr,
4696 wires.size() > 3 ? wires[3] : nullptr );
4697
4698 SCH_MARKER* marker = new SCH_MARKER( std::move( ercItem ), wires[0]->GetPosition() );
4699 screen->Append( marker );
4700
4701 return false;
4702 }
4703
4704 return true;
4705}
4706
4707
4708void CONNECTION_GRAPH::collectBusMemberSiblings( const CONNECTION_SUBGRAPH* aBusParent, const wxString& aMemberName,
4709 std::unordered_set<const CONNECTION_SUBGRAPH*>& aOut ) const
4710{
4711 while( aBusParent && aBusParent->m_absorbed )
4712 aBusParent = aBusParent->m_absorbed_by;
4713
4714 if( !aBusParent || !aBusParent->m_driver_connection )
4715 return;
4716
4717 auto busBucket = m_net_name_to_subgraphs_map.find( aBusParent->m_driver_connection->Name() );
4718
4719 if( busBucket == m_net_name_to_subgraphs_map.end() )
4720 return;
4721
4722 for( const CONNECTION_SUBGRAPH* siblingBus : busBucket->second )
4723 {
4724 for( const auto& [sibMemberConn, sibMembers] : siblingBus->m_bus_neighbors )
4725 {
4726 if( sibMemberConn->Name() != aMemberName )
4727 continue;
4728
4729 for( const CONNECTION_SUBGRAPH* sibling : sibMembers )
4730 aOut.insert( sibling );
4731 }
4732 }
4733}
4734
4735
4737{
4738 // Label connection rules:
4739 // Any label without a no-connect needs to have at least 2 pins, otherwise it is invalid
4740 // Local labels are flagged if they don't connect to any pins and don't have a no-connect
4741 // Global labels are flagged if they appear only once, don't connect to any local labels,
4742 // and don't have a no-connect marker
4743
4744 if( !aSubgraph->m_driver_connection )
4745 return true;
4746
4747 // Buses are excluded from this test: many users create buses with only a single instance
4748 // and it's not really a problem as long as the nets in the bus pass ERC
4749 if( aSubgraph->m_driver_connection->IsBus() )
4750 return true;
4751
4752 const SCH_SHEET_PATH& sheet = aSubgraph->m_sheet;
4753 ERC_SETTINGS& settings = m_schematic->ErcSettings();
4754 bool ok = true;
4755 size_t pinCount = 0;
4756 bool has_nc = !!aSubgraph->m_no_connect;
4757
4758 std::map<KICAD_T, std::vector<SCH_TEXT*>> label_map;
4759
4760
4761 auto hasPins =
4762 []( const CONNECTION_SUBGRAPH* aLocSubgraph ) -> size_t
4763 {
4764 return std::count_if( aLocSubgraph->m_items.begin(), aLocSubgraph->m_items.end(),
4765 []( const SCH_ITEM* item )
4766 {
4767 return item->Type() == SCH_PIN_T;
4768 } );
4769 };
4770
4771 auto reportError =
4772 [&]( SCH_TEXT* aText, int errCode )
4773 {
4774 if( settings.IsTestEnabled( errCode ) )
4775 {
4776 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( errCode );
4777 ercItem->SetSheetSpecificPath( sheet );
4778 ercItem->SetItems( aText );
4779
4780 SCH_MARKER* marker = new SCH_MARKER( std::move( ercItem ), aText->GetPosition() );
4781 aSubgraph->m_sheet.LastScreen()->Append( marker );
4782 }
4783 };
4784
4785 pinCount = hasPins( aSubgraph );
4786
4787 for( SCH_ITEM* item : aSubgraph->m_items )
4788 {
4789 switch( item->Type() )
4790 {
4791 case SCH_LABEL_T:
4792 case SCH_GLOBAL_LABEL_T:
4793 case SCH_HIER_LABEL_T:
4794 {
4795 SCH_TEXT* text = static_cast<SCH_TEXT*>( item );
4796
4797 label_map[item->Type()].push_back( text );
4798
4799 // Below, we'll create an ERC if the whole subgraph is unconnected. But, additionally,
4800 // we want to error if an individual label in the subgraph is floating, even if it's
4801 // connected to other valid things by way of another label on the same sheet.
4802 if( text->IsDangling() )
4803 {
4804 reportError( text, ERCE_LABEL_NOT_CONNECTED );
4805 return false;
4806 }
4807
4808 break;
4809 }
4810
4811 default:
4812 break;
4813 }
4814 }
4815
4816 if( label_map.empty() )
4817 return true;
4818
4819 // Walk m_bus_parents once. Bus parents may carry a no-connect that suppresses
4820 // an unconnected-label error, and they're how we reach bus members on other
4821 // sheets that share this net.
4822 std::unordered_set<const CONNECTION_SUBGRAPH*> busMemberSiblings;
4823
4824 for( auto& [memberConn, busParents] : aSubgraph->m_bus_parents )
4825 {
4826 wxString memberName = memberConn->Name();
4827
4828 for( CONNECTION_SUBGRAPH* busParent : busParents )
4829 {
4830 if( busParent->m_no_connect )
4831 has_nc = true;
4832
4833 for( CONNECTION_SUBGRAPH* hp = busParent->m_hier_parent; hp; hp = hp->m_hier_parent )
4834 {
4835 if( hp->m_no_connect )
4836 has_nc = true;
4837 }
4838
4839 collectBusMemberSiblings( busParent, memberName, busMemberSiblings );
4840 }
4841 }
4842
4843 wxString netName = GetResolvedSubgraphName( aSubgraph );
4844
4845 wxCHECK_MSG( m_schematic, true, wxS( "Null m_schematic in CONNECTION_GRAPH::ercCheckLabels" ) );
4846
4847 // Labels that have multiple pins connected are not dangling (may be used for naming segments)
4848 // so leave them without errors here
4849 if( pinCount > 1 )
4850 return true;
4851
4852 for( auto& [type, label_vec] : label_map )
4853 {
4854 for( SCH_TEXT* text : label_vec )
4855 {
4856 size_t allPins = pinCount;
4857 size_t localPins = pinCount;
4858 bool hasLocalHierarchy = false;
4859
4860 if( !aSubgraph->m_hier_pins.empty() || !aSubgraph->m_hier_ports.empty() )
4861 {
4862 // A label bridging multiple hierarchical connections
4863 // (e.g., connecting sheet pins from different sub-sheet
4864 // instances) is serving a valid routing purpose even
4865 // without local component pins.
4866 std::set<wxString> uniquePortNames;
4867 for( SCH_HIERLABEL* port : aSubgraph->m_hier_ports )
4868 uniquePortNames.insert( aSubgraph->GetNameForDriver( port ) );
4869
4870 if( aSubgraph->m_hier_pins.size() + uniquePortNames.size() > 1 )
4871 {
4872 hasLocalHierarchy = true;
4873 }
4874
4875 // Also check bus parents for bus-based hierarchical
4876 // routing on the same sheet.
4877 for( auto& [connection, busParents] : aSubgraph->m_bus_parents )
4878 {
4879 for( const CONNECTION_SUBGRAPH* busParent : busParents )
4880 {
4881 if( busParent->m_sheet == sheet
4882 && ( !busParent->m_hier_pins.empty()
4883 || !busParent->m_hier_ports.empty() ) )
4884 {
4885 hasLocalHierarchy = true;
4886 break;
4887 }
4888 }
4889
4890 if( hasLocalHierarchy )
4891 break;
4892 }
4893 }
4894
4895 std::unordered_set<const CONNECTION_SUBGRAPH*> creditedNeighbors;
4896 creditedNeighbors.insert( aSubgraph );
4897
4898 auto creditNeighbor = [&]( const CONNECTION_SUBGRAPH* neighbor )
4899 {
4900 if( !creditedNeighbors.insert( neighbor ).second )
4901 return;
4902
4903 if( neighbor->m_no_connect )
4904 has_nc = true;
4905
4906 size_t neighborPins = hasPins( neighbor );
4907 allPins += neighborPins;
4908
4909 if( neighbor->m_sheet == sheet )
4910 {
4911 localPins += neighborPins;
4912
4913 if( !neighbor->m_hier_pins.empty() || !neighbor->m_hier_ports.empty() )
4914 {
4915 hasLocalHierarchy = true;
4916 }
4917 }
4918 };
4919
4920 auto it = m_net_name_to_subgraphs_map.find( netName );
4921
4922 if( it != m_net_name_to_subgraphs_map.end() )
4923 {
4924 for( const CONNECTION_SUBGRAPH* neighbor : it->second )
4925 creditNeighbor( neighbor );
4926 }
4927
4928 for( const CONNECTION_SUBGRAPH* sibling : busMemberSiblings )
4929 creditNeighbor( sibling );
4930
4931 if( allPins == 1 && !has_nc )
4932 {
4933 reportError( text, ERCE_LABEL_SINGLE_PIN );
4934 ok = false;
4935 }
4936
4937 // A local label that connects to other subgraphs with
4938 // hierarchical connections on the same sheet (through bus
4939 // parents or net-name neighbors) is routing aggregated nets and should
4940 // not be flagged even without local component pins.
4941 if( allPins == 0
4942 || ( type == SCH_LABEL_T && localPins == 0 && allPins > 1
4943 && !has_nc && !hasLocalHierarchy ) )
4944 {
4945 reportError( text, ERCE_LABEL_NOT_CONNECTED );
4946 ok = false;
4947 }
4948 }
4949 }
4950
4951 return ok;
4952}
4953
4954
4956{
4957 int errors = 0;
4958
4959 std::map<wxString, std::tuple<int, const SCH_ITEM*, SCH_SHEET_PATH>> labelData;
4960
4961 for( const SCH_SHEET_PATH& sheet : m_sheetList )
4962 {
4963 for( SCH_ITEM* item : sheet.LastScreen()->Items().OfType( SCH_GLOBAL_LABEL_T ) )
4964 {
4965 SCH_TEXT* labelText = static_cast<SCH_TEXT*>( item );
4966 wxString resolvedLabelText = EscapeString( labelText->GetShownText( &sheet, FOR_NETNAME ), CTX_NETNAME );
4967
4968 if( labelData.find( resolvedLabelText ) == labelData.end() )
4969 {
4970 labelData[resolvedLabelText] = { 1, item, sheet };
4971 }
4972 else
4973 {
4974 std::get<0>( labelData[resolvedLabelText] ) += 1;
4975 std::get<1>( labelData[resolvedLabelText] ) = nullptr;
4976 std::get<2>( labelData[resolvedLabelText] ) = sheet;
4977 }
4978 }
4979 }
4980
4981 for( const auto& label : labelData )
4982 {
4983 if( std::get<0>( label.second ) == 1 )
4984 {
4985 const SCH_SHEET_PATH& sheet = std::get<2>( label.second );
4986 const SCH_ITEM* item = std::get<1>( label.second );
4987
4988 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_SINGLE_GLOBAL_LABEL );
4989 ercItem->SetItems( std::get<1>( label.second ) );
4990 ercItem->SetSheetSpecificPath( sheet );
4991 ercItem->SetItemsSheetPaths( sheet );
4992
4993 SCH_MARKER* marker = new SCH_MARKER( std::move( ercItem ), item->GetPosition() );
4994 sheet.LastScreen()->Append( marker );
4995
4996 errors++;
4997 }
4998 }
4999
5000 return errors;
5001}
5002
5003
5005{
5006 int error_count = 0;
5007
5008 for( const SCH_SHEET_PATH& sheet : m_sheetList )
5009 {
5010 for( SCH_ITEM* item : sheet.LastScreen()->Items().OfType( SCH_DIRECTIVE_LABEL_T ) )
5011 {
5012 SCH_LABEL* label = static_cast<SCH_LABEL*>( item );
5013
5014 if( label->IsDangling() )
5015 {
5016 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_LABEL_NOT_CONNECTED );
5017 SCH_TEXT* text = static_cast<SCH_TEXT*>( item );
5018 ercItem->SetSheetSpecificPath( sheet );
5019 ercItem->SetItems( text );
5020
5021 SCH_MARKER* marker = new SCH_MARKER( std::move( ercItem ), text->GetPosition() );
5022 sheet.LastScreen()->Append( marker );
5023 error_count++;
5024 }
5025 }
5026 }
5027
5028 return error_count;
5029}
5030
5031
5033{
5034 wxString msg;
5035 int errors = 0;
5036
5037 ERC_SETTINGS& settings = m_schematic->ErcSettings();
5038
5039 for( const SCH_SHEET_PATH& sheet : m_sheetList )
5040 {
5041 // Hierarchical labels in the top-level sheets cannot be connected to anything.
5042 if( sheet.Last()->IsTopLevelSheet() )
5043 {
5044 for( const SCH_ITEM* item : sheet.LastScreen()->Items().OfType( SCH_HIER_LABEL_T ) )
5045 {
5046 const SCH_HIERLABEL* label = static_cast<const SCH_HIERLABEL*>( item );
5047
5048 wxCHECK2( label, continue );
5049
5050 msg.Printf( _( "Hierarchical label '%s' in root sheet cannot be connected to non-existent "
5051 "parent sheet" ),
5052 label->GetShownText( &sheet, FOR_NETNAME ) );
5053 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_PIN_NOT_CONNECTED );
5054 ercItem->SetItems( item );
5055 ercItem->SetErrorMessage( msg );
5056
5057 SCH_MARKER* marker = new SCH_MARKER( std::move( ercItem ), item->GetPosition() );
5058 sheet.LastScreen()->Append( marker );
5059
5060 errors++;
5061 }
5062 }
5063
5064 for( SCH_ITEM* item : sheet.LastScreen()->Items().OfType( SCH_SHEET_T ) )
5065 {
5066 SCH_SHEET* parentSheet = static_cast<SCH_SHEET*>( item );
5067 SCH_SHEET_PATH parentSheetPath = sheet;
5068
5069 parentSheetPath.push_back( parentSheet );
5070
5071 std::map<wxString, SCH_SHEET_PIN*> pins;
5072 std::map<wxString, SCH_HIERLABEL*> labels;
5073
5074 for( SCH_SHEET_PIN* pin : parentSheet->GetPins() )
5075 {
5076 if( settings.IsTestEnabled( ERCE_HIERACHICAL_LABEL ) )
5077 pins[ pin->GetShownText( &parentSheetPath, FOR_NETNAME ) ] = pin;
5078
5079 if( pin->IsDangling() && settings.IsTestEnabled( ERCE_PIN_NOT_CONNECTED ) )
5080 {
5081 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_PIN_NOT_CONNECTED );
5082 ercItem->SetItems( pin );
5083 ercItem->SetSheetSpecificPath( sheet );
5084 ercItem->SetItemsSheetPaths( sheet );
5085
5086 SCH_MARKER* marker = new SCH_MARKER( std::move( ercItem ), pin->GetPosition() );
5087 sheet.LastScreen()->Append( marker );
5088
5089 errors++;
5090 }
5091 }
5092
5093 if( settings.IsTestEnabled( ERCE_HIERACHICAL_LABEL ) )
5094 {
5095 std::set<wxString> matchedPins;
5096
5097 for( SCH_ITEM* subItem : parentSheet->GetScreen()->Items() )
5098 {
5099 if( subItem->Type() == SCH_HIER_LABEL_T )
5100 {
5101 SCH_HIERLABEL* label = static_cast<SCH_HIERLABEL*>( subItem );
5102 wxString labelText = label->GetShownText( &parentSheetPath, FOR_NETNAME );
5103
5104 if( !pins.contains( labelText ) )
5105 labels[ labelText ] = label;
5106 else
5107 matchedPins.insert( labelText );
5108 }
5109 }
5110
5111 for( const wxString& matched : matchedPins )
5112 pins.erase( matched );
5113
5114 for( const auto& [name, pin] : pins )
5115 {
5116 msg.Printf( _( "Sheet pin %s has no matching hierarchical label inside the sheet" ),
5117 UnescapeString( name ) );
5118
5119 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_HIERACHICAL_LABEL );
5120 ercItem->SetItems( pin );
5121 ercItem->SetErrorMessage( msg );
5122 ercItem->SetSheetSpecificPath( sheet );
5123 ercItem->SetItemsSheetPaths( sheet );
5124
5125 SCH_MARKER* marker = new SCH_MARKER( std::move( ercItem ), pin->GetPosition() );
5126 sheet.LastScreen()->Append( marker );
5127
5128 errors++;
5129 }
5130
5131 for( const auto& [name, label] : labels )
5132 {
5133 msg.Printf( _( "Hierarchical label %s has no matching sheet pin in the parent sheet" ),
5134 UnescapeString( name ) );
5135
5136 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_HIERACHICAL_LABEL );
5137 ercItem->SetItems( label );
5138 ercItem->SetErrorMessage( msg );
5139 ercItem->SetSheetSpecificPath( parentSheetPath );
5140 ercItem->SetItemsSheetPaths( parentSheetPath );
5141
5142 SCH_MARKER* marker = new SCH_MARKER( std::move( ercItem ), label->GetPosition() );
5143 parentSheet->GetScreen()->Append( marker );
5144
5145 errors++;
5146 }
5147 }
5148 }
5149 }
5150
5151 return errors;
5152}
const char * name
constexpr EDA_IU_SCALE schIUScale
Definition base_units.h:123
This represents a sentry transaction which is used for time-performance metrics You start a transacti...
Definition app_monitor.h:60
void StartSpan(const std::string &aOperation, const std::string &aDescription)
static SCH_NETCHAIN * resolvePotentialChainByTerminals(const CHAIN_TERMINAL_REFS &aTermRefs, const std::map< std::pair< wxString, wxString >, wxString > &aRefPinToNet, const std::vector< std::unique_ptr< SCH_NETCHAIN > > &aPotentials, const wxString &aChainName)
Disambiguate the saved (refA.pinA, refB.pinB) terminal pair against the current set of potential net ...
int RunERC()
Run electrical rule checks on the connectivity graph.
std::shared_ptr< CONNECTION_GRAPH_LIFETIME > m_lifetime
Retired before graph teardown so late item destruction cannot enter this 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.
SCH_NETCHAIN * GetNetChainByName(const wxString &aName)
bool ercCheckLabels(const CONNECTION_SUBGRAPH *aSubgraph)
Check one subgraph for proper connection of labels.
std::unordered_map< SCH_ITEM *, std::vector< CONNECTION_SUBGRAPH * > > m_item_to_subgraph_map
Every subgraph referencing the item, one per instantiating sheet path for items on shared screens.
void RemoveItem(SCH_ITEM *aItem)
void collectAllDriverValues()
Map the driver values for each subgraph.
int ercCheckDirectiveLabels()
Check directive labels should be connected to something.
void recacheSubgraphName(CONNECTION_SUBGRAPH *aSubgraph, const wxString &aOldName)
CONNECTION_GRAPH(SCHEMATIC *aSchematic=nullptr, SCH_CONNECTIVITY::NETCHAIN_MANAGER *aNetChains=nullptr)
static std::function< void(SCH_CONNECTIVITY::NETCHAIN_MANAGER &)> & RebuildNetChainsTestHook()
QA hook receives candidate state before publication and may throw to test rollback.
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::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.
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.
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 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.
bool ercCheckNoConnects(const CONNECTION_SUBGRAPH *aSubgraph)
Check one subgraph for proper presence or absence of no-connect symbols.
SCH_CONNECTIVITY::NETCHAIN_MANAGER::CHAIN_TERMINAL_REFS CHAIN_TERMINAL_REFS
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.
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_CONNECTIVITY::NETCHAIN_MANAGER * m_netChains
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.
std::vector< CONNECTION_SUBGRAPH * > m_driver_subgraphs
Cache of a subset of m_subgraphs.
std::vector< wxString > GetEquivalentBusNames(const wxString &aBusName) const
Map a bus group name between its alias and expanded forms ({MIXED_BUS} <-> {FOO BAR HAM EGGS}...
void ExchangeItem(SCH_ITEM *aOldItem, SCH_ITEM *aNewItem)
Replace all references to #aOldItem with #aNewItem in the graph.
NET_MAP m_net_code_to_subgraphs_map
bool ercCheckFloatingWires(const CONNECTION_SUBGRAPH *aSubgraph)
Check one subgraph for floating wires.
void ApplyNetChainNetclasses()
Mirror each committed net chain's netclass override into the project NET_SETTINGS as a chain-derived ...
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.
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:348
virtual wxString GetItemDescription(UNITS_PROVIDER *aUnitsProvider, bool aFull) const
Return a user-visible description string of this item.
Definition eda_item.cpp:304
const KIID m_Uuid
Definition eda_item.h:597
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:110
EE_TYPE Overlapping(const BOX2I &aRect) const
Definition sch_rtree.h:253
EE_TYPE OfType(KICAD_T aType) const
Definition sch_rtree.h:248
static std::shared_ptr< ERC_ITEM > Create(int aErrorCode)
Constructs an ERC_ITEM for the given error code.
Definition erc_item.cpp:305
Container for ERC settings.
bool IsTestEnabled(int aErrorCode) const
Definition kiid.h:46
bool GetDuplicatePinNumbersAreJumpers() const
Definition lib_symbol.h:873
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:880
static bool ParseBusGroup(const wxString &aGroup, wxString *name, std::vector< wxString > *aMemberList, size_t *aPrefixEnd=nullptr)
Parse a bus group label into the name and a list of components.
A small class to help profiling.
Definition profile.h:46
void Show(std::ostream &aStream=std::cerr)
Print the elapsed time (in a suitable unit) to a stream.
Definition profile.h:103
void Stop()
Save the time when this function was called, and set the counter stane to stop.
Definition profile.h:86
A progress reporter interface for use in multi-threaded environments.
virtual bool KeepRefreshing(bool aWait=false)=0
Update the UI (if any).
virtual void SetCurrentProgress(double aProgress)=0
Set the progress value to aProgress (0..1).
Class for a bus to bus entry.
SCH_ITEM * m_connected_bus_items[2]
Pointer to the bus items (usually bus wires) connected to this bus-bus entry (either or both may be n...
bool IsStartDangling() const
VECTOR2I GetPosition() const override
bool IsEndDangling() const
std::vector< VECTOR2I > GetConnectionPoints() const override
Add all the connection points for this item to aPoints.
Class for a wire to bus entry.
SCH_ITEM * m_connected_bus_item
Pointer to the bus item (usually a bus wire) connected to this bus-wire entry, if it is connected to ...
Each graphical item can have a SCH_CONNECTION describing its logical connection (to a bus or net).
wxString FullLocalName() const
void ConfigureFromLabel(const wxString &aLabel)
Configures the connection given a label.
bool IsNet() const
void SetSubgraphCode(int aCode)
void SetBusCode(int aCode)
void SetName(const wxString &aName)
SCH_SHEET_PATH Sheet() const
CONNECTION_TYPE Type() const
int SubgraphCode() const
void SetNetCode(int aCode)
SCH_ITEM * m_driver
The SCH_ITEM that drives this connection's net.
bool IsDriver() const
Checks if the SCH_ITEM this connection is attached to can drive connections Drivers can be labels,...
void SetType(CONNECTION_TYPE aType)
wxString LocalName() const
wxString Name(bool aIgnoreSheet=false) const
bool IsSubsetOf(SCH_CONNECTION *aOther) const
Returns true if this connection is contained within aOther (but not the same as aOther)
void SetDriver(SCH_ITEM *aItem)
bool IsBus() const
void Clone(const SCH_CONNECTION &aOther)
Copies connectivity information (but not parent) from another connection.
void SetGraph(CONNECTION_GRAPH *aGraph)
const std::vector< std::shared_ptr< SCH_CONNECTION > > & Members() const
long VectorIndex() const
Persistent chain configuration and the derived chains for one schematic.
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 ...
wxString GetUntranslatedName() const
Get the untranslated field name for storage, variable look-up, etc.
wxString GetShownText(const SCH_SHEET_PATH *aPath, RESOLUTION_CONTEXT aContext, const wxString &aVariantName=wxEmptyString, int aDepth=0) const
Base class for any item which can be embedded within the SCHEMATIC container class,...
Definition sch_item.h:165
void ClearConnectedItems(const SCH_SHEET_PATH &aPath)
Clear all connections to this item.
Definition sch_item.cpp:580
virtual void RunOnChildren(const std::function< void(SCH_ITEM *)> &aFunction, RECURSE_MODE aMode)
Definition sch_item.h:641
const SYMBOL * GetParentSymbol() const
Definition sch_item.cpp:287
virtual const wxString & GetCachedDriverName() const
Definition sch_item.cpp:652
const std::unordered_set< SCH_RULE_AREA * > & GetRuleAreaCache() const
Get the cache of rule areas enclosing this item.
Definition sch_item.h:694
SCH_CONNECTION * InitializeConnection(const SCH_SHEET_PATH &aPath, CONNECTION_GRAPH *aGraph)
Create a new connection object associated with this object.
Definition sch_item.cpp:613
const std::vector< SCH_ITEM * > & ConnectedItems(const SCH_SHEET_PATH &aPath) const
Retrieve the set of items connected to this item on the given sheet.
Definition sch_item.cpp:589
void AddConnectionTo(const SCH_SHEET_PATH &aPath, SCH_ITEM *aItem)
Add a connection link between this item and another.
Definition sch_item.cpp:597
int GetUnit() const
Definition sch_item.h:237
SCH_LAYER_ID GetLayer() const
Return the layer this item is on.
Definition sch_item.h:345
void SetConnectionGraph(CONNECTION_GRAPH *aGraph)
Update the connection graph for all connections in this item.
Definition sch_item.cpp:527
virtual void SetUnit(int aUnit)
Definition sch_item.h:236
virtual bool HasCachedDriverName() const
Definition sch_item.h:626
void registerConnectivityOwner(const std::shared_ptr< CONNECTION_GRAPH_LIFETIME > &aOwner)
Graph membership belongs to this item identity and must not propagate to clones.
Definition sch_item.cpp:109
SCH_CONNECTION * GetOrInitConnection(const SCH_SHEET_PATH &aPath, CONNECTION_GRAPH *aGraph)
Definition sch_item.cpp:637
SCH_CONNECTION * Connection(const SCH_SHEET_PATH *aSheet=nullptr) const
Retrieve the connection associated with this object in the given sheet.
Definition sch_item.cpp:503
virtual std::vector< VECTOR2I > GetConnectionPoints() const
Add all the connection points for this item to aPoints.
Definition sch_item.h:546
bool IsDangling() const override
Definition sch_label.h:332
LABEL_FLAG_SHAPE GetShape() const
Definition sch_label.h:178
wxString GetShownText(const SCH_SHEET_PATH *aPath, RESOLUTION_CONTEXT aContext, int aDepth=0) const override
Segment description base class to describe items which have 2 end points (track, wire,...
Definition sch_line.h:39
std::vector< VECTOR2I > GetConnectionPoints() const override
Add all the connection points for this item to aPoints.
Definition sch_line.cpp:806
bool IsStartDangling() const
Definition sch_line.h:327
bool IsEndDangling() const
Definition sch_line.h:328
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.
static wxString MakeKey(const wxString &aName, uint32_t aComponent)
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:457
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:476
SCH_PIN * GetLibPin() const
Definition sch_pin.h:107
bool IsStacked(const SCH_PIN *aPin) const
Definition sch_pin.cpp:579
wxString GetDefaultNetName(const SCH_SHEET_PATH &aPath, bool aForceNoConnect=false)
Definition sch_pin.cpp:1666
bool IsPower() const
Check if the pin is either a global or local power pin.
Definition sch_pin.cpp:483
ELECTRICAL_PINTYPE GetType() const
Definition sch_pin.cpp:411
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:118
SCH_LINE * GetBus(const VECTOR2I &aPosition, int aAccuracy=0, SCH_LINE_TEST_T aSearchType=ENTIRE_LENGTH_T) const
Definition sch_screen.h:457
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
KIID_PATH Path() const
Get the sheet path as an KIID_PATH.
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:384
SCH_SCREEN * GetScreen() const
Definition sch_sheet.h:145
std::vector< SCH_SHEET_PIN * > & GetPins()
Definition sch_sheet.h:241
Schematic symbol object.
Definition sch_symbol.h:75
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:183
VECTOR2I GetPosition() const override
Definition sch_text.h:143
virtual wxString GetShownText(const SCH_SHEET_PATH *aPath, RESOLUTION_CONTEXT aContext, int aDepth=0) const
Definition sch_text.cpp:359
A base class for LIB_SYMBOL and SCH_SYMBOL.
Definition symbol.h:59
virtual bool IsGlobalPower() const =0
virtual bool IsLocalPower() const =0
virtual bool IsPower() const =0
@ FOR_NETNAME
Definition common.h:90
std::pair< KIID_PATH, DRIVER_IDENTITY > SUBGRAPH_IDENTITY
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 DRIVER_IDENTITY stableDriverIdentity(SCH_ITEM *aDriver)
std::tuple< KIID, wxString, int, VECTOR2I > DRIVER_IDENTITY
#define _(s)
@ NO_RECURSE
Definition eda_item.h:52
#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.
static const wxChar ConnTrace[]
Flag to enable connectivity tracing.
@ LAYER_WIRE
Definition layer_ids.h:474
@ LAYER_BUS
Definition layer_ids.h:475
@ LAYER_JUNCTION
Definition layer_ids.h:476
@ LAYER_BUS_JUNCTION
Definition layer_ids.h:520
#define KI_FALLTHROUGH
The KI_FALLTHROUGH macro is to be used when switch statement cases should purposely fallthrough from ...
Definition macros.h:79
void remove_duplicates(_Container &__c)
Deletes all duplicate values from __c.
Definition kicad_algo.h:157
bool contains(const _Container &__container, _Value __value)
Returns true if the container contains the given value.
Definition kicad_algo.h:96
@ PT_NC
not connected (must be left open)
Definition pin_type.h:46
@ PT_NIC
not internally connected (may be connected to anything)
Definition pin_type.h:40
@ PT_POWER_IN
power input (GND, VCC for ICs). Must be connected to a power output.
Definition pin_type.h:42
CONNECTION_TYPE
@ BUS
This item represents a bus vector.
@ NET
This item represents a net.
@ BUS_GROUP
This item represents a bus group.
@ L_OUTPUT
Definition sch_label.h:99
Definition of the SCH_SHEET_PATH and SCH_SHEET_LIST classes for Eeschema.
std::vector< FAB_LAYER_COLOR > dummy
wxString UnescapeString(const wxString &aSource)
wxString EscapeString(const wxString &aSource, ESCAPE_CONTEXT aContext)
The Escape/Unescape routines use HTML-entity-reference-style encoding to handle characters which are:...
@ CTX_NETNAME
Immediate-use netchain input; shared-screen items are qualified by their instance.
std::pmr::monotonic_buffer_resource storage
std::string path
KIBIS_PIN * pin
KIBIS_PIN * pinA
VECTOR2I location
thread_pool & GetKiCadThreadPool()
Get a reference to the current thread pool.
static thread_pool * tp
BS::priority_thread_pool thread_pool
Definition thread_pool.h:27
wxLogTrace helper definitions.
#define kv
@ SCH_LINE_T
Definition typeinfo.h:159
@ SCH_NO_CONNECT_T
Definition typeinfo.h:156
@ SCH_SYMBOL_T
Definition typeinfo.h:168
@ SCH_FIELD_T
Definition typeinfo.h:146
@ SCH_DIRECTIVE_LABEL_T
Definition typeinfo.h:167
@ SCH_LABEL_T
Definition typeinfo.h:163
@ SCH_SHEET_T
Definition typeinfo.h:171
@ SCH_HIER_LABEL_T
Definition typeinfo.h:165
@ SCH_BUS_BUS_ENTRY_T
Definition typeinfo.h:158
@ SCH_SHEET_PIN_T
Definition typeinfo.h:170
@ SCH_TEXT_T
Definition typeinfo.h:147
@ SCH_BUS_WIRE_ENTRY_T
Definition typeinfo.h:157
@ SCH_GLOBAL_LABEL_T
Definition typeinfo.h:164
@ SCH_JUNCTION_T
Definition typeinfo.h:155
@ SCH_PIN_T
Definition typeinfo.h:149
Functions to provide common constants and other functions to assist in making a consistent UI.
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683