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