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 <future>
26#include <vector>
27#include <unordered_map>
28#include <app_monitor.h>
29#include <core/profile.h>
30#include <core/kicad_algo.h>
31#include <common.h>
32#include <erc/erc.h>
33#include <pin_type.h>
34#include <sch_bus_entry.h>
35#include <sch_symbol.h>
36#include <sch_edit_frame.h>
37#include <sch_line.h>
38#include <sch_marker.h>
39#include <sch_pin.h>
40#include <sch_rule_area.h>
41#include <sch_sheet.h>
42#include <sch_sheet_path.h>
43#include <sch_sheet_pin.h>
44#include <sch_text.h>
45#include <schematic.h>
46#include <symbol.h>
47#include <connection_graph.h>
50#include <widgets/ui_common.h>
51#include <string_utils.h>
52#include <thread_pool.h>
53#include <wx/log.h>
54
55#include <advanced_config.h> // for realtime connectivity switch in release builds
56
57
62static const wxChar DanglingProfileMask[] = wxT( "CONN_PROFILE" );
63
64
69static const wxChar ConnTrace[] = wxT( "CONN" );
70
71
73{
74 m_items.erase( aItem );
75 m_drivers.erase( aItem );
76
77 if( aItem == m_driver )
78 {
79 m_driver = nullptr;
80 m_driver_connection = nullptr;
81 }
82
83 if( aItem->Type() == SCH_SHEET_PIN_T )
84 m_hier_pins.erase( static_cast<SCH_SHEET_PIN*>( aItem ) );
85
86 if( aItem->Type() == SCH_HIER_LABEL_T )
87 m_hier_ports.erase( static_cast<SCH_HIERLABEL*>( aItem ) );
88}
89
90
92{
93 m_items.erase( aOldItem );
94 m_items.insert( aNewItem );
95
96 m_drivers.erase( aOldItem );
97 m_drivers.insert( aNewItem );
98
99 if( aOldItem == m_driver )
100 {
101 m_driver = aNewItem;
103 }
104
105 SCH_CONNECTION* old_conn = aOldItem->Connection( &m_sheet );
106 SCH_CONNECTION* new_conn = aNewItem->GetOrInitConnection( m_sheet, m_graph );
107
108 if( old_conn && new_conn )
109 {
110 new_conn->Clone( *old_conn );
111
112 if( old_conn->IsDriver() )
113 new_conn->SetDriver( aNewItem );
114
115 new_conn->ClearDirty();
116 }
117
118 if( aOldItem->Type() == SCH_SHEET_PIN_T )
119 {
120 m_hier_pins.erase( static_cast<SCH_SHEET_PIN*>( aOldItem ) );
121 m_hier_pins.insert( static_cast<SCH_SHEET_PIN*>( aNewItem ) );
122 }
123
124 if( aOldItem->Type() == SCH_HIER_LABEL_T )
125 {
126 m_hier_ports.erase( static_cast<SCH_HIERLABEL*>( aOldItem ) );
127 m_hier_ports.insert( static_cast<SCH_HIERLABEL*>( aNewItem ) );
128 }
129}
130
131
132bool CONNECTION_SUBGRAPH::ResolveDrivers( bool aCheckMultipleDrivers )
133{
134 std::lock_guard lock( m_driver_mutex );
135
136 // Collect candidate drivers of highest priority in a simple vector which will be
137 // sorted later. Using a vector makes the ranking logic explicit and easier to
138 // maintain than relying on the ordering semantics of std::set.
139 PRIORITY highest_priority = PRIORITY::INVALID;
140 std::vector<SCH_ITEM*> candidates;
141 std::set<SCH_ITEM*> strong_drivers;
142
143 m_driver = nullptr;
144
145 // Hierarchical labels are lower priority than local labels here,
146 // because on the first pass we want local labels to drive subgraphs
147 // so that we can identify same-sheet neighbors and link them together.
148 // Hierarchical labels will end up overriding the final net name if
149 // a higher-level sheet has a different name during the hierarchical
150 // pass.
151
152 for( SCH_ITEM* item : m_drivers )
153 {
154 PRIORITY item_priority = GetDriverPriority( item );
155
156 if( item_priority == PRIORITY::PIN )
157 {
158 SCH_PIN* pin = static_cast<SCH_PIN*>( item );
159
160 if( !static_cast<SCH_SYMBOL*>( pin->GetParentSymbol() )->IsInNetlist() )
161 continue;
162 }
163
164 if( item_priority >= PRIORITY::HIER_LABEL )
165 strong_drivers.insert( item );
166
167 if( item_priority > highest_priority )
168 {
169 candidates.clear();
170 candidates.push_back( item );
171 highest_priority = item_priority;
172 }
173 else if( !candidates.empty() && ( item_priority == highest_priority ) )
174 {
175 candidates.push_back( item );
176 }
177 }
178
179 if( highest_priority >= PRIORITY::HIER_LABEL )
180 m_strong_driver = true;
181
182 // Power pins are 5, global labels are 6
183 m_local_driver = ( highest_priority < PRIORITY::GLOBAL_POWER_PIN );
184
185 if( !candidates.empty() )
186 {
187 // Candidates are ranked using the following rules (in order):
188 // 1. Prefer bus supersets over subsets to keep the widest connection.
189 // 2. Prefer pins on power symbols (global first, then local) over regular pins.
190 // 3. Prefer output sheet pins over input sheet pins.
191 // 4. Prefer names that do not look auto-generated (avoid "-Pad" suffixes).
192 // 5. Fall back to alphabetical comparison for deterministic ordering.
193 auto candidate_cmp = [&]( SCH_ITEM* a, SCH_ITEM* b )
194 {
195 SCH_CONNECTION* ac = a->Connection( &m_sheet );
196 SCH_CONNECTION* bc = b->Connection( &m_sheet );
197
198 if( ac->IsBus() && bc->IsBus() )
199 {
200 if( bc->IsSubsetOf( ac ) && !ac->IsSubsetOf( bc ) )
201 {
202 return true;
203 }
204
205 if( !bc->IsSubsetOf( ac ) && ac->IsSubsetOf( bc ) )
206 {
207 return false;
208 }
209 }
210
211 if( a->Type() == SCH_PIN_T && b->Type() == SCH_PIN_T )
212 {
213 SCH_PIN* pa = static_cast<SCH_PIN*>( a );
214 SCH_PIN* pb = static_cast<SCH_PIN*>( b );
215
216 SYMBOL* aParent = pa->GetLibPin() ? pa->GetLibPin()->GetParentSymbol() : nullptr;
217 SYMBOL* bParent = pb->GetLibPin() ? pb->GetLibPin()->GetParentSymbol() : nullptr;
218
219 bool aGlobal = aParent && aParent->IsGlobalPower();
220 bool bGlobal = bParent && bParent->IsGlobalPower();
221
222 if( aGlobal != bGlobal )
223 return aGlobal;
224
225 bool aLocal = aParent && aParent->IsLocalPower();
226 bool bLocal = bParent && bParent->IsLocalPower();
227
228 if( aLocal != bLocal )
229 return aLocal;
230 }
231
232 if( a->Type() == SCH_SHEET_PIN_T && b->Type() == SCH_SHEET_PIN_T )
233 {
234 SCH_SHEET_PIN* sa = static_cast<SCH_SHEET_PIN*>( a );
235 SCH_SHEET_PIN* sb = static_cast<SCH_SHEET_PIN*>( b );
236
237 if( sa->GetShape() != sb->GetShape() )
238 {
240 return true;
242 return false;
243 }
244 }
245
246 const wxString& a_name = GetNameForDriver( a );
247 const wxString& b_name = GetNameForDriver( b );
248
249 bool a_lowQualityName = a_name.Contains( "-Pad" );
250 bool b_lowQualityName = b_name.Contains( "-Pad" );
251
252 if( a_lowQualityName != b_lowQualityName )
253 return !a_lowQualityName;
254
255 return a_name < b_name;
256 };
257
258 std::sort( candidates.begin(), candidates.end(), candidate_cmp );
259
260 m_driver = candidates.front();
261 }
262
263 if( strong_drivers.size() > 1 )
264 m_multiple_drivers = true;
265
266 // Drop weak drivers
267 if( m_strong_driver )
268 {
269 m_drivers.clear();
270 m_drivers.insert( strong_drivers.begin(), strong_drivers.end() );
271 }
272
273 // Cache driver connection
274 if( m_driver )
275 {
276 m_driver_connection = m_driver->Connection( &m_sheet );
277 m_driver_connection->ConfigureFromLabel( GetNameForDriver( m_driver ) );
278 m_driver_connection->SetDriver( m_driver );
279 m_driver_connection->ClearDirty();
280 }
281 else if( !m_is_bus_member )
282 {
283 m_driver_connection = nullptr;
284 }
285
286 return ( m_driver != nullptr );
287}
288
289
291 SCH_ITEM*>>& aItems,
292 std::set<CONNECTION_SUBGRAPH*>& aSubgraphs )
293{
294 CONNECTION_SUBGRAPH* sg = this;
295
296 while( sg->m_absorbed_by )
297 {
298 wxCHECK2( sg->m_graph == sg->m_absorbed_by->m_graph, continue );
299 sg = sg->m_absorbed_by;
300 }
301
302 // If we are unable to insert the subgraph into the set, then we have already
303 // visited it and don't need to add it again.
304 if( aSubgraphs.insert( sg ).second == false )
305 return;
306
307 aSubgraphs.insert( sg->m_absorbed_subgraphs.begin(), sg->m_absorbed_subgraphs.end() );
308
309 for( SCH_ITEM* item : sg->m_items )
310 aItems.emplace( m_sheet, item );
311
312 for( CONNECTION_SUBGRAPH* child_sg : sg->m_hier_children )
313 child_sg->getAllConnectedItems( aItems, aSubgraphs );
314}
315
316
318{
319 if( !m_driver || m_dirty )
320 return "";
321
322 if( !m_driver->Connection( &m_sheet ) )
323 {
324#ifdef CONNECTIVITY_DEBUG
325 wxASSERT_MSG( false, wxS( "Tried to get the net name of an item with no connection" ) );
326#endif
327
328 return "";
329 }
330
331 return m_driver->Connection( &m_sheet )->Name();
332}
333
334
335std::vector<SCH_ITEM*> CONNECTION_SUBGRAPH::GetAllBusLabels() const
336{
337 std::vector<SCH_ITEM*> labels;
338
339 for( SCH_ITEM* item : m_drivers )
340 {
341 switch( item->Type() )
342 {
343 case SCH_LABEL_T:
345 case SCH_HIER_LABEL_T:
346 {
347 CONNECTION_TYPE type = item->Connection( &m_sheet )->Type();
348
349 // Only consider bus vectors
350 if( type == CONNECTION_TYPE::BUS || type == CONNECTION_TYPE::BUS_GROUP )
351 labels.push_back( item );
352
353 break;
354 }
355
356 default:
357 break;
358 }
359 }
360
361 return labels;
362}
363
364
365std::vector<SCH_ITEM*> CONNECTION_SUBGRAPH::GetVectorBusLabels() const
366{
367 std::vector<SCH_ITEM*> labels;
368
369 for( SCH_ITEM* item : m_drivers )
370 {
371 switch( item->Type() )
372 {
373 case SCH_LABEL_T:
375 case SCH_HIER_LABEL_T:
376 {
377 SCH_CONNECTION* label_conn = item->Connection( &m_sheet );
378
379 // Only consider bus vectors
380 if( label_conn->Type() == CONNECTION_TYPE::BUS )
381 labels.push_back( item );
382
383 break;
384 }
385
386 default:
387 break;
388 }
389 }
390
391 return labels;
392}
393
394
396{
397 switch( aItem->Type() )
398 {
399 case SCH_PIN_T:
400 {
401 SCH_PIN* pin = static_cast<SCH_PIN*>( aItem );
402 bool forceNoConnect = m_no_connect != nullptr;
403
404 return pin->GetDefaultNetName( m_sheet, forceNoConnect );
405 }
406
407 case SCH_LABEL_T:
409 case SCH_HIER_LABEL_T:
410 {
411 SCH_LABEL_BASE* label = static_cast<SCH_LABEL_BASE*>( aItem );
412
413 // NB: any changes here will need corresponding changes in SCH_LABEL_BASE::cacheShownText()
414 return EscapeString( label->GetShownText( &m_sheet, false ), CTX_NETNAME );
415 }
416
417 case SCH_SHEET_PIN_T:
418 {
419 // Sheet pins need to use their parent sheet as their starting sheet or they will resolve
420 // variables on the current sheet first
421 SCH_SHEET_PIN* sheetPin = static_cast<SCH_SHEET_PIN*>( aItem );
423
424 if( path.Last() != sheetPin->GetParent() )
425 path.push_back( sheetPin->GetParent() );
426
427 return EscapeString( sheetPin->GetShownText( &path, false ), CTX_NETNAME );
428 }
429
430 default:
431 wxFAIL_MSG( wxS( "Unhandled item type in GetNameForDriver" ) );
432 return wxEmptyString;
433 }
434}
435
436
437const wxString& CONNECTION_SUBGRAPH::GetNameForDriver( SCH_ITEM* aItem ) const
438{
439 if( aItem->HasCachedDriverName() )
440 return aItem->GetCachedDriverName();
441
442 std::lock_guard lock( m_driver_name_cache_mutex );
443 auto it = m_driver_name_cache.find( aItem );
444
445 if( it != m_driver_name_cache.end() )
446 return it->second;
447
448 return m_driver_name_cache.emplace( aItem, driverName( aItem ) ).first->second;
449}
450
451
452const std::vector<std::pair<wxString, SCH_ITEM*>>
454{
455 std::vector<std::pair<wxString, SCH_ITEM*>> foundNetclasses;
456
457 const std::unordered_set<SCH_RULE_AREA*>& ruleAreaCache = aItem->GetRuleAreaCache();
458
459 // Get netclasses on attached rule areas
460 for( SCH_RULE_AREA* ruleArea : ruleAreaCache )
461 {
462 const std::vector<std::pair<wxString, SCH_ITEM*>> ruleAreaNetclasses =
463 ruleArea->GetResolvedNetclasses( &m_sheet );
464
465 if( ruleAreaNetclasses.size() > 0 )
466 {
467 foundNetclasses.insert( foundNetclasses.end(), ruleAreaNetclasses.begin(),
468 ruleAreaNetclasses.end() );
469 }
470 }
471
472 // Get netclasses on child fields
473 aItem->RunOnChildren(
474 [&]( SCH_ITEM* aChild )
475 {
476 if( aChild->Type() == SCH_FIELD_T )
477 {
478 SCH_FIELD* field = static_cast<SCH_FIELD*>( aChild );
479
480 if( field->GetCanonicalName() == wxT( "Netclass" ) )
481 {
482 wxString netclass = field->GetShownText( &m_sheet, false );
483
484 if( netclass != wxEmptyString )
485 foundNetclasses.push_back( { netclass, aItem } );
486 }
487 }
488 },
490
491 std::sort(
492 foundNetclasses.begin(), foundNetclasses.end(),
493 []( const std::pair<wxString, SCH_ITEM*>& i1, const std::pair<wxString, SCH_ITEM*>& i2 )
494 {
495 return i1.first < i2.first;
496 } );
497
498 return foundNetclasses;
499}
500
501
503{
504 wxCHECK( m_sheet == aOther->m_sheet, /* void */ );
505
506 for( SCH_ITEM* item : aOther->m_items )
507 {
509 AddItem( item );
510 }
511
512 m_absorbed_subgraphs.insert( aOther );
513 m_absorbed_subgraphs.insert( aOther->m_absorbed_subgraphs.begin(),
514 aOther->m_absorbed_subgraphs.end() );
515
516 m_bus_neighbors.insert( aOther->m_bus_neighbors.begin(), aOther->m_bus_neighbors.end() );
517 m_bus_parents.insert( aOther->m_bus_parents.begin(), aOther->m_bus_parents.end() );
518
520
521 std::function<void( CONNECTION_SUBGRAPH* )> set_absorbed_by =
522 [ & ]( CONNECTION_SUBGRAPH *child )
523 {
524 child->m_absorbed_by = this;
525
526 for( CONNECTION_SUBGRAPH* subchild : child->m_absorbed_subgraphs )
527 set_absorbed_by( subchild );
528 };
529
530 aOther->m_absorbed = true;
531 aOther->m_dirty = false;
532 aOther->m_driver = nullptr;
533 aOther->m_driver_connection = nullptr;
534
535 set_absorbed_by( aOther );
536}
537
538
540{
541 m_items.insert( aItem );
542
543 if( aItem->Connection( &m_sheet )->IsDriver() )
544 m_drivers.insert( aItem );
545
546 if( aItem->Type() == SCH_SHEET_PIN_T )
547 m_hier_pins.insert( static_cast<SCH_SHEET_PIN*>( aItem ) );
548 else if( aItem->Type() == SCH_HIER_LABEL_T )
549 m_hier_ports.insert( static_cast<SCH_HIERLABEL*>( aItem ) );
550}
551
552
554{
556 return;
557
558 for( SCH_ITEM* item : m_items )
559 {
560 SCH_CONNECTION* item_conn = item->GetOrInitConnection( m_sheet, m_graph );
561
562 if( !item_conn )
563 continue;
564
565 if( ( m_driver_connection->IsBus() && item_conn->IsNet() ) ||
566 ( m_driver_connection->IsNet() && item_conn->IsBus() ) )
567 {
568 continue;
569 }
570
571 item_conn->Clone( *m_driver_connection );
572 item_conn->ClearDirty();
573 }
574}
575
576
578{
579 if( !aDriver )
580 return PRIORITY::NONE;
581
582 auto libSymbolRef =
583 []( const SCH_SYMBOL* symbol ) -> wxString
584 {
585 if( const std::unique_ptr<LIB_SYMBOL>& part = symbol->GetLibSymbolRef() )
586 return part->GetReferenceField().GetText();
587
588 return wxEmptyString;
589 };
590
591 switch( aDriver->Type() )
592 {
597
598 case SCH_PIN_T:
599 {
600 SCH_PIN* sch_pin = static_cast<SCH_PIN*>( aDriver );
601 const SCH_SYMBOL* sym = static_cast<SCH_SYMBOL*>( sch_pin->GetParentSymbol() );
602
603 if( sch_pin->IsGlobalPower() )
605 else if( sch_pin->IsLocalPower() )
607 else if( !sym || sym->GetExcludedFromBoard() || libSymbolRef( sym ).StartsWith( '#' ) )
608 return PRIORITY::NONE;
609 else
610 return PRIORITY::PIN;
611 }
612
613 default:
614 return PRIORITY::NONE;
615 }
616}
617
618
620{
621 std::copy( aGraph.m_items.begin(), aGraph.m_items.end(),
622 std::back_inserter( m_items ) );
623
624 for( SCH_ITEM* item : aGraph.m_items )
625 item->SetConnectionGraph( this );
626
627 std::copy( aGraph.m_subgraphs.begin(), aGraph.m_subgraphs.end(),
628 std::back_inserter( m_subgraphs ) );
629
630 for( CONNECTION_SUBGRAPH* sg : aGraph.m_subgraphs )
631 {
632 if( sg->m_driver_connection )
633 sg->m_driver_connection->SetGraph( this );
634
635 sg->m_graph = this;
636 }
637
638 std::copy( aGraph.m_driver_subgraphs.begin(), aGraph.m_driver_subgraphs.end(),
639 std::back_inserter( m_driver_subgraphs ) );
640
641 std::copy( aGraph.m_global_power_pins.begin(), aGraph.m_global_power_pins.end(),
642 std::back_inserter( m_global_power_pins ) );
643
644 for( auto& [key, value] : aGraph.m_net_name_to_subgraphs_map )
645 m_net_name_to_subgraphs_map.insert_or_assign( key, value );
646
647 for( auto& [key, value] : aGraph.m_sheet_to_subgraphs_map )
648 m_sheet_to_subgraphs_map.insert_or_assign( key, value );
649
650 for( auto& [key, value] : aGraph.m_net_name_to_code_map )
651 m_net_name_to_code_map.insert_or_assign( key, value );
652
653 for( auto& [key, value] : aGraph.m_bus_name_to_code_map )
654 m_bus_name_to_code_map.insert_or_assign( key, value );
655
656 for( auto& [key, value] : aGraph.m_net_code_to_subgraphs_map )
657 m_net_code_to_subgraphs_map.insert_or_assign( key, value );
658
659 for( auto& [key, value] : aGraph.m_item_to_subgraph_map )
660 m_item_to_subgraph_map.insert_or_assign( key, value );
661
662 for( auto& [key, value] : aGraph.m_local_label_cache )
663 m_local_label_cache.insert_or_assign( key, value );
664
665 for( auto& [key, value] : aGraph.m_global_label_cache )
666 m_global_label_cache.insert_or_assign( key, value );
667
668 m_last_bus_code = std::max( m_last_bus_code, aGraph.m_last_bus_code );
669 m_last_net_code = std::max( m_last_net_code, aGraph.m_last_net_code );
671
672}
673
674
676{
677 wxCHECK2( aOldItem->Type() == aNewItem->Type(), return );
678
679 auto exchange = [&]( SCH_ITEM* aOld, SCH_ITEM* aNew )
680 {
681 auto it = m_item_to_subgraph_map.find( aOld );
682
683 if( it == m_item_to_subgraph_map.end() )
684 return;
685
686 CONNECTION_SUBGRAPH* sg = it->second;
687
688 sg->ExchangeItem( aOld, aNew );
689
690 m_item_to_subgraph_map.erase( it );
691 m_item_to_subgraph_map.emplace( aNew, sg );
692
693 for( auto it2 = m_items.begin(); it2 != m_items.end(); ++it2 )
694 {
695 if( *it2 == aOld )
696 {
697 *it2 = aNew;
698 break;
699 }
700 }
701 };
702
703 exchange( aOldItem, aNewItem );
704
705 if( aOldItem->Type() == SCH_SYMBOL_T )
706 {
707 SCH_SYMBOL* oldSymbol = static_cast<SCH_SYMBOL*>( aOldItem );
708 SCH_SYMBOL* newSymbol = static_cast<SCH_SYMBOL*>( aNewItem );
709 std::vector<SCH_PIN*> oldPins = oldSymbol->GetPins( &m_schematic->CurrentSheet() );
710 std::vector<SCH_PIN*> newPins = newSymbol->GetPins( &m_schematic->CurrentSheet() );
711
712 wxCHECK2( oldPins.size() == newPins.size(), return );
713
714 for( size_t ii = 0; ii < oldPins.size(); ii++ )
715 {
716 exchange( oldPins[ii], newPins[ii] );
717 }
718 }
719}
720
721
723{
724 for( auto& subgraph : m_subgraphs )
725 {
727 if( subgraph->m_graph == this )
728 delete subgraph;
729 }
730
731 m_items.clear();
732 m_subgraphs.clear();
733 m_driver_subgraphs.clear();
735 m_global_power_pins.clear();
736 m_bus_alias_cache.clear();
742 m_local_label_cache.clear();
743 m_global_label_cache.clear();
744 m_last_net_code = 1;
745 m_last_bus_code = 1;
747}
748
749
750void CONNECTION_GRAPH::Recalculate( const SCH_SHEET_LIST& aSheetList, bool aUnconditional,
751 std::function<void( SCH_ITEM* )>* aChangedItemHandler,
752 PROGRESS_REPORTER* aProgressReporter )
753{
754 APP_MONITOR::TRANSACTION monitorTrans( "CONNECTION_GRAPH::Recalculate", "Recalculate" );
755 PROF_TIMER recalc_time( "CONNECTION_GRAPH::Recalculate" );
756 monitorTrans.Start();
757
758 if( aUnconditional )
759 Reset();
760
761 monitorTrans.StartSpan( "updateItemConnectivity", "" );
762 PROF_TIMER update_items( "updateItemConnectivity" );
763
764 m_sheetList = aSheetList;
765 std::set<SCH_ITEM*> dirty_items;
766
767 int count = aSheetList.size() * 2;
768 int done = 0;
769
770 for( const SCH_SHEET_PATH& sheet : aSheetList )
771 {
772 if( aProgressReporter )
773 {
774 aProgressReporter->SetCurrentProgress( done++ / (double) count );
775 aProgressReporter->KeepRefreshing();
776 }
777
778 std::vector<SCH_ITEM*> items;
779
780 // Store current unit value, to replace it after calculations
781 std::vector<std::pair<SCH_SYMBOL*, int>> symbolsChanged;
782
783 for( SCH_ITEM* item : sheet.LastScreen()->Items() )
784 {
785 if( item->IsConnectable() && ( aUnconditional || item->IsConnectivityDirty() ) )
786 {
787 wxLogTrace( ConnTrace, wxT( "Adding item %s to connectivity graph update" ),
788 item->GetTypeDesc() );
789 items.push_back( item );
790 dirty_items.insert( item );
791
792 // Add any symbol dirty pins to the dirty_items list
793 if( item->Type() == SCH_SYMBOL_T )
794 {
795 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
796
797 for( SCH_PIN* pin : symbol->GetPins( &sheet ) )
798 {
799 if( pin->IsConnectivityDirty() )
800 {
801 dirty_items.insert( pin );
802 }
803 }
804 }
805 }
806 // If the symbol isn't dirty, look at the pins
807 // TODO: remove symbols from connectivity graph and only use pins
808 else if( item->Type() == SCH_SYMBOL_T )
809 {
810 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
811
812 for( SCH_PIN* pin : symbol->GetPins( &sheet ) )
813 {
814 if( pin->IsConnectivityDirty() )
815 {
816 items.push_back( pin );
817 dirty_items.insert( pin );
818 }
819 }
820 }
821 else if( item->Type() == SCH_SHEET_T )
822 {
823 SCH_SHEET* sheetItem = static_cast<SCH_SHEET*>( item );
824
825 for( SCH_SHEET_PIN* pin : sheetItem->GetPins() )
826 {
827 if( pin->IsConnectivityDirty() )
828 {
829 items.push_back( pin );
830 dirty_items.insert( pin );
831 }
832 }
833 }
834
835 // Ensure the hierarchy info stored in the SCH_SCREEN (such as symbol units) reflects
836 // the current SCH_SHEET_PATH
837 if( item->Type() == SCH_SYMBOL_T )
838 {
839 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
840 int new_unit = symbol->GetUnitSelection( &sheet );
841
842 // Store the initial unit value so we can restore it after calculations
843 if( symbol->GetUnit() != new_unit )
844 symbolsChanged.push_back( { symbol, symbol->GetUnit() } );
845
846 symbol->SetUnit( new_unit );
847 }
848 }
849
850 m_items.reserve( m_items.size() + items.size() );
851
852 updateItemConnectivity( sheet, items );
853
854 if( aProgressReporter )
855 {
856 aProgressReporter->SetCurrentProgress( done++ / count );
857 aProgressReporter->KeepRefreshing();
858 }
859
860 // UpdateDanglingState() also adds connected items for SCH_TEXT
861 sheet.LastScreen()->TestDanglingEnds( &sheet, aChangedItemHandler );
862
863 // Restore the m_unit member variables where we had to change them
864 for( const auto& [ symbol, originalUnit ] : symbolsChanged )
865 symbol->SetUnit( originalUnit );
866 }
867
868 // Restore the dangling states of items in the current SCH_SCREEN to match the current
869 // SCH_SHEET_PATH.
870 SCH_SCREEN* currentScreen = m_schematic->CurrentSheet().LastScreen();
871
872 if( currentScreen )
873 currentScreen->TestDanglingEnds( &m_schematic->CurrentSheet(), aChangedItemHandler );
874
875 for( SCH_ITEM* item : dirty_items )
876 item->SetConnectivityDirty( false );
877
878
879 monitorTrans.FinishSpan();
880 if( wxLog::IsAllowedTraceMask( DanglingProfileMask ) )
881 update_items.Show();
882
883 PROF_TIMER build_graph( "buildConnectionGraph" );
884 monitorTrans.StartSpan( "BuildConnectionGraph", "" );
885
886 buildConnectionGraph( aChangedItemHandler, aUnconditional );
887
888 if( wxLog::IsAllowedTraceMask( DanglingProfileMask ) )
889 build_graph.Show();
890
891 monitorTrans.FinishSpan();
892
893 recalc_time.Stop();
894
895 if( wxLog::IsAllowedTraceMask( DanglingProfileMask ) )
896 recalc_time.Show();
897
898 monitorTrans.Finish();
899}
900
901
902std::set<std::pair<SCH_SHEET_PATH, SCH_ITEM*>> CONNECTION_GRAPH::ExtractAffectedItems(
903 const std::set<SCH_ITEM*> &aItems )
904{
905 std::set<std::pair<SCH_SHEET_PATH, SCH_ITEM*>> retvals;
906 std::set<CONNECTION_SUBGRAPH*> subgraphs;
907
908 auto traverse_subgraph = [&retvals, &subgraphs]( CONNECTION_SUBGRAPH* aSubgraph )
909 {
910 // Find the primary subgraph on this sheet
911 while( aSubgraph->m_absorbed_by )
912 {
913 // Should we skip this if the absorbed by sub-graph is not this sub-grap?
914 wxASSERT( aSubgraph->m_graph == aSubgraph->m_absorbed_by->m_graph );
915 aSubgraph = aSubgraph->m_absorbed_by;
916 }
917
918 // Find the top most connected subgraph on all sheets
919 while( aSubgraph->m_hier_parent )
920 {
921 // Should we skip this if the absorbed by sub-graph is not this sub-grap?
922 wxASSERT( aSubgraph->m_graph == aSubgraph->m_hier_parent->m_graph );
923 aSubgraph = aSubgraph->m_hier_parent;
924 }
925
926 // Recurse through all subsheets to collect connected items
927 aSubgraph->getAllConnectedItems( retvals, subgraphs );
928 };
929
930 auto extract_element = [&]( SCH_ITEM* aItem )
931 {
932 CONNECTION_SUBGRAPH* item_sg = GetSubgraphForItem( aItem );
933
934 if( !item_sg )
935 {
936 wxLogTrace( ConnTrace, wxT( "Item %s not found in connection graph" ),
937 aItem->GetTypeDesc() );
938 return;
939 }
940
941 if( !item_sg->ResolveDrivers( true ) )
942 {
943 wxLogTrace( ConnTrace, wxT( "Item %s in subgraph %ld (%p) has no driver" ),
944 aItem->GetTypeDesc(), item_sg->m_code, item_sg );
945 }
946
947 std::vector<CONNECTION_SUBGRAPH*> sg_to_scan = GetAllSubgraphs( item_sg->GetNetName() );
948
949 if( sg_to_scan.empty() )
950 {
951 wxLogTrace( ConnTrace, wxT( "Item %s in subgraph %ld with net %s has no neighbors" ),
952 aItem->GetTypeDesc(), item_sg->m_code, item_sg->GetNetName() );
953 sg_to_scan.push_back( item_sg );
954 }
955
956 wxLogTrace( ConnTrace,
957 wxT( "Removing all item %s connections from subgraph %ld with net %s: Found "
958 "%zu subgraphs" ),
959 aItem->GetTypeDesc(), item_sg->m_code, item_sg->GetNetName(),
960 sg_to_scan.size() );
961
962 for( CONNECTION_SUBGRAPH* sg : sg_to_scan )
963 {
964 traverse_subgraph( sg );
965
966 for( auto& bus_it : sg->m_bus_neighbors )
967 {
968 for( CONNECTION_SUBGRAPH* bus_sg : bus_it.second )
969 traverse_subgraph( bus_sg );
970 }
971
972 for( auto& bus_it : sg->m_bus_parents )
973 {
974 for( CONNECTION_SUBGRAPH* bus_sg : bus_it.second )
975 traverse_subgraph( bus_sg );
976 }
977 }
978
979 std::erase( m_items, aItem );
980 };
981
982 for( SCH_ITEM* item : aItems )
983 {
984 if( item->Type() == SCH_SHEET_T )
985 {
986 SCH_SHEET* sheet = static_cast<SCH_SHEET*>( item );
987
988 for( SCH_SHEET_PIN* pin : sheet->GetPins() )
989 extract_element( pin );
990 }
991 else if ( item->Type() == SCH_SYMBOL_T )
992 {
993 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
994
995 for( SCH_PIN* pin : symbol->GetPins( &m_schematic->CurrentSheet() ) )
996 extract_element( pin );
997 }
998 else
999 {
1000 extract_element( item );
1001 }
1002 }
1003
1004 removeSubgraphs( subgraphs );
1005
1006 for( const auto& [path, item] : retvals )
1007 std::erase( m_items, item );
1008
1009 return retvals;
1010}
1011
1012
1014{
1015 auto it = m_item_to_subgraph_map.find( aItem );
1016
1017 if( it == m_item_to_subgraph_map.end() )
1018 return;
1019
1020 CONNECTION_SUBGRAPH* subgraph = it->second;
1021
1022 while(subgraph->m_absorbed_by )
1023 subgraph = subgraph->m_absorbed_by;
1024
1025 subgraph->RemoveItem( aItem );
1026 std::erase( m_items, aItem );
1027 m_item_to_subgraph_map.erase( it );
1028}
1029
1030
1031void CONNECTION_GRAPH::removeSubgraphs( std::set<CONNECTION_SUBGRAPH*>& aSubgraphs )
1032{
1033 wxLogTrace( ConnTrace, wxT( "Removing %zu subgraphs" ), aSubgraphs.size() );
1034 std::sort( m_driver_subgraphs.begin(), m_driver_subgraphs.end() );
1035 std::sort( m_subgraphs.begin(), m_subgraphs.end() );
1036 std::set<int> codes_to_remove;
1037
1038 for( auto& el : m_sheet_to_subgraphs_map )
1039 {
1040 std::sort( el.second.begin(), el.second.end() );
1041 }
1042
1043 for( CONNECTION_SUBGRAPH* sg : aSubgraphs )
1044 {
1045 for( auto& it : sg->m_bus_neighbors )
1046 {
1047 for( CONNECTION_SUBGRAPH* neighbor : it.second )
1048 {
1049 auto& parents = neighbor->m_bus_parents[it.first];
1050
1051 for( auto test = parents.begin(); test != parents.end(); )
1052 {
1053 if( *test == sg )
1054 test = parents.erase( test );
1055 else
1056 ++test;
1057 }
1058
1059 if( parents.empty() )
1060 neighbor->m_bus_parents.erase( it.first );
1061 }
1062 }
1063
1064 for( auto& it : sg->m_bus_parents )
1065 {
1066 for( CONNECTION_SUBGRAPH* parent : it.second )
1067 {
1068 auto& neighbors = parent->m_bus_neighbors[it.first];
1069
1070 for( auto test = neighbors.begin(); test != neighbors.end(); )
1071 {
1072 if( *test == sg )
1073 test = neighbors.erase( test );
1074 else
1075 ++test;
1076 }
1077
1078 if( neighbors.empty() )
1079 parent->m_bus_neighbors.erase( it.first );
1080 }
1081 }
1082
1083 {
1084 auto it = std::lower_bound( m_driver_subgraphs.begin(), m_driver_subgraphs.end(), sg );
1085
1086 while( it != m_driver_subgraphs.end() && *it == sg )
1087 it = m_driver_subgraphs.erase( it );
1088 }
1089
1090 {
1091 auto it = std::lower_bound( m_subgraphs.begin(), m_subgraphs.end(), sg );
1092
1093 while( it != m_subgraphs.end() && *it == sg )
1094 it = m_subgraphs.erase( it );
1095 }
1096
1097 for( auto& el : m_sheet_to_subgraphs_map )
1098 {
1099 auto it = std::lower_bound( el.second.begin(), el.second.end(), sg );
1100
1101 while( it != el.second.end() && *it == sg )
1102 it = el.second.erase( it );
1103 }
1104
1105 auto remove_sg = [sg]( auto it ) -> bool
1106 {
1107 for( const CONNECTION_SUBGRAPH* test_sg : it->second )
1108 {
1109 if( sg == test_sg )
1110 return true;
1111 }
1112
1113 return false;
1114 };
1115
1116 for( auto it = m_global_label_cache.begin(); it != m_global_label_cache.end(); )
1117 {
1118 if( remove_sg( it ) )
1119 it = m_global_label_cache.erase( it );
1120 else
1121 ++it;
1122 }
1123
1124 for( auto it = m_local_label_cache.begin(); it != m_local_label_cache.end(); )
1125 {
1126 if( remove_sg( it ) )
1127 it = m_local_label_cache.erase( it );
1128 else
1129 ++it;
1130 }
1131
1132 for( auto it = m_net_code_to_subgraphs_map.begin();
1133 it != m_net_code_to_subgraphs_map.end(); )
1134 {
1135 if( remove_sg( it ) )
1136 {
1137 codes_to_remove.insert( it->first.Netcode );
1138 it = m_net_code_to_subgraphs_map.erase( it );
1139 }
1140 else
1141 {
1142 ++it;
1143 }
1144 }
1145
1146 for( auto it = m_net_name_to_subgraphs_map.begin();
1147 it != m_net_name_to_subgraphs_map.end(); )
1148 {
1149 if( remove_sg( it ) )
1150 it = m_net_name_to_subgraphs_map.erase( it );
1151 else
1152 ++it;
1153 }
1154
1155 for( auto it = m_item_to_subgraph_map.begin(); it != m_item_to_subgraph_map.end(); )
1156 {
1157 if( it->second == sg )
1158 it = m_item_to_subgraph_map.erase( it );
1159 else
1160 ++it;
1161 }
1162
1163
1164 }
1165
1166 for( auto it = m_net_name_to_code_map.begin(); it != m_net_name_to_code_map.end(); )
1167 {
1168 if( codes_to_remove.contains( it->second ) )
1169 it = m_net_name_to_code_map.erase( it );
1170 else
1171 ++it;
1172 }
1173
1174 for( auto it = m_bus_name_to_code_map.begin(); it != m_bus_name_to_code_map.end(); )
1175 {
1176 if( codes_to_remove.contains( it->second ) )
1177 it = m_bus_name_to_code_map.erase( it );
1178 else
1179 ++it;
1180 }
1181
1182 for( CONNECTION_SUBGRAPH* sg : aSubgraphs )
1183 {
1184 sg->m_code = -1;
1185 sg->m_graph = nullptr;
1186 delete sg;
1187 }
1188}
1189
1190
1192 std::map<VECTOR2I, std::vector<SCH_ITEM*>>& aConnectionMap )
1193{
1194 auto updatePin =
1195 [&]( SCH_PIN* aPin, SCH_CONNECTION* aConn )
1196 {
1197 aConn->SetType( CONNECTION_TYPE::NET );
1198 wxString name = aPin->GetDefaultNetName( aSheet );
1199 aPin->ClearConnectedItems( aSheet );
1200
1201 if( aPin->IsGlobalPower() )
1202 {
1203 aConn->SetName( name );
1204 m_global_power_pins.emplace_back( std::make_pair( aSheet, aPin ) );
1205 }
1206 };
1207
1208 std::map<wxString, std::vector<SCH_PIN*>> pinNumberMap;
1209
1210 for( SCH_PIN* pin : aSymbol->GetPins( &aSheet ) )
1211 {
1212 m_items.emplace_back( pin );
1213 SCH_CONNECTION* conn = pin->InitializeConnection( aSheet, this );
1214 updatePin( pin, conn );
1215 aConnectionMap[ pin->GetPosition() ].push_back( pin );
1216 pinNumberMap[pin->GetNumber()].emplace_back( pin );
1217 }
1218
1219 auto linkPinsInVec =
1220 [&]( const std::vector<SCH_PIN*>& aVec )
1221 {
1222 for( size_t i = 0; i < aVec.size(); ++i )
1223 {
1224 for( size_t j = i + 1; j < aVec.size(); ++j )
1225 {
1226 aVec[i]->AddConnectionTo( aSheet, aVec[j] );
1227 aVec[j]->AddConnectionTo( aSheet, aVec[i] );
1228 }
1229 }
1230 };
1231
1232 if( aSymbol->GetLibSymbolRef() )
1233 {
1235 {
1236 for( const auto& [number, group] : pinNumberMap )
1237 linkPinsInVec( group );
1238 }
1239
1240 for( const std::set<wxString>& group : aSymbol->GetLibSymbolRef()->JumperPinGroups() )
1241 {
1242 std::vector<SCH_PIN*> pins;
1243
1244 for( const wxString& pinNumber : group )
1245 {
1246 if( SCH_PIN* pin = aSymbol->GetPin( pinNumber ) )
1247 pins.emplace_back( pin );
1248 }
1249
1250 linkPinsInVec( pins );
1251 }
1252 }
1253}
1254
1255
1257{
1258 aConn->SetType( CONNECTION_TYPE::NET );
1259
1260 // because calling the first time is not thread-safe
1261 wxString name = aPin->GetDefaultNetName( aSheet );
1262 aPin->ClearConnectedItems( aSheet );
1263
1264 if( aPin->IsGlobalPower() )
1265 {
1266 aConn->SetName( name );
1267 m_global_power_pins.emplace_back( std::make_pair( aSheet, aPin ) );
1268 }
1269}
1270
1271
1273 std::map<VECTOR2I, std::vector<SCH_ITEM*>>& aConnectionMap )
1274{
1275 std::vector<VECTOR2I> points = aItem->GetConnectionPoints();
1276 aItem->ClearConnectedItems( aSheet );
1277
1278 m_items.emplace_back( aItem );
1279 SCH_CONNECTION* conn = aItem->InitializeConnection( aSheet, this );
1280
1281 switch( aItem->Type() )
1282 {
1283 case SCH_LINE_T:
1285 break;
1286
1289 static_cast<SCH_BUS_BUS_ENTRY*>( aItem )->m_connected_bus_items[0] = nullptr;
1290 static_cast<SCH_BUS_BUS_ENTRY*>( aItem )->m_connected_bus_items[1] = nullptr;
1291 break;
1292
1293 case SCH_PIN_T:
1294 if( points.empty() )
1295 points = { static_cast<SCH_PIN*>( aItem )->GetPosition() };
1296
1297 updatePinConnectivity( aSheet, static_cast<SCH_PIN*>( aItem ), conn );
1298 break;
1299
1302 static_cast<SCH_BUS_WIRE_ENTRY*>( aItem )->m_connected_bus_item = nullptr;
1303 break;
1304
1305 default: break;
1306 }
1307
1308 for( const VECTOR2I& point : points )
1309 aConnectionMap[point].push_back( aItem );
1310}
1311
1312
1314 const std::vector<SCH_ITEM*>& aItemList )
1315{
1316 wxLogTrace( wxT( "Updating connectivity for sheet %s with %zu items" ),
1317 aSheet.Last()->GetFileName(), aItemList.size() );
1318 std::map<VECTOR2I, std::vector<SCH_ITEM*>> connection_map;
1319
1320 for( SCH_ITEM* item : aItemList )
1321 {
1322 std::vector<VECTOR2I> points = item->GetConnectionPoints();
1323 item->ClearConnectedItems( aSheet );
1324
1325 if( item->Type() == SCH_SHEET_T )
1326 {
1327 for( SCH_SHEET_PIN* pin : static_cast<SCH_SHEET*>( item )->GetPins() )
1328 {
1329 pin->InitializeConnection( aSheet, this );
1330
1331 pin->ClearConnectedItems( aSheet );
1332
1333 connection_map[ pin->GetTextPos() ].push_back( pin );
1334 m_items.emplace_back( pin );
1335 }
1336 }
1337 else if( item->Type() == SCH_SYMBOL_T )
1338 {
1339 updateSymbolConnectivity( aSheet, static_cast<SCH_SYMBOL*>( item ), connection_map );
1340 }
1341 else
1342 {
1343 updateGenericItemConnectivity( aSheet, item, connection_map );
1344
1348 if( dynamic_cast<SCH_LABEL_BASE*>( item ) )
1349 {
1350 VECTOR2I point = item->GetPosition();
1351 SCH_SCREEN* screen = aSheet.LastScreen();
1352 auto items = screen->Items().Overlapping( point );
1353 std::vector<SCH_ITEM*> overlapping_items;
1354
1355 std::copy_if( items.begin(), items.end(), std::back_inserter( overlapping_items ),
1356 [&]( SCH_ITEM* test_item )
1357 {
1358 return test_item->Type() == SCH_LINE_T
1359 && test_item->HitTest( point, -1 );
1360 } );
1361
1362 // We need at least two connnectable lines that are not the label here
1363 // Otherwise, the label will be normally assigned to one or the other
1364 if( overlapping_items.size() < 2 ) continue;
1365
1366 for( SCH_ITEM* test_item : overlapping_items )
1367 connection_map[point].push_back( test_item );
1368 }
1369
1370 // Junctions connect wires that pass through their position as midpoints.
1371 // This handles schematics where a wire was not split at a junction point,
1372 // which can happen when a wire is placed over an existing junction without
1373 // the schematic topology being updated.
1374 if( item->Type() == SCH_JUNCTION_T )
1375 {
1376 VECTOR2I point = item->GetPosition();
1377 SCH_SCREEN* screen = aSheet.LastScreen();
1378
1379 for( SCH_LINE* wire : screen->GetBusesAndWires( point, true ) )
1380 connection_map[point].push_back( wire );
1381 }
1382 }
1383 }
1384
1385 for( auto& [point, connection_vec] : connection_map )
1386 {
1387 std::sort( connection_vec.begin(), connection_vec.end() );
1388 alg::remove_duplicates( connection_vec );
1389
1390 // Pre-scan to see if we have a bus at this location
1391 SCH_LINE* busLine = aSheet.LastScreen()->GetBus( point );
1392
1393 for( SCH_ITEM* connected_item : connection_vec )
1394 {
1395 // Bus entries are special: they can have connection points in the
1396 // middle of a wire segment, because the junction algo doesn't split
1397 // the segment in two where you place a bus entry. This means that
1398 // bus entries that don't land on the end of a line segment need to
1399 // have "virtual" connection points to the segments they graphically
1400 // touch.
1401 if( connected_item->Type() == SCH_BUS_WIRE_ENTRY_T )
1402 {
1403 // If this location only has the connection point of the bus
1404 // entry itself, this means that either the bus entry is not
1405 // connected to anything graphically, or that it is connected to
1406 // a segment at some point other than at one of the endpoints.
1407 if( connection_vec.size() == 1 )
1408 {
1409 if( busLine )
1410 {
1411 auto bus_entry = static_cast<SCH_BUS_WIRE_ENTRY*>( connected_item );
1412 bus_entry->m_connected_bus_item = busLine;
1413 }
1414 }
1415 }
1416 // Bus-to-bus entries are treated just like bus wires
1417 else if( connected_item->Type() == SCH_BUS_BUS_ENTRY_T )
1418 {
1419 if( busLine )
1420 {
1421 auto bus_entry = static_cast<SCH_BUS_BUS_ENTRY*>( connected_item );
1422
1423 if( point == bus_entry->GetPosition() )
1424 bus_entry->m_connected_bus_items[0] = busLine;
1425 else
1426 bus_entry->m_connected_bus_items[1] = busLine;
1427
1428 bus_entry->AddConnectionTo( aSheet, busLine );
1429 busLine->AddConnectionTo( aSheet, bus_entry );
1430 continue;
1431 }
1432 }
1433 // Change junctions to be on bus junction layer if they are touching a bus
1434 else if( connected_item->Type() == SCH_JUNCTION_T )
1435 {
1436 connected_item->SetLayer( busLine ? LAYER_BUS_JUNCTION : LAYER_JUNCTION );
1437 }
1438
1439 for( SCH_ITEM* test_item : connection_vec )
1440 {
1441 bool bus_connection_ok = true;
1442
1443 if( test_item == connected_item )
1444 continue;
1445
1446 // Set up the link between the bus entry net and the bus
1447 if( connected_item->Type() == SCH_BUS_WIRE_ENTRY_T )
1448 {
1449 if( test_item->GetLayer() == LAYER_BUS )
1450 {
1451 auto bus_entry = static_cast<SCH_BUS_WIRE_ENTRY*>( connected_item );
1452 bus_entry->m_connected_bus_item = test_item;
1453 }
1454 }
1455
1456 // Bus entries only connect to bus lines on the end that is touching a bus line.
1457 // If the user has overlapped another net line with the endpoint of the bus entry
1458 // where the entry connects to a bus, we don't want to short-circuit it.
1459 if( connected_item->Type() == SCH_BUS_WIRE_ENTRY_T )
1460 {
1461 bus_connection_ok = !busLine || test_item->GetLayer() == LAYER_BUS;
1462 }
1463 else if( test_item->Type() == SCH_BUS_WIRE_ENTRY_T )
1464 {
1465 bus_connection_ok = !busLine || connected_item->GetLayer() == LAYER_BUS;
1466 }
1467
1468 if( connected_item->ConnectionPropagatesTo( test_item )
1469 && test_item->ConnectionPropagatesTo( connected_item )
1470 && bus_connection_ok )
1471 {
1472 connected_item->AddConnectionTo( aSheet, test_item );
1473 }
1474 }
1475
1476 // If we got this far and did not find a connected bus item for a bus entry,
1477 // we should do a manual scan in case there is a bus item on this connection
1478 // point but we didn't pick it up earlier because there is *also* a net item here.
1479 if( connected_item->Type() == SCH_BUS_WIRE_ENTRY_T )
1480 {
1481 auto bus_entry = static_cast<SCH_BUS_WIRE_ENTRY*>( connected_item );
1482
1483 if( !bus_entry->m_connected_bus_item )
1484 {
1485 SCH_SCREEN* screen = aSheet.LastScreen();
1486 SCH_LINE* bus = screen->GetBus( point );
1487
1488 if( bus )
1489 bus_entry->m_connected_bus_item = bus;
1490 }
1491 }
1492 }
1493 }
1494}
1495
1496
1498{
1499 // Recache all bus aliases for later use
1500 wxCHECK_RET( m_schematic, wxS( "Connection graph cannot be built without schematic pointer" ) );
1501
1502 m_bus_alias_cache.clear();
1503
1504 for( const std::shared_ptr<BUS_ALIAS>& alias : m_schematic->GetAllBusAliases() )
1505 {
1506 if( alias )
1507 m_bus_alias_cache[alias->GetName()] = alias;
1508 }
1509
1510 // Build subgraphs from items (on a per-sheet basis)
1511 for( SCH_ITEM* item : m_items )
1512 {
1513 for( const auto& [sheet, connection] : item->m_connection_map )
1514 {
1515 if( connection->SubgraphCode() == 0 )
1516 {
1517 CONNECTION_SUBGRAPH* subgraph = new CONNECTION_SUBGRAPH( this );
1518
1519 subgraph->m_code = m_last_subgraph_code++;
1520 subgraph->m_sheet = sheet;
1521
1522 subgraph->AddItem( item );
1523
1524 connection->SetSubgraphCode( subgraph->m_code );
1525 m_item_to_subgraph_map[item] = subgraph;
1526
1527 std::list<SCH_ITEM*> memberlist;
1528
1529 auto get_items =
1530 [&]( SCH_ITEM* aItem ) -> bool
1531 {
1532 SCH_CONNECTION* conn = aItem->GetOrInitConnection( sheet, this );
1533 bool unique = !( aItem->GetFlags() & CONNECTIVITY_CANDIDATE );
1534
1535 if( conn && !conn->SubgraphCode() )
1536 aItem->SetFlags( CONNECTIVITY_CANDIDATE );
1537
1538 return ( unique && conn && ( conn->SubgraphCode() == 0 ) );
1539 };
1540
1541 std::copy_if( item->ConnectedItems( sheet ).begin(),
1542 item->ConnectedItems( sheet ).end(),
1543 std::back_inserter( memberlist ), get_items );
1544
1545 for( SCH_ITEM* connected_item : memberlist )
1546 {
1547 if( connected_item->Type() == SCH_NO_CONNECT_T )
1548 subgraph->m_no_connect = connected_item;
1549
1550 SCH_CONNECTION* connected_conn = connected_item->Connection( &sheet );
1551
1552 wxCHECK2( connected_conn, continue );
1553
1554 if( connected_conn->SubgraphCode() == 0 )
1555 {
1556 connected_conn->SetSubgraphCode( subgraph->m_code );
1557 m_item_to_subgraph_map[connected_item] = subgraph;
1558 subgraph->AddItem( connected_item );
1559 const SCH_ITEM_VEC& citemset = connected_item->ConnectedItems( sheet );
1560
1561 for( SCH_ITEM* citem : citemset )
1562 {
1563 if( citem->HasFlag( CONNECTIVITY_CANDIDATE ) )
1564 continue;
1565
1566 if( get_items( citem ) )
1567 memberlist.push_back( citem );
1568 }
1569 }
1570 }
1571
1572 for( SCH_ITEM* connected_item : memberlist )
1573 connected_item->ClearFlags( CONNECTIVITY_CANDIDATE );
1574
1575 subgraph->m_dirty = true;
1576 m_subgraphs.push_back( subgraph );
1577 }
1578 }
1579 }
1580}
1581
1582
1584{
1585 // Resolve drivers for subgraphs and propagate connectivity info
1586 std::vector<CONNECTION_SUBGRAPH*> dirty_graphs;
1587
1588 std::copy_if( m_subgraphs.begin(), m_subgraphs.end(), std::back_inserter( dirty_graphs ),
1589 [&] ( const CONNECTION_SUBGRAPH* candidate )
1590 {
1591 return candidate->m_dirty;
1592 } );
1593
1594 wxLogTrace( ConnTrace, wxT( "Resolving drivers for %zu subgraphs" ), dirty_graphs.size() );
1595
1596 std::vector<std::future<size_t>> returns( dirty_graphs.size() );
1597
1598 auto update_lambda =
1599 []( CONNECTION_SUBGRAPH* subgraph ) -> size_t
1600 {
1601 if( !subgraph->m_dirty )
1602 return 0;
1603
1604 // Special processing for some items
1605 for( SCH_ITEM* item : subgraph->m_items )
1606 {
1607 switch( item->Type() )
1608 {
1609 case SCH_NO_CONNECT_T:
1610 subgraph->m_no_connect = item;
1611 break;
1612
1614 subgraph->m_bus_entry = item;
1615 break;
1616
1617 case SCH_PIN_T:
1618 {
1619 auto pin = static_cast<SCH_PIN*>( item );
1620
1621 if( pin->GetType() == ELECTRICAL_PINTYPE::PT_NC )
1622 subgraph->m_no_connect = item;
1623
1624 break;
1625 }
1626
1627 default:
1628 break;
1629 }
1630 }
1631
1632 subgraph->ResolveDrivers( true );
1633 subgraph->m_dirty = false;
1634
1635 return 1;
1636 };
1637
1639
1640 auto results = tp.submit_loop( 0, dirty_graphs.size(),
1641 [&]( const int ii )
1642 {
1643 update_lambda( dirty_graphs[ii] );
1644 } );
1645 results.wait();
1646
1647 // Now discard any non-driven subgraphs from further consideration
1648
1649 std::copy_if( m_subgraphs.begin(), m_subgraphs.end(), std::back_inserter( m_driver_subgraphs ),
1650 [&] ( const CONNECTION_SUBGRAPH* candidate ) -> bool
1651 {
1652 return candidate->m_driver;
1653 } );
1654}
1655
1656
1658{
1659 // Check for subgraphs with the same net name but only weak drivers.
1660 // For example, two wires that are both connected to hierarchical
1661 // sheet pins that happen to have the same name, but are not the same.
1662
1663 for( auto&& subgraph : m_driver_subgraphs )
1664 {
1665 wxString full_name = subgraph->m_driver_connection->Name();
1666 wxString name = subgraph->m_driver_connection->Name( true );
1667 m_net_name_to_subgraphs_map[full_name].emplace_back( subgraph );
1668
1669 // For vector buses, we need to cache the prefix also, as two different instances of the
1670 // weakly driven pin may have the same prefix but different vector start and end. We need
1671 // to treat those as needing renaming also, because otherwise if they end up on a sheet with
1672 // common usage, they will be incorrectly merged.
1673 if( subgraph->m_driver_connection->Type() == CONNECTION_TYPE::BUS )
1674 {
1675 wxString prefixOnly = full_name.BeforeFirst( '[' ) + wxT( "[]" );
1676 m_net_name_to_subgraphs_map[prefixOnly].emplace_back( subgraph );
1677 }
1678
1679 subgraph->m_dirty = true;
1680
1681 if( subgraph->m_strong_driver )
1682 {
1683 SCH_ITEM* driver = subgraph->m_driver;
1684 SCH_SHEET_PATH sheet = subgraph->m_sheet;
1685
1686 switch( driver->Type() )
1687 {
1688 case SCH_LABEL_T:
1689 case SCH_HIER_LABEL_T:
1690 {
1691 m_local_label_cache[std::make_pair( sheet, name )].push_back( subgraph );
1692 break;
1693 }
1694 case SCH_GLOBAL_LABEL_T:
1695 {
1696 m_global_label_cache[name].push_back( subgraph );
1697 break;
1698 }
1699 case SCH_PIN_T:
1700 {
1701 SCH_PIN* pin = static_cast<SCH_PIN*>( driver );
1702 if( pin->IsGlobalPower() )
1703 {
1704 m_global_label_cache[name].push_back( subgraph );
1705 }
1706 else if( pin->IsLocalPower() )
1707 {
1708 m_local_label_cache[std::make_pair( sheet, name )].push_back( subgraph );
1709 }
1710 else
1711 {
1712 UNITS_PROVIDER unitsProvider( schIUScale, EDA_UNITS::MM );
1713 wxLogTrace( ConnTrace, wxS( "Unexpected normal pin %s" ),
1714 driver->GetItemDescription( &unitsProvider, true ) );
1715 }
1716
1717 break;
1718 }
1719 default:
1720 {
1721 UNITS_PROVIDER unitsProvider( schIUScale, EDA_UNITS::MM );
1722
1723 wxLogTrace( ConnTrace, wxS( "Unexpected strong driver %s" ),
1724 driver->GetItemDescription( &unitsProvider, true ) );
1725 break;
1726 }
1727 }
1728 }
1729 }
1730}
1731
1732
1734{
1735 std::vector<CONNECTION_SUBGRAPH*> new_subgraphs;
1736
1737 for( CONNECTION_SUBGRAPH* subgraph : m_driver_subgraphs )
1738 {
1739 SCH_ITEM_VEC vec = subgraph->GetAllBusLabels();
1740
1741 for( SCH_ITEM* item : vec )
1742 {
1743 SCH_LABEL_BASE* label = static_cast<SCH_LABEL_BASE*>( item );
1744
1745 SCH_CONNECTION dummy( item, subgraph->m_sheet );
1746 dummy.SetGraph( this );
1747 dummy.ConfigureFromLabel( label->GetShownText( &subgraph->m_sheet, false ) );
1748
1749 wxLogTrace( ConnTrace, wxS( "new bus label (%s)" ),
1750 label->GetShownText( &subgraph->m_sheet, false ) );
1751
1752 for( const auto& conn : dummy.Members() )
1753 {
1754 // Only create subgraphs for NET members, not nested buses
1755 if( !conn->IsNet() )
1756 continue;
1757
1758 wxString name = conn->FullLocalName();
1759
1760 CONNECTION_SUBGRAPH* new_sg = new CONNECTION_SUBGRAPH( this );
1761
1762 // This connection cannot form a part of the item because the item is not, itself
1763 // connected to this subgraph. It exists as part of a virtual item that may be
1764 // connected to other items but is not in the schematic.
1765 auto new_conn = std::make_unique<SCH_CONNECTION>( item, subgraph->m_sheet );
1766 new_conn->SetGraph( this );
1767 new_conn->SetName( name );
1768 new_conn->SetType( CONNECTION_TYPE::NET );
1769
1770 SCH_CONNECTION* new_conn_ptr = subgraph->StoreImplicitConnection( std::move( new_conn ) );
1771 int code = assignNewNetCode( *new_conn_ptr );
1772
1773 wxLogTrace( ConnTrace, wxS( "SG(%ld), Adding full local name (%s) with sg (%d) on subsheet %s" ),
1774 subgraph->m_code, name, code, subgraph->m_sheet.PathHumanReadable() );
1775
1776 new_sg->m_driver_connection = new_conn_ptr;
1777 new_sg->m_code = m_last_subgraph_code++;
1778 new_sg->m_sheet = subgraph->GetSheet();
1779 new_sg->m_is_bus_member = true;
1780 new_sg->m_strong_driver = true;
1781
1783 NET_NAME_CODE_CACHE_KEY key = { new_sg->GetNetName(), code };
1784 m_net_code_to_subgraphs_map[ key ].push_back( new_sg );
1785 m_net_name_to_subgraphs_map[ name ].push_back( new_sg );
1786 m_subgraphs.push_back( new_sg );
1787 new_subgraphs.push_back( new_sg );
1788 }
1789 }
1790 }
1791
1792 std::copy( new_subgraphs.begin(), new_subgraphs.end(),
1793 std::back_inserter( m_driver_subgraphs ) );
1794}
1795
1796
1798{
1799 // Generate subgraphs for global power pins. These will be merged with other subgraphs
1800 // on the same sheet in the next loop.
1801 // These are NOT limited to power symbols, we support legacy invisible + power-in pins
1802 // on non-power symbols.
1803
1804 // Sort power pins for deterministic processing order. This ensures that when multiple
1805 // power pins share the same net name, the same pin consistently creates the subgraph
1806 // across different ERC runs.
1807 std::sort( m_global_power_pins.begin(), m_global_power_pins.end(),
1808 []( const std::pair<SCH_SHEET_PATH, SCH_PIN*>& a,
1809 const std::pair<SCH_SHEET_PATH, SCH_PIN*>& b )
1810 {
1811 int pathCmp = a.first.Cmp( b.first );
1812
1813 if( pathCmp != 0 )
1814 return pathCmp < 0;
1815
1816 const SCH_SYMBOL* symA = static_cast<const SCH_SYMBOL*>( a.second->GetParentSymbol() );
1817 const SCH_SYMBOL* symB = static_cast<const SCH_SYMBOL*>( b.second->GetParentSymbol() );
1818
1819 wxString refA = symA ? symA->GetRef( &a.first, false ) : wxString();
1820 wxString refB = symB ? symB->GetRef( &b.first, false ) : wxString();
1821
1822 int refCmp = refA.Cmp( refB );
1823
1824 if( refCmp != 0 )
1825 return refCmp < 0;
1826
1827 return a.second->GetNumber().Cmp( b.second->GetNumber() ) < 0;
1828 } );
1829
1830 std::unordered_map<int, CONNECTION_SUBGRAPH*> global_power_pin_subgraphs;
1831
1832 for( const auto& [sheet, pin] : m_global_power_pins )
1833 {
1834 SYMBOL* libParent = pin->GetLibPin() ? pin->GetLibPin()->GetParentSymbol() : nullptr;
1835
1836 if( !pin->ConnectedItems( sheet ).empty()
1837 && ( !libParent || !libParent->IsGlobalPower() ) )
1838 {
1839 // ERC will warn about this: user has wired up an invisible pin
1840 continue;
1841 }
1842
1843 SCH_CONNECTION* connection = pin->GetOrInitConnection( sheet, this );
1844
1845 // If this pin already has a subgraph, don't need to process
1846 if( !connection || connection->SubgraphCode() > 0 )
1847 continue;
1848
1849 // Proper modern power symbols get their net name from the value field
1850 // in the symbol, but we support legacy non-power symbols with global
1851 // power connections based on invisible, power-in, pin's names.
1852 if( libParent && libParent->IsGlobalPower() )
1853 connection->SetName( pin->GetParentSymbol()->GetValue( true, &sheet, false ) );
1854 else
1855 connection->SetName( pin->GetShownName() );
1856
1857 int code = assignNewNetCode( *connection );
1858
1859 connection->SetNetCode( code );
1860
1861 CONNECTION_SUBGRAPH* subgraph;
1862 auto jj = global_power_pin_subgraphs.find( code );
1863
1864 if( jj != global_power_pin_subgraphs.end() )
1865 {
1866 subgraph = jj->second;
1867 subgraph->AddItem( pin );
1868 }
1869 else
1870 {
1871 subgraph = new CONNECTION_SUBGRAPH( this );
1872
1873 subgraph->m_code = m_last_subgraph_code++;
1874 subgraph->m_sheet = sheet;
1875
1876 subgraph->AddItem( pin );
1877 subgraph->ResolveDrivers();
1878
1879 NET_NAME_CODE_CACHE_KEY key = { subgraph->GetNetName(), code };
1880 m_net_code_to_subgraphs_map[ key ].push_back( subgraph );
1881 m_subgraphs.push_back( subgraph );
1882 m_driver_subgraphs.push_back( subgraph );
1883
1884 global_power_pin_subgraphs[code] = subgraph;
1885 }
1886
1887 connection->SetSubgraphCode( subgraph->m_code );
1888 }
1889}
1890
1891
1893{
1894 // Here we do all the local (sheet) processing of each subgraph, including assigning net
1895 // codes, merging subgraphs together that use label connections, etc.
1896
1897 // Cache remaining valid subgraphs by sheet path
1898 for( CONNECTION_SUBGRAPH* subgraph : m_driver_subgraphs )
1899 m_sheet_to_subgraphs_map[ subgraph->m_sheet ].emplace_back( subgraph );
1900
1901 std::unordered_set<CONNECTION_SUBGRAPH*> invalidated_subgraphs;
1902
1903 for( CONNECTION_SUBGRAPH* subgraph : m_driver_subgraphs )
1904 {
1905 if( subgraph->m_absorbed )
1906 continue;
1907
1908 SCH_CONNECTION* connection = subgraph->m_driver_connection;
1909 SCH_SHEET_PATH sheet = subgraph->m_sheet;
1910 wxString name = connection->Name();
1911
1912 // Test subgraphs with weak drivers for net name conflicts and fix them
1913 unsigned suffix = 1;
1914
1915 auto create_new_name =
1916 [&suffix]( SCH_CONNECTION* aConn ) -> wxString
1917 {
1918 wxString newName;
1919 wxString suffixStr = std::to_wstring( suffix );
1920
1921 // For group buses with a prefix, we can add the suffix to the prefix.
1922 // If they don't have a prefix, we force the creation of a prefix so that
1923 // two buses don't get inadvertently shorted together.
1924 if( aConn->Type() == CONNECTION_TYPE::BUS_GROUP )
1925 {
1926 wxString prefix = aConn->BusPrefix();
1927
1928 if( prefix.empty() )
1929 prefix = wxT( "BUS" ); // So result will be "BUS_1{...}"
1930
1931 // Use BusPrefix length to skip past any formatting markers
1932 // in the prefix (e.g. ~{RESET}) rather than AfterFirst('{')
1933 // which would split at a formatting brace.
1934 wxString members = aConn->Name().Mid( aConn->BusPrefix().length() );
1935
1936 newName << prefix << wxT( "_" ) << suffixStr << members;
1937
1938 aConn->ConfigureFromLabel( newName );
1939 }
1940 else
1941 {
1942 newName << aConn->Name() << wxT( "_" ) << suffixStr;
1943 aConn->SetSuffix( wxString( wxT( "_" ) ) << suffixStr );
1944 }
1945
1946 suffix++;
1947 return newName;
1948 };
1949
1950 if( !subgraph->m_strong_driver )
1951 {
1952 std::vector<CONNECTION_SUBGRAPH*> vec_empty;
1953 std::vector<CONNECTION_SUBGRAPH*>* vec = &vec_empty;
1954
1955 if( m_net_name_to_subgraphs_map.count( name ) )
1956 vec = &m_net_name_to_subgraphs_map.at( name );
1957
1958 // If we are a unique bus vector, check if we aren't actually unique because of another
1959 // subgraph with a similar bus vector
1960 if( vec->size() <= 1 && subgraph->m_driver_connection->Type() == CONNECTION_TYPE::BUS )
1961 {
1962 wxString prefixOnly = name.BeforeFirst( '[' ) + wxT( "[]" );
1963
1964 if( m_net_name_to_subgraphs_map.count( prefixOnly ) )
1965 vec = &m_net_name_to_subgraphs_map.at( prefixOnly );
1966 }
1967
1968 if( vec->size() > 1 )
1969 {
1970 wxString new_name = create_new_name( connection );
1971
1972 while( m_net_name_to_subgraphs_map.contains( new_name ) )
1973 new_name = create_new_name( connection );
1974
1975 wxLogTrace( ConnTrace, wxS( "%ld (%s) is weakly driven and not unique. Changing to %s." ),
1976 subgraph->m_code, name, new_name );
1977
1978 std::erase( *vec, subgraph );
1979
1980 m_net_name_to_subgraphs_map[new_name].emplace_back( subgraph );
1981
1982 name = new_name;
1983 }
1984 else if( subgraph->m_driver )
1985 {
1986 // If there is no conflict, promote sheet pins to be strong drivers so that they
1987 // will be considered below for propagation/merging.
1988
1989 // It is possible for this to generate a conflict if the sheet pin has the same
1990 // name as a global label on the same sheet, because global merging will then treat
1991 // this subgraph as if it had a matching local label. So, for those cases, we
1992 // don't apply this promotion
1993
1994 if( subgraph->m_driver->Type() == SCH_SHEET_PIN_T )
1995 {
1996 bool conflict = false;
1997 wxString global_name = connection->Name( true );
1998 auto kk = m_net_name_to_subgraphs_map.find( global_name );
1999
2000 if( kk != m_net_name_to_subgraphs_map.end() )
2001 {
2002 // A global will conflict if it is on the same sheet as this subgraph, since
2003 // it would be connected by implicit local label linking
2004 std::vector<CONNECTION_SUBGRAPH*>& candidates = kk->second;
2005
2006 for( const CONNECTION_SUBGRAPH* candidate : candidates )
2007 {
2008 if( candidate->m_sheet == sheet )
2009 conflict = true;
2010 }
2011 }
2012
2013 if( conflict )
2014 {
2015 wxLogTrace( ConnTrace, wxS( "%ld (%s) skipped for promotion due to potential conflict" ),
2016 subgraph->m_code, name );
2017 }
2018 else
2019 {
2020 UNITS_PROVIDER unitsProvider( schIUScale, EDA_UNITS::MM );
2021
2022 wxLogTrace( ConnTrace, wxS( "%ld (%s) weakly driven by unique sheet pin %s, promoting" ),
2023 subgraph->m_code, name,
2024 subgraph->m_driver->GetItemDescription( &unitsProvider, true ) );
2025
2026 subgraph->m_strong_driver = true;
2027 }
2028 }
2029 }
2030 }
2031
2032 // Assign net codes
2033 if( connection->IsBus() )
2034 {
2035 int code = -1;
2036 auto it = m_bus_name_to_code_map.find( name );
2037
2038 if( it != m_bus_name_to_code_map.end() )
2039 {
2040 code = it->second;
2041 }
2042 else
2043 {
2044 code = m_last_bus_code++;
2045 m_bus_name_to_code_map[ name ] = code;
2046 }
2047
2048 connection->SetBusCode( code );
2049 assignNetCodesToBus( connection );
2050 }
2051 else
2052 {
2053 assignNewNetCode( *connection );
2054 }
2055
2056 // Reset the flag for the next loop below
2057 subgraph->m_dirty = true;
2058
2059 // Next, we merge together subgraphs that have label connections, and create
2060 // neighbor links for subgraphs that are part of a bus on the same sheet.
2061 // For merging, we consider each possible strong driver.
2062
2063 // If this subgraph doesn't have a strong driver, let's skip it, since there is no
2064 // way it will be merged with anything.
2065 if( !subgraph->m_strong_driver )
2066 continue;
2067
2068 // candidate_subgraphs will contain each valid, non-bus subgraph on the same sheet
2069 // as the subgraph we are considering that has a strong driver.
2070 // Weakly driven subgraphs are not considered since they will never be absorbed or
2071 // form neighbor links.
2072 std::vector<CONNECTION_SUBGRAPH*> candidate_subgraphs;
2073 std::copy_if( m_sheet_to_subgraphs_map[ subgraph->m_sheet ].begin(),
2074 m_sheet_to_subgraphs_map[ subgraph->m_sheet ].end(),
2075 std::back_inserter( candidate_subgraphs ),
2076 [&] ( const CONNECTION_SUBGRAPH* candidate )
2077 {
2078 return ( !candidate->m_absorbed &&
2079 candidate->m_strong_driver &&
2080 candidate != subgraph );
2081 } );
2082
2083 // This is a list of connections on the current subgraph to compare to the
2084 // drivers of each candidate subgraph. If the current subgraph is a bus,
2085 // we should consider each bus member.
2086 std::vector< std::shared_ptr<SCH_CONNECTION> > connections_to_check;
2087
2088 // Also check the main driving connection
2089 connections_to_check.push_back( std::make_shared<SCH_CONNECTION>( *connection ) );
2090
2091 auto add_connections_to_check =
2092 [&] ( CONNECTION_SUBGRAPH* aSubgraph )
2093 {
2094 for( SCH_ITEM* possible_driver : aSubgraph->m_items )
2095 {
2096 if( possible_driver == aSubgraph->m_driver )
2097 continue;
2098
2099 auto c = getDefaultConnection( possible_driver, aSubgraph );
2100
2101 if( c )
2102 {
2103 if( c->Type() != aSubgraph->m_driver_connection->Type() )
2104 continue;
2105
2106 if( c->Name( true ) == aSubgraph->m_driver_connection->Name( true ) )
2107 continue;
2108
2109 connections_to_check.push_back( c );
2110 wxLogTrace( ConnTrace, wxS( "%lu (%s): Adding secondary driver %s" ),
2111 aSubgraph->m_code,
2112 aSubgraph->m_driver_connection->Name( true ),
2113 c->Name( true ) );
2114 }
2115 }
2116 };
2117
2118 // Now add other strong drivers
2119 // The actual connection attached to these items will have been overwritten
2120 // by the chosen driver of the subgraph, so we need to create a dummy connection
2121 add_connections_to_check( subgraph );
2122
2123 std::set<SCH_CONNECTION*> checked_connections;
2124
2125 for( unsigned i = 0; i < connections_to_check.size(); i++ )
2126 {
2127 auto member = connections_to_check[i];
2128
2129 // Don't check the same connection twice
2130 if( !checked_connections.insert( member.get() ).second )
2131 continue;
2132
2133 if( member->IsBus() )
2134 {
2135 connections_to_check.insert( connections_to_check.end(),
2136 member->Members().begin(),
2137 member->Members().end() );
2138 }
2139
2140 wxString test_name = member->Name( true );
2141
2142 for( CONNECTION_SUBGRAPH* candidate : candidate_subgraphs )
2143 {
2144 if( candidate->m_absorbed || candidate == subgraph )
2145 continue;
2146
2147 bool match = false;
2148
2149 if( candidate->m_driver_connection->Name( true ) == test_name )
2150 {
2151 match = true;
2152 }
2153 else
2154 {
2155 if( !candidate->m_multiple_drivers )
2156 continue;
2157
2158 for( SCH_ITEM *driver : candidate->m_drivers )
2159 {
2160 if( driver == candidate->m_driver )
2161 continue;
2162
2163 // Sheet pins are not candidates for merging
2164 if( driver->Type() == SCH_SHEET_PIN_T )
2165 continue;
2166
2167 if( driver->Type() == SCH_PIN_T )
2168 {
2169 auto pin = static_cast<SCH_PIN*>( driver );
2170
2171 if( pin->IsPower()
2172 && pin->GetDefaultNetName( sheet ) == test_name )
2173 {
2174 match = true;
2175 break;
2176 }
2177 }
2178 else
2179 {
2180 // Should we skip this if the driver type is not one of these types?
2181 wxASSERT( driver->Type() == SCH_LABEL_T ||
2182 driver->Type() == SCH_GLOBAL_LABEL_T ||
2183 driver->Type() == SCH_HIER_LABEL_T );
2184
2185 if( subgraph->GetNameForDriver( driver ) == test_name )
2186 {
2187 match = true;
2188 break;
2189 }
2190 }
2191 }
2192 }
2193
2194 if( match )
2195 {
2196 if( connection->IsBus() && candidate->m_driver_connection->IsNet() )
2197 {
2198 wxLogTrace( ConnTrace, wxS( "%lu (%s) has bus child %lu (%s)" ),
2199 subgraph->m_code, connection->Name(),
2200 candidate->m_code, member->Name() );
2201
2202 subgraph->m_bus_neighbors[member].insert( candidate );
2203 candidate->m_bus_parents[member].insert( subgraph );
2204 }
2205 else if( ( !connection->IsBus()
2206 && !candidate->m_driver_connection->IsBus() )
2207 || connection->Type() == candidate->m_driver_connection->Type() )
2208 {
2209 wxLogTrace( ConnTrace, wxS( "%lu (%s) absorbs neighbor %lu (%s)" ),
2210 subgraph->m_code, connection->Name(),
2211 candidate->m_code, candidate->m_driver_connection->Name() );
2212
2213 // Candidate may have other non-chosen drivers we need to follow
2214 add_connections_to_check( candidate );
2215
2216 subgraph->Absorb( candidate );
2217 invalidated_subgraphs.insert( subgraph );
2218 }
2219 }
2220 }
2221 }
2222 }
2223
2224 // Update any subgraph that was invalidated above
2225 for( CONNECTION_SUBGRAPH* subgraph : invalidated_subgraphs )
2226 {
2227 if( subgraph->m_absorbed )
2228 continue;
2229
2230 if( !subgraph->ResolveDrivers() )
2231 continue;
2232
2233 if( subgraph->m_driver_connection->IsBus() )
2234 assignNetCodesToBus( subgraph->m_driver_connection );
2235 else
2236 assignNewNetCode( *subgraph->m_driver_connection );
2237
2238 wxLogTrace( ConnTrace, wxS( "Re-resolving drivers for %lu (%s)" ),
2239 subgraph->m_code, subgraph->m_driver_connection->Name() );
2240 }
2241
2242}
2243
2244
2245// TODO(JE) This won't give the same subgraph IDs (and eventually net/graph codes)
2246// to the same subgraph necessarily if it runs over and over again on the same
2247// sheet. We need:
2248//
2249// a) a cache of net/bus codes, like used before
2250// b) to persist the CONNECTION_GRAPH globally so the cache is persistent,
2251// c) some way of trying to avoid changing net names. so we should keep track
2252// of the previous driver of a net, and if it comes down to choosing between
2253// equally-prioritized drivers, choose the one that already exists as a driver
2254// on some portion of the items.
2255
2256
2257void CONNECTION_GRAPH::buildConnectionGraph( std::function<void( SCH_ITEM* )>* aChangedItemHandler,
2258 bool aUnconditional )
2259{
2260 // Recache all bus aliases for later use
2261 wxCHECK_RET( m_schematic, wxT( "Connection graph cannot be built without schematic pointer" ) );
2262
2263 m_bus_alias_cache.clear();
2264
2265 for( const std::shared_ptr<BUS_ALIAS>& alias : m_schematic->GetAllBusAliases() )
2266 {
2267 if( alias )
2268 m_bus_alias_cache[alias->GetName()] = alias;
2269 }
2270
2271 PROF_TIMER sub_graph( "buildItemSubGraphs" );
2273
2274 if( wxLog::IsAllowedTraceMask( DanglingProfileMask ) )
2275 sub_graph.Show();
2276
2281
2283
2285
2287
2289
2290 PROF_TIMER proc_sub_graph( "ProcessSubGraphs" );
2292
2293 if( wxLog::IsAllowedTraceMask( DanglingProfileMask ) )
2294 proc_sub_graph.Show();
2295
2296 // Absorbed subgraphs should no longer be considered
2297 std::erase_if( m_driver_subgraphs, [&]( const CONNECTION_SUBGRAPH* candidate ) -> bool
2298 {
2299 return candidate->m_absorbed;
2300 } );
2301
2302 // Store global subgraphs for later reference
2303 std::vector<CONNECTION_SUBGRAPH*> global_subgraphs;
2304 std::copy_if( m_driver_subgraphs.begin(), m_driver_subgraphs.end(),
2305 std::back_inserter( global_subgraphs ),
2306 [&] ( const CONNECTION_SUBGRAPH* candidate ) -> bool
2307 {
2308 return !candidate->m_local_driver;
2309 } );
2310
2311 // Recache remaining valid subgraphs by sheet path
2313
2314 for( CONNECTION_SUBGRAPH* subgraph : m_driver_subgraphs )
2315 m_sheet_to_subgraphs_map[ subgraph->m_sheet ].emplace_back( subgraph );
2316
2318
2319 auto results = tp.submit_loop( 0, m_driver_subgraphs.size(),
2320 [&]( const int ii )
2321 {
2322 m_driver_subgraphs[ii]->UpdateItemConnections();
2323 });
2324
2325 results.wait();
2326
2327 // Next time through the subgraphs, we do some post-processing to handle things like
2328 // connecting bus members to their neighboring subgraphs, and then propagate connections
2329 // through the hierarchy
2330 for( CONNECTION_SUBGRAPH* subgraph : m_driver_subgraphs )
2331 {
2332 if( !subgraph->m_dirty )
2333 continue;
2334
2335 wxLogTrace( ConnTrace, wxS( "Processing %lu (%s) for propagation" ),
2336 subgraph->m_code, subgraph->m_driver_connection->Name() );
2337
2338 // For subgraphs that are driven by a global (power port or label) and have more
2339 // than one global driver, we need to seek out other subgraphs driven by the
2340 // same name as the non-chosen driver and update them to match the chosen one.
2341
2342 if( !subgraph->m_local_driver && subgraph->m_multiple_drivers )
2343 {
2344 for( SCH_ITEM* driver : subgraph->m_drivers )
2345 {
2346 if( driver == subgraph->m_driver )
2347 continue;
2348
2349 const wxString& secondary_name = subgraph->GetNameForDriver( driver );
2350
2351 if( secondary_name == subgraph->m_driver_connection->Name() )
2352 continue;
2353
2354 bool secondary_is_global = CONNECTION_SUBGRAPH::GetDriverPriority( driver )
2356
2357 for( CONNECTION_SUBGRAPH* candidate : global_subgraphs )
2358 {
2359 if( candidate == subgraph )
2360 continue;
2361
2362 if( !secondary_is_global && candidate->m_sheet != subgraph->m_sheet )
2363 continue;
2364
2365 for( SCH_ITEM* candidate_driver : candidate->m_drivers )
2366 {
2367 if( candidate->GetNameForDriver( candidate_driver ) == secondary_name )
2368 {
2369 wxLogTrace( ConnTrace, wxS( "Global %lu (%s) promoted to %s" ),
2370 candidate->m_code, candidate->m_driver_connection->Name(),
2371 subgraph->m_driver_connection->Name() );
2372
2373 candidate->m_driver_connection->Clone( *subgraph->m_driver_connection );
2374
2375 candidate->m_dirty = false;
2376 propagateToNeighbors( candidate, false );
2377 }
2378 }
2379 }
2380 }
2381 }
2382
2383 // This call will handle descending the hierarchy and updating child subgraphs
2384 propagateToNeighbors( subgraph, false );
2385 }
2386
2387 // After processing and allowing some to be skipped if they have hierarchical
2388 // pins connecting both up and down the hierarchy, we check to see if any of them
2389 // have not been processed. This would indicate that they do not have off-sheet connections
2390 // but we still need to handle the subgraph
2391 for( CONNECTION_SUBGRAPH* subgraph : m_driver_subgraphs )
2392 {
2393 if( subgraph->m_dirty )
2394 propagateToNeighbors( subgraph, true );
2395 }
2396
2397 // Handle buses that have been linked together somewhere by member (net) connections.
2398 // This feels a bit hacky, perhaps this algorithm should be revisited in the future.
2399
2400 // For net subgraphs that have more than one bus parent, we need to ensure that those
2401 // buses are linked together in the final netlist. The final name of each bus might not
2402 // match the local name that was used to establish the parent-child relationship, because
2403 // the bus may have been renamed by a hierarchical connection. So, for each of these cases,
2404 // we need to identify the appropriate bus members to link together (and their final names),
2405 // and then update all instances of the old name in the hierarchy.
2406 for( CONNECTION_SUBGRAPH* subgraph : m_driver_subgraphs )
2407 {
2408 // All SGs should have been processed by propagateToNeighbors above
2409 // Should we skip all of this if the subgraph is not dirty?
2410 wxASSERT_MSG( !subgraph->m_dirty,
2411 wxS( "Subgraph not processed by propagateToNeighbors!" ) );
2412
2413 if( subgraph->m_bus_parents.size() < 2 )
2414 continue;
2415
2416 SCH_CONNECTION* conn = subgraph->m_driver_connection;
2417
2418 wxLogTrace( ConnTrace, wxS( "%lu (%s) has multiple bus parents" ),
2419 subgraph->m_code, conn->Name() );
2420
2421 // Should we skip everything after this if this is not a net?
2422 wxCHECK2( conn->IsNet(), continue );
2423
2424 for( const auto& ii : subgraph->m_bus_parents )
2425 {
2426 SCH_CONNECTION* link_member = ii.first.get();
2427
2428 for( CONNECTION_SUBGRAPH* parent : ii.second )
2429 {
2430 while( parent->m_absorbed )
2431 parent = parent->m_absorbed_by;
2432
2433 SCH_CONNECTION* match = matchBusMember( parent->m_driver_connection, link_member );
2434
2435 if( !match )
2436 {
2437 wxLogTrace( ConnTrace, wxS( "Warning: could not match %s inside %lu (%s)" ),
2438 conn->Name(), parent->m_code, parent->m_driver_connection->Name() );
2439 continue;
2440 }
2441
2442 if( conn->Name() != match->Name() )
2443 {
2444 wxString old_name = match->Name();
2445
2446 wxLogTrace( ConnTrace, wxS( "Updating %lu (%s) member %s to %s" ),
2447 parent->m_code, parent->m_driver_connection->Name(), old_name, conn->Name() );
2448
2449 match->Clone( *conn );
2450
2451 auto jj = m_net_name_to_subgraphs_map.find( old_name );
2452
2453 if( jj == m_net_name_to_subgraphs_map.end() )
2454 continue;
2455
2456 // Copy the vector to avoid iterator invalidation when recaching
2457 std::vector<CONNECTION_SUBGRAPH*> old_subgraphs = jj->second;
2458
2459 for( CONNECTION_SUBGRAPH* old_sg : old_subgraphs )
2460 {
2461 while( old_sg->m_absorbed )
2462 old_sg = old_sg->m_absorbed_by;
2463
2464 wxString old_sg_name = old_sg->m_driver_connection->Name();
2465 old_sg->m_driver_connection->Clone( *conn );
2466
2467 if( old_sg_name != old_sg->m_driver_connection->Name() )
2468 recacheSubgraphName( old_sg, old_sg_name );
2469 }
2470 }
2471 }
2472 }
2473 }
2474
2475 auto updateItemConnectionsTask =
2476 [&]( CONNECTION_SUBGRAPH* subgraph ) -> size_t
2477 {
2478 // Make sure weakly-driven single-pin nets get the unconnected_ prefix
2479 if( !subgraph->m_strong_driver
2480 && subgraph->m_drivers.size() == 1
2481 && subgraph->m_driver->Type() == SCH_PIN_T )
2482 {
2483 SCH_PIN* pin = static_cast<SCH_PIN*>( subgraph->m_driver );
2484 wxString name = pin->GetDefaultNetName( subgraph->m_sheet, true );
2485
2486 subgraph->m_driver_connection->ConfigureFromLabel( name );
2487 }
2488
2489 subgraph->m_dirty = false;
2490 subgraph->UpdateItemConnections();
2491
2492 // No other processing to do on buses
2493 if( subgraph->m_driver_connection->IsBus() )
2494 return 0;
2495
2496 // As a visual aid, we can check sheet pins that are driven by themselves to see
2497 // if they should be promoted to buses
2498 if( subgraph->m_driver && subgraph->m_driver->Type() == SCH_SHEET_PIN_T )
2499 {
2500 SCH_SHEET_PIN* pin = static_cast<SCH_SHEET_PIN*>( subgraph->m_driver );
2501
2502 if( SCH_SHEET* sheet = pin->GetParent() )
2503 {
2504 wxString pinText = pin->GetShownText( false );
2505 SCH_SCREEN* screen = sheet->GetScreen();
2506
2507 for( SCH_ITEM* item : screen->Items().OfType( SCH_HIER_LABEL_T ) )
2508 {
2509 SCH_HIERLABEL* label = static_cast<SCH_HIERLABEL*>( item );
2510
2511 if( label->GetShownText( &subgraph->m_sheet, false ) == pinText )
2512 {
2513 SCH_SHEET_PATH path = subgraph->m_sheet;
2514 path.push_back( sheet );
2515
2516 SCH_CONNECTION* parent_conn = label->Connection( &path );
2517
2518 if( parent_conn && parent_conn->IsBus() )
2519 subgraph->m_driver_connection->SetType( CONNECTION_TYPE::BUS );
2520
2521 break;
2522 }
2523 }
2524
2525 if( subgraph->m_driver_connection->IsBus() )
2526 return 0;
2527 }
2528 }
2529
2530 return 1;
2531 };
2532
2533 auto results2 = tp.submit_loop( 0, m_driver_subgraphs.size(),
2534 [&]( const int ii )
2535 {
2536 updateItemConnectionsTask( m_driver_subgraphs[ii] );
2537 } );
2538 results2.wait();
2539
2542
2543 for( CONNECTION_SUBGRAPH* subgraph : m_driver_subgraphs )
2544 {
2545 NET_NAME_CODE_CACHE_KEY key = { subgraph->GetNetName(),
2546 subgraph->m_driver_connection->NetCode() };
2547 m_net_code_to_subgraphs_map[ key ].push_back( subgraph );
2548
2549 m_net_name_to_subgraphs_map[subgraph->m_driver_connection->Name()].push_back( subgraph );
2550 }
2551
2552 std::shared_ptr<NET_SETTINGS>& netSettings = m_schematic->Project().GetProjectFile().m_NetSettings;
2553 std::map<wxString, std::set<wxString>> oldAssignments = netSettings->GetNetclassLabelAssignments();
2554 std::set<wxString> affectedNetclassNetAssignments;
2555
2556 netSettings->ClearNetclassLabelAssignments();
2557
2558 auto dirtySubgraphs =
2559 [&]( const std::vector<CONNECTION_SUBGRAPH*>& subgraphs )
2560 {
2561 if( aChangedItemHandler )
2562 {
2563 for( const CONNECTION_SUBGRAPH* subgraph : subgraphs )
2564 {
2565 for( SCH_ITEM* item : subgraph->m_items )
2566 (*aChangedItemHandler)( item );
2567 }
2568 }
2569 };
2570
2571 auto checkNetclassDrivers =
2572 [&]( const wxString& netName, const std::vector<CONNECTION_SUBGRAPH*>& subgraphs )
2573 {
2574 wxCHECK_RET( !subgraphs.empty(), wxS( "Invalid empty subgraph" ) );
2575
2576 std::set<wxString> netclasses;
2577
2578 // Collect all netclasses on all subgraphs for this net
2579 for( const CONNECTION_SUBGRAPH* subgraph : subgraphs )
2580 {
2581 for( SCH_ITEM* item : subgraph->m_items )
2582 {
2583 for( const auto& [name, provider] : subgraph->GetNetclassesForDriver( item ) )
2584 netclasses.insert( name );
2585 }
2586 }
2587
2588 // Append the netclasses to any included bus members
2589 for( const CONNECTION_SUBGRAPH* subgraph : subgraphs )
2590 {
2591 if( subgraph->m_driver_connection->IsBus() )
2592 {
2593 auto processBusMember = [&, this]( const SCH_CONNECTION* member )
2594 {
2595 if( !netclasses.empty() )
2596 {
2597 netSettings->AppendNetclassLabelAssignment( member->Name(), netclasses );
2598 }
2599
2600 auto ii = m_net_name_to_subgraphs_map.find( member->Name() );
2601
2602 if( oldAssignments.count( member->Name() ) )
2603 {
2604 if( oldAssignments[member->Name()] != netclasses )
2605 {
2606 affectedNetclassNetAssignments.insert( member->Name() );
2607
2608 if( ii != m_net_name_to_subgraphs_map.end() )
2609 dirtySubgraphs( ii->second );
2610 }
2611 }
2612 else if( !netclasses.empty() )
2613 {
2614 affectedNetclassNetAssignments.insert( member->Name() );
2615
2616 if( ii != m_net_name_to_subgraphs_map.end() )
2617 dirtySubgraphs( ii->second );
2618 }
2619 };
2620
2621 for( const std::shared_ptr<SCH_CONNECTION>& member : subgraph->m_driver_connection->Members() )
2622 {
2623 // Check if this member itself is a bus (which can be the case for vector buses as members
2624 // of a bus, see https://gitlab.com/kicad/code/kicad/-/issues/16545
2625 if( member->IsBus() )
2626 {
2627 for( const std::shared_ptr<SCH_CONNECTION>& nestedMember : member->Members() )
2628 processBusMember( nestedMember.get() );
2629 }
2630 else
2631 {
2632 processBusMember( member.get() );
2633 }
2634 }
2635 }
2636 }
2637
2638 // Assign the netclasses to the root netname
2639 if( !netclasses.empty() )
2640 {
2641 netSettings->AppendNetclassLabelAssignment( netName, netclasses );
2642 }
2643
2644 if( oldAssignments.count( netName ) )
2645 {
2646 if( oldAssignments[netName] != netclasses )
2647 {
2648 affectedNetclassNetAssignments.insert( netName );
2649 dirtySubgraphs( subgraphs );
2650 }
2651 }
2652 else if( !netclasses.empty() )
2653 {
2654 affectedNetclassNetAssignments.insert( netName );
2655 dirtySubgraphs( subgraphs );
2656 }
2657 };
2658
2659 // Check for netclass assignments
2660 for( const auto& [ netname, subgraphs ] : m_net_name_to_subgraphs_map )
2661 checkNetclassDrivers( netname, subgraphs );
2662
2663 if( !aUnconditional )
2664 {
2665 for( auto& [netname, netclasses] : oldAssignments )
2666 {
2667 if( netSettings->GetNetclassLabelAssignments().count( netname )
2668 || affectedNetclassNetAssignments.count( netname ) )
2669 {
2670 continue;
2671 }
2672
2673 netSettings->SetNetclassLabelAssignment( netname, netclasses );
2674 }
2675 }
2676}
2677
2678
2679int CONNECTION_GRAPH::getOrCreateNetCode( const wxString& aNetName )
2680{
2681 int code;
2682
2683 auto it = m_net_name_to_code_map.find( aNetName );
2684
2685 if( it == m_net_name_to_code_map.end() )
2686 {
2687 code = m_last_net_code++;
2688 m_net_name_to_code_map[ aNetName ] = code;
2689 }
2690 else
2691 {
2692 code = it->second;
2693 }
2694
2695 return code;
2696}
2697
2698
2700{
2701 int code = getOrCreateNetCode( aConnection.Name() );
2702
2703 aConnection.SetNetCode( code );
2704
2705 return code;
2706}
2707
2708
2710{
2711 std::vector<std::shared_ptr<SCH_CONNECTION>> connections_to_check( aConnection->Members() );
2712
2713 for( unsigned i = 0; i < connections_to_check.size(); i++ )
2714 {
2715 const std::shared_ptr<SCH_CONNECTION>& member = connections_to_check[i];
2716
2717 if( member->IsBus() )
2718 {
2719 connections_to_check.insert( connections_to_check.end(),
2720 member->Members().begin(),
2721 member->Members().end() );
2722 continue;
2723 }
2724
2725 assignNewNetCode( *member );
2726 }
2727}
2728
2729
2731{
2732 SCH_CONNECTION* conn = aSubgraph->m_driver_connection;
2733 std::vector<CONNECTION_SUBGRAPH*> search_list;
2734 std::unordered_set<CONNECTION_SUBGRAPH*> visited;
2735 std::unordered_set<SCH_CONNECTION*> stale_bus_members;
2736
2737 auto visit =[&]( CONNECTION_SUBGRAPH* aParent )
2738 {
2739 for( SCH_SHEET_PIN* pin : aParent->m_hier_pins )
2740 {
2741 SCH_SHEET_PATH path = aParent->m_sheet;
2742 path.push_back( pin->GetParent() );
2743
2744 auto it = m_sheet_to_subgraphs_map.find( path );
2745
2746 if( it == m_sheet_to_subgraphs_map.end() )
2747 continue;
2748
2749 for( CONNECTION_SUBGRAPH* candidate : it->second )
2750 {
2751 if( !candidate->m_strong_driver
2752 || candidate->m_hier_ports.empty()
2753 || visited.contains( candidate ) )
2754 {
2755 continue;
2756 }
2757
2758 for( SCH_HIERLABEL* label : candidate->m_hier_ports )
2759 {
2760 if( candidate->GetNameForDriver( label ) == aParent->GetNameForDriver( pin ) )
2761 {
2762 wxLogTrace( ConnTrace, wxS( "%lu: found child %lu (%s)" ), aParent->m_code,
2763 candidate->m_code, candidate->m_driver_connection->Name() );
2764
2765 candidate->m_hier_parent = aParent;
2766 aParent->m_hier_children.insert( candidate );
2767
2768 // Should we skip adding the candidate to the list if the parent and candidate subgraphs
2769 // are not the same?
2770 wxASSERT( candidate->m_graph == aParent->m_graph );
2771
2772 search_list.push_back( candidate );
2773 break;
2774 }
2775 }
2776 }
2777 }
2778
2779 for( SCH_HIERLABEL* label : aParent->m_hier_ports )
2780 {
2781 SCH_SHEET_PATH path = aParent->m_sheet;
2782 path.pop_back();
2783
2784 auto it = m_sheet_to_subgraphs_map.find( path );
2785
2786 if( it == m_sheet_to_subgraphs_map.end() )
2787 continue;
2788
2789 for( CONNECTION_SUBGRAPH* candidate : it->second )
2790 {
2791 if( candidate->m_hier_pins.empty()
2792 || visited.contains( candidate )
2793 || candidate->m_driver_connection->Type() != aParent->m_driver_connection->Type() )
2794 {
2795 continue;
2796 }
2797
2798 const KIID& last_parent_uuid = aParent->m_sheet.Last()->m_Uuid;
2799
2800 for( SCH_SHEET_PIN* pin : candidate->m_hier_pins )
2801 {
2802 // If the last sheet UUIDs won't match, no need to check the full path
2803 if( pin->GetParent()->m_Uuid != last_parent_uuid )
2804 continue;
2805
2806 SCH_SHEET_PATH pin_path = path;
2807 pin_path.push_back( pin->GetParent() );
2808
2809 if( pin_path != aParent->m_sheet )
2810 continue;
2811
2812 if( aParent->GetNameForDriver( label ) == candidate->GetNameForDriver( pin ) )
2813 {
2814 wxLogTrace( ConnTrace, wxS( "%lu: found additional parent %lu (%s)" ),
2815 aParent->m_code, candidate->m_code, candidate->m_driver_connection->Name() );
2816
2817 aParent->m_hier_children.insert( candidate );
2818 search_list.push_back( candidate );
2819 break;
2820 }
2821 }
2822 }
2823 }
2824 };
2825
2826 auto propagate_bus_neighbors = [&]( CONNECTION_SUBGRAPH* aParentGraph )
2827 {
2828 // Sort bus neighbors by name to ensure deterministic processing order.
2829 // When multiple bus members (e.g., A0, A1, A2, A3) all connect to the same
2830 // shorted net in a child sheet, the first one processed "wins" and sets
2831 // the net name. Sorting ensures the alphabetically-first name is chosen.
2832 std::vector<std::shared_ptr<SCH_CONNECTION>> sortedMembers;
2833
2834 for( const auto& kv : aParentGraph->m_bus_neighbors )
2835 sortedMembers.push_back( kv.first );
2836
2837 std::sort( sortedMembers.begin(), sortedMembers.end(),
2838 []( const std::shared_ptr<SCH_CONNECTION>& a,
2839 const std::shared_ptr<SCH_CONNECTION>& b )
2840 {
2841 return a->Name() < b->Name();
2842 } );
2843
2844 for( const std::shared_ptr<SCH_CONNECTION>& member_conn : sortedMembers )
2845 {
2846 const auto& kv_it = aParentGraph->m_bus_neighbors.find( member_conn );
2847
2848 if( kv_it == aParentGraph->m_bus_neighbors.end() )
2849 continue;
2850
2851 for( CONNECTION_SUBGRAPH* neighbor : kv_it->second )
2852 {
2853 // May have been absorbed but won't have been deleted
2854 while( neighbor->m_absorbed )
2855 neighbor = neighbor->m_absorbed_by;
2856
2857 SCH_CONNECTION* parent = aParentGraph->m_driver_connection;
2858
2859 // Now member may be out of date, since we just cloned the
2860 // connection from higher up in the hierarchy. We need to
2861 // figure out what the actual new connection is.
2862 SCH_CONNECTION* member = matchBusMember( parent, member_conn.get() );
2863
2864 if( !member )
2865 {
2866 // Try harder: we might match on a secondary driver
2867 for( CONNECTION_SUBGRAPH* sg : kv_it->second )
2868 {
2869 if( sg->m_multiple_drivers )
2870 {
2871 SCH_SHEET_PATH sheet = sg->m_sheet;
2872
2873 for( SCH_ITEM* driver : sg->m_drivers )
2874 {
2875 auto c = getDefaultConnection( driver, sg );
2876 member = matchBusMember( parent, c.get() );
2877
2878 if( member )
2879 break;
2880 }
2881 }
2882
2883 if( member )
2884 break;
2885 }
2886 }
2887
2888 // This is bad, probably an ERC error
2889 if( !member )
2890 {
2891 wxLogTrace( ConnTrace, wxS( "Could not match bus member %s in %s" ),
2892 member_conn->Name(), parent->Name() );
2893 continue;
2894 }
2895
2896 SCH_CONNECTION* neighbor_conn = neighbor->m_driver_connection;
2897
2898 wxCHECK2( neighbor_conn, continue );
2899
2900 wxString neighbor_name = neighbor_conn->Name();
2901
2902 // Matching name: no update needed
2903 if( neighbor_name == member->Name() )
2904 continue;
2905
2906 // Was this neighbor already updated from a different sheet? Don't rename it again,
2907 // unless this same parent bus updated it and the bus member name has since changed
2908 // (which can happen when a bus member is renamed via stale member update, issue #18299).
2909 if( neighbor_conn->Sheet() != neighbor->m_sheet )
2910 {
2911 // If the neighbor's connection sheet doesn't match this parent bus's sheet,
2912 // it was updated by a different bus entirely. Don't override.
2913 if( neighbor_conn->Sheet() != parent->Sheet() )
2914 continue;
2915
2916 // If the neighbor's connection sheet matches this parent bus's sheet but
2917 // the names differ, check if the neighbor's current name still matches
2918 // a member of this bus. If it does, the neighbor was updated by a different
2919 // member of this same bus and we should preserve that (determinism).
2920 // If it doesn't match any member, the bus member was renamed and we should
2921 // update. We compare by name rather than VectorIndex because non-bus
2922 // connections (e.g., "GND" from power pin propagation) have a default
2923 // VectorIndex of 0 that falsely matches the first bus member.
2924 bool alreadyUpdatedByBusMember = false;
2925
2926 for( const auto& m : parent->Members() )
2927 {
2928 if( m->Name() == neighbor_name )
2929 {
2930 alreadyUpdatedByBusMember = true;
2931 break;
2932 }
2933 }
2934
2935 if( alreadyUpdatedByBusMember )
2936 continue;
2937 }
2938
2939 // Safety check against infinite recursion
2940 wxCHECK2_MSG( neighbor_conn->IsNet(), continue,
2941 wxS( "\"" ) + neighbor_name + wxS( "\" is not a net." ) );
2942
2943 wxLogTrace( ConnTrace, wxS( "%lu (%s) connected to bus member %s (local %s)" ),
2944 neighbor->m_code, neighbor_name, member->Name(), member->LocalName() );
2945
2946 // Take whichever name is higher priority
2949 {
2950 member->Clone( *neighbor_conn );
2951 stale_bus_members.insert( member );
2952 }
2953 else
2954 {
2955 neighbor_conn->Clone( *member );
2956
2957 recacheSubgraphName( neighbor, neighbor_name );
2958
2959 // Recurse onto this neighbor in case it needs to re-propagate
2960 neighbor->m_dirty = true;
2961 propagateToNeighbors( neighbor, aForce );
2962
2963 // After hierarchy propagation, the neighbor's connection may have been
2964 // updated to a higher-priority driver (e.g., a power symbol discovered
2965 // through hierarchical sheet pins). If so, update the bus member to match.
2966 // This ensures that net names propagate correctly through bus connections
2967 // that span hierarchical boundaries (issue #18119).
2968 if( neighbor_conn->Name() != member->Name() )
2969 {
2970 member->Clone( *neighbor_conn );
2971 stale_bus_members.insert( member );
2972 }
2973 }
2974 }
2975 }
2976 };
2977
2978 // If we are a bus, we must propagate to local neighbors and then the hierarchy
2979 if( conn->IsBus() )
2980 propagate_bus_neighbors( aSubgraph );
2981
2982 // If we have both ports and pins, skip processing as we'll be visited by a parent or child.
2983 // If we only have one or the other, process (we can either go bottom-up or top-down depending
2984 // on which subgraph comes up first)
2985 if( !aForce && !aSubgraph->m_hier_ports.empty() && !aSubgraph->m_hier_pins.empty() )
2986 {
2987 wxLogTrace( ConnTrace, wxS( "%lu (%s) has both hier ports and pins; deferring processing" ),
2988 aSubgraph->m_code, conn->Name() );
2989 return;
2990 }
2991 else if( aSubgraph->m_hier_ports.empty() && aSubgraph->m_hier_pins.empty() )
2992 {
2993 wxLogTrace( ConnTrace, wxS( "%lu (%s) has no hier pins or ports on sheet %s; marking clean" ),
2994 aSubgraph->m_code, conn->Name(), aSubgraph->m_sheet.PathHumanReadable() );
2995 aSubgraph->m_dirty = false;
2996 return;
2997 }
2998
2999 visited.insert( aSubgraph );
3000
3001 wxLogTrace( ConnTrace, wxS( "Propagating %lu (%s) to subsheets" ),
3002 aSubgraph->m_code, aSubgraph->m_driver_connection->Name() );
3003
3004 visit( aSubgraph );
3005
3006 for( unsigned i = 0; i < search_list.size(); i++ )
3007 {
3008 auto child = search_list[i];
3009
3010 if( visited.insert( child ).second )
3011 visit( child );
3012
3013 child->m_dirty = false;
3014 }
3015
3016 // Now, find the best driver for this chain of subgraphs
3017 CONNECTION_SUBGRAPH* bestDriver = aSubgraph;
3019 bool bestIsStrong = ( highest >= CONNECTION_SUBGRAPH::PRIORITY::HIER_LABEL );
3020 wxString bestName = aSubgraph->m_driver_connection->Name();
3021
3022 // Check if a subsheet has a higher-priority connection to the same net
3024 {
3025 for( CONNECTION_SUBGRAPH* subgraph : visited )
3026 {
3027 if( subgraph == aSubgraph )
3028 continue;
3029
3031 CONNECTION_SUBGRAPH::GetDriverPriority( subgraph->m_driver );
3032
3033 bool candidateStrong = ( priority >= CONNECTION_SUBGRAPH::PRIORITY::HIER_LABEL );
3034 wxString candidateName = subgraph->m_driver_connection->Name();
3035 bool shorterPath = subgraph->m_sheet.size() < bestDriver->m_sheet.size();
3036 bool asGoodPath = subgraph->m_sheet.size() <= bestDriver->m_sheet.size();
3037
3038 // Pick a better driving subgraph if it:
3039 // a) has a power pin or global driver
3040 // b) is a strong driver and we're a weak driver
3041 // c) is a higher priority strong driver
3042 // d) matches our priority, is a strong driver, and has a shorter path
3043 // e) matches our strength and is at least as short, and is alphabetically lower
3044
3046 ( !bestIsStrong && candidateStrong ) ||
3047 ( priority > highest && candidateStrong ) ||
3048 ( priority == highest && candidateStrong && shorterPath ) ||
3049 ( ( bestIsStrong == candidateStrong ) && asGoodPath && ( priority == highest ) &&
3050 ( candidateName < bestName ) ) )
3051 {
3052 bestDriver = subgraph;
3053 highest = priority;
3054 bestIsStrong = candidateStrong;
3055 bestName = candidateName;
3056 }
3057 }
3058 }
3059
3060 if( bestDriver != aSubgraph )
3061 {
3062 wxLogTrace( ConnTrace, wxS( "%lu (%s) overridden by new driver %lu (%s)" ),
3063 aSubgraph->m_code, aSubgraph->m_driver_connection->Name(), bestDriver->m_code,
3064 bestDriver->m_driver_connection->Name() );
3065 }
3066
3067 conn = bestDriver->m_driver_connection;
3068
3069 for( CONNECTION_SUBGRAPH* subgraph : visited )
3070 {
3071 wxString old_name = subgraph->m_driver_connection->Name();
3072
3073 subgraph->m_driver_connection->Clone( *conn );
3074
3075 if( old_name != conn->Name() )
3076 recacheSubgraphName( subgraph, old_name );
3077
3078 if( conn->IsBus() )
3079 propagate_bus_neighbors( subgraph );
3080 }
3081
3082 // Somewhere along the way, a bus member may have been upgraded to a global or power label.
3083 // Because this can happen anywhere, we need a second pass to update all instances of that bus
3084 // member to have the correct connection info
3085 if( conn->IsBus() && !stale_bus_members.empty() )
3086 {
3087 std::unordered_set<SCH_CONNECTION*> cached_members = stale_bus_members;
3088
3089 for( SCH_CONNECTION* stale_member : cached_members )
3090 {
3091 for( CONNECTION_SUBGRAPH* subgraph : visited )
3092 {
3093 SCH_CONNECTION* member = matchBusMember( subgraph->m_driver_connection, stale_member );
3094
3095 if( !member )
3096 {
3097 wxLogTrace( ConnTrace, wxS( "WARNING: failed to match stale member %s in %s." ),
3098 stale_member->Name(), subgraph->m_driver_connection->Name() );
3099 continue;
3100 }
3101
3102 wxLogTrace( ConnTrace, wxS( "Updating %lu (%s) member %s to %s" ), subgraph->m_code,
3103 subgraph->m_driver_connection->Name(), member->LocalName(), stale_member->Name() );
3104
3105 member->Clone( *stale_member );
3106
3107 propagate_bus_neighbors( subgraph );
3108 }
3109 }
3110 }
3111
3112 aSubgraph->m_dirty = false;
3113}
3114
3115
3116std::shared_ptr<SCH_CONNECTION> CONNECTION_GRAPH::getDefaultConnection( SCH_ITEM* aItem,
3117 CONNECTION_SUBGRAPH* aSubgraph )
3118{
3119 std::shared_ptr<SCH_CONNECTION> c = std::shared_ptr<SCH_CONNECTION>( nullptr );
3120
3121 switch( aItem->Type() )
3122 {
3123 case SCH_PIN_T:
3124 if( static_cast<SCH_PIN*>( aItem )->IsPower() )
3125 c = std::make_shared<SCH_CONNECTION>( aItem, aSubgraph->m_sheet );
3126
3127 break;
3128
3129 case SCH_GLOBAL_LABEL_T:
3130 case SCH_HIER_LABEL_T:
3131 case SCH_LABEL_T:
3132 c = std::make_shared<SCH_CONNECTION>( aItem, aSubgraph->m_sheet );
3133 break;
3134
3135 default:
3136 break;
3137 }
3138
3139 if( c )
3140 {
3141 c->SetGraph( this );
3142 c->ConfigureFromLabel( aSubgraph->GetNameForDriver( aItem ) );
3143 }
3144
3145 return c;
3146}
3147
3148
3150 SCH_CONNECTION* aSearch )
3151{
3152 if( !aBusConnection->IsBus() )
3153 return nullptr;
3154
3155 SCH_CONNECTION* match = nullptr;
3156
3157 if( aBusConnection->Type() == CONNECTION_TYPE::BUS )
3158 {
3159 // Vector bus: compare against index, because we allow the name
3160 // to be different
3161
3162 for( const std::shared_ptr<SCH_CONNECTION>& bus_member : aBusConnection->Members() )
3163 {
3164 if( bus_member->VectorIndex() == aSearch->VectorIndex() )
3165 {
3166 match = bus_member.get();
3167 break;
3168 }
3169 }
3170 }
3171 else
3172 {
3173 // Group bus
3174 for( const std::shared_ptr<SCH_CONNECTION>& c : aBusConnection->Members() )
3175 {
3176 // Vector inside group: compare names, because for bus groups
3177 // we expect the naming to be consistent across all usages
3178 // TODO(JE) explain this in the docs
3179 if( c->Type() == CONNECTION_TYPE::BUS )
3180 {
3181 for( const std::shared_ptr<SCH_CONNECTION>& bus_member : c->Members() )
3182 {
3183 if( bus_member->LocalName() == aSearch->LocalName() )
3184 {
3185 match = bus_member.get();
3186 break;
3187 }
3188 }
3189 }
3190 else if( c->LocalName() == aSearch->LocalName() )
3191 {
3192 match = c.get();
3193 break;
3194 }
3195 }
3196 }
3197
3198 return match;
3199}
3200
3201
3202void CONNECTION_GRAPH::recacheSubgraphName( CONNECTION_SUBGRAPH* aSubgraph, const wxString& aOldName )
3203{
3204 auto it = m_net_name_to_subgraphs_map.find( aOldName );
3205
3206 if( it != m_net_name_to_subgraphs_map.end() )
3207 {
3208 std::vector<CONNECTION_SUBGRAPH*>& vec = it->second;
3209 std::erase( vec, aSubgraph );
3210 }
3211
3212 wxLogTrace( ConnTrace, wxS( "recacheSubgraphName: %s => %s" ), aOldName,
3213 aSubgraph->m_driver_connection->Name() );
3214
3215 m_net_name_to_subgraphs_map[aSubgraph->m_driver_connection->Name()].push_back( aSubgraph );
3216}
3217
3218
3219std::shared_ptr<BUS_ALIAS> CONNECTION_GRAPH::GetBusAlias( const wxString& aName )
3220{
3221 auto it = m_bus_alias_cache.find( aName );
3222
3223 return it != m_bus_alias_cache.end() ? it->second : nullptr;
3224}
3225
3226
3227std::vector<const CONNECTION_SUBGRAPH*> CONNECTION_GRAPH::GetBusesNeedingMigration()
3228{
3229 std::vector<const CONNECTION_SUBGRAPH*> ret;
3230
3231 for( CONNECTION_SUBGRAPH* subgraph : m_subgraphs )
3232 {
3233 // Graph is supposed to be up-to-date before calling this
3234 // Should we continue if the subgraph is not up to date?
3235 wxASSERT( !subgraph->m_dirty );
3236
3237 if( !subgraph->m_driver )
3238 continue;
3239
3240 SCH_SHEET_PATH* sheet = &subgraph->m_sheet;
3241 SCH_CONNECTION* connection = subgraph->m_driver->Connection( sheet );
3242
3243 if( !connection->IsBus() )
3244 continue;
3245
3246 auto labels = subgraph->GetVectorBusLabels();
3247
3248 if( labels.size() > 1 )
3249 {
3250 bool different = false;
3251 wxString first = static_cast<SCH_TEXT*>( labels.at( 0 ) )->GetShownText( sheet, false );
3252
3253 for( unsigned i = 1; i < labels.size(); ++i )
3254 {
3255 if( static_cast<SCH_TEXT*>( labels.at( i ) )->GetShownText( sheet, false ) != first )
3256 {
3257 different = true;
3258 break;
3259 }
3260 }
3261
3262 if( !different )
3263 continue;
3264
3265 wxLogTrace( ConnTrace, wxS( "SG %ld (%s) has multiple bus labels" ), subgraph->m_code,
3266 connection->Name() );
3267
3268 ret.push_back( subgraph );
3269 }
3270 }
3271
3272 return ret;
3273}
3274
3275
3277{
3278 wxString retval = aSubGraph->GetNetName();
3279 bool found = false;
3280
3281 // This is a hacky way to find the true subgraph net name (why do we not store it?)
3282 // TODO: Remove once the actual netname of the subgraph is stored with the subgraph
3283
3284 for( auto it = m_net_name_to_subgraphs_map.begin();
3285 it != m_net_name_to_subgraphs_map.end() && !found; ++it )
3286 {
3287 for( CONNECTION_SUBGRAPH* graph : it->second )
3288 {
3289 if( graph == aSubGraph )
3290 {
3291 retval = it->first;
3292 found = true;
3293 break;
3294 }
3295 }
3296 }
3297
3298 return retval;
3299}
3300
3301
3303 const SCH_SHEET_PATH& aPath )
3304{
3305 auto it = m_net_name_to_subgraphs_map.find( aNetName );
3306
3307 if( it == m_net_name_to_subgraphs_map.end() )
3308 return nullptr;
3309
3310 for( CONNECTION_SUBGRAPH* sg : it->second )
3311 {
3312 // Cache is supposed to be valid by now
3313 // Should we continue if the cache is not valid?
3314 wxASSERT( sg && !sg->m_absorbed && sg->m_driver_connection );
3315
3316 if( sg->m_sheet == aPath && sg->m_driver_connection->Name() == aNetName )
3317 return sg;
3318 }
3319
3320 return nullptr;
3321}
3322
3323
3325{
3326 auto it = m_net_name_to_subgraphs_map.find( aNetName );
3327
3328 if( it == m_net_name_to_subgraphs_map.end() )
3329 return nullptr;
3330
3331 // Should this return a nullptr if the map entry is empty?
3332 wxASSERT( !it->second.empty() );
3333
3334 return it->second[0];
3335}
3336
3337
3339{
3340 auto it = m_item_to_subgraph_map.find( aItem );
3341 CONNECTION_SUBGRAPH* ret = it != m_item_to_subgraph_map.end() ? it->second : nullptr;
3342
3343 while( ret && ret->m_absorbed )
3344 ret = ret->m_absorbed_by;
3345
3346 return ret;
3347}
3348
3349
3350const std::vector<CONNECTION_SUBGRAPH*>&
3351CONNECTION_GRAPH::GetAllSubgraphs( const wxString& aNetName ) const
3352{
3353 static const std::vector<CONNECTION_SUBGRAPH*> subgraphs;
3354
3355 auto it = m_net_name_to_subgraphs_map.find( aNetName );
3356
3357 if( it == m_net_name_to_subgraphs_map.end() )
3358 return subgraphs;
3359
3360 return it->second;
3361}
3362
3363
3365{
3366 int error_count = 0;
3367
3368 wxCHECK_MSG( m_schematic, true, wxS( "Null m_schematic in CONNECTION_GRAPH::RunERC" ) );
3369
3370 ERC_SETTINGS& settings = m_schematic->ErcSettings();
3371
3372 // We don't want to run many ERC checks more than once on a given screen even though it may
3373 // represent multiple sheets with multiple subgraphs. We can tell these apart by drivers.
3374 std::set<SCH_ITEM*> seenDriverInstances;
3375
3376 for( CONNECTION_SUBGRAPH* subgraph : m_subgraphs )
3377 {
3378 // There shouldn't be any null sub-graph pointers.
3379 wxCHECK2( subgraph, continue );
3380
3381 // Graph is supposed to be up-to-date before calling RunERC()
3382 // Should we continue if the subgraph is not up to date?
3383 wxASSERT( !subgraph->m_dirty );
3384
3385 if( subgraph->m_absorbed )
3386 continue;
3387
3388 if( seenDriverInstances.count( subgraph->m_driver ) )
3389 continue;
3390
3391 if( subgraph->m_driver )
3392 seenDriverInstances.insert( subgraph->m_driver );
3393
3404 if( settings.IsTestEnabled( ERCE_DRIVER_CONFLICT ) )
3405 {
3406 if( !ercCheckMultipleDrivers( subgraph ) )
3407 error_count++;
3408 }
3409
3410 subgraph->ResolveDrivers( false );
3411
3412 if( settings.IsTestEnabled( ERCE_BUS_TO_NET_CONFLICT ) )
3413 {
3414 if( !ercCheckBusToNetConflicts( subgraph ) )
3415 error_count++;
3416 }
3417
3418 if( settings.IsTestEnabled( ERCE_BUS_ENTRY_CONFLICT ) )
3419 {
3420 if( !ercCheckBusToBusEntryConflicts( subgraph ) )
3421 error_count++;
3422 }
3423
3424 if( settings.IsTestEnabled( ERCE_BUS_TO_BUS_CONFLICT ) )
3425 {
3426 if( !ercCheckBusToBusConflicts( subgraph ) )
3427 error_count++;
3428 }
3429
3430 if( settings.IsTestEnabled( ERCE_WIRE_DANGLING ) )
3431 {
3432 if( !ercCheckFloatingWires( subgraph ) )
3433 error_count++;
3434 }
3435
3437 {
3438 if( !ercCheckDanglingWireEndpoints( subgraph ) )
3439 error_count++;
3440 }
3441
3444 || settings.IsTestEnabled( ERCE_PIN_NOT_CONNECTED ) )
3445 {
3446 if( !ercCheckNoConnects( subgraph ) )
3447 error_count++;
3448 }
3449
3451 || settings.IsTestEnabled( ERCE_LABEL_SINGLE_PIN ) )
3452 {
3453 if( !ercCheckLabels( subgraph ) )
3454 error_count++;
3455 }
3456 }
3457
3458 if( settings.IsTestEnabled( ERCE_LABEL_NOT_CONNECTED ) )
3459 {
3460 error_count += ercCheckDirectiveLabels();
3461 }
3462
3463 // Hierarchical sheet checking is done at the schematic level
3465 || settings.IsTestEnabled( ERCE_PIN_NOT_CONNECTED ) )
3466 {
3467 error_count += ercCheckHierSheets();
3468 }
3469
3470 if( settings.IsTestEnabled( ERCE_SINGLE_GLOBAL_LABEL ) )
3471 {
3472 error_count += ercCheckSingleGlobalLabel();
3473 }
3474
3475 return error_count;
3476}
3477
3478
3480{
3481 wxCHECK( aSubgraph, false );
3482
3483 if( aSubgraph->m_multiple_drivers )
3484 {
3485 for( SCH_ITEM* driver : aSubgraph->m_drivers )
3486 {
3487 if( driver == aSubgraph->m_driver )
3488 continue;
3489
3490 if( driver->Type() == SCH_GLOBAL_LABEL_T
3491 || driver->Type() == SCH_HIER_LABEL_T
3492 || driver->Type() == SCH_LABEL_T
3493 || ( driver->Type() == SCH_PIN_T && static_cast<SCH_PIN*>( driver )->IsPower() ) )
3494 {
3495 const wxString& primaryName = aSubgraph->GetNameForDriver( aSubgraph->m_driver );
3496 const wxString& secondaryName = aSubgraph->GetNameForDriver( driver );
3497
3498 if( primaryName == secondaryName )
3499 continue;
3500
3501 wxString msg = wxString::Format( _( "Both %s and %s are attached to the same "
3502 "items; %s will be used in the netlist" ),
3503 primaryName, secondaryName, primaryName );
3504
3505 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_DRIVER_CONFLICT );
3506 ercItem->SetItems( aSubgraph->m_driver, driver );
3507 ercItem->SetSheetSpecificPath( aSubgraph->GetSheet() );
3508 ercItem->SetItemsSheetPaths( aSubgraph->GetSheet(), aSubgraph->m_sheet );
3509 ercItem->SetErrorMessage( msg );
3510
3511 SCH_MARKER* marker = new SCH_MARKER( std::move( ercItem ), driver->GetPosition() );
3512 aSubgraph->m_sheet.LastScreen()->Append( marker );
3513
3514 return false;
3515 }
3516 }
3517 }
3518
3519 return true;
3520}
3521
3522
3524{
3525 const SCH_SHEET_PATH& sheet = aSubgraph->m_sheet;
3526 SCH_SCREEN* screen = sheet.LastScreen();
3527
3528 SCH_ITEM* net_item = nullptr;
3529 SCH_ITEM* bus_item = nullptr;
3530 SCH_CONNECTION conn( this );
3531
3532 for( SCH_ITEM* item : aSubgraph->m_items )
3533 {
3534 switch( item->Type() )
3535 {
3536 case SCH_LINE_T:
3537 {
3538 if( item->GetLayer() == LAYER_BUS )
3539 bus_item = ( !bus_item ) ? item : bus_item;
3540 else
3541 net_item = ( !net_item ) ? item : net_item;
3542
3543 break;
3544 }
3545
3546 case SCH_LABEL_T:
3547 case SCH_GLOBAL_LABEL_T:
3548 case SCH_SHEET_PIN_T:
3549 case SCH_HIER_LABEL_T:
3550 {
3551 SCH_TEXT* text = static_cast<SCH_TEXT*>( item );
3552 conn.ConfigureFromLabel( EscapeString( text->GetShownText( &sheet, false ), CTX_NETNAME ) );
3553
3554 if( conn.IsBus() )
3555 bus_item = ( !bus_item ) ? item : bus_item;
3556 else
3557 net_item = ( !net_item ) ? item : net_item;
3558
3559 break;
3560 }
3561
3562 default:
3563 break;
3564 }
3565 }
3566
3567 if( net_item && bus_item )
3568 {
3569 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_BUS_TO_NET_CONFLICT );
3570 ercItem->SetSheetSpecificPath( sheet );
3571 ercItem->SetItems( net_item, bus_item );
3572
3573 SCH_MARKER* marker = new SCH_MARKER( std::move( ercItem ), net_item->GetPosition() );
3574 screen->Append( marker );
3575
3576 return false;
3577 }
3578
3579 return true;
3580}
3581
3582
3584{
3585 const SCH_SHEET_PATH& sheet = aSubgraph->m_sheet;
3586 SCH_SCREEN* screen = sheet.LastScreen();
3587
3588 SCH_ITEM* label = nullptr;
3589 SCH_ITEM* port = nullptr;
3590
3591 for( SCH_ITEM* item : aSubgraph->m_items )
3592 {
3593 switch( item->Type() )
3594 {
3595 case SCH_TEXT_T:
3596 case SCH_GLOBAL_LABEL_T:
3597 if( !label && item->Connection( &sheet )->IsBus() )
3598 label = item;
3599 break;
3600
3601 case SCH_SHEET_PIN_T:
3602 case SCH_HIER_LABEL_T:
3603 if( !port && item->Connection( &sheet )->IsBus() )
3604 port = item;
3605 break;
3606
3607 default:
3608 break;
3609 }
3610 }
3611
3612 if( label && port )
3613 {
3614 bool match = false;
3615
3616 for( const auto& member : label->Connection( &sheet )->Members() )
3617 {
3618 for( const auto& test : port->Connection( &sheet )->Members() )
3619 {
3620 if( test != member && member->Name() == test->Name() )
3621 {
3622 match = true;
3623 break;
3624 }
3625 }
3626
3627 if( match )
3628 break;
3629 }
3630
3631 if( !match )
3632 {
3633 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_BUS_TO_BUS_CONFLICT );
3634 ercItem->SetSheetSpecificPath( sheet );
3635 ercItem->SetItems( label, port );
3636
3637 SCH_MARKER* marker = new SCH_MARKER( std::move( ercItem ), label->GetPosition() );
3638 screen->Append( marker );
3639
3640 return false;
3641 }
3642 }
3643
3644 return true;
3645}
3646
3647
3649{
3650 bool conflict = false;
3651 const SCH_SHEET_PATH& sheet = aSubgraph->m_sheet;
3652 SCH_SCREEN* screen = sheet.LastScreen();
3653
3654 SCH_BUS_WIRE_ENTRY* bus_entry = nullptr;
3655 SCH_ITEM* bus_wire = nullptr;
3656 wxString bus_name;
3657
3658 if( !aSubgraph->m_driver_connection )
3659 {
3660 // Incomplete bus entry. Let the unconnected tests handle it.
3661 return true;
3662 }
3663
3664 for( SCH_ITEM* item : aSubgraph->m_items )
3665 {
3666 switch( item->Type() )
3667 {
3669 if( !bus_entry )
3670 bus_entry = static_cast<SCH_BUS_WIRE_ENTRY*>( item );
3671
3672 break;
3673
3674 default:
3675 break;
3676 }
3677 }
3678
3679 if( bus_entry && bus_entry->m_connected_bus_item )
3680 {
3681 bus_wire = bus_entry->m_connected_bus_item;
3682
3683 // Should we continue if the type is not a line?
3684 wxASSERT( bus_wire->Type() == SCH_LINE_T );
3685
3686 // In some cases, the connection list (SCH_CONNECTION*) can be null.
3687 // Skip null connections.
3688 if( bus_entry->Connection( &sheet )
3689 && bus_wire->Type() == SCH_LINE_T
3690 && bus_wire->Connection( &sheet ) )
3691 {
3692 conflict = true; // Assume a conflict; we'll reset if we find it's OK
3693
3694 bus_name = bus_wire->Connection( &sheet )->Name();
3695
3696 std::set<wxString> test_names;
3697 test_names.insert( bus_entry->Connection( &sheet )->FullLocalName() );
3698
3699 wxString baseName = sheet.PathHumanReadable();
3700
3701 for( SCH_ITEM* driver : aSubgraph->m_drivers )
3702 test_names.insert( baseName + aSubgraph->GetNameForDriver( driver ) );
3703
3704 for( const auto& member : bus_wire->Connection( &sheet )->Members() )
3705 {
3706 if( member->Type() == CONNECTION_TYPE::BUS )
3707 {
3708 for( const auto& sub_member : member->Members() )
3709 {
3710 if( test_names.count( sub_member->FullLocalName() ) )
3711 conflict = false;
3712 }
3713 }
3714 else if( test_names.count( member->FullLocalName() ) )
3715 {
3716 conflict = false;
3717 }
3718 }
3719 }
3720 }
3721
3722 // Don't report warnings if this bus member has been overridden by a higher priority power pin
3723 // or global label
3724 if( conflict && CONNECTION_SUBGRAPH::GetDriverPriority( aSubgraph->m_driver )
3726 {
3727 conflict = false;
3728 }
3729
3730 if( conflict )
3731 {
3732 wxString netName = aSubgraph->m_driver_connection->Name();
3733 wxString msg = wxString::Format( _( "Net %s is graphically connected to bus %s but is not a"
3734 " member of that bus" ),
3735 UnescapeString( netName ),
3736 UnescapeString( bus_name ) );
3737 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_BUS_ENTRY_CONFLICT );
3738 ercItem->SetSheetSpecificPath( sheet );
3739 ercItem->SetItems( bus_entry, bus_wire );
3740 ercItem->SetErrorMessage( msg );
3741
3742 SCH_MARKER* marker = new SCH_MARKER( std::move( ercItem ), bus_entry->GetPosition() );
3743 screen->Append( marker );
3744
3745 return false;
3746 }
3747
3748 return true;
3749}
3750
3751
3753{
3754 ERC_SETTINGS& settings = m_schematic->ErcSettings();
3755 const SCH_SHEET_PATH& sheet = aSubgraph->m_sheet;
3756 SCH_SCREEN* screen = sheet.LastScreen();
3757 bool ok = true;
3758 SCH_PIN* pin = nullptr;
3759
3760 std::set<SCH_PIN*> unique_pins;
3761 std::set<SCH_LABEL_BASE*> unique_labels;
3762
3763 wxString netName = GetResolvedSubgraphName( aSubgraph );
3764
3765 auto process_subgraph = [&]( const CONNECTION_SUBGRAPH* aProcessGraph )
3766 {
3767 // Any subgraph that contains a no-connect should not
3768 // more than one pin (which would indicate it is connected
3769 for( SCH_ITEM* item : aProcessGraph->m_items )
3770 {
3771 switch( item->Type() )
3772 {
3773 case SCH_PIN_T:
3774 {
3775 SCH_PIN* test_pin = static_cast<SCH_PIN*>( item );
3776
3777 // Only link NC to pin on the current subgraph being checked
3778 if( aProcessGraph == aSubgraph )
3779 pin = test_pin;
3780
3781 if( std::none_of( unique_pins.begin(), unique_pins.end(),
3782 [test_pin]( SCH_PIN* aPin )
3783 {
3784 return test_pin->IsStacked( aPin );
3785 }
3786 ))
3787 {
3788 unique_pins.insert( test_pin );
3789 }
3790
3791 break;
3792 }
3793
3794 case SCH_LABEL_T:
3795 case SCH_GLOBAL_LABEL_T:
3796 case SCH_HIER_LABEL_T:
3797 unique_labels.insert( static_cast<SCH_LABEL_BASE*>( item ) );
3799 default:
3800 break;
3801 }
3802 }
3803 };
3804
3805 auto it = m_net_name_to_subgraphs_map.find( netName );
3806
3807 if( it != m_net_name_to_subgraphs_map.end() )
3808 {
3809 for( const CONNECTION_SUBGRAPH* subgraph : it->second )
3810 {
3811 process_subgraph( subgraph );
3812 }
3813 }
3814 else
3815 {
3816 process_subgraph( aSubgraph );
3817 }
3818
3819 if( aSubgraph->m_no_connect != nullptr )
3820 {
3821 // Special case: If the subgraph being checked consists of only a hier port/pin and
3822 // a no-connect, we don't issue a "no-connect connected" warning just because
3823 // connections exist on the sheet on the other side of the link.
3824 VECTOR2I noConnectPos = aSubgraph->m_no_connect->GetPosition();
3825
3826 for( SCH_SHEET_PIN* hierPin : aSubgraph->m_hier_pins )
3827 {
3828 if( hierPin->GetPosition() == noConnectPos )
3829 return true;
3830 }
3831
3832 for( SCH_HIERLABEL* hierLabel : aSubgraph->m_hier_ports )
3833 {
3834 if( hierLabel->GetPosition() == noConnectPos )
3835 return true;
3836 }
3837
3838 for( SCH_ITEM* item : screen->Items().Overlapping( SCH_SYMBOL_T, noConnectPos ) )
3839 {
3840 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
3841
3842 const SCH_PIN* test_pin = symbol->GetPin( noConnectPos );
3843
3844 if( test_pin && test_pin->GetType() == ELECTRICAL_PINTYPE::PT_NC )
3845 return true;
3846 }
3847
3848 if( unique_pins.size() > 1 && settings.IsTestEnabled( ERCE_NOCONNECT_CONNECTED ) )
3849 {
3850 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_NOCONNECT_CONNECTED );
3851 ercItem->SetSheetSpecificPath( sheet );
3852 ercItem->SetItemsSheetPaths( sheet );
3853
3854 VECTOR2I pos;
3855
3856 if( pin )
3857 {
3858 ercItem->SetItems( pin, aSubgraph->m_no_connect );
3859 pos = pin->GetPosition();
3860 }
3861 else
3862 {
3863 ercItem->SetItems( aSubgraph->m_no_connect );
3864 pos = aSubgraph->m_no_connect->GetPosition();
3865 }
3866
3867 SCH_MARKER* marker = new SCH_MARKER( std::move( ercItem ), pos );
3868 screen->Append( marker );
3869
3870 ok = false;
3871 }
3872
3873 if( unique_pins.empty() && unique_labels.empty() &&
3875 {
3876 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_NOCONNECT_NOT_CONNECTED );
3877 ercItem->SetItems( aSubgraph->m_no_connect );
3878 ercItem->SetSheetSpecificPath( sheet );
3879 ercItem->SetItemsSheetPaths( sheet );
3880
3881 SCH_MARKER* marker = new SCH_MARKER( std::move( ercItem ), aSubgraph->m_no_connect->GetPosition() );
3882 screen->Append( marker );
3883
3884 ok = false;
3885 }
3886 }
3887 else
3888 {
3889 bool has_other_connections = false;
3890 std::vector<SCH_PIN*> pins;
3891
3892 // Any subgraph that lacks a no-connect and contains a pin should also
3893 // contain at least one other potential driver
3894
3895 for( SCH_ITEM* item : aSubgraph->m_items )
3896 {
3897 switch( item->Type() )
3898 {
3899 case SCH_PIN_T:
3900 {
3901 SCH_PIN* test_pin = static_cast<SCH_PIN*>( item );
3902
3903 // Stacked pins do not count as other connections but non-stacked pins do
3904 if( !has_other_connections && !pins.empty()
3905 && !test_pin->GetParentSymbol()->IsPower() )
3906 {
3907 for( SCH_PIN* other_pin : pins )
3908 {
3909 if( !test_pin->IsStacked( other_pin ) )
3910 {
3911 has_other_connections = true;
3912 break;
3913 }
3914 }
3915 }
3916
3917 pins.emplace_back( static_cast<SCH_PIN*>( item ) );
3918
3919 break;
3920 }
3921
3922 default:
3923 if( aSubgraph->GetDriverPriority( item ) != CONNECTION_SUBGRAPH::PRIORITY::NONE )
3924 has_other_connections = true;
3925
3926 break;
3927 }
3928 }
3929
3930 // For many checks, we can just use the first pin
3931 pin = pins.empty() ? nullptr : pins[0];
3932
3933 // But if there is a power pin, it might be connected elsewhere
3934 for( SCH_PIN* test_pin : pins )
3935 {
3936 // Prefer the pin is part of a real component rather than some stray power symbol
3937 // Or else we may fail walking connected components to a power symbol pin since we
3938 // reject starting at a power symbol
3939 if( test_pin->GetType() == ELECTRICAL_PINTYPE::PT_POWER_IN && !test_pin->IsPower() )
3940 {
3941 pin = test_pin;
3942 break;
3943 }
3944 }
3945
3946 // Check if power input pins connect to anything else via net name,
3947 // but not for power symbols (with visible or legacy invisible pins).
3948 // We want to throw unconnected errors for power symbols even if they are connected to other
3949 // net items by name, because usually failing to connect them graphically is a mistake
3950 SYMBOL* pinLibParent = ( pin && pin->GetLibPin() )
3951 ? pin->GetLibPin()->GetParentSymbol() : nullptr;
3952
3953 if( pin && !has_other_connections
3954 && !pin->IsPower()
3955 && ( !pinLibParent || !pinLibParent->IsPower() ) )
3956 {
3957 wxString name = pin->Connection( &sheet )->Name();
3958 wxString local_name = pin->Connection( &sheet )->Name( true );
3959
3960 if( m_global_label_cache.count( name )
3961 || m_local_label_cache.count( std::make_pair( sheet, local_name ) ) )
3962 {
3963 has_other_connections = true;
3964 }
3965 }
3966
3967 // Only one pin, and it's not a no-connect pin
3968 if( pin && !has_other_connections
3969 && pin->GetType() != ELECTRICAL_PINTYPE::PT_NC
3970 && pin->GetType() != ELECTRICAL_PINTYPE::PT_NIC
3971 && settings.IsTestEnabled( ERCE_PIN_NOT_CONNECTED ) )
3972 {
3973 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_PIN_NOT_CONNECTED );
3974 ercItem->SetSheetSpecificPath( sheet );
3975 ercItem->SetItemsSheetPaths( sheet );
3976 ercItem->SetItems( pin );
3977
3978 SCH_MARKER* marker = new SCH_MARKER( std::move( ercItem ), pin->GetPosition() );
3979 screen->Append( marker );
3980
3981 ok = false;
3982 }
3983
3984 // If there are multiple pins in this SG, they might be indirectly connected (by netname)
3985 // rather than directly connected (by wires). We want to flag dangling pins even if they
3986 // join nets with another pin, as it's often a mistake
3987 if( pins.size() > 1 )
3988 {
3989 for( SCH_PIN* testPin : pins )
3990 {
3991 // We only apply this test to power symbols, because other symbols have
3992 // pins that are meant to be dangling, but the power symbols have pins
3993 // that are *not* meant to be dangling.
3994 SYMBOL* testLibParent = testPin->GetLibPin()
3995 ? testPin->GetLibPin()->GetParentSymbol()
3996 : nullptr;
3997
3998 if( testLibParent && testLibParent->IsPower()
3999 && testPin->ConnectedItems( sheet ).empty()
4000 && settings.IsTestEnabled( ERCE_PIN_NOT_CONNECTED ) )
4001 {
4002 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_PIN_NOT_CONNECTED );
4003 ercItem->SetSheetSpecificPath( sheet );
4004 ercItem->SetItemsSheetPaths( sheet );
4005 ercItem->SetItems( testPin );
4006
4007 SCH_MARKER* marker = new SCH_MARKER( std::move( ercItem ), testPin->GetPosition() );
4008 screen->Append( marker );
4009
4010 ok = false;
4011 }
4012 }
4013 }
4014 }
4015
4016 return ok;
4017}
4018
4019
4021{
4022 int err_count = 0;
4023 const SCH_SHEET_PATH& sheet = aSubgraph->m_sheet;
4024
4025 for( SCH_ITEM* item : aSubgraph->m_items )
4026 {
4027 if( item->GetLayer() != LAYER_WIRE )
4028 continue;
4029
4030 if( item->Type() == SCH_LINE_T )
4031 {
4032 SCH_LINE* line = static_cast<SCH_LINE*>( item );
4033
4034 if( line->IsGraphicLine() )
4035 continue;
4036
4037 auto report_error = [&]( VECTOR2I& location )
4038 {
4039 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_UNCONNECTED_WIRE_ENDPOINT );
4040
4041 ercItem->SetItems( line );
4042 ercItem->SetSheetSpecificPath( sheet );
4043 ercItem->SetErrorMessage( _( "Unconnected wire endpoint" ) );
4044
4045 SCH_MARKER* marker = new SCH_MARKER( std::move( ercItem ), location );
4046 sheet.LastScreen()->Append( marker );
4047
4048 err_count++;
4049 };
4050
4051 if( line->IsStartDangling() )
4052 report_error( line->GetConnectionPoints()[0] );
4053
4054 if( line->IsEndDangling() )
4055 report_error( line->GetConnectionPoints()[1] );
4056 }
4057 else if( item->Type() == SCH_BUS_WIRE_ENTRY_T )
4058 {
4059 SCH_BUS_WIRE_ENTRY* entry = static_cast<SCH_BUS_WIRE_ENTRY*>( item );
4060
4061 auto report_error = [&]( VECTOR2I& location )
4062 {
4063 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_UNCONNECTED_WIRE_ENDPOINT );
4064
4065 ercItem->SetItems( entry );
4066 ercItem->SetSheetSpecificPath( sheet );
4067 ercItem->SetErrorMessage( _( "Unconnected wire to bus entry" ) );
4068
4069 SCH_MARKER* marker = new SCH_MARKER( std::move( ercItem ), location );
4070 sheet.LastScreen()->Append( marker );
4071
4072 err_count++;
4073 };
4074
4075 if( entry->IsStartDangling() )
4076 report_error( entry->GetConnectionPoints()[0] );
4077
4078 if( entry->IsEndDangling() )
4079 report_error( entry->GetConnectionPoints()[1] );
4080 }
4081
4082 }
4083
4084 return err_count > 0;
4085}
4086
4087
4089{
4090 if( aSubgraph->m_driver )
4091 return true;
4092
4093 const SCH_SHEET_PATH& sheet = aSubgraph->m_sheet;
4094 std::vector<SCH_ITEM*> wires;
4095
4096 // We've gotten this far, so we know we have no valid driver. All we need to do is check
4097 // for a wire that we can place the error on.
4098 for( SCH_ITEM* item : aSubgraph->m_items )
4099 {
4100 if( item->Type() == SCH_LINE_T && item->GetLayer() == LAYER_WIRE )
4101 wires.emplace_back( item );
4102 else if( item->Type() == SCH_BUS_WIRE_ENTRY_T )
4103 wires.emplace_back( item );
4104 }
4105
4106 if( !wires.empty() )
4107 {
4108 SCH_SCREEN* screen = aSubgraph->m_sheet.LastScreen();
4109
4110 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_WIRE_DANGLING );
4111 ercItem->SetSheetSpecificPath( sheet );
4112 ercItem->SetItems( wires[0],
4113 wires.size() > 1 ? wires[1] : nullptr,
4114 wires.size() > 2 ? wires[2] : nullptr,
4115 wires.size() > 3 ? wires[3] : nullptr );
4116
4117 SCH_MARKER* marker = new SCH_MARKER( std::move( ercItem ), wires[0]->GetPosition() );
4118 screen->Append( marker );
4119
4120 return false;
4121 }
4122
4123 return true;
4124}
4125
4126
4128{
4129 // Label connection rules:
4130 // Any label without a no-connect needs to have at least 2 pins, otherwise it is invalid
4131 // Local labels are flagged if they don't connect to any pins and don't have a no-connect
4132 // Global labels are flagged if they appear only once, don't connect to any local labels,
4133 // and don't have a no-connect marker
4134
4135 if( !aSubgraph->m_driver_connection )
4136 return true;
4137
4138 // Buses are excluded from this test: many users create buses with only a single instance
4139 // and it's not really a problem as long as the nets in the bus pass ERC
4140 if( aSubgraph->m_driver_connection->IsBus() )
4141 return true;
4142
4143 const SCH_SHEET_PATH& sheet = aSubgraph->m_sheet;
4144 ERC_SETTINGS& settings = m_schematic->ErcSettings();
4145 bool ok = true;
4146 size_t pinCount = 0;
4147 bool has_nc = !!aSubgraph->m_no_connect;
4148
4149 std::map<KICAD_T, std::vector<SCH_TEXT*>> label_map;
4150
4151
4152 auto hasPins =
4153 []( const CONNECTION_SUBGRAPH* aLocSubgraph ) -> size_t
4154 {
4155 return std::count_if( aLocSubgraph->m_items.begin(), aLocSubgraph->m_items.end(),
4156 []( const SCH_ITEM* item )
4157 {
4158 return item->Type() == SCH_PIN_T;
4159 } );
4160 };
4161
4162 auto reportError =
4163 [&]( SCH_TEXT* aText, int errCode )
4164 {
4165 if( settings.IsTestEnabled( errCode ) )
4166 {
4167 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( errCode );
4168 ercItem->SetSheetSpecificPath( sheet );
4169 ercItem->SetItems( aText );
4170
4171 SCH_MARKER* marker = new SCH_MARKER( std::move( ercItem ), aText->GetPosition() );
4172 aSubgraph->m_sheet.LastScreen()->Append( marker );
4173 }
4174 };
4175
4176 pinCount = hasPins( aSubgraph );
4177
4178 for( SCH_ITEM* item : aSubgraph->m_items )
4179 {
4180 switch( item->Type() )
4181 {
4182 case SCH_LABEL_T:
4183 case SCH_GLOBAL_LABEL_T:
4184 case SCH_HIER_LABEL_T:
4185 {
4186 SCH_TEXT* text = static_cast<SCH_TEXT*>( item );
4187
4188 label_map[item->Type()].push_back( text );
4189
4190 // Below, we'll create an ERC if the whole subgraph is unconnected. But, additionally,
4191 // we want to error if an individual label in the subgraph is floating, even if it's
4192 // connected to other valid things by way of another label on the same sheet.
4193 if( text->IsDangling() )
4194 {
4195 reportError( text, ERCE_LABEL_NOT_CONNECTED );
4196 return false;
4197 }
4198
4199 break;
4200 }
4201
4202 default:
4203 break;
4204 }
4205 }
4206
4207 if( label_map.empty() )
4208 return true;
4209
4210 // No-connects on net neighbors will be noticed before, but to notice them on bus parents we
4211 // need to walk the graph
4212 for( auto& [ connection, subgraphs ] : aSubgraph->m_bus_parents )
4213 {
4214 for( CONNECTION_SUBGRAPH* busParent : subgraphs )
4215 {
4216 if( busParent->m_no_connect )
4217 {
4218 has_nc = true;
4219 break;
4220 }
4221
4222 CONNECTION_SUBGRAPH* hp = busParent->m_hier_parent;
4223
4224 while( hp )
4225 {
4226 if( hp->m_no_connect )
4227 {
4228 has_nc = true;
4229 break;
4230 }
4231
4232 hp = hp->m_hier_parent;
4233 }
4234 }
4235 }
4236
4237 wxString netName = GetResolvedSubgraphName( aSubgraph );
4238
4239 wxCHECK_MSG( m_schematic, true, wxS( "Null m_schematic in CONNECTION_GRAPH::ercCheckLabels" ) );
4240
4241 // Labels that have multiple pins connected are not dangling (may be used for naming segments)
4242 // so leave them without errors here
4243 if( pinCount > 1 )
4244 return true;
4245
4246 for( auto& [type, label_vec] : label_map )
4247 {
4248 for( SCH_TEXT* text : label_vec )
4249 {
4250 size_t allPins = pinCount;
4251 size_t localPins = pinCount;
4252 bool hasLocalHierarchy = false;
4253
4254 if( !aSubgraph->m_hier_pins.empty() || !aSubgraph->m_hier_ports.empty() )
4255 {
4256 // A label bridging multiple hierarchical connections
4257 // (e.g., connecting sheet pins from different sub-sheet
4258 // instances) is serving a valid routing purpose even
4259 // without local component pins.
4260 std::set<wxString> uniquePortNames;
4261 for( SCH_HIERLABEL* port : aSubgraph->m_hier_ports )
4262 uniquePortNames.insert( aSubgraph->GetNameForDriver( port ) );
4263
4264 if( aSubgraph->m_hier_pins.size() + uniquePortNames.size() > 1 )
4265 {
4266 hasLocalHierarchy = true;
4267 }
4268
4269 // Also check bus parents for bus-based hierarchical
4270 // routing on the same sheet.
4271 for( auto& [connection, busParents] : aSubgraph->m_bus_parents )
4272 {
4273 for( const CONNECTION_SUBGRAPH* busParent : busParents )
4274 {
4275 if( busParent->m_sheet == sheet
4276 && ( !busParent->m_hier_pins.empty()
4277 || !busParent->m_hier_ports.empty() ) )
4278 {
4279 hasLocalHierarchy = true;
4280 break;
4281 }
4282 }
4283
4284 if( hasLocalHierarchy )
4285 break;
4286 }
4287 }
4288
4289 auto it = m_net_name_to_subgraphs_map.find( netName );
4290
4291 if( it != m_net_name_to_subgraphs_map.end() )
4292 {
4293 for( const CONNECTION_SUBGRAPH* neighbor : it->second )
4294 {
4295 if( neighbor == aSubgraph )
4296 continue;
4297
4298 if( neighbor->m_no_connect )
4299 has_nc = true;
4300
4301 size_t neighborPins = hasPins( neighbor );
4302 allPins += neighborPins;
4303
4304 if( neighbor->m_sheet == sheet )
4305 {
4306 localPins += neighborPins;
4307
4308 if( !neighbor->m_hier_pins.empty()
4309 || !neighbor->m_hier_ports.empty() )
4310 {
4311 hasLocalHierarchy = true;
4312 }
4313 }
4314 }
4315 }
4316
4317 if( allPins == 1 && !has_nc )
4318 {
4319 reportError( text, ERCE_LABEL_SINGLE_PIN );
4320 ok = false;
4321 }
4322
4323 // A local label that connects to other subgraphs with
4324 // hierarchical connections on the same sheet (through bus
4325 // parents or net-name neighbors) is routing signals and should
4326 // not be flagged even without local component pins.
4327 if( allPins == 0
4328 || ( type == SCH_LABEL_T && localPins == 0 && allPins > 1
4329 && !has_nc && !hasLocalHierarchy ) )
4330 {
4331 reportError( text, ERCE_LABEL_NOT_CONNECTED );
4332 ok = false;
4333 }
4334 }
4335 }
4336
4337 return ok;
4338}
4339
4340
4342{
4343 int errors = 0;
4344
4345 std::map<wxString, std::tuple<int, const SCH_ITEM*, SCH_SHEET_PATH>> labelData;
4346
4347 for( const SCH_SHEET_PATH& sheet : m_sheetList )
4348 {
4349 for( SCH_ITEM* item : sheet.LastScreen()->Items().OfType( SCH_GLOBAL_LABEL_T ) )
4350 {
4351 SCH_TEXT* labelText = static_cast<SCH_TEXT*>( item );
4352 wxString resolvedLabelText =
4353 EscapeString( labelText->GetShownText( &sheet, false ), CTX_NETNAME );
4354
4355 if( labelData.find( resolvedLabelText ) == labelData.end() )
4356 {
4357 labelData[resolvedLabelText] = { 1, item, sheet };
4358 }
4359 else
4360 {
4361 std::get<0>( labelData[resolvedLabelText] ) += 1;
4362 std::get<1>( labelData[resolvedLabelText] ) = nullptr;
4363 std::get<2>( labelData[resolvedLabelText] ) = sheet;
4364 }
4365 }
4366 }
4367
4368 for( const auto& label : labelData )
4369 {
4370 if( std::get<0>( label.second ) == 1 )
4371 {
4372 const SCH_SHEET_PATH& sheet = std::get<2>( label.second );
4373 const SCH_ITEM* item = std::get<1>( label.second );
4374
4375 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_SINGLE_GLOBAL_LABEL );
4376 ercItem->SetItems( std::get<1>( label.second ) );
4377 ercItem->SetSheetSpecificPath( sheet );
4378 ercItem->SetItemsSheetPaths( sheet );
4379
4380 SCH_MARKER* marker = new SCH_MARKER( std::move( ercItem ), item->GetPosition() );
4381 sheet.LastScreen()->Append( marker );
4382
4383 errors++;
4384 }
4385 }
4386
4387 return errors;
4388}
4389
4390
4392{
4393 int error_count = 0;
4394
4395 for( const SCH_SHEET_PATH& sheet : m_sheetList )
4396 {
4397 for( SCH_ITEM* item : sheet.LastScreen()->Items().OfType( SCH_DIRECTIVE_LABEL_T ) )
4398 {
4399 SCH_LABEL* label = static_cast<SCH_LABEL*>( item );
4400
4401 if( label->IsDangling() )
4402 {
4403 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_LABEL_NOT_CONNECTED );
4404 SCH_TEXT* text = static_cast<SCH_TEXT*>( item );
4405 ercItem->SetSheetSpecificPath( sheet );
4406 ercItem->SetItems( text );
4407
4408 SCH_MARKER* marker = new SCH_MARKER( std::move( ercItem ), text->GetPosition() );
4409 sheet.LastScreen()->Append( marker );
4410 error_count++;
4411 }
4412 }
4413 }
4414
4415 return error_count;
4416}
4417
4418
4420{
4421 wxString msg;
4422 int errors = 0;
4423
4424 ERC_SETTINGS& settings = m_schematic->ErcSettings();
4425
4426 for( const SCH_SHEET_PATH& sheet : m_sheetList )
4427 {
4428 // Hierarchical labels in the top-level sheets cannot be connected to anything.
4429 if( sheet.Last()->IsTopLevelSheet() )
4430 {
4431 for( const SCH_ITEM* item : sheet.LastScreen()->Items().OfType( SCH_HIER_LABEL_T ) )
4432 {
4433 const SCH_HIERLABEL* label = static_cast<const SCH_HIERLABEL*>( item );
4434
4435 wxCHECK2( label, continue );
4436
4437 msg.Printf( _( "Hierarchical label '%s' in root sheet cannot be connected to non-existent "
4438 "parent sheet" ),
4439 label->GetShownText( &sheet, true ) );
4440 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_PIN_NOT_CONNECTED );
4441 ercItem->SetItems( item );
4442 ercItem->SetErrorMessage( msg );
4443
4444 SCH_MARKER* marker = new SCH_MARKER( std::move( ercItem ), item->GetPosition() );
4445 sheet.LastScreen()->Append( marker );
4446
4447 errors++;
4448 }
4449 }
4450
4451 for( SCH_ITEM* item : sheet.LastScreen()->Items().OfType( SCH_SHEET_T ) )
4452 {
4453 SCH_SHEET* parentSheet = static_cast<SCH_SHEET*>( item );
4454 SCH_SHEET_PATH parentSheetPath = sheet;
4455
4456 parentSheetPath.push_back( parentSheet );
4457
4458 std::map<wxString, SCH_SHEET_PIN*> pins;
4459 std::map<wxString, SCH_HIERLABEL*> labels;
4460
4461 for( SCH_SHEET_PIN* pin : parentSheet->GetPins() )
4462 {
4463 if( settings.IsTestEnabled( ERCE_HIERACHICAL_LABEL ) )
4464 pins[ pin->GetShownText( &parentSheetPath, false ) ] = pin;
4465
4466 if( pin->IsDangling() && settings.IsTestEnabled( ERCE_PIN_NOT_CONNECTED ) )
4467 {
4468 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_PIN_NOT_CONNECTED );
4469 ercItem->SetItems( pin );
4470 ercItem->SetSheetSpecificPath( sheet );
4471 ercItem->SetItemsSheetPaths( sheet );
4472
4473 SCH_MARKER* marker = new SCH_MARKER( std::move( ercItem ), pin->GetPosition() );
4474 sheet.LastScreen()->Append( marker );
4475
4476 errors++;
4477 }
4478 }
4479
4480 if( settings.IsTestEnabled( ERCE_HIERACHICAL_LABEL ) )
4481 {
4482 std::set<wxString> matchedPins;
4483
4484 for( SCH_ITEM* subItem : parentSheet->GetScreen()->Items() )
4485 {
4486 if( subItem->Type() == SCH_HIER_LABEL_T )
4487 {
4488 SCH_HIERLABEL* label = static_cast<SCH_HIERLABEL*>( subItem );
4489 wxString labelText = label->GetShownText( &parentSheetPath, false );
4490
4491 if( !pins.contains( labelText ) )
4492 labels[ labelText ] = label;
4493 else
4494 matchedPins.insert( labelText );
4495 }
4496 }
4497
4498 for( const wxString& matched : matchedPins )
4499 pins.erase( matched );
4500
4501 for( const auto& [name, pin] : pins )
4502 {
4503 msg.Printf( _( "Sheet pin %s has no matching hierarchical label inside the sheet" ),
4504 UnescapeString( name ) );
4505
4506 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_HIERACHICAL_LABEL );
4507 ercItem->SetItems( pin );
4508 ercItem->SetErrorMessage( msg );
4509 ercItem->SetSheetSpecificPath( sheet );
4510 ercItem->SetItemsSheetPaths( sheet );
4511
4512 SCH_MARKER* marker = new SCH_MARKER( std::move( ercItem ), pin->GetPosition() );
4513 sheet.LastScreen()->Append( marker );
4514
4515 errors++;
4516 }
4517
4518 for( const auto& [name, label] : labels )
4519 {
4520 msg.Printf( _( "Hierarchical label %s has no matching sheet pin in the parent sheet" ),
4521 UnescapeString( name ) );
4522
4523 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_HIERACHICAL_LABEL );
4524 ercItem->SetItems( label );
4525 ercItem->SetErrorMessage( msg );
4526 ercItem->SetSheetSpecificPath( parentSheetPath );
4527 ercItem->SetItemsSheetPaths( parentSheetPath );
4528
4529 SCH_MARKER* marker = new SCH_MARKER( std::move( ercItem ), label->GetPosition() );
4530 parentSheet->GetScreen()->Append( marker );
4531
4532 errors++;
4533 }
4534 }
4535 }
4536 }
4537
4538 return errors;
4539}
const char * name
constexpr EDA_IU_SCALE schIUScale
Definition base_units.h:114
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)
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.
bool ercCheckLabels(const CONNECTION_SUBGRAPH *aSubgraph)
Check one subgraph for proper connection of labels.
void RemoveItem(SCH_ITEM *aItem)
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::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.
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::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.
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 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
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.
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.
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.
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 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.
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
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:112
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
bool GetDuplicatePinNumbersAreJumpers() const
Definition lib_symbol.h:753
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:760
A small class to help profiling.
Definition profile.h:49
void Show(std::ostream &aStream=std::cerr)
Print the elapsed time (in a suitable unit) to a stream.
Definition profile.h:105
void Stop()
Save the time when this function was called, and set the counter stane to stop.
Definition profile.h:88
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:550
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:275
virtual const wxString & GetCachedDriverName() const
Definition sch_item.cpp:620
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:581
void AddConnectionTo(const SCH_SHEET_PATH &aPath, SCH_ITEM *aItem)
Add a connection link between this item and another.
Definition sch_item.cpp:565
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:512
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:605
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:488
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:337
LABEL_FLAG_SHAPE GetShape() const
Definition sch_label.h:180
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:717
bool IsStartDangling() const
Definition sch_line.h:303
bool IsEndDangling() const
Definition sch_line.h:304
bool IsGraphicLine() const
Return if the line is a graphic (non electrical line)
Definition sch_line.cpp:986
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:357
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:376
SCH_PIN * GetLibPin() const
Definition sch_pin.h:89
bool IsStacked(const SCH_PIN *aPin) const
Definition sch_pin.cpp:479
wxString GetDefaultNetName(const SCH_SHEET_PATH &aPath, bool aForceNoConnect=false)
Definition sch_pin.cpp:1435
bool IsPower() const
Check if the pin is either a global or local power pin.
Definition sch_pin.cpp:383
ELECTRICAL_PINTYPE GetType() const
Definition sch_pin.cpp:311
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:451
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:371
SCH_SCREEN * GetScreen() const
Definition sch_sheet.h:140
std::vector< SCH_SHEET_PIN * > & GetPins()
Definition sch_sheet.h:228
Schematic symbol object.
Definition sch_symbol.h:76
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
VECTOR2I GetPosition() const override
Definition sch_text.h:147
virtual wxString GetShownText(const SCH_SHEET_PATH *aPath, bool aAllowExtraText, int aDepth=0) const
Definition sch_text.cpp:318
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.
#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.
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:101
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::string path
KIBIS_PIN * pin
VECTOR2I location
thread_pool & GetKiCadThreadPool()
Get a reference to the current thread pool.
static thread_pool * tp
BS::priority_thread_pool thread_pool
Definition thread_pool.h:31
#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