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