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