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