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 (C) 2021-2022 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 <list>
24#include <future>
25#include <vector>
26#include <unordered_map>
27#include <core/profile.h>
28#include <core/kicad_algo.h>
29#include <common.h>
30#include <core/kicad_algo.h>
31#include <erc.h>
32#include <pin_type.h>
33#include <sch_bus_entry.h>
34#include <sch_symbol.h>
35#include <sch_edit_frame.h>
36#include <sch_line.h>
37#include <sch_marker.h>
38#include <sch_pin.h>
39#include <sch_sheet.h>
40#include <sch_sheet_path.h>
41#include <sch_sheet_pin.h>
42#include <sch_text.h>
43#include <schematic.h>
44#include <connection_graph.h>
47#include <widgets/ui_common.h>
48#include <string_utils.h>
49#include <core/thread_pool.h>
50#include <wx/log.h>
51
52#include <advanced_config.h> // for realtime connectivity switch in release builds
53
54
55/*
56 * Flag to enable connectivity profiling
57 * @ingroup trace_env_vars
58 */
59static const wxChar DanglingProfileMask[] = wxT( "CONN_PROFILE" );
60
61/*
62 * Flag to enable connectivity tracing
63 * @ingroup trace_env_vars
64 */
65static const wxChar ConnTrace[] = wxT( "CONN" );
66
67
69{
70 m_items.erase( aItem );
71 m_drivers.erase( aItem );
72
73 if( aItem == m_driver )
74 {
75 m_driver = nullptr;
76 m_driver_connection = nullptr;
77 }
78
79 if( aItem->Type() == SCH_SHEET_PIN_T )
80 m_hier_pins.erase( static_cast<SCH_SHEET_PIN*>( aItem ) );
81
82 if( aItem->Type() == SCH_HIER_LABEL_T )
83 m_hier_ports.erase( static_cast<SCH_HIERLABEL*>( aItem ) );
84}
85
86
87bool CONNECTION_SUBGRAPH::ResolveDrivers( bool aCheckMultipleDrivers )
88{
89 auto candidate_cmp = [&]( SCH_ITEM* a, SCH_ITEM* b ) -> bool
90 {
91 // meet irreflexive requirements of std::sort
92 if( a == b )
93 return false;
94
96 SCH_CONNECTION* bc = b->Connection( &m_sheet );
97
98 // Ensure we don't pick the subset over the superset
99 if( ac->IsBus() && bc->IsBus() )
100 return bc->IsSubsetOf( ac );
101
102 // Ensure we don't pick a hidden power pin on a regular symbol over
103 // one on a power symbol
104 if( a->Type() == SCH_PIN_T && b->Type() == SCH_PIN_T )
105 {
106 SCH_PIN* pa = static_cast<SCH_PIN*>( a );
107 SCH_PIN* pb = static_cast<SCH_PIN*>( b );
108
109 bool aPower = pa->GetLibPin()->GetParent()->IsPower();
110 bool bPower = pb->GetLibPin()->GetParent()->IsPower();
111
112 if( aPower && !bPower )
113 return true;
114 else if( bPower && !aPower )
115 return false;
116 }
117
118 const wxString& a_name = GetNameForDriver( a );
119 const wxString& b_name = GetNameForDriver( b );
120 bool a_lowQualityName = a_name.Contains( "-Pad" );
121 bool b_lowQualityName = b_name.Contains( "-Pad" );
122
123 if( a_lowQualityName && !b_lowQualityName )
124 return false;
125 else if( b_lowQualityName && !a_lowQualityName )
126 return true;
127 else
128 return a_name < b_name;
129 };
130
131 auto strong_cmp = [this]( SCH_ITEM* a, SCH_ITEM* b ) -> bool
132 {
133 const wxString& a_name = GetNameForDriver( a );
134 const wxString& b_name = GetNameForDriver( b );
135 return a_name < b_name;
136 };
137
138 PRIORITY highest_priority = PRIORITY::INVALID;
139 std::set<SCH_ITEM*, decltype( candidate_cmp )> candidates( candidate_cmp );
140 std::set<SCH_ITEM*, decltype( strong_cmp )> strong_drivers( strong_cmp );
141
142 m_driver = nullptr;
143
144 // Hierarchical labels are lower priority than local labels here,
145 // because on the first pass we want local labels to drive subgraphs
146 // so that we can identify same-sheet neighbors and link them together.
147 // Hierarchical labels will end up overriding the final net name if
148 // a higher-level sheet has a different name during the hierarchical
149 // pass.
150
151 for( SCH_ITEM* item : m_drivers )
152 {
153 PRIORITY item_priority = GetDriverPriority( item );
154
155 if( item_priority == PRIORITY::PIN
156 && !static_cast<SCH_PIN*>( item )->GetParentSymbol()->IsInNetlist() )
157 continue;
158
159 if( item_priority >= PRIORITY::HIER_LABEL )
160 strong_drivers.insert( item );
161
162 if( item_priority > highest_priority )
163 {
164 candidates.clear();
165 candidates.insert( item );
166 highest_priority = item_priority;
167 }
168 else if( !candidates.empty() && ( item_priority == highest_priority ) )
169 {
170 candidates.insert( item );
171 }
172 }
173
174 if( highest_priority >= PRIORITY::HIER_LABEL )
175 m_strong_driver = true;
176
177 // Power pins are 5, global labels are 6
178 m_local_driver = ( highest_priority < PRIORITY::POWER_PIN );
179
180 if( !candidates.empty() )
181 {
182 if( candidates.size() > 1 )
183 {
184 if( highest_priority == PRIORITY::SHEET_PIN )
185 {
186 // We have multiple options, and they are all hierarchical
187 // sheet pins. Let's prefer outputs over inputs.
188
189 for( SCH_ITEM* c : candidates )
190 {
191 SCH_SHEET_PIN* p = static_cast<SCH_SHEET_PIN*>( c );
192
193 if( p->GetShape() == LABEL_FLAG_SHAPE::L_OUTPUT )
194 {
195 m_driver = c;
196 break;
197 }
198 }
199 }
200 }
201
202 if( !m_driver )
203 m_driver = *candidates.begin();
204 }
205
206 if( strong_drivers.size() > 1 )
207 m_multiple_drivers = true;
208
209 // Drop weak drivers
210 if( m_strong_driver )
211 {
212 m_drivers.clear();
213 m_drivers.insert( strong_drivers.begin(), strong_drivers.end() );
214 }
215
216 // Cache driver connection
217 if( m_driver )
218 {
223 }
224 else if( !m_is_bus_member )
225 {
226 m_driver_connection = nullptr;
227 }
228
229 return ( m_driver != nullptr );
230}
231
232
233void CONNECTION_SUBGRAPH::getAllConnectedItems( std::set<std::pair<SCH_SHEET_PATH, SCH_ITEM*>>& aItems, std::set<CONNECTION_SUBGRAPH*>& aSubgraphs )
234{
235 CONNECTION_SUBGRAPH* sg = this;
236
237 while( sg->m_absorbed_by )
238 {
239 wxASSERT( sg->m_graph == sg->m_absorbed_by->m_graph );
240 sg = sg->m_absorbed_by;
241 }
242
243 aSubgraphs.insert( sg );
244 aSubgraphs.insert( sg->m_absorbed_subgraphs.begin(), sg->m_absorbed_subgraphs.end() );
245
246 for( SCH_ITEM* item : sg->m_items )
247 aItems.emplace( m_sheet, item );
248
249 for( CONNECTION_SUBGRAPH* child_sg : sg->m_hier_children )
250 child_sg->getAllConnectedItems( aItems, aSubgraphs );
251}
252
253
255{
256 if( !m_driver || m_dirty )
257 return "";
258
259 if( !m_driver->Connection( &m_sheet ) )
260 {
261#ifdef CONNECTIVITY_DEBUG
262 wxASSERT_MSG( false, wxS( "Tried to get the net name of an item with no connection" ) );
263#endif
264
265 return "";
266 }
267
268 return m_driver->Connection( &m_sheet )->Name();
269}
270
271
272std::vector<SCH_ITEM*> CONNECTION_SUBGRAPH::GetAllBusLabels() const
273{
274 std::vector<SCH_ITEM*> labels;
275
276 for( SCH_ITEM* item : m_drivers )
277 {
278 switch( item->Type() )
279 {
280 case SCH_LABEL_T:
282 {
283 CONNECTION_TYPE type = item->Connection( &m_sheet )->Type();
284
285 // Only consider bus vectors
286 if( type == CONNECTION_TYPE::BUS || type == CONNECTION_TYPE::BUS_GROUP )
287 labels.push_back( item );
288
289 break;
290 }
291
292 default:
293 break;
294 }
295 }
296
297 return labels;
298}
299
300
301std::vector<SCH_ITEM*> CONNECTION_SUBGRAPH::GetVectorBusLabels() const
302{
303 std::vector<SCH_ITEM*> labels;
304
305 for( SCH_ITEM* item : m_drivers )
306 {
307 switch( item->Type() )
308 {
309 case SCH_LABEL_T:
311 {
312 SCH_CONNECTION* label_conn = item->Connection( &m_sheet );
313
314 // Only consider bus vectors
315 if( label_conn->Type() == CONNECTION_TYPE::BUS )
316 labels.push_back( item );
317
318 break;
319 }
320
321 default:
322 break;
323 }
324 }
325
326 return labels;
327}
328
329
331{
332 switch( aItem->Type() )
333 {
334 case SCH_PIN_T:
335 {
336 bool forceNoConnect = m_no_connect != nullptr;
337 SCH_PIN* pin = static_cast<SCH_PIN*>( aItem );
338 return pin->GetDefaultNetName( m_sheet, forceNoConnect );
339 break;
340 }
341
342 case SCH_LABEL_T:
344 case SCH_HIER_LABEL_T:
345 // NB: any changes here will need corresponding changes in SCH_LABEL_BASE::cacheShownText()
346 return EscapeString( static_cast<SCH_TEXT*>( aItem )->GetShownText( &m_sheet, false ),
347 CTX_NETNAME );
348
349 case SCH_SHEET_PIN_T:
350 // Sheet pins need to use their parent sheet as their starting sheet or they will
351 // resolve variables on the current sheet first
352 return EscapeString( static_cast<SCH_TEXT*>( aItem )->GetShownText( nullptr, false ),
353 CTX_NETNAME );
354
355 default:
356 wxFAIL_MSG( wxS( "Unhandled item type in GetNameForDriver" ) );
357 break;
358 }
359
360 return wxEmptyString;
361}
362
363
364const wxString& CONNECTION_SUBGRAPH::GetNameForDriver( SCH_ITEM* aItem ) const
365{
366 if( aItem->HasCachedDriverName() )
367 return aItem->GetCachedDriverName();
368
369 auto it = m_driver_name_cache.find( aItem );
370
371 if( it != m_driver_name_cache.end() )
372 return it->second;
373
374 return m_driver_name_cache.emplace( aItem, driverName( aItem ) ).first->second;
375}
376
377
379{
380 wxString netclass;
381
382 aItem->RunOnChildren(
383 [&]( SCH_ITEM* aChild )
384 {
385 if( aChild->Type() == SCH_FIELD_T )
386 {
387 SCH_FIELD* field = static_cast<SCH_FIELD*>( aChild );
388
389 if( field->GetCanonicalName() == wxT( "Netclass" ) )
390 {
391 netclass = field->GetText();
392 return false;
393 }
394 }
395
396 return true;
397 } );
398
399 return netclass;
400}
401
402
404{
405 wxASSERT( m_sheet == aOther->m_sheet );
406
407 for( SCH_ITEM* item : aOther->m_items )
408 {
410 AddItem( item );
411 }
412
413 m_absorbed_subgraphs.insert( aOther );
414 m_absorbed_subgraphs.insert( aOther->m_absorbed_subgraphs.begin(),
415 aOther->m_absorbed_subgraphs.end() );
416
417 m_bus_neighbors.insert( aOther->m_bus_neighbors.begin(), aOther->m_bus_neighbors.end() );
418 m_bus_parents.insert( aOther->m_bus_parents.begin(), aOther->m_bus_parents.end() );
419
421
422 std::function<void( CONNECTION_SUBGRAPH* )> set_absorbed_by =
423 [ & ]( CONNECTION_SUBGRAPH *child )
424 {
425 child->m_absorbed_by = this;
426
427 for( CONNECTION_SUBGRAPH* subchild : child->m_absorbed_subgraphs )
428 set_absorbed_by( subchild );
429 };
430
431 aOther->m_absorbed = true;
432 aOther->m_dirty = false;
433 aOther->m_driver = nullptr;
434 aOther->m_driver_connection = nullptr;
435
436 set_absorbed_by( aOther );
437}
438
439
441{
442 m_items.insert( aItem );
443
444 if( aItem->Connection( &m_sheet )->IsDriver() )
445 m_drivers.insert( aItem );
446
447 if( aItem->Type() == SCH_SHEET_PIN_T )
448 m_hier_pins.insert( static_cast<SCH_SHEET_PIN*>( aItem ) );
449 else if( aItem->Type() == SCH_HIER_LABEL_T )
450 m_hier_ports.insert( static_cast<SCH_HIERLABEL*>( aItem ) );
451}
452
453
455{
457 return;
458
459 for( SCH_ITEM* item : m_items )
460 {
461 SCH_CONNECTION* item_conn = item->GetOrInitConnection( m_sheet, m_graph );
462
463 if( !item_conn )
464 continue;
465
466 if( ( m_driver_connection->IsBus() && item_conn->IsNet() ) ||
467 ( m_driver_connection->IsNet() && item_conn->IsBus() ) )
468 {
469 continue;
470 }
471
472 if( item != m_driver )
473 {
474 item_conn->Clone( *m_driver_connection );
475 item_conn->ClearDirty();
476 }
477 }
478}
479
480
482{
483 if( !aDriver )
484 return PRIORITY::NONE;
485
486 switch( aDriver->Type() )
487 {
492 case SCH_PIN_T:
493 {
494 SCH_PIN* sch_pin = static_cast<SCH_PIN*>( aDriver );
495 SCH_SYMBOL* sym = sch_pin->GetParentSymbol();
496
497 if( sch_pin->IsGlobalPower() )
498 return PRIORITY::POWER_PIN;
499 else if( !sym || sym->GetExcludedFromBoard() || sym->GetLibSymbolRef()->GetReferenceField().GetText().StartsWith( '#' ) )
500 return PRIORITY::NONE;
501 else
502 return PRIORITY::PIN;
503 }
504
505 default: return PRIORITY::NONE;
506 }
507}
508
509
511{
512 std::copy( aGraph.m_items.begin(), aGraph.m_items.end(),
513 std::back_inserter( m_items ) );
514
515 for( SCH_ITEM* item : aGraph.m_items )
516 item->SetConnectionGraph( this );
517
518 std::copy( aGraph.m_subgraphs.begin(), aGraph.m_subgraphs.end(),
519 std::back_inserter( m_subgraphs ) );
520
521 for( CONNECTION_SUBGRAPH* sg : aGraph.m_subgraphs )
522 {
523 if( sg->m_driver_connection )
524 sg->m_driver_connection->SetGraph( this );
525
526 sg->m_graph = this;
527 }
528
529 std::copy( aGraph.m_driver_subgraphs.begin(),
530 aGraph.m_driver_subgraphs.end(),
531 std::back_inserter( m_driver_subgraphs ) );
532
533 std::copy( aGraph.m_global_power_pins.begin(),
534 aGraph.m_global_power_pins.end(),
535 std::back_inserter( m_global_power_pins ) );
536
537 for( auto& [key, value] : aGraph.m_net_name_to_subgraphs_map )
538 m_net_name_to_subgraphs_map.insert_or_assign( key, value );
539
540 for( auto& [key, value] : aGraph.m_sheet_to_subgraphs_map )
541 m_sheet_to_subgraphs_map.insert_or_assign( key, value );
542
543 for( auto& [key, value] : aGraph.m_net_name_to_code_map )
544 m_net_name_to_code_map.insert_or_assign( key, value );
545
546 for( auto& [key, value] : aGraph.m_bus_name_to_code_map )
547 m_bus_name_to_code_map.insert_or_assign( key, value );
548
549 for( auto& [key, value] : aGraph.m_net_code_to_subgraphs_map )
550 m_net_code_to_subgraphs_map.insert_or_assign( key, value );
551
552 for( auto& [key, value] : aGraph.m_item_to_subgraph_map )
553 m_item_to_subgraph_map.insert_or_assign( key, value );
554
555 for( auto& [key, value] : aGraph.m_local_label_cache )
556 m_local_label_cache.insert_or_assign( key, value );
557
558 for( auto& [key, value] : aGraph.m_global_label_cache )
559 m_global_label_cache.insert_or_assign( key, value );
560
561 m_last_bus_code = std::max( m_last_bus_code, aGraph.m_last_bus_code );
562 m_last_net_code = std::max( m_last_net_code, aGraph.m_last_net_code );
564
565}
566
567
569{
570 for( auto& subgraph : m_subgraphs )
571 {
573 if( subgraph->m_graph == this )
574 delete subgraph;
575 }
576
577 m_items.clear();
578 m_subgraphs.clear();
579 m_driver_subgraphs.clear();
581 m_global_power_pins.clear();
582 m_bus_alias_cache.clear();
588 m_local_label_cache.clear();
589 m_global_label_cache.clear();
590 m_last_net_code = 1;
591 m_last_bus_code = 1;
593}
594
595
596void CONNECTION_GRAPH::Recalculate( const SCH_SHEET_LIST& aSheetList, bool aUnconditional,
597 std::function<void( SCH_ITEM* )>* aChangedItemHandler )
598{
599 PROF_TIMER recalc_time( "CONNECTION_GRAPH::Recalculate" );
600
601 if( aUnconditional )
602 Reset();
603
604 PROF_TIMER update_items( "updateItemConnectivity" );
605
606 m_sheetList = aSheetList;
607 std::set<SCH_ITEM*> dirty_items;
608
609 for( const SCH_SHEET_PATH& sheet : aSheetList )
610 {
611 std::vector<SCH_ITEM*> items;
612 // Store current unit value, to regenerate it after calculations
613 // (useful in complex hierarchies)
614 std::vector<std::pair<SCH_SYMBOL*, int>> symbolsChanged;
615
616 for( SCH_ITEM* item : sheet.LastScreen()->Items() )
617 {
618 if( item->IsConnectable() && ( aUnconditional || item->IsConnectivityDirty() ) )
619 {
620 wxLogTrace( ConnTrace, wxT( "Adding item %s to connectivity graph update" ),
621 item->GetTypeDesc() );
622 items.push_back( item );
623 dirty_items.insert( item );
624 }
625 // Ensure the hierarchy info stored in SCREENS is built and up to date
626 // (multi-unit symbols)
627 if( item->Type() == SCH_SYMBOL_T )
628 {
629 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
630 int new_unit = symbol->GetUnitSelection( &sheet );
631
632 // Store the initial unit value, to regenerate it after calculations,
633 // if modified
634 if( symbol->GetUnit() != new_unit )
635 symbolsChanged.push_back( { symbol, symbol->GetUnit() } );
636
637 symbol->UpdateUnit( new_unit );
638 }
639 }
640
641 m_items.reserve( m_items.size() + items.size() );
642
643 updateItemConnectivity( sheet, items );
644
645 // UpdateDanglingState() also adds connected items for SCH_TEXT
646 sheet.LastScreen()->TestDanglingEnds( &sheet, aChangedItemHandler );
647
648 // Restore the m_unit member, to avoid changes in current active sheet path
649 // after calculations
650 for( auto& item : symbolsChanged )
651 {
652 item.first->UpdateUnit( item.second );
653 }
654 }
655
656 for( SCH_ITEM* item : dirty_items )
657 item->SetConnectivityDirty( false );
658
659 if( wxLog::IsAllowedTraceMask( DanglingProfileMask ) )
660 update_items.Show();
661
662 PROF_TIMER build_graph( "buildConnectionGraph" );
663
664 buildConnectionGraph( aChangedItemHandler );
665
666 if( wxLog::IsAllowedTraceMask( DanglingProfileMask ) )
667 build_graph.Show();
668
669 recalc_time.Stop();
670
671 if( wxLog::IsAllowedTraceMask( DanglingProfileMask ) )
672 recalc_time.Show();
673}
674
675std::set<std::pair<SCH_SHEET_PATH, SCH_ITEM*>> CONNECTION_GRAPH::ExtractAffectedItems(
676 const std::set<SCH_ITEM*> &aItems )
677{
678 std::set<std::pair<SCH_SHEET_PATH, SCH_ITEM*>> retvals;
679 std::set<CONNECTION_SUBGRAPH*> subgraphs;
680
681 auto traverse_subgraph = [&retvals, &subgraphs]( CONNECTION_SUBGRAPH* aSubgraph )
682 {
683 // Find the primary subgraph on this sheet
684 while( aSubgraph->m_absorbed_by )
685 {
686 wxASSERT( aSubgraph->m_graph == aSubgraph->m_absorbed_by->m_graph );
687 aSubgraph = aSubgraph->m_absorbed_by;
688 }
689
690 // Find the top most connected subgraph on all sheets
691 while( aSubgraph->m_hier_parent )
692 {
693 wxASSERT( aSubgraph->m_graph == aSubgraph->m_hier_parent->m_graph );
694 aSubgraph = aSubgraph->m_hier_parent;
695 }
696
697 // Recurse through all subsheets to collect connected items
698 aSubgraph->getAllConnectedItems( retvals, subgraphs );
699 };
700
701 for( SCH_ITEM* item : aItems )
702 {
703 auto it = m_item_to_subgraph_map.find( item );
704
705 if( it == m_item_to_subgraph_map.end() )
706 continue;
707
708 CONNECTION_SUBGRAPH* sg = it->second;
709
710 traverse_subgraph( sg );
711
712 for( auto& bus_it : sg->m_bus_neighbors )
713 {
714 for( CONNECTION_SUBGRAPH* bus_sg : bus_it.second )
715 traverse_subgraph( bus_sg );
716 }
717
718 for( auto& bus_it : sg->m_bus_parents )
719 {
720 for( CONNECTION_SUBGRAPH* bus_sg : bus_it.second )
721 traverse_subgraph( bus_sg );
722 }
723
725 }
726
727 removeSubgraphs( subgraphs );
728
729 return retvals;
730}
731
732
733void CONNECTION_GRAPH::removeSubgraphs( std::set<CONNECTION_SUBGRAPH*>& aSubgraphs )
734{
735 std::sort( m_driver_subgraphs.begin(), m_driver_subgraphs.end() );
736 std::sort( m_subgraphs.begin(), m_subgraphs.end() );
737 std::set<int> codes_to_remove;
738
739 for( auto& el : m_sheet_to_subgraphs_map )
740 {
741 std::sort( el.second.begin(), el.second.end() );
742 }
743
744 for( CONNECTION_SUBGRAPH* sg : aSubgraphs )
745 {
746 for( auto& it : sg->m_bus_neighbors )
747 {
748 for( CONNECTION_SUBGRAPH* neighbor : it.second )
749 {
750 auto& parents = neighbor->m_bus_parents[it.first];
751
752 for( auto test = parents.begin(); test != parents.end(); )
753 {
754 if( *test == sg )
755 test = parents.erase( test );
756 else
757 ++test;
758 }
759
760 if( parents.empty() )
761 neighbor->m_bus_parents.erase( it.first );
762 }
763 }
764
765 for( auto& it : sg->m_bus_parents )
766 {
767 for( CONNECTION_SUBGRAPH* parent : it.second )
768 {
769 auto& neighbors = parent->m_bus_neighbors[it.first];
770
771 for( auto test = neighbors.begin(); test != neighbors.end(); )
772 {
773 if( *test == sg )
774 test = neighbors.erase( test );
775 else
776 ++test;
777 }
778
779 if( neighbors.empty() )
780 parent->m_bus_neighbors.erase( it.first );
781 }
782 }
783
784 {
785 auto it = std::lower_bound( m_driver_subgraphs.begin(), m_driver_subgraphs.end(), sg );
786
787 while( it != m_driver_subgraphs.end() && *it == sg )
788 it = m_driver_subgraphs.erase( it );
789 }
790
791 {
792 auto it = std::lower_bound( m_subgraphs.begin(), m_subgraphs.end(), sg );
793
794 while( it != m_subgraphs.end() && *it == sg )
795 it = m_subgraphs.erase( it );
796 }
797
798 for( auto& el : m_sheet_to_subgraphs_map )
799 {
800 auto it = std::lower_bound( el.second.begin(), el.second.end(), sg );
801
802 while( it != el.second.end() && *it == sg )
803 it = el.second.erase( it );
804 }
805
806 auto remove_sg = [sg]( auto it ) -> bool
807 {
808 for( const CONNECTION_SUBGRAPH* test_sg : it->second )
809 {
810 if( sg == test_sg )
811 return true;
812 }
813
814 return false;
815 };
816
817 for( auto it = m_global_label_cache.begin(); it != m_global_label_cache.end(); )
818 {
819 if( remove_sg( it ) )
820 it = m_global_label_cache.erase( it );
821 else
822 ++it;
823 }
824
825 for( auto it = m_local_label_cache.begin(); it != m_local_label_cache.end(); )
826 {
827 if( remove_sg( it ) )
828 it = m_local_label_cache.erase( it );
829 else
830 ++it;
831 }
832
833 for( auto it = m_net_code_to_subgraphs_map.begin(); it != m_net_code_to_subgraphs_map.end(); )
834 {
835 if( remove_sg( it ) )
836 {
837 codes_to_remove.insert( it->first.Netcode );
838 it = m_net_code_to_subgraphs_map.erase( it );
839 }
840 else
841 ++it;
842 }
843
844 for( auto it = m_net_name_to_subgraphs_map.begin(); it != m_net_name_to_subgraphs_map.end(); )
845 {
846 if( remove_sg( it ) )
847 it = m_net_name_to_subgraphs_map.erase( it );
848 else
849 ++it;
850 }
851
852 for( auto it = m_item_to_subgraph_map.begin(); it != m_item_to_subgraph_map.end(); )
853 {
854 if( it->second == sg )
855 it = m_item_to_subgraph_map.erase( it );
856 else
857 ++it;
858 }
859
860
861 }
862
863 for( auto it = m_net_name_to_code_map.begin(); it != m_net_name_to_code_map.end(); )
864 {
865 if( codes_to_remove.find( it->second ) != codes_to_remove.end() )
866 it = m_net_name_to_code_map.erase( it );
867 else
868 ++it;
869 }
870
871 for( auto it = m_bus_name_to_code_map.begin(); it != m_bus_name_to_code_map.end(); )
872 {
873 if( codes_to_remove.find( it->second ) != codes_to_remove.end() )
874 it = m_bus_name_to_code_map.erase( it );
875 else
876 ++it;
877 }
878
879 for( CONNECTION_SUBGRAPH* sg : aSubgraphs )
880 {
881 sg->m_code = -1;
882 sg->m_graph = nullptr;
883 delete sg;
884 }
885}
886
887
889 const std::vector<SCH_ITEM*>& aItemList )
890{
891 std::map<VECTOR2I, std::vector<SCH_ITEM*>> connection_map;
892
893 for( SCH_ITEM* item : aItemList )
894 {
895 std::vector<VECTOR2I> points = item->GetConnectionPoints();
896 item->ConnectedItems( aSheet ).clear();
897
898 if( item->Type() == SCH_SHEET_T )
899 {
900 for( SCH_SHEET_PIN* pin : static_cast<SCH_SHEET*>( item )->GetPins() )
901 {
902 pin->InitializeConnection( aSheet, this );
903
904 pin->ConnectedItems( aSheet ).clear();
905
906 connection_map[ pin->GetTextPos() ].push_back( pin );
907 m_items.emplace_back( pin );
908 }
909 }
910 else if( item->Type() == SCH_SYMBOL_T )
911 {
912 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
913
914 for( SCH_PIN* pin : symbol->GetPins( &aSheet ) )
915 {
916 SCH_CONNECTION* conn = pin->InitializeConnection( aSheet, this );
917
918 VECTOR2I pos = pin->GetPosition();
919
920 // because calling the first time is not thread-safe
921 wxString name = pin->GetDefaultNetName( aSheet );
922 pin->ConnectedItems( aSheet ).clear();
923
924 // power symbol pins need to be post-processed later
925 if( pin->IsGlobalPower() )
926 {
927 conn->SetName( name );
928 m_global_power_pins.emplace_back( std::make_pair( aSheet, pin ) );
929 }
930
931 connection_map[ pos ].push_back( pin );
932 m_items.emplace_back( pin );
933 }
934 }
935 else
936 {
937 m_items.emplace_back( item );
938 SCH_CONNECTION* conn = item->InitializeConnection( aSheet, this );
939
940 // Set bus/net property here so that the propagation code uses it
941 switch( item->Type() )
942 {
943 case SCH_LINE_T:
944 conn->SetType( item->GetLayer() == LAYER_BUS ? CONNECTION_TYPE::BUS :
945 CONNECTION_TYPE::NET );
946 break;
947
949 conn->SetType( CONNECTION_TYPE::BUS );
950 // clean previous (old) links:
951 static_cast<SCH_BUS_BUS_ENTRY*>( item )->m_connected_bus_items[0] = nullptr;
952 static_cast<SCH_BUS_BUS_ENTRY*>( item )->m_connected_bus_items[1] = nullptr;
953 break;
954
955 case SCH_PIN_T:
956 conn->SetType( CONNECTION_TYPE::NET );
957 break;
958
960 conn->SetType( CONNECTION_TYPE::NET );
961 // clean previous (old) link:
962 static_cast<SCH_BUS_WIRE_ENTRY*>( item )->m_connected_bus_item = nullptr;
963 break;
964
965 default:
966 break;
967 }
968
969 for( const VECTOR2I& point : points )
970 connection_map[ point ].push_back( item );
971 }
972 }
973
974 for( const auto& it : connection_map )
975 {
976 std::vector<SCH_ITEM*> connection_vec = it.second;
977 std::sort( connection_vec.begin(), connection_vec.end() );
978 alg::remove_duplicates( connection_vec );
979
980 // Pre-scan to see if we have a bus at this location
981 SCH_LINE* busLine = aSheet.LastScreen()->GetBus( it.first );
982
983 std::mutex update_mutex;
984
985 auto update_lambda = [&]( SCH_ITEM* connected_item ) -> size_t
986 {
987 // Bus entries are special: they can have connection points in the
988 // middle of a wire segment, because the junction algo doesn't split
989 // the segment in two where you place a bus entry. This means that
990 // bus entries that don't land on the end of a line segment need to
991 // have "virtual" connection points to the segments they graphically
992 // touch.
993 if( connected_item->Type() == SCH_BUS_WIRE_ENTRY_T )
994 {
995 // If this location only has the connection point of the bus
996 // entry itself, this means that either the bus entry is not
997 // connected to anything graphically, or that it is connected to
998 // a segment at some point other than at one of the endpoints.
999 if( connection_vec.size() == 1 )
1000 {
1001 if( busLine )
1002 {
1003 auto bus_entry = static_cast<SCH_BUS_WIRE_ENTRY*>( connected_item );
1004 bus_entry->m_connected_bus_item = busLine;
1005 }
1006 }
1007 }
1008
1009 // Bus-to-bus entries are treated just like bus wires
1010 else if( connected_item->Type() == SCH_BUS_BUS_ENTRY_T )
1011 {
1012 if( connection_vec.size() < 2 )
1013 {
1014 if( busLine )
1015 {
1016 auto bus_entry = static_cast<SCH_BUS_BUS_ENTRY*>( connected_item );
1017
1018 if( it.first == bus_entry->GetPosition() )
1019 bus_entry->m_connected_bus_items[0] = busLine;
1020 else
1021 bus_entry->m_connected_bus_items[1] = busLine;
1022
1023 std::lock_guard<std::mutex> lock( update_mutex );
1024 bus_entry->AddConnectionTo( aSheet, busLine );
1025 busLine->AddConnectionTo( aSheet, bus_entry );
1026 }
1027 }
1028 }
1029
1030 // Change junctions to be on bus junction layer if they are touching a bus
1031 else if( connected_item->Type() == SCH_JUNCTION_T )
1032 {
1033 connected_item->SetLayer( busLine ? LAYER_BUS_JUNCTION : LAYER_JUNCTION );
1034 }
1035
1036 SCH_ITEM_SET& connected_set = connected_item->ConnectedItems( aSheet );
1037 connected_set.reserve( connection_vec.size() );
1038
1039 for( SCH_ITEM* test_item : connection_vec )
1040 {
1041 bool bus_connection_ok = true;
1042
1043 if( test_item == connected_item )
1044 continue;
1045
1046 // Set up the link between the bus entry net and the bus
1047 if( connected_item->Type() == SCH_BUS_WIRE_ENTRY_T )
1048 {
1049 if( test_item->GetLayer() == LAYER_BUS )
1050 {
1051 auto bus_entry = static_cast<SCH_BUS_WIRE_ENTRY*>( connected_item );
1052 bus_entry->m_connected_bus_item = test_item;
1053 }
1054 }
1055
1056 // Bus entries only connect to bus lines on the end that is touching a bus line.
1057 // If the user has overlapped another net line with the endpoint of the bus entry
1058 // where the entry connects to a bus, we don't want to short-circuit it.
1059 if( connected_item->Type() == SCH_BUS_WIRE_ENTRY_T )
1060 {
1061 bus_connection_ok = !busLine || test_item->GetLayer() == LAYER_BUS;
1062 }
1063 else if( test_item->Type() == SCH_BUS_WIRE_ENTRY_T )
1064 {
1065 bus_connection_ok = !busLine || connected_item->GetLayer() == LAYER_BUS;
1066 }
1067
1068 if( connected_item->ConnectionPropagatesTo( test_item ) &&
1069 test_item->ConnectionPropagatesTo( connected_item ) &&
1070 bus_connection_ok )
1071 {
1072 connected_set.push_back( test_item );
1073 }
1074 }
1075
1076 // If we got this far and did not find a connected bus item for a bus entry,
1077 // we should do a manual scan in case there is a bus item on this connection
1078 // point but we didn't pick it up earlier because there is *also* a net item here.
1079 if( connected_item->Type() == SCH_BUS_WIRE_ENTRY_T )
1080 {
1081 auto bus_entry = static_cast<SCH_BUS_WIRE_ENTRY*>( connected_item );
1082
1083 if( !bus_entry->m_connected_bus_item )
1084 {
1085 SCH_SCREEN* screen = aSheet.LastScreen();
1086 SCH_LINE* bus = screen->GetBus( it.first );
1087
1088 if( bus )
1089 bus_entry->m_connected_bus_item = bus;
1090 }
1091 }
1092
1093 return 1;
1094 };
1095
1097
1098 tp.push_loop( connection_vec.size(),
1099 [&]( const int a, const int b)
1100 {
1101 for( int ii = a; ii < b; ++ii )
1102 update_lambda( connection_vec[ii] );
1103 });
1104 tp.wait_for_tasks();
1105 }
1106}
1107
1108
1110{
1111 // Recache all bus aliases for later use
1112 wxCHECK_RET( m_schematic, wxS( "Connection graph cannot be built without schematic pointer" ) );
1113
1114 SCH_SHEET_LIST all_sheets = m_schematic->GetSheets();
1115
1116 for( unsigned i = 0; i < all_sheets.size(); i++ )
1117 {
1118 for( const std::shared_ptr<BUS_ALIAS>& alias : all_sheets[i].LastScreen()->GetBusAliases() )
1119 m_bus_alias_cache[ alias->GetName() ] = alias;
1120 }
1121
1122 // Build subgraphs from items (on a per-sheet basis)
1123
1124 for( SCH_ITEM* item : m_items )
1125 {
1126 for( const auto& it : item->m_connection_map )
1127 {
1128 const SCH_SHEET_PATH& sheet = it.first;
1129 SCH_CONNECTION* connection = it.second;
1130
1131 if( connection->SubgraphCode() == 0 )
1132 {
1133 CONNECTION_SUBGRAPH* subgraph = new CONNECTION_SUBGRAPH( this );
1134
1135 subgraph->m_code = m_last_subgraph_code++;
1136 subgraph->m_sheet = sheet;
1137
1138 subgraph->AddItem( item );
1139
1140 connection->SetSubgraphCode( subgraph->m_code );
1141 m_item_to_subgraph_map[item] = subgraph;
1142
1143 std::list<SCH_ITEM*> memberlist;
1144
1145 auto get_items =
1146 [&]( SCH_ITEM* aItem ) -> bool
1147 {
1148 SCH_CONNECTION* conn = aItem->GetOrInitConnection( sheet, this );
1149 bool unique = !( aItem->GetFlags() & CANDIDATE );
1150
1151 if( conn && !conn->SubgraphCode() )
1152 aItem->SetFlags( CANDIDATE );
1153
1154 return ( unique && conn && ( conn->SubgraphCode() == 0 ) );
1155 };
1156
1157 std::copy_if( item->ConnectedItems( sheet ).begin(),
1158 item->ConnectedItems( sheet ).end(),
1159 std::back_inserter( memberlist ), get_items );
1160
1161 for( SCH_ITEM* connected_item : memberlist )
1162 {
1163 if( connected_item->Type() == SCH_NO_CONNECT_T )
1164 subgraph->m_no_connect = connected_item;
1165
1166 SCH_CONNECTION* connected_conn = connected_item->Connection( &sheet );
1167
1168 wxASSERT( connected_conn );
1169
1170 if( connected_conn->SubgraphCode() == 0 )
1171 {
1172 connected_conn->SetSubgraphCode( subgraph->m_code );
1173 m_item_to_subgraph_map[connected_item] = subgraph;
1174 subgraph->AddItem( connected_item );
1175 SCH_ITEM_SET& citemset = connected_item->ConnectedItems( sheet );
1176
1177 for( SCH_ITEM* citem : citemset )
1178 {
1179 if( citem->HasFlag( CANDIDATE ) )
1180 continue;
1181
1182 if( get_items( citem ) )
1183 memberlist.push_back( citem );
1184 }
1185 }
1186 }
1187
1188 for( SCH_ITEM* connected_item : memberlist )
1189 connected_item->ClearFlags( CANDIDATE );
1190
1191 subgraph->m_dirty = true;
1192 m_subgraphs.push_back( subgraph );
1193 }
1194 }
1195 }
1196
1197}
1198
1200{
1201 // Resolve drivers for subgraphs and propagate connectivity info
1202 std::vector<CONNECTION_SUBGRAPH*> dirty_graphs;
1203
1204 std::copy_if( m_subgraphs.begin(), m_subgraphs.end(), std::back_inserter( dirty_graphs ),
1205 [&] ( const CONNECTION_SUBGRAPH* candidate )
1206 {
1207 return candidate->m_dirty;
1208 } );
1209
1210 std::vector<std::future<size_t>> returns( dirty_graphs.size() );
1211
1212 auto update_lambda = []( CONNECTION_SUBGRAPH* subgraph ) -> size_t
1213 {
1214 if( !subgraph->m_dirty )
1215 return 0;
1216
1217 // Special processing for some items
1218 for( SCH_ITEM* item : subgraph->m_items )
1219 {
1220 switch( item->Type() )
1221 {
1222 case SCH_NO_CONNECT_T:
1223 subgraph->m_no_connect = item;
1224 break;
1225
1227 subgraph->m_bus_entry = item;
1228 break;
1229
1230 case SCH_PIN_T:
1231 {
1232 auto pin = static_cast<SCH_PIN*>( item );
1233
1234 if( pin->GetType() == ELECTRICAL_PINTYPE::PT_NC )
1235 subgraph->m_no_connect = item;
1236
1237 break;
1238 }
1239
1240 default:
1241 break;
1242 }
1243 }
1244
1245 subgraph->ResolveDrivers( true );
1246 subgraph->m_dirty = false;
1247
1248 return 1;
1249 };
1250
1252
1253 tp.push_loop( dirty_graphs.size(),
1254 [&]( const int a, const int b)
1255 {
1256 for( int ii = a; ii < b; ++ii )
1257 update_lambda( dirty_graphs[ii] );
1258 });
1259 tp.wait_for_tasks();
1260
1261 // Now discard any non-driven subgraphs from further consideration
1262
1263 std::copy_if( m_subgraphs.begin(), m_subgraphs.end(), std::back_inserter( m_driver_subgraphs ),
1264 [&] ( const CONNECTION_SUBGRAPH* candidate ) -> bool
1265 {
1266 return candidate->m_driver;
1267 } );
1268}
1269
1270
1272{
1273 // Check for subgraphs with the same net name but only weak drivers.
1274 // For example, two wires that are both connected to hierarchical
1275 // sheet pins that happen to have the same name, but are not the same.
1276
1277 for( auto&& subgraph : m_driver_subgraphs )
1278 {
1279 wxString full_name = subgraph->m_driver_connection->Name();
1280 wxString name = subgraph->m_driver_connection->Name( true );
1281 m_net_name_to_subgraphs_map[full_name].emplace_back( subgraph );
1282
1283 // For vector buses, we need to cache the prefix also, as two different instances of the
1284 // weakly driven pin may have the same prefix but different vector start and end. We need
1285 // to treat those as needing renaming also, because otherwise if they end up on a sheet with
1286 // common usage, they will be incorrectly merged.
1287 if( subgraph->m_driver_connection->Type() == CONNECTION_TYPE::BUS )
1288 {
1289 wxString prefixOnly = full_name.BeforeFirst( '[' ) + wxT( "[]" );
1290 m_net_name_to_subgraphs_map[prefixOnly].emplace_back( subgraph );
1291 }
1292
1293 subgraph->m_dirty = true;
1294
1295 if( subgraph->m_strong_driver )
1296 {
1297 SCH_ITEM* driver = subgraph->m_driver;
1298 SCH_SHEET_PATH sheet = subgraph->m_sheet;
1299
1300 switch( driver->Type() )
1301 {
1302 case SCH_LABEL_T:
1303 case SCH_HIER_LABEL_T:
1304 {
1305 m_local_label_cache[std::make_pair( sheet, name )].push_back( subgraph );
1306 break;
1307 }
1308 case SCH_GLOBAL_LABEL_T:
1309 {
1310 m_global_label_cache[name].push_back( subgraph );
1311 break;
1312 }
1313 case SCH_PIN_T:
1314 {
1315 SCH_PIN* pin = static_cast<SCH_PIN*>( driver );
1316 wxASSERT( pin->IsGlobalPower() );
1317 m_global_label_cache[name].push_back( subgraph );
1318 break;
1319 }
1320 default:
1321 {
1322 UNITS_PROVIDER unitsProvider( schIUScale, EDA_UNITS::MILLIMETRES );
1323
1324 wxLogTrace( ConnTrace, wxS( "Unexpected strong driver %s" ),
1325 driver->GetItemDescription( &unitsProvider ) );
1326 break;
1327 }
1328 }
1329 }
1330 }
1331}
1332
1333
1335{
1336 std::vector<CONNECTION_SUBGRAPH*> new_subgraphs;
1337
1338 for( CONNECTION_SUBGRAPH* subgraph : m_driver_subgraphs )
1339 {
1340 SCH_ITEM_SET vec = subgraph->GetAllBusLabels();
1341
1342 for( SCH_ITEM* item : vec )
1343 {
1344 SCH_LABEL_BASE* label = static_cast<SCH_LABEL_BASE*>( item );
1345
1346 SCH_CONNECTION dummy( item, subgraph->m_sheet );
1347 dummy.SetGraph( this );
1348 dummy.ConfigureFromLabel( label->GetText() );
1349
1350 wxLogTrace( ConnTrace, wxS( "new bus label (%s)" ), label->GetText() );
1351
1352 for( const auto& conn : dummy.Members() )
1353 {
1354 wxString name = conn->FullLocalName();
1355
1356 CONNECTION_SUBGRAPH* new_sg = new CONNECTION_SUBGRAPH( this );
1357 SCH_CONNECTION* new_conn = new SCH_CONNECTION( item, subgraph->m_sheet );
1358 new_conn->SetGraph( this );
1359
1360 new_conn->SetName( name );
1361 new_conn->SetType( CONNECTION_TYPE::NET );
1362 int code = assignNewNetCode( *new_conn );
1363
1364 wxLogTrace( ConnTrace, wxS( "SG(%ld), Adding full local name (%s) with sg (%d) on subsheet %s" ), subgraph->m_code,
1365 name, code, subgraph->m_sheet.PathHumanReadable() );
1366
1367 new_sg->m_driver_connection = new_conn;
1368 new_sg->m_code = m_last_subgraph_code++;
1369 new_sg->m_sheet = subgraph->GetSheet();
1370 new_sg->m_is_bus_member = true;
1371 new_sg->m_strong_driver = true;
1373
1374 NET_NAME_CODE_CACHE_KEY key = { new_sg->GetNetName(), code };
1375 m_net_code_to_subgraphs_map[ key ].push_back( new_sg );
1376 m_net_name_to_subgraphs_map[ name ].push_back( new_sg );
1377 m_subgraphs.push_back( new_sg );
1378 new_subgraphs.push_back( new_sg );
1379 }
1380 }
1381 }
1382
1383 std::copy( new_subgraphs.begin(), new_subgraphs.end(), std::back_inserter( m_driver_subgraphs ) );
1384}
1385
1387{
1388 // Generate subgraphs for global power pins. These will be merged with other subgraphs
1389 // on the same sheet in the next loop.
1390 // These are NOT limited to power symbols, we support legacy invisible + power-in pins
1391 // on non-power symbols.
1392
1393 std::unordered_map<int, CONNECTION_SUBGRAPH*> global_power_pin_subgraphs;
1394
1395 for( const auto& it : m_global_power_pins )
1396 {
1397 SCH_SHEET_PATH sheet = it.first;
1398 SCH_PIN* pin = it.second;
1399
1400 if( !pin->ConnectedItems( sheet ).empty() && !pin->GetLibPin()->GetParent()->IsPower() )
1401 {
1402 // ERC will warn about this: user has wired up an invisible pin
1403 continue;
1404 }
1405
1406 SCH_CONNECTION* connection = pin->GetOrInitConnection( sheet, this );
1407
1408 // If this pin already has a subgraph, don't need to process
1409 if( !connection || connection->SubgraphCode() > 0 )
1410 continue;
1411
1412 // Proper modern power symbols get their net name from the value field
1413 // in the symbol, but we support legacy non-power symbols with global
1414 // power connections based on invisible, power-in, pin's names.
1415 if( pin->GetLibPin()->GetParent()->IsPower() )
1416 connection->SetName( pin->GetParentSymbol()->GetValueFieldText( true, &sheet, false ) );
1417 else
1418 connection->SetName( pin->GetShownName() );
1419
1420 int code = assignNewNetCode( *connection );
1421
1422 connection->SetNetCode( code );
1423
1424 CONNECTION_SUBGRAPH* subgraph;
1425 auto jj = global_power_pin_subgraphs.find( code );
1426
1427 if( jj != global_power_pin_subgraphs.end() )
1428 {
1429 subgraph = jj->second;
1430 subgraph->AddItem( pin );
1431 }
1432 else
1433 {
1434 subgraph = new CONNECTION_SUBGRAPH( this );
1435
1436 subgraph->m_code = m_last_subgraph_code++;
1437 subgraph->m_sheet = sheet;
1438
1439 subgraph->AddItem( pin );
1440 subgraph->ResolveDrivers();
1441
1442 NET_NAME_CODE_CACHE_KEY key = { subgraph->GetNetName(), code };
1443 m_net_code_to_subgraphs_map[ key ].push_back( subgraph );
1444 m_subgraphs.push_back( subgraph );
1445 m_driver_subgraphs.push_back( subgraph );
1446
1447 global_power_pin_subgraphs[code] = subgraph;
1448 }
1449
1450 connection->SetSubgraphCode( subgraph->m_code );
1451 }
1452}
1453
1454
1456{
1457 // Here we do all the local (sheet) processing of each subgraph, including assigning net
1458 // codes, merging subgraphs together that use label connections, etc.
1459
1460 // Cache remaining valid subgraphs by sheet path
1461 for( CONNECTION_SUBGRAPH* subgraph : m_driver_subgraphs )
1462 m_sheet_to_subgraphs_map[ subgraph->m_sheet ].emplace_back( subgraph );
1463
1464 std::unordered_set<CONNECTION_SUBGRAPH*> invalidated_subgraphs;
1465
1466 for( CONNECTION_SUBGRAPH* subgraph : m_driver_subgraphs )
1467 {
1468 if( subgraph->m_absorbed )
1469 continue;
1470
1471 SCH_CONNECTION* connection = subgraph->m_driver_connection;
1472 SCH_SHEET_PATH sheet = subgraph->m_sheet;
1473 wxString name = connection->Name();
1474
1475 // Test subgraphs with weak drivers for net name conflicts and fix them
1476 unsigned suffix = 1;
1477
1478 auto create_new_name =
1479 [&suffix]( SCH_CONNECTION* aConn ) -> wxString
1480 {
1481 wxString newName;
1482 wxString suffixStr = std::to_wstring( suffix );
1483
1484 // For group buses with a prefix, we can add the suffix to the prefix.
1485 // If they don't have a prefix, we force the creation of a prefix so that
1486 // two buses don't get inadvertently shorted together.
1487 if( aConn->Type() == CONNECTION_TYPE::BUS_GROUP )
1488 {
1489 wxString prefix = aConn->BusPrefix();
1490
1491 if( prefix.empty() )
1492 prefix = wxT( "BUS" ); // So result will be "BUS_1{...}"
1493
1494 wxString oldName = aConn->Name().AfterFirst( '{' );
1495
1496 newName << prefix << wxT( "_" ) << suffixStr << wxT( "{" ) << oldName;
1497
1498 aConn->ConfigureFromLabel( newName );
1499 }
1500 else
1501 {
1502 newName << aConn->Name() << wxT( "_" ) << suffixStr;
1503 aConn->SetSuffix( wxString( wxT( "_" ) ) << suffixStr );
1504 }
1505
1506 suffix++;
1507 return newName;
1508 };
1509
1510 if( !subgraph->m_strong_driver )
1511 {
1512 std::vector<CONNECTION_SUBGRAPH*>* vec = &m_net_name_to_subgraphs_map.at( name );
1513
1514 // If we are a unique bus vector, check if we aren't actually unique because of another
1515 // subgraph with a similar bus vector
1516 if( vec->size() <= 1 && subgraph->m_driver_connection->Type() == CONNECTION_TYPE::BUS )
1517 {
1518 wxString prefixOnly = name.BeforeFirst( '[' ) + wxT( "[]" );
1519 vec = &m_net_name_to_subgraphs_map.at( prefixOnly );
1520 }
1521
1522 if( vec->size() > 1 )
1523 {
1524 wxString new_name = create_new_name( connection );
1525
1526 while( m_net_name_to_subgraphs_map.count( new_name ) )
1527 new_name = create_new_name( connection );
1528
1529 wxLogTrace( ConnTrace, wxS( "%ld (%s) is weakly driven and not unique. Changing to %s." ),
1530 subgraph->m_code, name, new_name );
1531
1532 alg::delete_matching( *vec, subgraph );
1533
1534 m_net_name_to_subgraphs_map[new_name].emplace_back( subgraph );
1535
1536 name = new_name;
1537 }
1538 else if( subgraph->m_driver )
1539 {
1540 // If there is no conflict, promote sheet pins to be strong drivers so that they
1541 // will be considered below for propagation/merging.
1542
1543 // It is possible for this to generate a conflict if the sheet pin has the same
1544 // name as a global label on the same sheet, because global merging will then treat
1545 // this subgraph as if it had a matching local label. So, for those cases, we
1546 // don't apply this promotion
1547
1548 if( subgraph->m_driver->Type() == SCH_SHEET_PIN_T )
1549 {
1550 bool conflict = false;
1551 wxString global_name = connection->Name( true );
1552 auto kk = m_net_name_to_subgraphs_map.find( global_name );
1553
1554 if( kk != m_net_name_to_subgraphs_map.end() )
1555 {
1556 // A global will conflict if it is on the same sheet as this subgraph, since
1557 // it would be connected by implicit local label linking
1558 std::vector<CONNECTION_SUBGRAPH*>& candidates = kk->second;
1559
1560 for( const CONNECTION_SUBGRAPH* candidate : candidates )
1561 {
1562 if( candidate->m_sheet == sheet )
1563 conflict = true;
1564 }
1565 }
1566
1567 if( conflict )
1568 {
1569 wxLogTrace( ConnTrace,
1570 wxS( "%ld (%s) skipped for promotion due to potential conflict" ),
1571 subgraph->m_code, name );
1572 }
1573 else
1574 {
1575 UNITS_PROVIDER unitsProvider( schIUScale, EDA_UNITS::MILLIMETRES );
1576
1577 wxLogTrace( ConnTrace,
1578 wxS( "%ld (%s) weakly driven by unique sheet pin %s, promoting" ),
1579 subgraph->m_code, name,
1580 subgraph->m_driver->GetItemDescription( &unitsProvider ) );
1581
1582 subgraph->m_strong_driver = true;
1583 }
1584 }
1585 }
1586 }
1587
1588 // Assign net codes
1589
1590 if( connection->IsBus() )
1591 {
1592 int code = -1;
1593 auto it = m_bus_name_to_code_map.find( name );
1594
1595 if( it != m_bus_name_to_code_map.end() )
1596 {
1597 code = it->second;
1598 }
1599 else
1600 {
1601 code = m_last_bus_code++;
1602 m_bus_name_to_code_map[ name ] = code;
1603 }
1604
1605 connection->SetBusCode( code );
1606 assignNetCodesToBus( connection );
1607 }
1608 else
1609 {
1610 assignNewNetCode( *connection );
1611 }
1612
1613 // Reset the flag for the next loop below
1614 subgraph->m_dirty = true;
1615
1616 // Next, we merge together subgraphs that have label connections, and create
1617 // neighbor links for subgraphs that are part of a bus on the same sheet.
1618 // For merging, we consider each possible strong driver.
1619
1620 // If this subgraph doesn't have a strong driver, let's skip it, since there is no
1621 // way it will be merged with anything.
1622
1623 if( !subgraph->m_strong_driver )
1624 continue;
1625
1626 // candidate_subgraphs will contain each valid, non-bus subgraph on the same sheet
1627 // as the subgraph we are considering that has a strong driver.
1628 // Weakly driven subgraphs are not considered since they will never be absorbed or
1629 // form neighbor links.
1630
1631 std::vector<CONNECTION_SUBGRAPH*> candidate_subgraphs;
1632 std::copy_if( m_sheet_to_subgraphs_map[ subgraph->m_sheet ].begin(),
1633 m_sheet_to_subgraphs_map[ subgraph->m_sheet ].end(),
1634 std::back_inserter( candidate_subgraphs ),
1635 [&] ( const CONNECTION_SUBGRAPH* candidate )
1636 {
1637 return ( !candidate->m_absorbed &&
1638 candidate->m_strong_driver &&
1639 candidate != subgraph );
1640 } );
1641
1642 // This is a list of connections on the current subgraph to compare to the
1643 // drivers of each candidate subgraph. If the current subgraph is a bus,
1644 // we should consider each bus member.
1645 std::vector< std::shared_ptr<SCH_CONNECTION> > connections_to_check;
1646
1647 // Also check the main driving connection
1648 connections_to_check.push_back( std::make_shared<SCH_CONNECTION>( *connection ) );
1649
1650 auto add_connections_to_check =
1651 [&] ( CONNECTION_SUBGRAPH* aSubgraph )
1652 {
1653 for( SCH_ITEM* possible_driver : aSubgraph->m_items )
1654 {
1655 if( possible_driver == aSubgraph->m_driver )
1656 continue;
1657
1658 auto c = getDefaultConnection( possible_driver, aSubgraph );
1659
1660 if( c )
1661 {
1662 if( c->Type() != aSubgraph->m_driver_connection->Type() )
1663 continue;
1664
1665 if( c->Name( true ) == aSubgraph->m_driver_connection->Name( true ) )
1666 continue;
1667
1668 connections_to_check.push_back( c );
1669 wxLogTrace( ConnTrace,
1670 wxS( "%lu (%s): Adding secondary driver %s" ), aSubgraph->m_code,
1671 aSubgraph->m_driver_connection->Name( true ),
1672 c->Name( true ) );
1673 }
1674 }
1675 };
1676
1677 // Now add other strong drivers
1678 // The actual connection attached to these items will have been overwritten
1679 // by the chosen driver of the subgraph, so we need to create a dummy connection
1680 add_connections_to_check( subgraph );
1681
1682 std::set<SCH_CONNECTION*> checked_connections;
1683
1684 for( unsigned i = 0; i < connections_to_check.size(); i++ )
1685 {
1686 auto member = connections_to_check[i];
1687
1688 // Don't check the same connection twice
1689 if( !checked_connections.insert( member.get() ).second )
1690 continue;
1691
1692 if( member->IsBus() )
1693 {
1694 connections_to_check.insert( connections_to_check.end(),
1695 member->Members().begin(),
1696 member->Members().end() );
1697 }
1698
1699 wxString test_name = member->Name( true );
1700
1701 for( CONNECTION_SUBGRAPH* candidate : candidate_subgraphs )
1702 {
1703 if( candidate->m_absorbed || candidate == subgraph )
1704 continue;
1705
1706 bool match = false;
1707
1708 if( candidate->m_driver_connection->Name( true ) == test_name )
1709 {
1710 match = true;
1711 }
1712 else
1713 {
1714 if( !candidate->m_multiple_drivers )
1715 continue;
1716
1717 for( SCH_ITEM *driver : candidate->m_drivers )
1718 {
1719 if( driver == candidate->m_driver )
1720 continue;
1721
1722 // Sheet pins are not candidates for merging
1723 if( driver->Type() == SCH_SHEET_PIN_T )
1724 continue;
1725
1726 if( driver->Type() == SCH_PIN_T )
1727 {
1728 auto pin = static_cast<SCH_PIN*>( driver );
1729
1730 if( pin->IsGlobalPower()
1731 && pin->GetDefaultNetName( sheet ) == test_name )
1732 {
1733 match = true;
1734 break;
1735 }
1736 }
1737 else
1738 {
1739 wxASSERT( driver->Type() == SCH_LABEL_T ||
1740 driver->Type() == SCH_GLOBAL_LABEL_T ||
1741 driver->Type() == SCH_HIER_LABEL_T );
1742
1743 if( subgraph->GetNameForDriver( driver ) == test_name )
1744 {
1745 match = true;
1746 break;
1747 }
1748 }
1749 }
1750 }
1751
1752 if( match )
1753 {
1754 if( connection->IsBus() && candidate->m_driver_connection->IsNet() )
1755 {
1756 wxLogTrace( ConnTrace, wxS( "%lu (%s) has bus child %lu (%s)" ), subgraph->m_code,
1757 connection->Name(), candidate->m_code, member->Name() );
1758
1759 subgraph->m_bus_neighbors[member].insert( candidate );
1760 candidate->m_bus_parents[member].insert( subgraph );
1761 }
1762 else
1763 {
1764 wxLogTrace( ConnTrace, wxS( "%lu (%s) absorbs neighbor %lu (%s)" ),
1765 subgraph->m_code, connection->Name(),
1766 candidate->m_code, candidate->m_driver_connection->Name() );
1767
1768 // Candidate may have other non-chosen drivers we need to follow
1769 add_connections_to_check( candidate );
1770
1771 subgraph->Absorb( candidate );
1772 invalidated_subgraphs.insert( subgraph );
1773 }
1774 }
1775 }
1776 }
1777 }
1778
1779 // Update any subgraph that was invalidated above
1780 for( CONNECTION_SUBGRAPH* subgraph : invalidated_subgraphs )
1781 {
1782 if( subgraph->m_absorbed )
1783 continue;
1784
1785 if( !subgraph->ResolveDrivers() )
1786 continue;
1787
1788 if( subgraph->m_driver_connection->IsBus() )
1789 assignNetCodesToBus( subgraph->m_driver_connection );
1790 else
1791 assignNewNetCode( *subgraph->m_driver_connection );
1792
1793 wxLogTrace( ConnTrace, wxS( "Re-resolving drivers for %lu (%s)" ), subgraph->m_code,
1794 subgraph->m_driver_connection->Name() );
1795 }
1796
1797}
1798
1799
1800// TODO(JE) This won't give the same subgraph IDs (and eventually net/graph codes)
1801// to the same subgraph necessarily if it runs over and over again on the same
1802// sheet. We need:
1803//
1804// a) a cache of net/bus codes, like used before
1805// b) to persist the CONNECTION_GRAPH globally so the cache is persistent,
1806// c) some way of trying to avoid changing net names. so we should keep track
1807// of the previous driver of a net, and if it comes down to choosing between
1808// equally-prioritized drivers, choose the one that already exists as a driver
1809// on some portion of the items.
1810
1811
1812void CONNECTION_GRAPH::buildConnectionGraph( std::function<void( SCH_ITEM* )>* aChangedItemHandler )
1813{
1814 // Recache all bus aliases for later use
1815 wxCHECK_RET( m_schematic, wxT( "Connection graph cannot be built without schematic pointer" ) );
1816
1817 SCH_SHEET_LIST all_sheets = m_schematic->GetSheets();
1818
1819 for( unsigned i = 0; i < all_sheets.size(); i++ )
1820 {
1821 for( const std::shared_ptr<BUS_ALIAS>& alias : all_sheets[i].LastScreen()->GetBusAliases() )
1822 m_bus_alias_cache[ alias->GetName() ] = alias;
1823 }
1824
1825 PROF_TIMER sub_graph( "buildItemSubGraphs" );
1827
1828 if( wxLog::IsAllowedTraceMask( DanglingProfileMask ) )
1829 sub_graph.Show();
1830
1831
1838
1840
1842
1844
1845 PROF_TIMER proc_sub_graph( "ProcessSubGraphs" );
1847
1848 if( wxLog::IsAllowedTraceMask( DanglingProfileMask ) )
1849 proc_sub_graph.Show();
1850
1851 // Absorbed subgraphs should no longer be considered
1852 alg::delete_if( m_driver_subgraphs, [&]( const CONNECTION_SUBGRAPH* candidate ) -> bool
1853 {
1854 return candidate->m_absorbed;
1855 } );
1856
1857 // Store global subgraphs for later reference
1858 std::vector<CONNECTION_SUBGRAPH*> global_subgraphs;
1859 std::copy_if( m_driver_subgraphs.begin(), m_driver_subgraphs.end(),
1860 std::back_inserter( global_subgraphs ),
1861 [&] ( const CONNECTION_SUBGRAPH* candidate ) -> bool
1862 {
1863 return !candidate->m_local_driver;
1864 } );
1865
1866 // Recache remaining valid subgraphs by sheet path
1868
1869 for( CONNECTION_SUBGRAPH* subgraph : m_driver_subgraphs )
1870 m_sheet_to_subgraphs_map[ subgraph->m_sheet ].emplace_back( subgraph );
1871
1873
1874 tp.push_loop( m_driver_subgraphs.size(),
1875 [&]( const int a, const int b)
1876 {
1877 for( int ii = a; ii < b; ++ii )
1878 m_driver_subgraphs[ii]->UpdateItemConnections();
1879 });
1880 tp.wait_for_tasks();
1881
1882 // Next time through the subgraphs, we do some post-processing to handle things like
1883 // connecting bus members to their neighboring subgraphs, and then propagate connections
1884 // through the hierarchy
1885
1886 for( CONNECTION_SUBGRAPH* subgraph : m_driver_subgraphs )
1887 {
1888 if( !subgraph->m_dirty )
1889 continue;
1890
1891 wxLogTrace( ConnTrace, wxS( "Processing %lu (%s) for propagation" ), subgraph->m_code,
1892 subgraph->m_driver_connection->Name() );
1893
1894 // For subgraphs that are driven by a global (power port or label) and have more
1895 // than one global driver, we need to seek out other subgraphs driven by the
1896 // same name as the non-chosen driver and update them to match the chosen one.
1897
1898 if( !subgraph->m_local_driver && subgraph->m_multiple_drivers )
1899 {
1900 for( SCH_ITEM* driver : subgraph->m_drivers )
1901 {
1902 if( driver == subgraph->m_driver )
1903 continue;
1904
1905 const wxString& secondary_name = subgraph->GetNameForDriver( driver );
1906
1907 if( secondary_name == subgraph->m_driver_connection->Name() )
1908 continue;
1909
1910 bool secondary_is_global = CONNECTION_SUBGRAPH::GetDriverPriority( driver )
1912
1913 for( CONNECTION_SUBGRAPH* candidate : global_subgraphs )
1914 {
1915 if( candidate == subgraph )
1916 continue;
1917
1918 if( !secondary_is_global && candidate->m_sheet != subgraph->m_sheet )
1919 continue;
1920
1921 SCH_CONNECTION* conn = candidate->m_driver_connection;
1922
1923 if( conn->Name() == secondary_name )
1924 {
1925 wxLogTrace( ConnTrace, wxS( "Global %lu (%s) promoted to %s" ), candidate->m_code,
1926 conn->Name(), subgraph->m_driver_connection->Name() );
1927
1928 conn->Clone( *subgraph->m_driver_connection );
1929
1930 candidate->m_dirty = false;
1931 propagateToNeighbors( candidate, false );
1932 }
1933 }
1934 }
1935 }
1936
1937 // This call will handle descending the hierarchy and updating child subgraphs
1938 propagateToNeighbors( subgraph, false );
1939 }
1940
1941 // After processing and allowing some to be skipped if they have hierarchical
1942 // pins connecting both up and down the hierarchy, we check to see if any of them
1943 // have not been processed. This would indicate that they do not have off-sheet connections
1944 // but we still need to handle the subgraph
1945 for( CONNECTION_SUBGRAPH* subgraph : m_driver_subgraphs )
1946 {
1947 if( subgraph->m_dirty )
1948 propagateToNeighbors( subgraph, true );
1949 }
1950
1951 // Handle buses that have been linked together somewhere by member (net) connections.
1952 // This feels a bit hacky, perhaps this algorithm should be revisited in the future.
1953
1954 // For net subgraphs that have more than one bus parent, we need to ensure that those
1955 // buses are linked together in the final netlist. The final name of each bus might not
1956 // match the local name that was used to establish the parent-child relationship, because
1957 // the bus may have been renamed by a hierarchical connection. So, for each of these cases,
1958 // we need to identify the appropriate bus members to link together (and their final names),
1959 // and then update all instances of the old name in the hierarchy.
1960
1961 for( CONNECTION_SUBGRAPH* subgraph : m_driver_subgraphs )
1962 {
1963 // All SGs should have been processed by propagateToNeighbors above
1964 wxASSERT_MSG( !subgraph->m_dirty, wxS( "Subgraph not processed by propagateToNeighbors!" ) );
1965
1966 if( subgraph->m_bus_parents.size() < 2 )
1967 continue;
1968
1969 SCH_CONNECTION* conn = subgraph->m_driver_connection;
1970
1971 wxLogTrace( ConnTrace, wxS( "%lu (%s) has multiple bus parents" ),
1972 subgraph->m_code, conn->Name() );
1973
1974 wxASSERT( conn->IsNet() );
1975
1976 for( const auto& ii : subgraph->m_bus_parents )
1977 {
1978 SCH_CONNECTION* link_member = ii.first.get();
1979
1980 for( CONNECTION_SUBGRAPH* parent : ii.second )
1981 {
1982 while( parent->m_absorbed )
1983 parent = parent->m_absorbed_by;
1984
1985 SCH_CONNECTION* match = matchBusMember( parent->m_driver_connection, link_member );
1986
1987 if( !match )
1988 {
1989 wxLogTrace( ConnTrace, wxS( "Warning: could not match %s inside %lu (%s)" ),
1990 conn->Name(), parent->m_code, parent->m_driver_connection->Name() );
1991 continue;
1992 }
1993
1994 if( conn->Name() != match->Name() )
1995 {
1996 wxString old_name = match->Name();
1997
1998 wxLogTrace( ConnTrace, wxS( "Updating %lu (%s) member %s to %s" ), parent->m_code,
1999 parent->m_driver_connection->Name(), old_name, conn->Name() );
2000
2001 match->Clone( *conn );
2002
2003 auto jj = m_net_name_to_subgraphs_map.find( old_name );
2004
2005 if( jj == m_net_name_to_subgraphs_map.end() )
2006 continue;
2007
2008 for( CONNECTION_SUBGRAPH* old_sg : jj->second )
2009 {
2010 while( old_sg->m_absorbed )
2011 old_sg = old_sg->m_absorbed_by;
2012
2013 old_sg->m_driver_connection->Clone( *conn );
2014 }
2015 }
2016 }
2017 }
2018 }
2019
2020
2021 auto updateItemConnectionsTask =
2022 [&]( CONNECTION_SUBGRAPH* subgraph ) -> size_t
2023 {
2024 // Make sure weakly-driven single-pin nets get the unconnected_ prefix
2025 if( !subgraph->m_strong_driver && subgraph->m_drivers.size() == 1 &&
2026 subgraph->m_driver->Type() == SCH_PIN_T )
2027 {
2028 SCH_PIN* pin = static_cast<SCH_PIN*>( subgraph->m_driver );
2029 wxString name = pin->GetDefaultNetName( subgraph->m_sheet, true );
2030
2031 subgraph->m_driver_connection->ConfigureFromLabel( name );
2032 }
2033
2034 subgraph->m_dirty = false;
2035 subgraph->UpdateItemConnections();
2036
2037 // No other processing to do on buses
2038 if( subgraph->m_driver_connection->IsBus() )
2039 return 0;
2040
2041 // As a visual aid, we can check sheet pins that are driven by themselves to see
2042 // if they should be promoted to buses
2043
2044 if( subgraph->m_driver && subgraph->m_driver->Type() == SCH_SHEET_PIN_T )
2045 {
2046 SCH_SHEET_PIN* pin = static_cast<SCH_SHEET_PIN*>( subgraph->m_driver );
2047
2048 if( SCH_SHEET* sheet = pin->GetParent() )
2049 {
2050 wxString pinText = pin->GetText();
2051 SCH_SCREEN* screen = sheet->GetScreen();
2052
2053 for( SCH_ITEM* item : screen->Items().OfType( SCH_HIER_LABEL_T ) )
2054 {
2055 SCH_HIERLABEL* label = static_cast<SCH_HIERLABEL*>( item );
2056
2057 if( label->GetText() == pinText )
2058 {
2059 SCH_SHEET_PATH path = subgraph->m_sheet;
2060 path.push_back( sheet );
2061
2062 SCH_CONNECTION* parent_conn = label->Connection( &path );
2063
2064 if( parent_conn && parent_conn->IsBus() )
2065 subgraph->m_driver_connection->SetType( CONNECTION_TYPE::BUS );
2066
2067 break;
2068 }
2069 }
2070
2071 if( subgraph->m_driver_connection->IsBus() )
2072 return 0;
2073 }
2074 }
2075
2076 return 1;
2077 };
2078
2079 tp.push_loop( m_driver_subgraphs.size(),
2080 [&]( const int a, const int b)
2081 {
2082 for( int ii = a; ii < b; ++ii )
2083 updateItemConnectionsTask( m_driver_subgraphs[ii] );
2084 });
2085 tp.wait_for_tasks();
2086
2089
2090 for( CONNECTION_SUBGRAPH* subgraph : m_driver_subgraphs )
2091 {
2092 NET_NAME_CODE_CACHE_KEY key = { subgraph->GetNetName(),
2093 subgraph->m_driver_connection->NetCode() };
2094 m_net_code_to_subgraphs_map[ key ].push_back( subgraph );
2095
2096 m_net_name_to_subgraphs_map[subgraph->m_driver_connection->Name()].push_back( subgraph );
2097 }
2098
2099 std::shared_ptr<NET_SETTINGS>& netSettings = m_schematic->Prj().GetProjectFile().m_NetSettings;
2100 std::map<wxString, wxString> oldAssignments = netSettings->m_NetClassLabelAssignments;
2101
2102 netSettings->m_NetClassLabelAssignments.clear();
2103
2104 auto dirtySubgraphs =
2105 [&]( const std::vector<CONNECTION_SUBGRAPH*>& subgraphs )
2106 {
2107 if( aChangedItemHandler )
2108 {
2109 for( const CONNECTION_SUBGRAPH* subgraph : subgraphs )
2110 {
2111 for( SCH_ITEM* item : subgraph->m_items )
2112 (*aChangedItemHandler)( item );
2113 }
2114 }
2115 };
2116
2117 auto checkNetclassDrivers =
2118 [&]( const std::vector<CONNECTION_SUBGRAPH*>& subgraphs )
2119 {
2120 const CONNECTION_SUBGRAPH* driverSubgraph = nullptr;
2121 wxString netclass;
2122
2123 wxCHECK_RET( !subgraphs.empty(), wxT("Invalid empty subgraph" ) );
2124
2125 for( const CONNECTION_SUBGRAPH* subgraph : subgraphs )
2126 {
2127 for( SCH_ITEM* item : subgraph->m_items )
2128 {
2129 netclass = subgraph->GetNetclassForDriver( item );
2130
2131 if( !netclass.IsEmpty() )
2132 break;
2133 }
2134
2135 if( !netclass.IsEmpty() )
2136 {
2137 driverSubgraph = subgraph;
2138 break;
2139 }
2140 }
2141
2142 if( netclass.IsEmpty() )
2143 return;
2144
2145 if( !driverSubgraph )
2146 driverSubgraph = subgraphs.front();
2147
2148 const wxString netname = driverSubgraph->GetNetName();
2149
2150 if( driverSubgraph->m_driver_connection->IsBus() )
2151 {
2152 for( const auto& member : driverSubgraph->m_driver_connection->Members() )
2153 {
2154 netSettings->m_NetClassLabelAssignments[ member->Name() ] = netclass;
2155
2156 auto ii = m_net_name_to_subgraphs_map.find( member->Name() );
2157
2158 if( ii != m_net_name_to_subgraphs_map.end() )
2159 dirtySubgraphs( ii->second );
2160 }
2161 }
2162
2163 netSettings->m_NetClassLabelAssignments[ netname ] = netclass;
2164
2165 if( oldAssignments[ netname ] != netclass )
2166 dirtySubgraphs( subgraphs );
2167 };
2168
2169 for( const auto& [ netname, subgraphs ] : m_net_name_to_subgraphs_map )
2170 checkNetclassDrivers( subgraphs );
2171}
2172
2173
2174int CONNECTION_GRAPH::getOrCreateNetCode( const wxString& aNetName )
2175{
2176 int code;
2177
2178 auto it = m_net_name_to_code_map.find( aNetName );
2179
2180 if( it == m_net_name_to_code_map.end() )
2181 {
2182 code = m_last_net_code++;
2183 m_net_name_to_code_map[ aNetName ] = code;
2184 }
2185 else
2186 {
2187 code = it->second;
2188 }
2189
2190 return code;
2191}
2192
2193
2195{
2196 int code = getOrCreateNetCode( aConnection.Name() );
2197
2198 aConnection.SetNetCode( code );
2199
2200 return code;
2201}
2202
2203
2205{
2206 std::vector<std::shared_ptr<SCH_CONNECTION>> connections_to_check( aConnection->Members() );
2207
2208 for( unsigned i = 0; i < connections_to_check.size(); i++ )
2209 {
2210 const std::shared_ptr<SCH_CONNECTION>& member = connections_to_check[i];
2211
2212 if( member->IsBus() )
2213 {
2214 connections_to_check.insert( connections_to_check.end(),
2215 member->Members().begin(),
2216 member->Members().end() );
2217 continue;
2218 }
2219
2220 assignNewNetCode( *member );
2221 }
2222}
2223
2224
2226{
2227 SCH_CONNECTION* conn = aSubgraph->m_driver_connection;
2228 std::vector<CONNECTION_SUBGRAPH*> search_list;
2229 std::unordered_set<CONNECTION_SUBGRAPH*> visited;
2230 std::unordered_set<SCH_CONNECTION*> stale_bus_members;
2231
2232 auto visit =[&]( CONNECTION_SUBGRAPH* aParent )
2233 {
2234 for( SCH_SHEET_PIN* pin : aParent->m_hier_pins )
2235 {
2236 SCH_SHEET_PATH path = aParent->m_sheet;
2237 path.push_back( pin->GetParent() );
2238
2239 auto it = m_sheet_to_subgraphs_map.find( path );
2240
2241 if( it == m_sheet_to_subgraphs_map.end() )
2242 continue;
2243
2244 for( CONNECTION_SUBGRAPH* candidate : it->second )
2245 {
2246 if( !candidate->m_strong_driver
2247 || candidate->m_hier_ports.empty()
2248 || visited.count( candidate ) )
2249 {
2250 continue;
2251 }
2252
2253 for( SCH_HIERLABEL* label : candidate->m_hier_ports )
2254 {
2255 if( candidate->GetNameForDriver( label ) == aParent->GetNameForDriver( pin ) )
2256 {
2257 wxLogTrace( ConnTrace, wxS( "%lu: found child %lu (%s)" ), aParent->m_code,
2258 candidate->m_code, candidate->m_driver_connection->Name() );
2259
2260 candidate->m_hier_parent = aParent;
2261 aParent->m_hier_children.insert( candidate );
2262
2263 wxASSERT( candidate->m_graph == aParent->m_graph );
2264
2265 search_list.push_back( candidate );
2266 break;
2267 }
2268 }
2269 }
2270 }
2271
2272 for( SCH_HIERLABEL* label : aParent->m_hier_ports )
2273 {
2274 SCH_SHEET_PATH path = aParent->m_sheet;
2275 path.pop_back();
2276
2277 auto it = m_sheet_to_subgraphs_map.find( path );
2278
2279 if( it == m_sheet_to_subgraphs_map.end() )
2280 continue;
2281
2282 for( CONNECTION_SUBGRAPH* candidate : it->second )
2283 {
2284 if( candidate->m_hier_pins.empty()
2285 || visited.count( candidate )
2286 || candidate->m_driver_connection->Type() != aParent->m_driver_connection->Type() )
2287 {
2288 continue;
2289 }
2290
2291 const KIID& last_parent_uuid = aParent->m_sheet.Last()->m_Uuid;
2292
2293 for( SCH_SHEET_PIN* pin : candidate->m_hier_pins )
2294 {
2295 // If the last sheet UUIDs won't match, no need to check the full path
2296 if( pin->GetParent()->m_Uuid != last_parent_uuid )
2297 continue;
2298
2299 SCH_SHEET_PATH pin_path = path;
2300 pin_path.push_back( pin->GetParent() );
2301
2302 if( pin_path != aParent->m_sheet )
2303 continue;
2304
2305 if( aParent->GetNameForDriver( label ) == candidate->GetNameForDriver( pin ) )
2306 {
2307 wxLogTrace( ConnTrace, wxS( "%lu: found additional parent %lu (%s)" ),
2308 aParent->m_code, candidate->m_code,
2309 candidate->m_driver_connection->Name() );
2310
2311 aParent->m_hier_children.insert( candidate );
2312 search_list.push_back( candidate );
2313 break;
2314 }
2315 }
2316 }
2317 }
2318 };
2319
2320 auto propagate_bus_neighbors = [&]( CONNECTION_SUBGRAPH* aParentGraph )
2321 {
2322 for( const auto& kv : aParentGraph->m_bus_neighbors )
2323 {
2324 for( CONNECTION_SUBGRAPH* neighbor : kv.second )
2325 {
2326 // May have been absorbed but won't have been deleted
2327 while( neighbor->m_absorbed )
2328 neighbor = neighbor->m_absorbed_by;
2329
2330 SCH_CONNECTION* parent = aParentGraph->m_driver_connection;
2331
2332 // Now member may be out of date, since we just cloned the
2333 // connection from higher up in the hierarchy. We need to
2334 // figure out what the actual new connection is.
2335 SCH_CONNECTION* member = matchBusMember( parent, kv.first.get() );
2336
2337 if( !member )
2338 {
2339 // Try harder: we might match on a secondary driver
2340 for( CONNECTION_SUBGRAPH* sg : kv.second )
2341 {
2342 if( sg->m_multiple_drivers )
2343 {
2344 SCH_SHEET_PATH sheet = sg->m_sheet;
2345
2346 for( SCH_ITEM* driver : sg->m_drivers )
2347 {
2348 auto c = getDefaultConnection( driver, sg );
2349 member = matchBusMember( parent, c.get() );
2350
2351 if( member )
2352 break;
2353 }
2354 }
2355
2356 if( member )
2357 break;
2358 }
2359 }
2360
2361 // This is bad, probably an ERC error
2362 if( !member )
2363 {
2364 wxLogTrace( ConnTrace, wxS( "Could not match bus member %s in %s" ),
2365 kv.first->Name(), parent->Name() );
2366 continue;
2367 }
2368
2369 auto neighbor_conn = neighbor->m_driver_connection;
2370 auto neighbor_name = neighbor_conn->Name();
2371
2372 // Matching name: no update needed
2373 if( neighbor_name == member->Name() )
2374 continue;
2375
2376 // Was this neighbor already updated from a different sheet? Don't rename it again
2377 if( neighbor_conn->Sheet() != neighbor->m_sheet )
2378 continue;
2379
2380 // Safety check against infinite recursion
2381 wxASSERT( neighbor_conn->IsNet() );
2382
2383 wxLogTrace( ConnTrace, wxS( "%lu (%s) connected to bus member %s (local %s)" ),
2384 neighbor->m_code, neighbor_name, member->Name(), member->LocalName() );
2385
2386 // Take whichever name is higher priority
2389 {
2390 member->Clone( *neighbor_conn );
2391 stale_bus_members.insert( member );
2392 }
2393 else
2394 {
2395 neighbor_conn->Clone( *member );
2396
2397 recacheSubgraphName( neighbor, neighbor_name );
2398
2399 // Recurse onto this neighbor in case it needs to re-propagate
2400 neighbor->m_dirty = true;
2401 propagateToNeighbors( neighbor, aForce );
2402 }
2403 }
2404 }
2405 };
2406
2407 // If we are a bus, we must propagate to local neighbors and then the hierarchy
2408 if( conn->IsBus() )
2409 propagate_bus_neighbors( aSubgraph );
2410
2411 // If we have both ports and pins, skip processing as we'll be visited by a parent or child.
2412 // If we only have one or the other, process (we can either go bottom-up or top-down depending
2413 // on which subgraph comes up first)
2414 if( !aForce && !aSubgraph->m_hier_ports.empty() && !aSubgraph->m_hier_pins.empty() )
2415 {
2416 wxLogTrace( ConnTrace, wxS( "%lu (%s) has both hier ports and pins; deferring processing" ),
2417 aSubgraph->m_code, conn->Name() );
2418 return;
2419 }
2420 else if( aSubgraph->m_hier_ports.empty() && aSubgraph->m_hier_pins.empty() )
2421 {
2422 wxLogTrace( ConnTrace, wxS( "%lu (%s) has no hier pins or ports on sheet %s; marking clean" ),
2423 aSubgraph->m_code, conn->Name(), aSubgraph->m_sheet.PathHumanReadable() );
2424 aSubgraph->m_dirty = false;
2425 return;
2426 }
2427
2428 visited.insert( aSubgraph );
2429
2430 wxLogTrace( ConnTrace, wxS( "Propagating %lu (%s) to subsheets" ),
2431 aSubgraph->m_code, aSubgraph->m_driver_connection->Name() );
2432
2433 visit( aSubgraph );
2434
2435 for( unsigned i = 0; i < search_list.size(); i++ )
2436 {
2437 auto child = search_list[i];
2438
2439 if( visited.insert( child ).second )
2440 visit( child );
2441
2442 child->m_dirty = false;
2443 }
2444
2445 // Now, find the best driver for this chain of subgraphs
2446 CONNECTION_SUBGRAPH* bestDriver = aSubgraph;
2449 bool bestIsStrong = ( highest >= CONNECTION_SUBGRAPH::PRIORITY::HIER_LABEL );
2450 wxString bestName = aSubgraph->m_driver_connection->Name();
2451
2452 // Check if a subsheet has a higher-priority connection to the same net
2454 {
2455 for( CONNECTION_SUBGRAPH* subgraph : visited )
2456 {
2457 if( subgraph == aSubgraph )
2458 continue;
2459
2461 CONNECTION_SUBGRAPH::GetDriverPriority( subgraph->m_driver );
2462
2463 bool candidateStrong = ( priority >= CONNECTION_SUBGRAPH::PRIORITY::HIER_LABEL );
2464 wxString candidateName = subgraph->m_driver_connection->Name();
2465 bool shorterPath = subgraph->m_sheet.size() < bestDriver->m_sheet.size();
2466 bool asGoodPath = subgraph->m_sheet.size() <= bestDriver->m_sheet.size();
2467
2468 // Pick a better driving subgraph if it:
2469 // a) has a power pin or global driver
2470 // b) is a strong driver and we're a weak driver
2471 // c) is a higher priority strong driver
2472 // d) matches our priority, is a strong driver, and has a shorter path
2473 // e) matches our strength and is at least as short, and is alphabetically lower
2474
2475 if( ( priority >= CONNECTION_SUBGRAPH::PRIORITY::POWER_PIN ) ||
2476 ( !bestIsStrong && candidateStrong ) ||
2477 ( priority > highest && candidateStrong ) ||
2478 ( priority == highest && candidateStrong && shorterPath ) ||
2479 ( ( bestIsStrong == candidateStrong ) && asGoodPath && ( priority == highest ) &&
2480 ( candidateName < bestName ) ) )
2481 {
2482 bestDriver = subgraph;
2483 highest = priority;
2484 bestIsStrong = candidateStrong;
2485 bestName = candidateName;
2486 }
2487 }
2488 }
2489
2490 if( bestDriver != aSubgraph )
2491 {
2492 wxLogTrace( ConnTrace, wxS( "%lu (%s) overridden by new driver %lu (%s)" ),
2493 aSubgraph->m_code, aSubgraph->m_driver_connection->Name(), bestDriver->m_code,
2494 bestDriver->m_driver_connection->Name() );
2495 }
2496
2497 conn = bestDriver->m_driver_connection;
2498
2499 for( CONNECTION_SUBGRAPH* subgraph : visited )
2500 {
2501 wxString old_name = subgraph->m_driver_connection->Name();
2502
2503 subgraph->m_driver_connection->Clone( *conn );
2504
2505 if( old_name != conn->Name() )
2506 recacheSubgraphName( subgraph, old_name );
2507
2508 if( conn->IsBus() )
2509 propagate_bus_neighbors( subgraph );
2510 }
2511
2512 // Somewhere along the way, a bus member may have been upgraded to a global or power label.
2513 // Because this can happen anywhere, we need a second pass to update all instances of that bus
2514 // member to have the correct connection info
2515 if( conn->IsBus() && !stale_bus_members.empty() )
2516 {
2517 std::unordered_set<SCH_CONNECTION*> cached_members = stale_bus_members;
2518
2519 for( SCH_CONNECTION* stale_member : cached_members )
2520 {
2521 for( CONNECTION_SUBGRAPH* subgraph : visited )
2522 {
2523 SCH_CONNECTION* member = matchBusMember( subgraph->m_driver_connection,
2524 stale_member );
2525
2526 if( !member )
2527 {
2528 wxLogTrace( ConnTrace, wxS( "WARNING: failed to match stale member %s in %s." ),
2529 stale_member->Name(), subgraph->m_driver_connection->Name() );
2530 continue;
2531 }
2532
2533 wxLogTrace( ConnTrace, wxS( "Updating %lu (%s) member %s to %s" ), subgraph->m_code,
2534 subgraph->m_driver_connection->Name(), member->LocalName(),
2535 stale_member->Name() );
2536
2537 member->Clone( *stale_member );
2538
2539 propagate_bus_neighbors( subgraph );
2540 }
2541 }
2542 }
2543
2544 aSubgraph->m_dirty = false;
2545}
2546
2547
2548std::shared_ptr<SCH_CONNECTION> CONNECTION_GRAPH::getDefaultConnection( SCH_ITEM* aItem,
2549 CONNECTION_SUBGRAPH* aSubgraph )
2550{
2551 std::shared_ptr<SCH_CONNECTION> c = std::shared_ptr<SCH_CONNECTION>( nullptr );
2552
2553 switch( aItem->Type() )
2554 {
2555 case SCH_PIN_T:
2556 {
2557 SCH_PIN* pin = static_cast<SCH_PIN*>( aItem );
2558
2559 if( pin->IsGlobalPower() )
2560 c = std::make_shared<SCH_CONNECTION>( aItem, aSubgraph->m_sheet );
2561
2562 break;
2563 }
2564
2565 case SCH_GLOBAL_LABEL_T:
2566 case SCH_HIER_LABEL_T:
2567 case SCH_LABEL_T:
2568 {
2569 c = std::make_shared<SCH_CONNECTION>( aItem, aSubgraph->m_sheet );
2570 break;
2571 }
2572
2573 default:
2574 break;
2575 }
2576
2577 if( c )
2578 {
2579 c->SetGraph( this );
2580 c->ConfigureFromLabel( aSubgraph->GetNameForDriver( aItem ) );
2581 }
2582
2583 return c;
2584}
2585
2586
2588 SCH_CONNECTION* aSearch )
2589{
2590 wxASSERT( aBusConnection->IsBus() );
2591
2592 SCH_CONNECTION* match = nullptr;
2593
2594 if( aBusConnection->Type() == CONNECTION_TYPE::BUS )
2595 {
2596 // Vector bus: compare against index, because we allow the name
2597 // to be different
2598
2599 for( const std::shared_ptr<SCH_CONNECTION>& bus_member : aBusConnection->Members() )
2600 {
2601 if( bus_member->VectorIndex() == aSearch->VectorIndex() )
2602 {
2603 match = bus_member.get();
2604 break;
2605 }
2606 }
2607 }
2608 else
2609 {
2610 // Group bus
2611 for( const std::shared_ptr<SCH_CONNECTION>& c : aBusConnection->Members() )
2612 {
2613 // Vector inside group: compare names, because for bus groups
2614 // we expect the naming to be consistent across all usages
2615 // TODO(JE) explain this in the docs
2616 if( c->Type() == CONNECTION_TYPE::BUS )
2617 {
2618 for( const std::shared_ptr<SCH_CONNECTION>& bus_member : c->Members() )
2619 {
2620 if( bus_member->LocalName() == aSearch->LocalName() )
2621 {
2622 match = bus_member.get();
2623 break;
2624 }
2625 }
2626 }
2627 else if( c->LocalName() == aSearch->LocalName() )
2628 {
2629 match = c.get();
2630 break;
2631 }
2632 }
2633 }
2634
2635 return match;
2636}
2637
2638
2640 const wxString& aOldName )
2641{
2642 auto it = m_net_name_to_subgraphs_map.find( aOldName );
2643
2644 if( it != m_net_name_to_subgraphs_map.end() )
2645 {
2646 std::vector<CONNECTION_SUBGRAPH*>& vec = it->second;
2647 alg::delete_matching( vec, aSubgraph );
2648 }
2649
2650 wxLogTrace( ConnTrace, wxS( "recacheSubgraphName: %s => %s" ), aOldName,
2651 aSubgraph->m_driver_connection->Name() );
2652
2653 m_net_name_to_subgraphs_map[aSubgraph->m_driver_connection->Name()].push_back( aSubgraph );
2654}
2655
2656
2657std::shared_ptr<BUS_ALIAS> CONNECTION_GRAPH::GetBusAlias( const wxString& aName )
2658{
2659 auto it = m_bus_alias_cache.find( aName );
2660
2661 return it != m_bus_alias_cache.end() ? it->second : nullptr;
2662}
2663
2664
2665std::vector<const CONNECTION_SUBGRAPH*> CONNECTION_GRAPH::GetBusesNeedingMigration()
2666{
2667 std::vector<const CONNECTION_SUBGRAPH*> ret;
2668
2669 for( CONNECTION_SUBGRAPH* subgraph : m_subgraphs )
2670 {
2671 // Graph is supposed to be up-to-date before calling this
2672 wxASSERT( !subgraph->m_dirty );
2673
2674 if( !subgraph->m_driver )
2675 continue;
2676
2677 SCH_CONNECTION* connection = subgraph->m_driver->Connection( &subgraph->m_sheet );
2678
2679 if( !connection->IsBus() )
2680 continue;
2681
2682 auto labels = subgraph->GetVectorBusLabels();
2683
2684 if( labels.size() > 1 )
2685 {
2686 bool different = false;
2687 wxString first = static_cast<SCH_TEXT*>( labels.at( 0 ) )->GetShownText( false );
2688
2689 for( unsigned i = 1; i < labels.size(); ++i )
2690 {
2691 if( static_cast<SCH_TEXT*>( labels.at( i ) )->GetShownText( false ) != first )
2692 {
2693 different = true;
2694 break;
2695 }
2696 }
2697
2698 if( !different )
2699 continue;
2700
2701 wxLogTrace( ConnTrace, wxS( "SG %ld (%s) has multiple bus labels" ), subgraph->m_code,
2702 connection->Name() );
2703
2704 ret.push_back( subgraph );
2705 }
2706 }
2707
2708 return ret;
2709}
2710
2711
2713{
2714 wxString retval = aSubGraph->GetNetName();
2715 bool found = false;
2716
2717 // This is a hacky way to find the true subgraph net name (why do we not store it?)
2718 // TODO: Remove once the actual netname of the subgraph is stored with the subgraph
2719
2720 for( auto it = m_net_name_to_subgraphs_map.begin(); it != m_net_name_to_subgraphs_map.end() && !found; ++it )
2721 {
2722 for( CONNECTION_SUBGRAPH* graph : it->second )
2723 {
2724 if( graph == aSubGraph )
2725 {
2726 retval = it->first;
2727 found = true;
2728 break;
2729 }
2730 }
2731 }
2732
2733 return retval;
2734}
2735
2736
2738 const SCH_SHEET_PATH& aPath )
2739{
2740 auto it = m_net_name_to_subgraphs_map.find( aNetName );
2741
2742 if( it == m_net_name_to_subgraphs_map.end() )
2743 return nullptr;
2744
2745 for( CONNECTION_SUBGRAPH* sg : it->second )
2746 {
2747 // Cache is supposed to be valid by now
2748 wxASSERT( sg && !sg->m_absorbed && sg->m_driver_connection );
2749
2750 if( sg->m_sheet == aPath && sg->m_driver_connection->Name() == aNetName )
2751 return sg;
2752 }
2753
2754 return nullptr;
2755}
2756
2757
2759{
2760 auto it = m_net_name_to_subgraphs_map.find( aNetName );
2761
2762 if( it == m_net_name_to_subgraphs_map.end() )
2763 return nullptr;
2764
2765 wxASSERT( !it->second.empty() );
2766
2767 return it->second[0];
2768}
2769
2770
2772{
2773 auto it = m_item_to_subgraph_map.find( aItem );
2774 CONNECTION_SUBGRAPH* ret = it != m_item_to_subgraph_map.end() ? it->second : nullptr;
2775
2776 while( ret && ret->m_absorbed )
2777 ret = ret->m_absorbed_by;
2778
2779 return ret;
2780}
2781
2782
2783const std::vector<CONNECTION_SUBGRAPH*> CONNECTION_GRAPH::GetAllSubgraphs(
2784 const wxString& aNetName ) const
2785{
2786 std::vector<CONNECTION_SUBGRAPH*> subgraphs;
2787
2788 auto it = m_net_name_to_subgraphs_map.find( aNetName );
2789
2790 if( it == m_net_name_to_subgraphs_map.end() )
2791 return subgraphs;
2792
2793 return it->second;
2794}
2795
2796
2798{
2799 int error_count = 0;
2800
2801 wxCHECK_MSG( m_schematic, true, wxS( "Null m_schematic in CONNECTION_GRAPH::RunERC" ) );
2802
2803 ERC_SETTINGS& settings = m_schematic->ErcSettings();
2804
2805 // We don't want to run many ERC checks more than once on a given screen even though it may
2806 // represent multiple sheets with multiple subgraphs. We can tell these apart by drivers.
2807 std::set<SCH_ITEM*> seenDriverInstances;
2808
2809 for( CONNECTION_SUBGRAPH* subgraph : m_subgraphs )
2810 {
2811 // There shouldn't be any null sub-graph pointers.
2812 wxCHECK2( subgraph, continue );
2813
2814 // Graph is supposed to be up-to-date before calling RunERC()
2815 wxASSERT( !subgraph->m_dirty );
2816
2817 if( subgraph->m_absorbed )
2818 continue;
2819
2820 if( seenDriverInstances.count( subgraph->m_driver ) )
2821 continue;
2822
2823 if( subgraph->m_driver )
2824 seenDriverInstances.insert( subgraph->m_driver );
2825
2836 if( settings.IsTestEnabled( ERCE_DRIVER_CONFLICT ) )
2837 {
2838 if( !ercCheckMultipleDrivers( subgraph ) )
2839 error_count++;
2840 }
2841
2842 subgraph->ResolveDrivers( false );
2843
2844 if( settings.IsTestEnabled( ERCE_BUS_TO_NET_CONFLICT ) )
2845 {
2846 if( !ercCheckBusToNetConflicts( subgraph ) )
2847 error_count++;
2848 }
2849
2850 if( settings.IsTestEnabled( ERCE_BUS_ENTRY_CONFLICT ) )
2851 {
2852 if( !ercCheckBusToBusEntryConflicts( subgraph ) )
2853 error_count++;
2854 }
2855
2856 if( settings.IsTestEnabled( ERCE_BUS_TO_BUS_CONFLICT ) )
2857 {
2858 if( !ercCheckBusToBusConflicts( subgraph ) )
2859 error_count++;
2860 }
2861
2862 if( settings.IsTestEnabled( ERCE_WIRE_DANGLING ) )
2863 {
2864 if( !ercCheckFloatingWires( subgraph ) )
2865 error_count++;
2866 }
2867
2870 || settings.IsTestEnabled( ERCE_PIN_NOT_CONNECTED ) )
2871 {
2872 if( !ercCheckNoConnects( subgraph ) )
2873 error_count++;
2874 }
2875
2877 || settings.IsTestEnabled( ERCE_GLOBLABEL ) )
2878 {
2879 if( !ercCheckLabels( subgraph ) )
2880 error_count++;
2881 }
2882 }
2883
2884 // Hierarchical sheet checking is done at the schematic level
2886 || settings.IsTestEnabled( ERCE_PIN_NOT_CONNECTED ) )
2887 {
2888 error_count += ercCheckHierSheets();
2889 }
2890
2891 if( settings.IsTestEnabled( ERCE_NETCLASS_CONFLICT ) )
2892 {
2893 for( const auto& [ netname, subgraphs ] : m_net_name_to_subgraphs_map )
2894 {
2895 if( !ercCheckNetclassConflicts( subgraphs ) )
2896 error_count++;
2897 }
2898 }
2899
2900 return error_count;
2901}
2902
2903
2905{
2906 wxCHECK( aSubgraph, false );
2907
2908 if( aSubgraph->m_multiple_drivers )
2909 {
2910 for( SCH_ITEM* driver : aSubgraph->m_drivers )
2911 {
2912 if( driver == aSubgraph->m_driver )
2913 continue;
2914
2915 if( driver->Type() == SCH_GLOBAL_LABEL_T
2916 || driver->Type() == SCH_HIER_LABEL_T
2917 || driver->Type() == SCH_LABEL_T
2918 || ( driver->Type() == SCH_PIN_T
2919 && static_cast<SCH_PIN*>( driver )->IsGlobalPower() ) )
2920 {
2921 const wxString& primaryName = aSubgraph->GetNameForDriver( aSubgraph->m_driver );
2922 const wxString& secondaryName = aSubgraph->GetNameForDriver( driver );
2923
2924 if( primaryName == secondaryName )
2925 continue;
2926
2927 wxString msg = wxString::Format( _( "Both %s and %s are attached to the same "
2928 "items; %s will be used in the netlist" ),
2929 primaryName, secondaryName, primaryName );
2930
2931 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_DRIVER_CONFLICT );
2932 ercItem->SetItems( aSubgraph->m_driver, driver );
2933 ercItem->SetErrorMessage( msg );
2934
2935 SCH_MARKER* marker = new SCH_MARKER( ercItem, driver->GetPosition() );
2936 aSubgraph->m_sheet.LastScreen()->Append( marker );
2937
2938 return false;
2939 }
2940 }
2941 }
2942
2943 return true;
2944}
2945
2946
2947bool CONNECTION_GRAPH::ercCheckNetclassConflicts( const std::vector<CONNECTION_SUBGRAPH*>& subgraphs )
2948{
2949 wxString firstNetclass;
2950 SCH_ITEM* firstNetclassDriver = nullptr;
2951
2952 for( const CONNECTION_SUBGRAPH* subgraph : subgraphs )
2953 {
2954 for( SCH_ITEM* item : subgraph->m_items )
2955 {
2956 const wxString netclass = subgraph->GetNetclassForDriver( item );
2957
2958 if( netclass.IsEmpty() )
2959 continue;
2960
2961 if( netclass != firstNetclass )
2962 {
2963 if( !firstNetclassDriver )
2964 {
2965 firstNetclass = netclass;
2966 firstNetclassDriver = item;
2967 continue;
2968 }
2969
2970 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_NETCLASS_CONFLICT );
2971 ercItem->SetItems( firstNetclassDriver, item );
2972
2973 SCH_MARKER* marker = new SCH_MARKER( ercItem, item->GetPosition() );
2974 subgraph->m_sheet.LastScreen()->Append( marker );
2975
2976 return false;
2977 }
2978 }
2979 }
2980
2981 return true;
2982}
2983
2984
2986{
2987 const SCH_SHEET_PATH& sheet = aSubgraph->m_sheet;
2988 SCH_SCREEN* screen = sheet.LastScreen();
2989
2990 SCH_ITEM* net_item = nullptr;
2991 SCH_ITEM* bus_item = nullptr;
2992 SCH_CONNECTION conn( this );
2993
2994 for( SCH_ITEM* item : aSubgraph->m_items )
2995 {
2996 switch( item->Type() )
2997 {
2998 case SCH_LINE_T:
2999 {
3000 if( item->GetLayer() == LAYER_BUS )
3001 bus_item = ( !bus_item ) ? item : bus_item;
3002 else
3003 net_item = ( !net_item ) ? item : net_item;
3004 break;
3005 }
3006
3007 case SCH_LABEL_T:
3008 case SCH_GLOBAL_LABEL_T:
3009 case SCH_SHEET_PIN_T:
3010 case SCH_HIER_LABEL_T:
3011 {
3012 SCH_TEXT* text = static_cast<SCH_TEXT*>( item );
3013 conn.ConfigureFromLabel( EscapeString( text->GetShownText( false ), CTX_NETNAME ) );
3014
3015 if( conn.IsBus() )
3016 bus_item = ( !bus_item ) ? item : bus_item;
3017 else
3018 net_item = ( !net_item ) ? item : net_item;
3019 break;
3020 }
3021
3022 default:
3023 break;
3024 }
3025 }
3026
3027 if( net_item && bus_item )
3028 {
3029 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_BUS_TO_NET_CONFLICT );
3030 ercItem->SetItems( net_item, bus_item );
3031
3032 SCH_MARKER* marker = new SCH_MARKER( ercItem, net_item->GetPosition() );
3033 screen->Append( marker );
3034
3035 return false;
3036 }
3037
3038 return true;
3039}
3040
3041
3043{
3044 const SCH_SHEET_PATH& sheet = aSubgraph->m_sheet;
3045 SCH_SCREEN* screen = sheet.LastScreen();
3046
3047 SCH_ITEM* label = nullptr;
3048 SCH_ITEM* port = nullptr;
3049
3050 for( SCH_ITEM* item : aSubgraph->m_items )
3051 {
3052 switch( item->Type() )
3053 {
3054 case SCH_TEXT_T:
3055 case SCH_GLOBAL_LABEL_T:
3056 {
3057 if( !label && item->Connection( &sheet )->IsBus() )
3058 label = item;
3059 break;
3060 }
3061
3062 case SCH_SHEET_PIN_T:
3063 case SCH_HIER_LABEL_T:
3064 {
3065 if( !port && item->Connection( &sheet )->IsBus() )
3066 port = item;
3067 break;
3068 }
3069
3070 default:
3071 break;
3072 }
3073 }
3074
3075 if( label && port )
3076 {
3077 bool match = false;
3078
3079 for( const auto& member : label->Connection( &sheet )->Members() )
3080 {
3081 for( const auto& test : port->Connection( &sheet )->Members() )
3082 {
3083 if( test != member && member->Name() == test->Name() )
3084 {
3085 match = true;
3086 break;
3087 }
3088 }
3089
3090 if( match )
3091 break;
3092 }
3093
3094 if( !match )
3095 {
3096 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_BUS_TO_BUS_CONFLICT );
3097 ercItem->SetItems( label, port );
3098
3099 SCH_MARKER* marker = new SCH_MARKER( ercItem, label->GetPosition() );
3100 screen->Append( marker );
3101
3102 return false;
3103 }
3104 }
3105
3106 return true;
3107}
3108
3109
3111{
3112 bool conflict = false;
3113 const SCH_SHEET_PATH& sheet = aSubgraph->m_sheet;
3114 SCH_SCREEN* screen = sheet.LastScreen();
3115
3116 SCH_BUS_WIRE_ENTRY* bus_entry = nullptr;
3117 SCH_ITEM* bus_wire = nullptr;
3118 wxString bus_name;
3119
3120 if( !aSubgraph->m_driver_connection )
3121 {
3122 // Incomplete bus entry. Let the unconnected tests handle it.
3123 return true;
3124 }
3125
3126 for( SCH_ITEM* item : aSubgraph->m_items )
3127 {
3128 switch( item->Type() )
3129 {
3131 {
3132 if( !bus_entry )
3133 bus_entry = static_cast<SCH_BUS_WIRE_ENTRY*>( item );
3134 break;
3135 }
3136
3137 default:
3138 break;
3139 }
3140 }
3141
3142 if( bus_entry && bus_entry->m_connected_bus_item )
3143 {
3144 bus_wire = bus_entry->m_connected_bus_item;
3145
3146 wxASSERT( bus_wire->Type() == SCH_LINE_T );
3147
3148 // In some cases, the connection list (SCH_CONNECTION*) can be null.
3149 // Skip null connections.
3150 if( bus_entry->Connection( &sheet )
3151 && bus_wire->Type() == SCH_LINE_T
3152 && bus_wire->Connection( &sheet ) )
3153 {
3154 conflict = true; // Assume a conflict; we'll reset if we find it's OK
3155
3156 bus_name = bus_wire->Connection( &sheet )->Name();
3157
3158 std::set<wxString> test_names;
3159 test_names.insert( bus_entry->Connection( &sheet )->Name() );
3160
3161 wxString baseName = sheet.PathHumanReadable();
3162
3163 for( SCH_ITEM* driver : aSubgraph->m_drivers )
3164 test_names.insert( baseName + aSubgraph->GetNameForDriver( driver ) );
3165
3166 for( const auto& member : bus_wire->Connection( &sheet )->Members() )
3167 {
3168 if( member->Type() == CONNECTION_TYPE::BUS )
3169 {
3170 for( const auto& sub_member : member->Members() )
3171 {
3172 if( test_names.count( sub_member->Name() ) )
3173 conflict = false;
3174 }
3175 }
3176 else if( test_names.count( member->Name() ) )
3177 {
3178 conflict = false;
3179 }
3180 }
3181 }
3182 }
3183
3184 // Don't report warnings if this bus member has been overridden by a higher priority power pin
3185 // or global label
3186 if( conflict && CONNECTION_SUBGRAPH::GetDriverPriority( aSubgraph->m_driver )
3188 {
3189 conflict = false;
3190 }
3191
3192 if( conflict )
3193 {
3194 wxString netName = aSubgraph->m_driver_connection->Name();
3195 wxString msg = wxString::Format( _( "Net %s is graphically connected to bus %s but is not a"
3196 " member of that bus" ),
3197 UnescapeString( netName ),
3198 UnescapeString( bus_name ) );
3199 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_BUS_ENTRY_CONFLICT );
3200 ercItem->SetItems( bus_entry, bus_wire );
3201 ercItem->SetErrorMessage( msg );
3202
3203 SCH_MARKER* marker = new SCH_MARKER( ercItem, bus_entry->GetPosition() );
3204 screen->Append( marker );
3205
3206 return false;
3207 }
3208
3209 return true;
3210}
3211
3212
3213// TODO(JE) Check sheet pins here too?
3215{
3216 ERC_SETTINGS& settings = m_schematic->ErcSettings();
3217 const SCH_SHEET_PATH& sheet = aSubgraph->m_sheet;
3218 SCH_SCREEN* screen = sheet.LastScreen();
3219 bool ok = true;
3220 SCH_PIN* pin = nullptr;
3221
3222 std::set<SCH_PIN*> unique_pins;
3223 std::set<SCH_LABEL_BASE*> unique_labels;
3224
3225 wxString netName = GetResolvedSubgraphName( aSubgraph );
3226
3227 auto process_subgraph = [&]( const CONNECTION_SUBGRAPH* aProcessGraph )
3228 {
3229 // Any subgraph that contains a no-connect should not
3230 // more than one pin (which would indicate it is connected
3231
3232 for( SCH_ITEM* item : aProcessGraph->m_items )
3233 {
3234 switch( item->Type() )
3235 {
3236 case SCH_PIN_T:
3237 {
3238 SCH_PIN* test_pin = static_cast<SCH_PIN*>( item );
3239
3240 // Only link NC to pin on the current subgraph being checked
3241 if( aProcessGraph == aSubgraph )
3242 pin = test_pin;
3243
3244 if( std::none_of( unique_pins.begin(), unique_pins.end(),
3245 [test_pin]( SCH_PIN* aPin )
3246 {
3247 return test_pin->IsStacked( aPin );
3248 }
3249 ))
3250 {
3251 unique_pins.insert( test_pin );
3252 }
3253
3254 break;
3255 }
3256
3257 case SCH_LABEL_T:
3258 case SCH_GLOBAL_LABEL_T:
3259 case SCH_HIER_LABEL_T:
3260 unique_labels.insert( static_cast<SCH_LABEL_BASE*>( item ) );
3262 default:
3263 break;
3264 }
3265 }
3266 };
3267
3268 auto it = m_net_name_to_subgraphs_map.find( netName );
3269
3270 if( it != m_net_name_to_subgraphs_map.end() )
3271 {
3272 for( const CONNECTION_SUBGRAPH* subgraph : it->second )
3273 {
3274 process_subgraph( subgraph );
3275 }
3276 }
3277 else
3278 {
3279 process_subgraph( aSubgraph );
3280 }
3281
3282 if( aSubgraph->m_no_connect != nullptr )
3283 {
3284 if( unique_pins.size() > 1 && settings.IsTestEnabled( ERCE_NOCONNECT_CONNECTED ) )
3285 {
3286 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_NOCONNECT_CONNECTED );
3287 VECTOR2I pos;
3288
3289 if( pin )
3290 {
3291 ercItem->SetItems( pin, aSubgraph->m_no_connect );
3292 pos = pin->GetTransformedPosition();
3293 }
3294 else
3295 {
3296 ercItem->SetItems( aSubgraph->m_no_connect );
3297 pos = aSubgraph->m_no_connect->GetPosition();
3298 }
3299
3300 SCH_MARKER* marker = new SCH_MARKER( ercItem, pos );
3301 screen->Append( marker );
3302
3303 ok = false;
3304 }
3305
3306 if( unique_pins.empty() && unique_labels.empty() && settings.IsTestEnabled( ERCE_NOCONNECT_NOT_CONNECTED ) )
3307 {
3308 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_NOCONNECT_NOT_CONNECTED );
3309 ercItem->SetItems( aSubgraph->m_no_connect );
3310
3311 SCH_MARKER* marker = new SCH_MARKER( ercItem, aSubgraph->m_no_connect->GetPosition() );
3312 screen->Append( marker );
3313
3314 ok = false;
3315 }
3316 }
3317 else
3318 {
3319 bool has_other_connections = false;
3320 std::vector<SCH_PIN*> pins;
3321
3322 // Any subgraph that lacks a no-connect and contains a pin should also
3323 // contain at least one other potential driver
3324
3325 for( SCH_ITEM* item : aSubgraph->m_items )
3326 {
3327 switch( item->Type() )
3328 {
3329 case SCH_PIN_T:
3330 {
3331 // Stacked pins do not count as other connections but non-stacked pins do
3332 if( !has_other_connections && !pins.empty() )
3333 {
3334 SCH_PIN* test_pin = static_cast<SCH_PIN*>( item );
3335
3336 for( SCH_PIN* other_pin : pins )
3337 {
3338 if( !test_pin->IsStacked( other_pin ) )
3339 {
3340 has_other_connections = true;
3341 break;
3342 }
3343 }
3344 }
3345
3346 pins.emplace_back( static_cast<SCH_PIN*>( item ) );
3347
3348 break;
3349 }
3350
3351 default:
3352 if( aSubgraph->GetDriverPriority( item ) != CONNECTION_SUBGRAPH::PRIORITY::NONE )
3353 has_other_connections = true;
3354
3355 break;
3356 }
3357 }
3358
3359 // For many checks, we can just use the first pin
3360 pin = pins.empty() ? nullptr : pins[0];
3361
3362 // But if there is a power pin, it might be connected elsewhere
3363 for( SCH_PIN* test_pin : pins )
3364 {
3365 if( test_pin->GetType() == ELECTRICAL_PINTYPE::PT_POWER_IN )
3366 {
3367 pin = test_pin;
3368 break;
3369 }
3370 }
3371
3372 // Check if power input pins connect to anything else via net name,
3373 // but not for power symbols (with visible or legacy invisible pins).
3374 // We want to throw unconnected errors for power symbols even if they are connected to other
3375 // net items by name, because usually failing to connect them graphically is a mistake
3376 if( pin && !has_other_connections
3377 && pin->IsGlobalPower()
3378 && !pin->GetLibPin()->GetParent()->IsPower() )
3379 {
3380 wxString name = pin->Connection( &sheet )->Name();
3381 wxString local_name = pin->Connection( &sheet )->Name( true );
3382
3383 if( m_global_label_cache.count( name )
3384 || m_local_label_cache.count( std::make_pair( sheet, local_name ) ) )
3385 {
3386 has_other_connections = true;
3387 }
3388 }
3389
3390 // Only one pin, and it's not a no-connect pin
3391 if( pin && !has_other_connections
3392 && pin->GetType() != ELECTRICAL_PINTYPE::PT_NC
3393 && pin->GetType() != ELECTRICAL_PINTYPE::PT_NIC
3394 && settings.IsTestEnabled( ERCE_PIN_NOT_CONNECTED ) )
3395 {
3396 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_PIN_NOT_CONNECTED );
3397 ercItem->SetItems( pin );
3398
3399 SCH_MARKER* marker = new SCH_MARKER( ercItem, pin->GetTransformedPosition() );
3400 screen->Append( marker );
3401
3402 ok = false;
3403 }
3404
3405 // If there are multiple pins in this SG, they might be indirectly connected (by netname)
3406 // rather than directly connected (by wires). We want to flag dangling pins even if they
3407 // join nets with another pin, as it's often a mistake
3408 if( pins.size() > 1 )
3409 {
3410 for( SCH_PIN* testPin : pins )
3411 {
3412 // We only apply this test to power symbols, because other symbols have
3413 // pins that are meant to be dangling, but the power symbols have pins
3414 // that are *not* meant to be dangling.
3415 if( testPin->GetLibPin()->GetParent()->IsPower()
3416 && testPin->ConnectedItems( sheet ).empty()
3417 && settings.IsTestEnabled( ERCE_PIN_NOT_CONNECTED ) )
3418 {
3419 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_PIN_NOT_CONNECTED );
3420 ercItem->SetItems( testPin );
3421
3422 SCH_MARKER* marker = new SCH_MARKER( ercItem,
3423 testPin->GetTransformedPosition() );
3424 screen->Append( marker );
3425
3426 ok = false;
3427 }
3428 }
3429 }
3430 }
3431
3432 return ok;
3433}
3434
3435
3437{
3438 if( aSubgraph->m_driver )
3439 return true;
3440
3441 std::vector<SCH_ITEM*> wires;
3442
3443 // We've gotten this far, so we know we have no valid driver. All we need to do is check
3444 // for a wire that we can place the error on.
3445
3446 for( SCH_ITEM* item : aSubgraph->m_items )
3447 {
3448 if( item->Type() == SCH_LINE_T && item->GetLayer() == LAYER_WIRE )
3449 wires.emplace_back( item );
3450 else if( item->Type() == SCH_BUS_WIRE_ENTRY_T )
3451 wires.emplace_back( item );
3452 }
3453
3454 if( !wires.empty() )
3455 {
3456 SCH_SCREEN* screen = aSubgraph->m_sheet.LastScreen();
3457
3458 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_WIRE_DANGLING );
3459 ercItem->SetItems( wires[0],
3460 wires.size() > 1 ? wires[1] : nullptr,
3461 wires.size() > 2 ? wires[2] : nullptr,
3462 wires.size() > 3 ? wires[3] : nullptr );
3463
3464 SCH_MARKER* marker = new SCH_MARKER( ercItem, wires[0]->GetPosition() );
3465 screen->Append( marker );
3466
3467 return false;
3468 }
3469
3470 return true;
3471}
3472
3473
3475{
3476 // Label connection rules:
3477 // Any label without a no-connect needs to have at least 2 pins, otherwise it is invalid
3478 // Local labels are flagged if they don't connect to any pins and don't have a no-connect
3479 // Global labels are flagged if they appear only once, don't connect to any local labels,
3480 // and don't have a no-connect marker
3481
3482
3483 if( !aSubgraph->m_driver_connection )
3484 return true;
3485
3486 // Buses are excluded from this test: many users create buses with only a single instance
3487 // and it's not really a problem as long as the nets in the bus pass ERC
3488 if( aSubgraph->m_driver_connection->IsBus() )
3489 return true;
3490
3491 ERC_SETTINGS& settings = m_schematic->ErcSettings();
3492 bool ok = true;
3493 int pinCount = 0;
3494 bool has_nc = !!aSubgraph->m_no_connect;
3495
3496 std::map<KICAD_T, std::vector<SCH_TEXT*>> label_map;
3497
3498
3499 auto hasPins =
3500 []( const CONNECTION_SUBGRAPH* aLocSubgraph ) -> int
3501 {
3502 return
3503 std::count_if( aLocSubgraph->m_items.begin(), aLocSubgraph->m_items.end(), []( const SCH_ITEM* item )
3504 { return item->Type() == SCH_PIN_T; } );
3505 };
3506
3507 auto reportError = [&]( SCH_TEXT* aText, int errCode )
3508 {
3509 if( settings.IsTestEnabled( errCode ) )
3510 {
3511 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( errCode );
3512 ercItem->SetItems( aText );
3513
3514 SCH_MARKER* marker = new SCH_MARKER( ercItem, aText->GetPosition() );
3515 aSubgraph->m_sheet.LastScreen()->Append( marker );
3516 }
3517 };
3518
3519 pinCount = hasPins( aSubgraph );
3520
3521 for( SCH_ITEM* item : aSubgraph->m_items )
3522 {
3523 switch( item->Type() )
3524 {
3525 case SCH_LABEL_T:
3526 case SCH_GLOBAL_LABEL_T:
3527 case SCH_HIER_LABEL_T:
3528 {
3529 SCH_TEXT* text = static_cast<SCH_TEXT*>( item );
3530
3531 label_map[item->Type()].push_back( text );
3532
3533 // Below, we'll create an ERC if the whole subgraph is unconnected. But, additionally,
3534 // we want to error if an individual label in the subgraph is floating, even if it's
3535 // connected to other valid things by way of another label on the same sheet.
3536 if( text->IsDangling() )
3537 {
3538 reportError( text, ERCE_LABEL_NOT_CONNECTED );
3539 return false;
3540 }
3541
3542 break;
3543 }
3544
3545 default:
3546 break;
3547 }
3548 }
3549
3550 if( label_map.empty() )
3551 return true;
3552
3553 wxString netName = GetResolvedSubgraphName( aSubgraph );
3554
3555 wxCHECK_MSG( m_schematic, true, wxS( "Null m_schematic in CONNECTION_GRAPH::ercCheckLabels" ) );
3556
3557 // Labels that have multiple pins connected are not dangling (may be used for naming segments)
3558 // so leave them without errors here
3559 if( pinCount > 1 )
3560 return true;
3561
3562 for( auto& [type, label_vec] : label_map )
3563 {
3564
3565 switch( type )
3566 {
3567 case SCH_GLOBAL_LABEL_T:
3568 if( !settings.IsTestEnabled( ERCE_GLOBLABEL ) )
3569 continue;
3570
3571 break;
3572 default:
3573 if( !settings.IsTestEnabled( ERCE_LABEL_NOT_CONNECTED ) )
3574 continue;
3575
3576 break;
3577 }
3578
3579 for( SCH_TEXT* text : label_vec )
3580 {
3581 int allPins = pinCount;
3582
3583 auto it = m_net_name_to_subgraphs_map.find( netName );
3584
3585 if( it != m_net_name_to_subgraphs_map.end() )
3586 {
3587 for( const CONNECTION_SUBGRAPH* neighbor : it->second )
3588 {
3589 if( neighbor == aSubgraph )
3590 continue;
3591
3592 if( neighbor->m_no_connect )
3593 has_nc = true;
3594
3595 allPins += hasPins( neighbor );
3596 }
3597 }
3598
3599 if( allPins == 1 && !has_nc )
3600 {
3601 reportError( text,
3603 ok = false;
3604 }
3605
3606 if( allPins == 0 )
3607 {
3608 reportError( text,
3610 ok = false;
3611 }
3612 }
3613 }
3614
3615 return ok;
3616}
3617
3618
3620{
3621 int errors = 0;
3622
3623 ERC_SETTINGS& settings = m_schematic->ErcSettings();
3624
3625 for( const SCH_SHEET_PATH& sheet : m_sheetList )
3626 {
3627 for( SCH_ITEM* item : sheet.LastScreen()->Items() )
3628 {
3629 if( item->Type() != SCH_SHEET_T )
3630 continue;
3631
3632 SCH_SHEET* parentSheet = static_cast<SCH_SHEET*>( item );
3633
3634 std::map<wxString, SCH_SHEET_PIN*> pins;
3635 std::map<wxString, SCH_HIERLABEL*> labels;
3636
3637 for( SCH_SHEET_PIN* pin : parentSheet->GetPins() )
3638 {
3639 if( settings.IsTestEnabled( ERCE_HIERACHICAL_LABEL ) )
3640 pins[pin->GetText()] = pin;
3641
3642 if( pin->IsDangling() && settings.IsTestEnabled( ERCE_PIN_NOT_CONNECTED ) )
3643 {
3644 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_PIN_NOT_CONNECTED );
3645 ercItem->SetItems( pin );
3646
3647 SCH_MARKER* marker = new SCH_MARKER( ercItem, pin->GetPosition() );
3648 sheet.LastScreen()->Append( marker );
3649
3650 errors++;
3651 }
3652 }
3653
3654 if( settings.IsTestEnabled( ERCE_HIERACHICAL_LABEL ) )
3655 {
3656 std::set<wxString> matchedPins;
3657
3658 for( SCH_ITEM* subItem : parentSheet->GetScreen()->Items() )
3659 {
3660 if( subItem->Type() == SCH_HIER_LABEL_T )
3661 {
3662 SCH_HIERLABEL* label = static_cast<SCH_HIERLABEL*>( subItem );
3663
3664 if( !pins.count( label->GetText() ) )
3665 labels[label->GetText()] = label;
3666 else
3667 matchedPins.insert( label->GetText() );
3668 }
3669 }
3670
3671 for( const wxString& matched : matchedPins )
3672 pins.erase( matched );
3673
3674 for( const std::pair<const wxString, SCH_SHEET_PIN*>& unmatched : pins )
3675 {
3676 wxString msg = wxString::Format( _( "Sheet pin %s has no matching hierarchical "
3677 "label inside the sheet" ),
3678 UnescapeString( unmatched.first ) );
3679
3680 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_HIERACHICAL_LABEL );
3681 ercItem->SetItems( unmatched.second );
3682 ercItem->SetErrorMessage( msg );
3683 ercItem->SetSheetSpecificPath( sheet );
3684 ercItem->SetItemsSheetPaths( sheet );
3685
3686 SCH_MARKER* marker = new SCH_MARKER( ercItem, unmatched.second->GetPosition() );
3687 sheet.LastScreen()->Append( marker );
3688
3689 errors++;
3690 }
3691
3692 for( const std::pair<const wxString, SCH_HIERLABEL*>& unmatched : labels )
3693 {
3694 wxString msg = wxString::Format( _( "Hierarchical label %s has no matching "
3695 "sheet pin in the parent sheet" ),
3696 UnescapeString( unmatched.first ) );
3697
3698 SCH_SHEET_PATH parentSheetPath = sheet;
3699 parentSheetPath.push_back( parentSheet );
3700
3701 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_HIERACHICAL_LABEL );
3702 ercItem->SetItems( unmatched.second );
3703 ercItem->SetErrorMessage( msg );
3704 ercItem->SetSheetSpecificPath( parentSheetPath );
3705 ercItem->SetItemsSheetPaths( parentSheetPath );
3706
3707 SCH_MARKER* marker = new SCH_MARKER( ercItem, unmatched.second->GetPosition() );
3708 parentSheet->GetScreen()->Append( marker );
3709
3710 errors++;
3711 }
3712 }
3713 }
3714 }
3715
3716 return errors;
3717}
const char * name
Definition: DXF_plotter.cpp:57
constexpr EDA_IU_SCALE schIUScale
Definition: base_units.h:111
Calculates the connectivity of a schematic and generates netlists.
int RunERC()
Runs electrical rule checks on the connectivity graph.
bool ercCheckBusToBusConflicts(const CONNECTION_SUBGRAPH *aSubgraph)
Checks one subgraph for conflicting connections between two bus items.
void processSubGraphs()
Process all subgraphs to assign netcodes and merge subgraphs based on labels.
bool ercCheckNetclassConflicts(const std::vector< CONNECTION_SUBGRAPH * > &subgraphs)
bool ercCheckLabels(const CONNECTION_SUBGRAPH *aSubgraph)
Checks one subgraph for proper connection of labels.
void collectAllDriverValues()
Maps the driver values for each subgraph.
CONNECTION_SUBGRAPH * FindSubgraphByName(const wxString &aNetName, const SCH_SHEET_PATH &aPath)
Returns the subgraph for a given net name on a given sheet.
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.
std::unordered_map< SCH_SHEET_PATH, std::vector< CONNECTION_SUBGRAPH * > > m_sheet_to_subgraphs_map
CONNECTION_SUBGRAPH * FindFirstSubgraphByName(const wxString &aNetName)
Retrieves a subgraph for the given net name, if one exists.
void propagateToNeighbors(CONNECTION_SUBGRAPH *aSubgraph, bool aForce)
Updates all neighbors of a subgraph with this one's connectivity info.
void buildItemSubGraphs()
Generates individual item subgraphs on a per-sheet basis.
bool ercCheckMultipleDrivers(const CONNECTION_SUBGRAPH *aSubgraph)
If the subgraph has multiple drivers of equal priority that are graphically connected,...
CONNECTION_SUBGRAPH * GetSubgraphForItem(SCH_ITEM *aItem)
SCH_SHEET_LIST m_sheetList
void generateGlobalPowerPinSubGraphs()
Iterate through the global power pins to collect the global labels as drivers.
const std::vector< CONNECTION_SUBGRAPH * > GetAllSubgraphs(const wxString &aNetName) const
std::unordered_map< wxString, int > m_net_name_to_code_map
void buildConnectionGraph(std::function< void(SCH_ITEM *)> *aChangedItemHandler)
Generates the connection graph (after all item connectivity has been updated)
int ercCheckHierSheets()
Checks that a hierarchical sheet has at least one matching label inside the sheet for each port on th...
bool ercCheckBusToNetConflicts(const CONNECTION_SUBGRAPH *aSubgraph)
Checks one subgraph for conflicting connections between net and bus labels.
std::shared_ptr< SCH_CONNECTION > getDefaultConnection(SCH_ITEM *aItem, CONNECTION_SUBGRAPH *aSubgraph)
Builds a new default connection for the given item based on its properties.
std::vector< const CONNECTION_SUBGRAPH * > GetBusesNeedingMigration()
Determines which subgraphs have more than one conflicting bus label.
int assignNewNetCode(SCH_CONNECTION &aConnection)
Helper to assign a new net code to a connection.
std::map< std::pair< SCH_SHEET_PATH, wxString >, std::vector< const CONNECTION_SUBGRAPH * > > m_local_label_cache
int getOrCreateNetCode(const wxString &aNetName)
void assignNetCodesToBus(SCH_CONNECTION *aConnection)
Ensures 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
std::vector< std::pair< SCH_SHEET_PATH, SCH_PIN * > > m_global_power_pins
bool ercCheckNoConnects(const CONNECTION_SUBGRAPH *aSubgraph)
Checks 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
std::unordered_map< wxString, std::vector< CONNECTION_SUBGRAPH * > > m_net_name_to_subgraphs_map
std::shared_ptr< BUS_ALIAS > GetBusAlias(const wxString &aName)
Returns a bus alias pointer for the given name if it exists (from cache)
void removeSubgraphs(std::set< CONNECTION_SUBGRAPH * > &aSubgraphs)
Removes 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
Returns the fully-resolved netname for a given subgraph.
bool ercCheckBusToBusEntryConflicts(const CONNECTION_SUBGRAPH *aSubgraph)
Checks one subgraph for conflicting bus entry to bus connections.
std::vector< CONNECTION_SUBGRAPH * > m_driver_subgraphs
NET_MAP m_net_code_to_subgraphs_map
bool ercCheckFloatingWires(const CONNECTION_SUBGRAPH *aSubgraph)
Checks one subgraph for floating wires.
void Merge(CONNECTION_GRAPH &aGraph)
Combines the input graph contents into the current graph.
void resolveAllDrivers()
Finds all subgraphs in the connection graph and calls ResolveDrivers() in parallel.
void updateItemConnectivity(const SCH_SHEET_PATH &aSheet, const std::vector< SCH_ITEM * > &aItemList)
Updates the graphical connectivity between items (i.e.
void Recalculate(const SCH_SHEET_LIST &aSheetList, bool aUnconditional=false, std::function< void(SCH_ITEM *)> *aChangedItemHandler=nullptr)
Updates the connection graph for the given list of sheets.
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
PRIORITY GetDriverPriority()
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.
SCH_SHEET_PATH m_sheet
On which logical sheet is the subgraph contained.
void UpdateItemConnections()
Updates all items to match the driver connection.
const wxString GetNetclassForDriver(SCH_ITEM *aItem) const
std::set< SCH_SHEET_PIN * > m_hier_pins
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
Returns all the all bus labels attached to this subgraph (if any)
std::unordered_map< SCH_ITEM *, wxString > m_driver_name_cache
A cache of escaped netnames from schematic items.
const wxString & GetNameForDriver(SCH_ITEM *aItem) const
Returns the candidate net name for a driver.
wxString GetNetName() const
Returns the fully-qualified net name for this subgraph (if one exists)
std::vector< SCH_ITEM * > GetVectorBusLabels() const
Returns all the vector-based bus labels attached to this subgraph (if any)
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)
Determines 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
void AddItem(SCH_ITEM *aItem)
Adds a new item to the subgraph.
void Absorb(CONNECTION_SUBGRAPH *aOther)
Combines 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)
bool m_is_bus_member
True if the subgraph is not actually part of a net.
CONNECTION_SUBGRAPH * m_hier_parent
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
void getAllConnectedItems(std::set< std::pair< SCH_SHEET_PATH, SCH_ITEM * > > &aItems, std::set< CONNECTION_SUBGRAPH * > &aSubgraphs)
Finds all items in the subgraph as well as child subgraphs recursively.
virtual VECTOR2I GetPosition() const
Definition: eda_item.h:239
KICAD_T Type() const
Returns the type of object.
Definition: eda_item.h:97
virtual wxString GetItemDescription(UNITS_PROVIDER *aUnitsProvider) const
Return a user-visible description string of this item.
Definition: eda_item.cpp:108
virtual const wxString & GetText() const
Return the string associated with the text object.
Definition: eda_text.h:95
EE_TYPE OfType(KICAD_T aType) const
Definition: sch_rtree.h:238
static std::shared_ptr< ERC_ITEM > Create(int aErrorCode)
Constructs an ERC_ITEM for the given error code.
Definition: erc_item.cpp:232
Container for ERC settings.
Definition: erc_settings.h:114
bool IsTestEnabled(int aErrorCode) const
Definition: erc_settings.h:130
Definition: kiid.h:49
LIB_SYMBOL * GetParent() const
Definition: lib_item.h:202
bool IsPower() const
Definition: lib_symbol.cpp:714
A small class to help profiling.
Definition: profile.h:47
void Show(std::ostream &aStream=std::cerr)
Print the elapsed time (in a suitable unit) to a stream.
Definition: profile.h:103
std::shared_ptr< NET_SETTINGS > m_NetSettings
Net settings for this project (owned here)
Definition: project_file.h:172
virtual PROJECT_FILE & GetProjectFile() const
Definition: project.h:166
SCH_SHEET_LIST GetSheets() const override
Builds and returns an updated schematic hierarchy TODO: can this be cached?
Definition: schematic.h:100
PROJECT & Prj() const override
Return a reference to the project this schematic is part of.
Definition: schematic.h:90
ERC_SETTINGS & ErcSettings() const
Definition: schematic.cpp:216
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...
VECTOR2I GetPosition() const override
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).
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)
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
Base class for any item which can be embedded within the SCHEMATIC container class,...
Definition: sch_item.h:150
virtual const wxString & GetCachedDriverName() const
Definition: sch_item.cpp:254
void AddConnectionTo(const SCH_SHEET_PATH &aPath, SCH_ITEM *aItem)
Add a connection link between this item and another.
Definition: sch_item.cpp:203
SCH_LAYER_ID GetLayer() const
Return the layer this item is on.
Definition: sch_item.h:257
virtual void RunOnChildren(const std::function< void(SCH_ITEM *)> &aFunction)
Definition: sch_item.h:463
void SetConnectionGraph(CONNECTION_GRAPH *aGraph)
Updates the connection graph for all connections in this item.
Definition: sch_item.cpp:164
virtual bool HasCachedDriverName() const
Definition: sch_item.h:433
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:147
LABEL_FLAG_SHAPE GetShape() const
Definition: sch_label.h:147
Segment description base class to describe items which have 2 end points (track, wire,...
Definition: sch_line.h:40
bool IsGlobalPower() const
Definition: sch_pin.h:160
LIB_PIN * GetLibPin() const
Definition: sch_pin.h:59
bool IsStacked(const SCH_PIN *aPin) const
Definition: sch_pin.cpp:250
SCH_SYMBOL * GetParentSymbol() const
Definition: sch_pin.cpp:189
void Append(SCH_ITEM *aItem, bool aUpdateLibSymbol=true)
Definition: sch_screen.cpp:152
EE_RTREE & Items()
Gets the full RTree, usually for iterating.
Definition: sch_screen.h:109
SCH_LINE * GetBus(const VECTOR2I &aPosition, int aAccuracy=0, SCH_LINE_TEST_T aSearchType=ENTIRE_LENGTH_T) const
Definition: sch_screen.h:435
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
wxString PathHumanReadable(bool aUseShortRootName=true, bool aStripTrailingSeparator=false) const
Return the sheet path in a human readable form made from the sheet names.
SCH_SCREEN * LastScreen()
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.
Definition: sch_sheet_pin.h:66
Sheet symbol placed in a schematic, and is the entry point for a sub schematic.
Definition: sch_sheet.h:57
SCH_SCREEN * GetScreen() const
Definition: sch_sheet.h:110
std::vector< SCH_SHEET_PIN * > & GetPins()
Definition: sch_sheet.h:185
Schematic symbol object.
Definition: sch_symbol.h:81
int GetUnit() const
Definition: sch_symbol.h:226
std::vector< SCH_PIN * > GetPins(const SCH_SHEET_PATH *aSheet=nullptr) const
Retrieve a list of the SCH_PINs for the given sheet path.
int GetUnitSelection(const SCH_SHEET_PATH *aSheet) const
Return the instance-specific unit selection for the given sheet path.
Definition: sch_symbol.cpp:839
bool GetExcludedFromBoard() const
Definition: sch_symbol.h:744
std::unique_ptr< LIB_SYMBOL > & GetLibSymbolRef()
Definition: sch_symbol.h:190
VECTOR2I GetPosition() const override
Definition: sch_text.h:126
virtual wxString GetShownText(const SCH_SHEET_PATH *aPath, bool aAllowExtraText, int aDepth=0) const
Definition: sch_text.cpp:266
The common library.
static const wxChar DanglingProfileMask[]
static const wxChar ConnTrace[]
#define _(s)
#define CANDIDATE
flag indicating that the structure is connected
@ ERCE_DRIVER_CONFLICT
Conflicting drivers (labels, etc) on a subgraph.
Definition: erc_settings.h:61
@ ERCE_LABEL_NOT_CONNECTED
Label not connected to anything.
Definition: erc_settings.h:50
@ ERCE_BUS_TO_BUS_CONFLICT
A connection between bus objects doesn't share at least one net.
Definition: erc_settings.h:64
@ ERCE_BUS_ENTRY_CONFLICT
A wire connected to a bus doesn't match the bus.
Definition: erc_settings.h:62
@ ERCE_NETCLASS_CONFLICT
Multiple labels assign different netclasses to same net.
Definition: erc_settings.h:68
@ ERCE_GLOBLABEL
A global label is unique.
Definition: erc_settings.h:69
@ ERCE_BUS_TO_NET_CONFLICT
A bus wire is graphically connected to a net port/pin (or vice versa).
Definition: erc_settings.h:66
@ ERCE_NOCONNECT_NOT_CONNECTED
A no connect symbol is not connected to anything.
Definition: erc_settings.h:49
@ ERCE_PIN_NOT_CONNECTED
Pin not connected and not no connect symbol.
Definition: erc_settings.h:42
@ ERCE_NOCONNECT_CONNECTED
A no connect symbol is connected to more than 1 pin.
Definition: erc_settings.h:48
@ ERCE_HIERACHICAL_LABEL
Mismatch between hierarchical labels and pins sheets.
Definition: erc_settings.h:47
@ ERCE_WIRE_DANGLING
Some wires are not connected to anything else.
Definition: erc_settings.h:72
@ LAYER_WIRE
Definition: layer_ids.h:349
@ LAYER_BUS
Definition: layer_ids.h:350
@ LAYER_JUNCTION
Definition: layer_ids.h:351
@ LAYER_BUS_JUNCTION
Definition: layer_ids.h:390
#define KI_FALLTHROUGH
The KI_FALLTHROUGH macro is to be used when switch statement cases should purposely fallthrough from ...
Definition: macros.h:83
void delete_if(_Container &__c, _Function &&__f)
Deletes all values from __c for which __f returns true.
Definition: kicad_algo.h:173
void remove_duplicates(_Container &__c)
Deletes all duplicate values from __c.
Definition: kicad_algo.h:182
void delete_matching(_Container &__c, _Value __value)
Covers for the horrifically named std::remove and std::remove_if (neither of which remove anything).
Definition: kicad_algo.h:164
CONNECTION_TYPE
std::vector< SCH_ITEM * > SCH_ITEM_SET
Definition: sch_item.h:139
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
Definition: string_utils.h:53
static thread_pool * tp
Definition: thread_pool.cpp:30
BS::thread_pool thread_pool
Definition: thread_pool.h:30
thread_pool & GetKiCadThreadPool()
Get a reference to the current thread pool.
Definition: thread_pool.cpp:32
#define kv
@ SCH_LINE_T
Definition: typeinfo.h:145
@ SCH_NO_CONNECT_T
Definition: typeinfo.h:142
@ SCH_SYMBOL_T
Definition: typeinfo.h:155
@ SCH_FIELD_T
Definition: typeinfo.h:154
@ SCH_LABEL_T
Definition: typeinfo.h:150
@ SCH_SHEET_T
Definition: typeinfo.h:157
@ SCH_HIER_LABEL_T
Definition: typeinfo.h:152
@ SCH_BUS_BUS_ENTRY_T
Definition: typeinfo.h:144
@ SCH_SHEET_PIN_T
Definition: typeinfo.h:156
@ SCH_TEXT_T
Definition: typeinfo.h:149
@ SCH_BUS_WIRE_ENTRY_T
Definition: typeinfo.h:143
@ SCH_GLOBAL_LABEL_T
Definition: typeinfo.h:151
@ SCH_JUNCTION_T
Definition: typeinfo.h:141
@ SCH_PIN_T
Definition: typeinfo.h:158
Functions to provide common constants and other functions to assist in making a consistent UI.