KiCad PCB EDA Suite
connection_graph.cpp
Go to the documentation of this file.
1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2018 CERN
5  * Copyright (C) 2021 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 <thread>
25 #include <future>
26 #include <vector>
27 #include <unordered_map>
28 #include <profile.h>
29 #include <common.h>
30 #include <core/kicad_algo.h>
31 #include <erc.h>
32 #include <pin_type.h>
33 #include <sch_bus_entry.h>
34 #include <sch_symbol.h>
35 #include <sch_edit_frame.h>
36 #include <sch_line.h>
37 #include <sch_marker.h>
38 #include <sch_pin.h>
39 #include <sch_sheet.h>
40 #include <sch_sheet_path.h>
41 #include <sch_sheet_pin.h>
42 #include <sch_text.h>
43 #include <schematic.h>
44 #include <connection_graph.h>
45 #include <widgets/ui_common.h>
46 #include <string_utils.h>
47 #include <wx/log.h>
48 
49 #include <advanced_config.h> // for realtime connectivity switch in release builds
50 
51 
52 /*
53  * Flag to enable connectivity profiling
54  * @ingroup trace_env_vars
55  */
56 static const wxChar ConnProfileMask[] = wxT( "CONN_PROFILE" );
57 
58 /*
59  * Flag to enable connectivity tracing
60  * @ingroup trace_env_vars
61  */
62 static const wxChar ConnTrace[] = wxT( "CONN" );
63 
64 
65 bool CONNECTION_SUBGRAPH::ResolveDrivers( bool aCheckMultipleDrivers )
66 {
67  PRIORITY highest_priority = PRIORITY::INVALID;
68  std::vector<SCH_ITEM*> candidates;
69  std::vector<SCH_ITEM*> strong_drivers;
70 
71  m_driver = nullptr;
72 
73  // Hierarchical labels are lower priority than local labels here,
74  // because on the first pass we want local labels to drive subgraphs
75  // so that we can identify same-sheet neighbors and link them together.
76  // Hierarchical labels will end up overriding the final net name if
77  // a higher-level sheet has a different name during the hierarchical
78  // pass.
79 
80  for( SCH_ITEM* item : m_drivers )
81  {
82  PRIORITY item_priority = GetDriverPriority( item );
83 
84  if( item_priority == PRIORITY::PIN
85  && !static_cast<SCH_PIN*>( item )->GetParentSymbol()->IsInNetlist() )
86  continue;
87 
88  if( item_priority >= PRIORITY::HIER_LABEL )
89  strong_drivers.push_back( item );
90 
91  if( item_priority > highest_priority )
92  {
93  candidates.clear();
94  candidates.push_back( item );
95  highest_priority = item_priority;
96  }
97  else if( !candidates.empty() && ( item_priority == highest_priority ) )
98  {
99  candidates.push_back( item );
100  }
101  }
102 
103  if( highest_priority >= PRIORITY::HIER_LABEL )
104  m_strong_driver = true;
105 
106  // Power pins are 5, global labels are 6
107  m_local_driver = ( highest_priority < PRIORITY::POWER_PIN );
108 
109  if( !candidates.empty() )
110  {
111  if( candidates.size() > 1 )
112  {
113  if( highest_priority == PRIORITY::SHEET_PIN )
114  {
115  // We have multiple options, and they are all hierarchical
116  // sheet pins. Let's prefer outputs over inputs.
117 
118  for( SCH_ITEM* c : candidates )
119  {
120  SCH_SHEET_PIN* p = static_cast<SCH_SHEET_PIN*>( c );
121 
123  {
124  m_driver = c;
125  break;
126  }
127  }
128  }
129  else
130  {
131  // See if a previous driver is still a candidate
132  void* previousDriver = nullptr;
133 
134  for( SCH_ITEM* member : m_items )
135  {
136  if( SCH_CONNECTION* mc = member->Connection( &m_sheet ) )
137  {
138  if( mc->GetLastDriver() )
139  {
140  previousDriver = mc->GetLastDriver();
141  break;
142  }
143  }
144  }
145 
146  // For all other driver types, sort by name
147  std::sort( candidates.begin(), candidates.end(),
148  [&]( SCH_ITEM* a, SCH_ITEM* b ) -> bool
149  {
150  // meet irreflexive requirements of std::sort
151  if( a == b )
152  return false;
153 
154  SCH_CONNECTION* ac = a->Connection( &m_sheet );
155  SCH_CONNECTION* bc = b->Connection( &m_sheet );
156 
157  // Ensure we don't pick the subset over the superset
158  if( ac->IsBus() && bc->IsBus() )
159  return bc->IsSubsetOf( ac );
160 
161  if( a == previousDriver )
162  return true;
163  else if( b == previousDriver )
164  return false;
165  else
166  return GetNameForDriver( a ) < GetNameForDriver( b );
167  } );
168  }
169  }
170 
171  if( !m_driver )
172  m_driver = candidates[0];
173  }
174 
175  if( strong_drivers.size() > 1 )
176  m_multiple_drivers = true;
177 
178  // Drop weak drivers
179  if( m_strong_driver )
180  m_drivers = strong_drivers;
181 
182  // Cache driver connection
183  if( m_driver )
184  {
189  }
190  else
191  {
192  m_driver_connection = nullptr;
193  }
194 
195  if( aCheckMultipleDrivers && m_multiple_drivers )
196  {
197  // First check if all the candidates are actually the same
198  bool same = true;
199  wxString first = GetNameForDriver( candidates[0] );
200  SCH_ITEM* second_item = nullptr;
201 
202  for( unsigned i = 1; i < candidates.size(); i++ )
203  {
204  if( GetNameForDriver( candidates[i] ) != first )
205  {
206  second_item = candidates[i];
207  same = false;
208  break;
209  }
210  }
211 
212  if( !same )
213  {
215  m_second_driver = second_item;
216  }
217  }
218 
219  return ( m_driver != nullptr );
220 }
221 
222 
224 {
225  if( !m_driver || m_dirty )
226  return "";
227 
228  if( !m_driver->Connection( &m_sheet ) )
229  {
230 #ifdef CONNECTIVITY_DEBUG
231  wxASSERT_MSG( false, wxT( "Tried to get the net name of an item with no connection" ) );
232 #endif
233 
234  return "";
235  }
236 
237  return m_driver->Connection( &m_sheet )->Name();
238 }
239 
240 
241 std::vector<SCH_ITEM*> CONNECTION_SUBGRAPH::GetBusLabels() const
242 {
243  std::vector<SCH_ITEM*> labels;
244 
245  for( SCH_ITEM* item : m_drivers )
246  {
247  switch( item->Type() )
248  {
249  case SCH_LABEL_T:
250  case SCH_GLOBAL_LABEL_T:
251  {
252  SCH_CONNECTION* label_conn = item->Connection( &m_sheet );
253 
254  // Only consider bus vectors
255  if( label_conn->Type() == CONNECTION_TYPE::BUS )
256  labels.push_back( item );
257 
258  break;
259  }
260 
261  default: break;
262  }
263  }
264 
265  return labels;
266 }
267 
268 
270 {
271  switch( aItem->Type() )
272  {
273  case SCH_PIN_T:
274  {
275  bool forceNoConnect = m_no_connect != nullptr;
276  SCH_PIN* pin = static_cast<SCH_PIN*>( aItem );
277  return pin->GetDefaultNetName( m_sheet, forceNoConnect );
278  break;
279  }
280 
281  case SCH_LABEL_T:
282  case SCH_GLOBAL_LABEL_T:
283  case SCH_HIER_LABEL_T:
284  case SCH_SHEET_PIN_T:
285  {
286  return EscapeString( static_cast<SCH_TEXT*>( aItem )->GetShownText(), CTX_NETNAME );
287  break;
288  }
289 
290  default:
291  wxFAIL_MSG( wxT( "Unhandled item type in GetNameForDriver" ) );
292  break;
293  }
294 
295  return wxEmptyString;
296 }
297 
298 
300 {
301  auto it = m_driver_name_cache.find( aItem );
302 
303  if( it != m_driver_name_cache.end() )
304  return it->second;
305 
306  m_driver_name_cache[aItem] = driverName( aItem );
307 
308  return m_driver_name_cache.at( aItem );
309 }
310 
311 
312 const wxString CONNECTION_SUBGRAPH::GetNameForDriver( SCH_ITEM* aItem ) const
313 {
314  auto it = m_driver_name_cache.find( aItem );
315 
316  if( it != m_driver_name_cache.end() )
317  return it->second;
318 
319  return driverName( aItem );
320 }
321 
322 
324 {
325  wxASSERT( m_sheet == aOther->m_sheet );
326 
327  for( SCH_ITEM* item : aOther->m_items )
328  {
330  AddItem( item );
331  }
332 
333  m_bus_neighbors.insert( aOther->m_bus_neighbors.begin(), aOther->m_bus_neighbors.end() );
334  m_bus_parents.insert( aOther->m_bus_parents.begin(), aOther->m_bus_parents.end() );
335 
337 
338  aOther->m_absorbed = true;
339  aOther->m_dirty = false;
340  aOther->m_driver = nullptr;
341  aOther->m_driver_connection = nullptr;
342  aOther->m_absorbed_by = this;
343 }
344 
345 
347 {
348  m_items.push_back( aItem );
349 
350  if( aItem->Connection( &m_sheet )->IsDriver() )
351  m_drivers.push_back( aItem );
352 
353  if( aItem->Type() == SCH_SHEET_PIN_T )
354  m_hier_pins.push_back( static_cast<SCH_SHEET_PIN*>( aItem ) );
355  else if( aItem->Type() == SCH_HIER_LABEL_T )
356  m_hier_ports.push_back( static_cast<SCH_HIERLABEL*>( aItem ) );
357 }
358 
359 
361 {
362  if( !m_driver_connection )
363  return;
364 
365  for( SCH_ITEM* item : m_items )
366  {
367  SCH_CONNECTION* item_conn = item->GetOrInitConnection( m_sheet, m_graph );
368 
369  if( !item_conn )
370  continue;
371 
372  if( ( m_driver_connection->IsBus() && item_conn->IsNet() ) ||
373  ( m_driver_connection->IsNet() && item_conn->IsBus() ) )
374  {
375  continue;
376  }
377 
378  if( item != m_driver )
379  {
380  item_conn->Clone( *m_driver_connection );
381  item_conn->ClearDirty();
382  }
383  }
384 }
385 
386 
388 {
389  if( !aDriver )
390  return PRIORITY::NONE;
391 
392  switch( aDriver->Type() )
393  {
396  case SCH_LABEL_T: return PRIORITY::LOCAL_LABEL;
398  case SCH_PIN_T:
399  {
400  SCH_PIN* sch_pin = static_cast<SCH_PIN*>( aDriver );
401 
402  if( sch_pin->IsPowerConnection() )
403  return PRIORITY::POWER_PIN;
404  else
405  return PRIORITY::PIN;
406  }
407 
408  default: return PRIORITY::NONE;
409  }
410 }
411 
412 
414 
415 
417 {
418  for( auto& subgraph : m_subgraphs )
419  delete subgraph;
420 
421  m_items.clear();
422  m_subgraphs.clear();
423  m_driver_subgraphs.clear();
424  m_sheet_to_subgraphs_map.clear();
425  m_invisible_power_pins.clear();
426  m_bus_alias_cache.clear();
427  m_net_name_to_code_map.clear();
428  m_bus_name_to_code_map.clear();
431  m_item_to_subgraph_map.clear();
432  m_local_label_cache.clear();
433  m_global_label_cache.clear();
434  m_last_net_code = 1;
435  m_last_bus_code = 1;
437 }
438 
439 
440 void CONNECTION_GRAPH::Recalculate( const SCH_SHEET_LIST& aSheetList, bool aUnconditional,
441  std::function<void( SCH_ITEM* )>* aChangedItemHandler )
442 {
443  PROF_TIMER recalc_time( "CONNECTION_GRAPH::Recalculate" );
444 
445  if( aUnconditional )
446  Reset();
447 
448  PROF_TIMER update_items( "updateItemConnectivity" );
449 
450  m_sheetList = aSheetList;
451 
452  for( const SCH_SHEET_PATH& sheet : aSheetList )
453  {
454  std::vector<SCH_ITEM*> items;
455 
456  for( SCH_ITEM* item : sheet.LastScreen()->Items() )
457  {
458  if( item->IsConnectable() && ( aUnconditional || item->IsConnectivityDirty() ) )
459  items.push_back( item );
460  }
461 
462  m_items.reserve( m_items.size() + items.size() );
463 
464  updateItemConnectivity( sheet, items );
465 
466  // UpdateDanglingState() also adds connected items for SCH_TEXT
467  sheet.LastScreen()->TestDanglingEnds( &sheet, aChangedItemHandler );
468  }
469 
470  if( wxLog::IsAllowedTraceMask( ConnProfileMask ) )
471  update_items.Show();
472 
473  PROF_TIMER build_graph( "buildConnectionGraph" );
474 
476 
477  if( wxLog::IsAllowedTraceMask( ConnProfileMask ) )
478  build_graph.Show();
479 
480  recalc_time.Stop();
481 
482  if( wxLog::IsAllowedTraceMask( ConnProfileMask ) )
483  recalc_time.Show();
484 
485 #ifndef DEBUG
486  // Pressure relief valve for release builds
487  const double max_recalc_time_msecs = 250.;
488 
489  if( m_allowRealTime && ADVANCED_CFG::GetCfg().m_RealTimeConnectivity &&
490  recalc_time.msecs() > max_recalc_time_msecs )
491  {
492  m_allowRealTime = false;
493  }
494 #endif
495 }
496 
497 
499  const std::vector<SCH_ITEM*>& aItemList )
500 {
501  std::map< wxPoint, std::vector<SCH_ITEM*> > connection_map;
502 
503  for( SCH_ITEM* item : aItemList )
504  {
505  std::vector< wxPoint > points = item->GetConnectionPoints();
506  item->ConnectedItems( aSheet ).clear();
507 
508  if( item->Type() == SCH_SHEET_T )
509  {
510  for( SCH_SHEET_PIN* pin : static_cast<SCH_SHEET*>( item )->GetPins() )
511  {
512  pin->InitializeConnection( aSheet, this );
513 
514  pin->ConnectedItems( aSheet ).clear();
515 
516  connection_map[ pin->GetTextPos() ].push_back( pin );
517  m_items.emplace_back( pin );
518  }
519  }
520  else if( item->Type() == SCH_SYMBOL_T )
521  {
522  SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
523 
524  for( SCH_PIN* pin : symbol->GetPins( &aSheet ) )
525  {
526  pin->InitializeConnection( aSheet, this );
527 
528  wxPoint pos = pin->GetPosition();
529 
530  // because calling the first time is not thread-safe
531  pin->GetDefaultNetName( aSheet );
532  pin->ConnectedItems( aSheet ).clear();
533 
534  // Invisible power pins need to be post-processed later
535 
536  if( pin->IsPowerConnection() && !pin->IsVisible() )
537  m_invisible_power_pins.emplace_back( std::make_pair( aSheet, pin ) );
538 
539  connection_map[ pos ].push_back( pin );
540  m_items.emplace_back( pin );
541  }
542  }
543  else
544  {
545  m_items.emplace_back( item );
546  SCH_CONNECTION* conn = item->InitializeConnection( aSheet, this );
547 
548  // Set bus/net property here so that the propagation code uses it
549  switch( item->Type() )
550  {
551  case SCH_LINE_T:
552  conn->SetType( item->GetLayer() == LAYER_BUS ? CONNECTION_TYPE::BUS :
554  break;
555 
556  case SCH_BUS_BUS_ENTRY_T:
557  conn->SetType( CONNECTION_TYPE::BUS );
558  // clean previous (old) links:
559  static_cast<SCH_BUS_BUS_ENTRY*>( item )->m_connected_bus_items[0] = nullptr;
560  static_cast<SCH_BUS_BUS_ENTRY*>( item )->m_connected_bus_items[1] = nullptr;
561  break;
562 
563  case SCH_PIN_T:
564  conn->SetType( CONNECTION_TYPE::NET );
565  break;
566 
568  conn->SetType( CONNECTION_TYPE::NET );
569  // clean previous (old) link:
570  static_cast<SCH_BUS_WIRE_ENTRY*>( item )->m_connected_bus_item = nullptr;
571  break;
572 
573  default:
574  break;
575  }
576 
577  for( const wxPoint& point : points )
578  connection_map[ point ].push_back( item );
579  }
580 
581  item->SetConnectivityDirty( false );
582  }
583 
584  for( const auto& it : connection_map )
585  {
586  const std::vector<SCH_ITEM*>& connection_vec = it.second;
587 
588  // Pre-scan to see if we have a bus at this location
589  SCH_LINE* busLine = aSheet.LastScreen()->GetBus( it.first );
590 
591  // We don't want to spin up a new thread for fewer than 4 items (overhead costs)
592  size_t parallelThreadCount = std::min<size_t>( std::thread::hardware_concurrency(),
593  ( connection_vec.size() + 3 ) / 4 );
594 
595  std::atomic<size_t> nextItem( 0 );
596  std::mutex update_mutex;
597  std::vector<std::future<size_t>> returns( parallelThreadCount );
598 
599  auto update_lambda = [&]() -> size_t
600  {
601  for( size_t ii = nextItem++; ii < connection_vec.size(); ii = nextItem++ )
602  {
603  SCH_ITEM* connected_item = connection_vec[ii];
604  // Bus entries are special: they can have connection points in the
605  // middle of a wire segment, because the junction algo doesn't split
606  // the segment in two where you place a bus entry. This means that
607  // bus entries that don't land on the end of a line segment need to
608  // have "virtual" connection points to the segments they graphically
609  // touch.
610  if( connected_item->Type() == SCH_BUS_WIRE_ENTRY_T )
611  {
612  // If this location only has the connection point of the bus
613  // entry itself, this means that either the bus entry is not
614  // connected to anything graphically, or that it is connected to
615  // a segment at some point other than at one of the endpoints.
616  if( connection_vec.size() == 1 )
617  {
618  if( busLine )
619  {
620  auto bus_entry = static_cast<SCH_BUS_WIRE_ENTRY*>( connected_item );
621  bus_entry->m_connected_bus_item = busLine;
622  }
623  }
624  }
625 
626  // Bus-to-bus entries are treated just like bus wires
627  else if( connected_item->Type() == SCH_BUS_BUS_ENTRY_T )
628  {
629  if( connection_vec.size() < 2 )
630  {
631  if( busLine )
632  {
633  auto bus_entry = static_cast<SCH_BUS_BUS_ENTRY*>( connected_item );
634 
635  if( it.first == bus_entry->GetPosition() )
636  bus_entry->m_connected_bus_items[0] = busLine;
637  else
638  bus_entry->m_connected_bus_items[1] = busLine;
639 
640  std::lock_guard<std::mutex> lock( update_mutex );
641  bus_entry->AddConnectionTo( aSheet, busLine );
642  busLine->AddConnectionTo( aSheet, bus_entry );
643  }
644  }
645  }
646 
647  // Change junctions to be on bus junction layer if they are touching a bus
648  else if( connected_item->Type() == SCH_JUNCTION_T )
649  {
650  connected_item->SetLayer( busLine ? LAYER_BUS_JUNCTION : LAYER_JUNCTION );
651  }
652 
653  SCH_ITEM_SET& connected_set = connected_item->ConnectedItems( aSheet );
654  connected_set.reserve( connection_vec.size() );
655 
656  for( SCH_ITEM* test_item : connection_vec )
657  {
658  bool bus_connection_ok = true;
659 
660  if( test_item == connected_item )
661  continue;
662 
663  // Set up the link between the bus entry net and the bus
664  if( connected_item->Type() == SCH_BUS_WIRE_ENTRY_T )
665  {
666  if( test_item->GetLayer() == LAYER_BUS )
667  {
668  auto bus_entry = static_cast<SCH_BUS_WIRE_ENTRY*>( connected_item );
669  bus_entry->m_connected_bus_item = test_item;
670  }
671  }
672 
673  // Bus entries only connect to bus lines on the end that is touching a bus line.
674  // If the user has overlapped another net line with the endpoint of the bus entry
675  // where the entry connects to a bus, we don't want to short-circuit it.
676  if( connected_item->Type() == SCH_BUS_WIRE_ENTRY_T )
677  {
678  bus_connection_ok = !busLine || test_item->GetLayer() == LAYER_BUS;
679  }
680  else if( test_item->Type() == SCH_BUS_WIRE_ENTRY_T )
681  {
682  bus_connection_ok = !busLine || connected_item->GetLayer() == LAYER_BUS;
683  }
684 
685  if( connected_item->ConnectionPropagatesTo( test_item ) &&
686  test_item->ConnectionPropagatesTo( connected_item ) &&
687  bus_connection_ok )
688  {
689  connected_set.push_back( test_item );
690  }
691  }
692 
693  // If we got this far and did not find a connected bus item for a bus entry,
694  // we should do a manual scan in case there is a bus item on this connection
695  // point but we didn't pick it up earlier because there is *also* a net item here.
696  if( connected_item->Type() == SCH_BUS_WIRE_ENTRY_T )
697  {
698  auto bus_entry = static_cast<SCH_BUS_WIRE_ENTRY*>( connected_item );
699 
700  if( !bus_entry->m_connected_bus_item )
701  {
702  SCH_SCREEN* screen = aSheet.LastScreen();
703  SCH_LINE* bus = screen->GetBus( it.first );
704 
705  if( bus )
706  bus_entry->m_connected_bus_item = bus;
707  }
708  }
709  }
710 
711  return 1;
712  };
713 
714 
715  if( parallelThreadCount == 1 )
716  update_lambda();
717  else
718  {
719  for( size_t ii = 0; ii < parallelThreadCount; ++ii )
720  returns[ii] = std::async( std::launch::async, update_lambda );
721 
722  // Finalize the threads
723  for( size_t ii = 0; ii < parallelThreadCount; ++ii )
724  returns[ii].wait();
725  }
726  }
727 }
728 
729 
731 {
732  // Build subgraphs from items (on a per-sheet basis)
733 
734  for( SCH_ITEM* item : m_items )
735  {
736  for( const auto& it : item->m_connection_map )
737  {
738  const SCH_SHEET_PATH& sheet = it.first;
739  SCH_CONNECTION* connection = it.second;
740 
741  if( connection->SubgraphCode() == 0 )
742  {
743  CONNECTION_SUBGRAPH* subgraph = new CONNECTION_SUBGRAPH( this );
744 
745  subgraph->m_code = m_last_subgraph_code++;
746  subgraph->m_sheet = sheet;
747 
748  subgraph->AddItem( item );
749 
750  connection->SetSubgraphCode( subgraph->m_code );
751  m_item_to_subgraph_map[item] = subgraph;
752 
753  std::list<SCH_ITEM*> memberlist;
754 
755  auto get_items =
756  [&]( SCH_ITEM* aItem ) -> bool
757  {
758  SCH_CONNECTION* conn = aItem->GetOrInitConnection( sheet, this );
759  bool unique = !( aItem->GetFlags() & CANDIDATE );
760 
761  if( conn && !conn->SubgraphCode() )
762  aItem->SetFlags( CANDIDATE );
763 
764  return ( unique && conn && ( conn->SubgraphCode() == 0 ) );
765  };
766 
767  std::copy_if( item->ConnectedItems( sheet ).begin(),
768  item->ConnectedItems( sheet ).end(),
769  std::back_inserter( memberlist ), get_items );
770 
771  for( SCH_ITEM* connected_item : memberlist )
772  {
773  if( connected_item->Type() == SCH_NO_CONNECT_T )
774  subgraph->m_no_connect = connected_item;
775 
776  SCH_CONNECTION* connected_conn = connected_item->Connection( &sheet );
777 
778  wxASSERT( connected_conn );
779 
780  if( connected_conn->SubgraphCode() == 0 )
781  {
782  connected_conn->SetSubgraphCode( subgraph->m_code );
783  m_item_to_subgraph_map[connected_item] = subgraph;
784  subgraph->AddItem( connected_item );
785  SCH_ITEM_SET& citemset = connected_item->ConnectedItems( sheet );
786 
787  for( SCH_ITEM* citem : citemset )
788  {
789  if( citem->HasFlag( CANDIDATE ) )
790  continue;
791 
792  if( get_items( citem ) )
793  memberlist.push_back( citem );
794  }
795  }
796  }
797 
798  for( SCH_ITEM* connected_item : memberlist )
799  connected_item->ClearFlags( CANDIDATE );
800 
801  subgraph->m_dirty = true;
802  m_subgraphs.push_back( subgraph );
803  }
804  }
805  }
806 
807 }
808 
810 {
811  // Resolve drivers for subgraphs and propagate connectivity info
812 
813  // We don't want to spin up a new thread for fewer than 8 nets (overhead costs)
814  size_t parallelThreadCount = std::min<size_t>( std::thread::hardware_concurrency(),
815  ( m_subgraphs.size() + 3 ) / 4 );
816 
817  std::atomic<size_t> nextSubgraph( 0 );
818  std::vector<std::future<size_t>> returns( parallelThreadCount );
819  std::vector<CONNECTION_SUBGRAPH*> dirty_graphs;
820 
821  std::copy_if( m_subgraphs.begin(), m_subgraphs.end(), std::back_inserter( dirty_graphs ),
822  [&] ( const CONNECTION_SUBGRAPH* candidate )
823  {
824  return candidate->m_dirty;
825  } );
826 
827  auto update_lambda = [&nextSubgraph, &dirty_graphs]() -> size_t
828  {
829  for( size_t subgraphId = nextSubgraph++; subgraphId < dirty_graphs.size(); subgraphId = nextSubgraph++ )
830  {
831  auto subgraph = dirty_graphs[subgraphId];
832 
833  if( !subgraph->m_dirty )
834  continue;
835 
836  // Special processing for some items
837  for( auto item : subgraph->m_items )
838  {
839  switch( item->Type() )
840  {
841  case SCH_NO_CONNECT_T:
842  subgraph->m_no_connect = item;
843  break;
844 
846  subgraph->m_bus_entry = item;
847  break;
848 
849  case SCH_PIN_T:
850  {
851  auto pin = static_cast<SCH_PIN*>( item );
852 
853  if( pin->GetType() == ELECTRICAL_PINTYPE::PT_NC )
854  subgraph->m_no_connect = item;
855 
856  break;
857  }
858 
859  default:
860  break;
861  }
862  }
863 
864  subgraph->ResolveDrivers( true );
865  subgraph->m_dirty = false;
866  }
867 
868  return 1;
869  };
870 
871  if( parallelThreadCount == 1 )
872  update_lambda();
873  else
874  {
875  for( size_t ii = 0; ii < parallelThreadCount; ++ii )
876  returns[ii] = std::async( std::launch::async, update_lambda );
877 
878  // Finalize the threads
879  for( size_t ii = 0; ii < parallelThreadCount; ++ii )
880  returns[ii].wait();
881  }
882 
883  // Now discard any non-driven subgraphs from further consideration
884 
885  std::copy_if( m_subgraphs.begin(), m_subgraphs.end(), std::back_inserter( m_driver_subgraphs ),
886  [&] ( const CONNECTION_SUBGRAPH* candidate ) -> bool
887  {
888  return candidate->m_driver;
889  } );
890 }
891 
892 
894 {
895  // Check for subgraphs with the same net name but only weak drivers.
896  // For example, two wires that are both connected to hierarchical
897  // sheet pins that happen to have the same name, but are not the same.
898 
899  for( auto&& subgraph : m_driver_subgraphs )
900  {
901  wxString full_name = subgraph->m_driver_connection->Name();
902  wxString name = subgraph->m_driver_connection->Name( true );
903  m_net_name_to_subgraphs_map[full_name].emplace_back( subgraph );
904 
905  // For vector buses, we need to cache the prefix also, as two different instances of the
906  // weakly driven pin may have the same prefix but different vector start and end. We need
907  // to treat those as needing renaming also, because otherwise if they end up on a sheet with
908  // common usage, they will be incorrectly merged.
909  if( subgraph->m_driver_connection->Type() == CONNECTION_TYPE::BUS )
910  {
911  wxString prefixOnly = full_name.BeforeFirst( '[' ) + wxT( "[]" );
912  m_net_name_to_subgraphs_map[prefixOnly].emplace_back( subgraph );
913  }
914 
915  subgraph->m_dirty = true;
916 
917  if( subgraph->m_strong_driver )
918  {
919  SCH_ITEM* driver = subgraph->m_driver;
920  SCH_SHEET_PATH sheet = subgraph->m_sheet;
921 
922  switch( driver->Type() )
923  {
924  case SCH_LABEL_T:
925  case SCH_HIER_LABEL_T:
926  {
927  m_local_label_cache[std::make_pair( sheet, name )].push_back( subgraph );
928  break;
929  }
930  case SCH_GLOBAL_LABEL_T:
931  {
932  m_global_label_cache[name].push_back( subgraph );
933  break;
934  }
935  case SCH_PIN_T:
936  {
937  SCH_PIN* pin = static_cast<SCH_PIN*>( driver );
938  wxASSERT( pin->IsPowerConnection() );
939  m_global_label_cache[name].push_back( subgraph );
940  break;
941  }
942  default:
943  wxLogTrace( ConnTrace, wxT( "Unexpected strong driver %s" ),
945  break;
946  }
947  }
948  }
949 }
950 
951 
953 {
954  // Generate subgraphs for invisible power pins. These will be merged with other subgraphs
955  // on the same sheet in the next loop.
956 
957  std::unordered_map<int, CONNECTION_SUBGRAPH*> invisible_pin_subgraphs;
958 
959  for( const auto& it : m_invisible_power_pins )
960  {
961  SCH_SHEET_PATH sheet = it.first;
962  SCH_PIN* pin = it.second;
963 
964  if( !pin->ConnectedItems( sheet ).empty() && !pin->GetLibPin()->GetParent()->IsPower() )
965  {
966  // ERC will warn about this: user has wired up an invisible pin
967  continue;
968  }
969 
970  SCH_CONNECTION* connection = pin->GetOrInitConnection( sheet, this );
971 
972  // If this pin already has a subgraph, don't need to process
973  if( !connection || connection->SubgraphCode() > 0 )
974  continue;
975 
976  connection->SetName( pin->GetShownName() );
977 
978  int code = assignNewNetCode( *connection );
979 
980  connection->SetNetCode( code );
981 
982  CONNECTION_SUBGRAPH* subgraph;
983  auto jj = invisible_pin_subgraphs.find( code );
984 
985  if( jj != invisible_pin_subgraphs.end() )
986  {
987  subgraph = jj->second;
988  subgraph->AddItem( pin );
989  }
990  else
991  {
992  subgraph = new CONNECTION_SUBGRAPH( this );
993 
994  subgraph->m_code = m_last_subgraph_code++;
995  subgraph->m_sheet = sheet;
996 
997  subgraph->AddItem( pin );
998  subgraph->ResolveDrivers();
999 
1000  auto key = std::make_pair( subgraph->GetNetName(), code );
1001  m_net_code_to_subgraphs_map[ key ].push_back( subgraph );
1002  m_subgraphs.push_back( subgraph );
1003  m_driver_subgraphs.push_back( subgraph );
1004 
1005  invisible_pin_subgraphs[code] = subgraph;
1006  }
1007 
1008  connection->SetSubgraphCode( subgraph->m_code );
1009  }
1010 }
1011 
1012 
1014 {
1015  // Here we do all the local (sheet) processing of each subgraph, including assigning net
1016  // codes, merging subgraphs together that use label connections, etc.
1017 
1018  // Cache remaining valid subgraphs by sheet path
1019  for( CONNECTION_SUBGRAPH* subgraph : m_driver_subgraphs )
1020  m_sheet_to_subgraphs_map[ subgraph->m_sheet ].emplace_back( subgraph );
1021 
1022  std::unordered_set<CONNECTION_SUBGRAPH*> invalidated_subgraphs;
1023 
1024  for( CONNECTION_SUBGRAPH* subgraph : m_driver_subgraphs )
1025  {
1026  if( subgraph->m_absorbed )
1027  continue;
1028 
1029  SCH_CONNECTION* connection = subgraph->m_driver_connection;
1030  SCH_SHEET_PATH sheet = subgraph->m_sheet;
1031  wxString name = connection->Name();
1032 
1033  // Test subgraphs with weak drivers for net name conflicts and fix them
1034  unsigned suffix = 1;
1035 
1036  auto create_new_name =
1037  [&suffix]( SCH_CONNECTION* aConn ) -> wxString
1038  {
1039  wxString newName;
1040 
1041  // For group buses with a prefix, we can add the suffix to the prefix.
1042  // If they don't have a prefix, we force the creation of a prefix so that
1043  // two buses don't get inadvertently shorted together.
1044  if( aConn->Type() == CONNECTION_TYPE::BUS_GROUP )
1045  {
1046  wxString prefix = aConn->BusPrefix();
1047 
1048  if( prefix.empty() )
1049  prefix = wxT( "BUS" ); // So result will be "BUS_1{...}"
1050 
1051  wxString oldName = aConn->Name().AfterFirst( '{' );
1052 
1053  newName = wxString::Format( wxT( "%s_%u{%s" ), prefix, suffix, oldName );
1054 
1055  aConn->ConfigureFromLabel( newName );
1056  }
1057  else
1058  {
1059  newName = wxString::Format( wxT( "%s_%u" ), aConn->Name(), suffix );
1060  aConn->SetSuffix( wxString::Format( wxT( "_%u" ), suffix ) );
1061  }
1062 
1063  suffix++;
1064  return newName;
1065  };
1066 
1067  if( !subgraph->m_strong_driver )
1068  {
1069  std::vector<CONNECTION_SUBGRAPH*>* vec = &m_net_name_to_subgraphs_map.at( name );
1070 
1071  // If we are a unique bus vector, check if we aren't actually unique because of another
1072  // subgraph with a similar bus vector
1073  if( vec->size() <= 1 && subgraph->m_driver_connection->Type() == CONNECTION_TYPE::BUS )
1074  {
1075  wxString prefixOnly = name.BeforeFirst( '[' ) + wxT( "[]" );
1076  vec = &m_net_name_to_subgraphs_map.at( prefixOnly );
1077  }
1078 
1079  if( vec->size() > 1 )
1080  {
1081  wxString new_name = create_new_name( connection );
1082 
1083  while( m_net_name_to_subgraphs_map.count( new_name ) )
1084  new_name = create_new_name( connection );
1085 
1086  wxLogTrace( ConnTrace, wxT( "%ld (%s) is weakly driven and not unique. Changing to %s." ),
1087  subgraph->m_code, name, new_name );
1088 
1089  alg::delete_matching( *vec, subgraph );
1090 
1091  m_net_name_to_subgraphs_map[new_name].emplace_back( subgraph );
1092 
1093  name = new_name;
1094  }
1095  else
1096  {
1097  // If there is no conflict, promote sheet pins to be strong drivers so that they
1098  // will be considered below for propagation/merging.
1099 
1100  // It is possible for this to generate a conflict if the sheet pin has the same
1101  // name as a global label on the same sheet, because global merging will then treat
1102  // this subgraph as if it had a matching local label. So, for those cases, we
1103  // don't apply this promotion
1104 
1105  if( subgraph->m_driver->Type() == SCH_SHEET_PIN_T )
1106  {
1107  bool conflict = false;
1108  wxString global_name = connection->Name( true );
1109  auto kk = m_net_name_to_subgraphs_map.find( global_name );
1110 
1111  if( kk != m_net_name_to_subgraphs_map.end() )
1112  {
1113  // A global will conflict if it is on the same sheet as this subgraph, since
1114  // it would be connected by implicit local label linking
1115  std::vector<CONNECTION_SUBGRAPH*>& candidates = kk->second;
1116 
1117  for( const CONNECTION_SUBGRAPH* candidate : candidates )
1118  {
1119  if( candidate->m_sheet == sheet )
1120  conflict = true;
1121  }
1122  }
1123 
1124  if( conflict )
1125  {
1126  wxLogTrace( ConnTrace,
1127  wxT( "%ld (%s) skipped for promotion due to potential conflict" ),
1128  subgraph->m_code, name );
1129  }
1130  else
1131  {
1132  wxLogTrace( ConnTrace,
1133  wxT( "%ld (%s) weakly driven by unique sheet pin %s, promoting" ),
1134  subgraph->m_code, name,
1135  subgraph->m_driver->GetSelectMenuText( EDA_UNITS::MILLIMETRES ) );
1136 
1137  subgraph->m_strong_driver = true;
1138  }
1139  }
1140  }
1141  }
1142 
1143  // Assign net codes
1144 
1145  if( connection->IsBus() )
1146  {
1147  int code = -1;
1148  auto it = m_bus_name_to_code_map.find( name );
1149 
1150  if( it != m_bus_name_to_code_map.end() )
1151  {
1152  code = it->second;
1153  }
1154  else
1155  {
1156  code = m_last_bus_code++;
1157  m_bus_name_to_code_map[ name ] = code;
1158  }
1159 
1160  connection->SetBusCode( code );
1161  assignNetCodesToBus( connection );
1162  }
1163  else
1164  {
1165  assignNewNetCode( *connection );
1166  }
1167 
1168  // Reset the flag for the next loop below
1169  subgraph->m_dirty = true;
1170 
1171  // Next, we merge together subgraphs that have label connections, and create
1172  // neighbor links for subgraphs that are part of a bus on the same sheet.
1173  // For merging, we consider each possible strong driver.
1174 
1175  // If this subgraph doesn't have a strong driver, let's skip it, since there is no
1176  // way it will be merged with anything.
1177 
1178  if( !subgraph->m_strong_driver )
1179  continue;
1180 
1181  // candidate_subgraphs will contain each valid, non-bus subgraph on the same sheet
1182  // as the subgraph we are considering that has a strong driver.
1183  // Weakly driven subgraphs are not considered since they will never be absorbed or
1184  // form neighbor links.
1185 
1186  std::vector<CONNECTION_SUBGRAPH*> candidate_subgraphs;
1187  std::copy_if( m_sheet_to_subgraphs_map[ subgraph->m_sheet ].begin(),
1188  m_sheet_to_subgraphs_map[ subgraph->m_sheet ].end(),
1189  std::back_inserter( candidate_subgraphs ),
1190  [&] ( const CONNECTION_SUBGRAPH* candidate )
1191  {
1192  return ( !candidate->m_absorbed &&
1193  candidate->m_strong_driver &&
1194  candidate != subgraph );
1195  } );
1196 
1197  // This is a list of connections on the current subgraph to compare to the
1198  // drivers of each candidate subgraph. If the current subgraph is a bus,
1199  // we should consider each bus member.
1200  std::vector< std::shared_ptr<SCH_CONNECTION> > connections_to_check;
1201 
1202  // Also check the main driving connection
1203  connections_to_check.push_back( std::make_shared<SCH_CONNECTION>( *connection ) );
1204 
1205  auto add_connections_to_check = [&] ( CONNECTION_SUBGRAPH* aSubgraph ) {
1206  for( SCH_ITEM* possible_driver : aSubgraph->m_items )
1207  {
1208  if( possible_driver == aSubgraph->m_driver )
1209  continue;
1210 
1211  auto c = getDefaultConnection( possible_driver, aSubgraph );
1212 
1213  if( c )
1214  {
1215  if( c->Type() != aSubgraph->m_driver_connection->Type() )
1216  continue;
1217 
1218  if( c->Name( true ) == aSubgraph->m_driver_connection->Name( true ) )
1219  continue;
1220 
1221  connections_to_check.push_back( c );
1222  wxLogTrace( ConnTrace,
1223  wxT( "%lu (%s): Adding secondary driver %s" ), aSubgraph->m_code,
1224  aSubgraph->m_driver_connection->Name( true ), c->Name( true ) );
1225  }
1226  }
1227  };
1228 
1229  // Now add other strong drivers
1230  // The actual connection attached to these items will have been overwritten
1231  // by the chosen driver of the subgraph, so we need to create a dummy connection
1232  add_connections_to_check( subgraph );
1233 
1234  for( unsigned i = 0; i < connections_to_check.size(); i++ )
1235  {
1236  auto member = connections_to_check[i];
1237 
1238  if( member->IsBus() )
1239  {
1240  connections_to_check.insert( connections_to_check.end(),
1241  member->Members().begin(),
1242  member->Members().end() );
1243  }
1244 
1245  wxString test_name = member->Name( true );
1246 
1247  for( auto candidate : candidate_subgraphs )
1248  {
1249  if( candidate->m_absorbed )
1250  continue;
1251 
1252  bool match = false;
1253 
1254  if( candidate->m_driver_connection->Name( true ) == test_name )
1255  {
1256  match = true;
1257  }
1258  else
1259  {
1260  if( !candidate->m_multiple_drivers )
1261  continue;
1262 
1263  for( SCH_ITEM *driver : candidate->m_drivers )
1264  {
1265  if( driver == candidate->m_driver )
1266  continue;
1267 
1268  // Sheet pins are not candidates for merging
1269  if( driver->Type() == SCH_SHEET_PIN_T )
1270  continue;
1271 
1272  if( driver->Type() == SCH_PIN_T )
1273  {
1274  auto pin = static_cast<SCH_PIN*>( driver );
1275 
1276  if( pin->IsPowerConnection() && pin->GetShownName() == test_name )
1277  {
1278  match = true;
1279  break;
1280  }
1281  }
1282  else
1283  {
1284  wxASSERT( driver->Type() == SCH_LABEL_T ||
1285  driver->Type() == SCH_GLOBAL_LABEL_T ||
1286  driver->Type() == SCH_HIER_LABEL_T );
1287 
1288  if( subgraph->GetNameForDriver( driver ) == test_name )
1289  {
1290  match = true;
1291  break;
1292  }
1293  }
1294  }
1295  }
1296 
1297  if( match )
1298  {
1299  if( connection->IsBus() && candidate->m_driver_connection->IsNet() )
1300  {
1301  wxLogTrace( ConnTrace, wxT( "%lu (%s) has bus child %lu (%s)" ), subgraph->m_code,
1302  connection->Name(), candidate->m_code, member->Name() );
1303 
1304  subgraph->m_bus_neighbors[member].insert( candidate );
1305  candidate->m_bus_parents[member].insert( subgraph );
1306  }
1307  else
1308  {
1309  wxLogTrace( ConnTrace, wxT( "%lu (%s) absorbs neighbor %lu (%s)" ),
1310  subgraph->m_code, connection->Name(),
1311  candidate->m_code, candidate->m_driver_connection->Name() );
1312 
1313  // Candidate may have other non-chosen drivers we need to follow
1314  add_connections_to_check( candidate );
1315 
1316  subgraph->Absorb( candidate );
1317  invalidated_subgraphs.insert( subgraph );
1318  }
1319  }
1320  }
1321  }
1322  }
1323 
1324  // Update any subgraph that was invalidated above
1325  for( CONNECTION_SUBGRAPH* subgraph : invalidated_subgraphs )
1326  {
1327  if( subgraph->m_absorbed )
1328  continue;
1329 
1330  subgraph->ResolveDrivers();
1331 
1332  if( subgraph->m_driver_connection->IsBus() )
1333  assignNetCodesToBus( subgraph->m_driver_connection );
1334  else
1335  assignNewNetCode( *subgraph->m_driver_connection );
1336 
1337  wxLogTrace( ConnTrace, wxT( "Re-resolving drivers for %lu (%s)" ), subgraph->m_code,
1338  subgraph->m_driver_connection->Name() );
1339  }
1340 
1341 }
1342 
1343 
1344 // TODO(JE) This won't give the same subgraph IDs (and eventually net/graph codes)
1345 // to the same subgraph necessarily if it runs over and over again on the same
1346 // sheet. We need:
1347 //
1348 // a) a cache of net/bus codes, like used before
1349 // b) to persist the CONNECTION_GRAPH globally so the cache is persistent,
1350 // c) some way of trying to avoid changing net names. so we should keep track
1351 // of the previous driver of a net, and if it comes down to choosing between
1352 // equally-prioritized drivers, choose the one that already exists as a driver
1353 // on some portion of the items.
1354 
1355 
1357 {
1358  // Recache all bus aliases for later use
1359  wxCHECK_RET( m_schematic, wxT( "Connection graph cannot be built without schematic pointer" ) );
1360 
1361  SCH_SHEET_LIST all_sheets = m_schematic->GetSheets();
1362 
1363  for( unsigned i = 0; i < all_sheets.size(); i++ )
1364  {
1365  for( const auto& alias : all_sheets[i].LastScreen()->GetBusAliases() )
1366  m_bus_alias_cache[ alias->GetName() ] = alias;
1367  }
1368 
1369  PROF_TIMER sub_graph( "buildItemSubGraphs" );
1371 
1372  if( wxLog::IsAllowedTraceMask( ConnProfileMask ) )
1373  sub_graph.Show();
1374 
1375 
1382 
1384 
1386 
1387  PROF_TIMER proc_sub_graph( "ProcessSubGraphs" );
1388  processSubGraphs();
1389 
1390  if( wxLog::IsAllowedTraceMask( ConnProfileMask ) )
1391  proc_sub_graph.Show();
1392 
1393  // Absorbed subgraphs should no longer be considered
1394  alg::delete_if( m_driver_subgraphs, [&]( const CONNECTION_SUBGRAPH* candidate ) -> bool
1395  {
1396  return candidate->m_absorbed;
1397  } );
1398 
1399  // Store global subgraphs for later reference
1400  std::vector<CONNECTION_SUBGRAPH*> global_subgraphs;
1401  std::copy_if( m_driver_subgraphs.begin(), m_driver_subgraphs.end(),
1402  std::back_inserter( global_subgraphs ),
1403  [&] ( const CONNECTION_SUBGRAPH* candidate ) -> bool
1404  {
1405  return !candidate->m_local_driver;
1406  } );
1407 
1408  // Recache remaining valid subgraphs by sheet path
1409  m_sheet_to_subgraphs_map.clear();
1410 
1411  for( CONNECTION_SUBGRAPH* subgraph : m_driver_subgraphs )
1412  m_sheet_to_subgraphs_map[ subgraph->m_sheet ].emplace_back( subgraph );
1413 
1414  // Update item connections at this point so that neighbor propagation works
1415  std::atomic<size_t> nextSubgraph( 0 );
1416 
1417  // We don't want to spin up a new thread for fewer than 8 nets (overhead costs)
1418  size_t parallelThreadCount = std::min<size_t>( std::thread::hardware_concurrency(),
1419  ( m_subgraphs.size() + 3 ) / 4 );
1420 
1421  std::vector<std::future<size_t>> returns( parallelThreadCount );
1422 
1423  auto preliminaryUpdateTask =
1424  [&]() -> size_t
1425  {
1426  for( size_t subgraphId = nextSubgraph++;
1427  subgraphId < m_driver_subgraphs.size();
1428  subgraphId = nextSubgraph++ )
1429  {
1430  m_driver_subgraphs[subgraphId]->UpdateItemConnections();
1431  }
1432 
1433  return 1;
1434  };
1435 
1436  if( parallelThreadCount == 1 )
1437  preliminaryUpdateTask();
1438  else
1439  {
1440  for( size_t ii = 0; ii < parallelThreadCount; ++ii )
1441  returns[ii] = std::async( std::launch::async, preliminaryUpdateTask );
1442 
1443  // Finalize the threads
1444  for( size_t ii = 0; ii < parallelThreadCount; ++ii )
1445  returns[ii].wait();
1446  }
1447 
1448  // Next time through the subgraphs, we do some post-processing to handle things like
1449  // connecting bus members to their neighboring subgraphs, and then propagate connections
1450  // through the hierarchy
1451 
1452  for( CONNECTION_SUBGRAPH* subgraph : m_driver_subgraphs )
1453  {
1454  if( !subgraph->m_dirty )
1455  continue;
1456 
1457  wxLogTrace( ConnTrace, wxT( "Processing %lu (%s) for propagation" ), subgraph->m_code,
1458  subgraph->m_driver_connection->Name() );
1459 
1460  // For subgraphs that are driven by a global (power port or label) and have more
1461  // than one global driver, we need to seek out other subgraphs driven by the
1462  // same name as the non-chosen driver and update them to match the chosen one.
1463 
1464  if( !subgraph->m_local_driver && subgraph->m_multiple_drivers )
1465  {
1466  for( SCH_ITEM* driver : subgraph->m_drivers )
1467  {
1468  if( driver == subgraph->m_driver )
1469  continue;
1470 
1471  wxString secondary_name = subgraph->GetNameForDriver( driver );
1472 
1473  if( secondary_name == subgraph->m_driver_connection->Name() )
1474  continue;
1475 
1476  bool secondary_is_global = CONNECTION_SUBGRAPH::GetDriverPriority( driver )
1478 
1479  for( CONNECTION_SUBGRAPH* candidate : global_subgraphs )
1480  {
1481  if( candidate == subgraph )
1482  continue;
1483 
1484  if( !secondary_is_global && candidate->m_sheet != subgraph->m_sheet )
1485  continue;
1486 
1487  SCH_CONNECTION* conn = candidate->m_driver_connection;
1488 
1489  if( conn->Name() == secondary_name )
1490  {
1491  wxLogTrace( ConnTrace, wxT( "Global %lu (%s) promoted to %s" ), candidate->m_code,
1492  conn->Name(), subgraph->m_driver_connection->Name() );
1493 
1494  conn->Clone( *subgraph->m_driver_connection );
1495 
1496  candidate->m_dirty = false;
1497  }
1498  }
1499  }
1500  }
1501 
1502  // This call will handle descending the hierarchy and updating child subgraphs
1503  propagateToNeighbors( subgraph );
1504  }
1505 
1506  // Handle buses that have been linked together somewhere by member (net) connections.
1507  // This feels a bit hacky, perhaps this algorithm should be revisited in the future.
1508 
1509  // For net subgraphs that have more than one bus parent, we need to ensure that those
1510  // buses are linked together in the final netlist. The final name of each bus might not
1511  // match the local name that was used to establish the parent-child relationship, because
1512  // the bus may have been renamed by a hierarchical connection. So, for each of these cases,
1513  // we need to identify the appropriate bus members to link together (and their final names),
1514  // and then update all instances of the old name in the hierarchy.
1515 
1516  for( CONNECTION_SUBGRAPH* subgraph : m_driver_subgraphs )
1517  {
1518  // All SGs should have been processed by propagateToNeighbors above
1519  wxASSERT_MSG( !subgraph->m_dirty, wxT( "Subgraph not processed by propagateToNeighbors!" ) );
1520 
1521  if( subgraph->m_bus_parents.size() < 2 )
1522  continue;
1523 
1524  SCH_CONNECTION* conn = subgraph->m_driver_connection;
1525 
1526  wxLogTrace( ConnTrace, wxT( "%lu (%s) has multiple bus parents" ),
1527  subgraph->m_code, conn->Name() );
1528 
1529  wxASSERT( conn->IsNet() );
1530 
1531  for( const auto& ii : subgraph->m_bus_parents )
1532  {
1533  SCH_CONNECTION* link_member = ii.first.get();
1534 
1535  for( CONNECTION_SUBGRAPH* parent : ii.second )
1536  {
1537  while( parent->m_absorbed )
1538  parent = parent->m_absorbed_by;
1539 
1540  SCH_CONNECTION* match = matchBusMember( parent->m_driver_connection, link_member );
1541 
1542  if( !match )
1543  {
1544  wxLogTrace( ConnTrace, wxT( "Warning: could not match %s inside %lu (%s)" ),
1545  conn->Name(), parent->m_code, parent->m_driver_connection->Name() );
1546  continue;
1547  }
1548 
1549  if( conn->Name() != match->Name() )
1550  {
1551  wxString old_name = match->Name();
1552 
1553  wxLogTrace( ConnTrace, wxT( "Updating %lu (%s) member %s to %s" ), parent->m_code,
1554  parent->m_driver_connection->Name(), old_name, conn->Name() );
1555 
1556  match->Clone( *conn );
1557 
1558  auto jj = m_net_name_to_subgraphs_map.find( old_name );
1559 
1560  if( jj == m_net_name_to_subgraphs_map.end() )
1561  continue;
1562 
1563  for( CONNECTION_SUBGRAPH* old_sg : jj->second )
1564  {
1565  while( old_sg->m_absorbed )
1566  old_sg = old_sg->m_absorbed_by;
1567 
1568  old_sg->m_driver_connection->Clone( *conn );
1569  }
1570  }
1571  }
1572  }
1573  }
1574 
1575  nextSubgraph.store( 0 );
1576 
1577  auto updateItemConnectionsTask =
1578  [&]() -> size_t
1579  {
1580  for( size_t subgraphId = nextSubgraph++;
1581  subgraphId < m_driver_subgraphs.size();
1582  subgraphId = nextSubgraph++ )
1583  {
1584  CONNECTION_SUBGRAPH* subgraph = m_driver_subgraphs[subgraphId];
1585 
1586  // Make sure weakly-driven single-pin nets get the unconnected_ prefix
1587  if( !subgraph->m_strong_driver && subgraph->m_drivers.size() == 1 &&
1588  subgraph->m_driver->Type() == SCH_PIN_T )
1589  {
1590  SCH_PIN* pin = static_cast<SCH_PIN*>( subgraph->m_driver );
1591  wxString name = pin->GetDefaultNetName( subgraph->m_sheet, true );
1592 
1594  }
1595 
1596  subgraph->m_dirty = false;
1597  subgraph->UpdateItemConnections();
1598 
1599  // No other processing to do on buses
1600  if( subgraph->m_driver_connection->IsBus() )
1601  continue;
1602 
1603  // As a visual aid, we can check sheet pins that are driven by themselves to see
1604  // if they should be promoted to buses
1605 
1606  if( subgraph->m_driver->Type() == SCH_SHEET_PIN_T )
1607  {
1608  SCH_SHEET_PIN* pin = static_cast<SCH_SHEET_PIN*>( subgraph->m_driver );
1609 
1610  if( SCH_SHEET* sheet = pin->GetParent() )
1611  {
1612  wxString pinText = pin->GetText();
1613  SCH_SCREEN* screen = sheet->GetScreen();
1614 
1615  for( SCH_ITEM* item : screen->Items().OfType( SCH_HIER_LABEL_T ) )
1616  {
1617  SCH_HIERLABEL* label = static_cast<SCH_HIERLABEL*>( item );
1618 
1619  if( label->GetText() == pinText )
1620  {
1621  SCH_SHEET_PATH path = subgraph->m_sheet;
1622  path.push_back( sheet );
1623 
1624  SCH_CONNECTION* parent_conn = label->Connection( &path );
1625 
1626  if( parent_conn && parent_conn->IsBus() )
1628 
1629  break;
1630  }
1631  }
1632 
1633  if( subgraph->m_driver_connection->IsBus() )
1634  continue;
1635  }
1636  }
1637  }
1638 
1639  return 1;
1640  };
1641 
1642  if( parallelThreadCount == 1 )
1643  updateItemConnectionsTask();
1644  else
1645  {
1646  for( size_t ii = 0; ii < parallelThreadCount; ++ii )
1647  returns[ii] = std::async( std::launch::async, updateItemConnectionsTask );
1648 
1649  // Finalize the threads
1650  for( size_t ii = 0; ii < parallelThreadCount; ++ii )
1651  returns[ii].wait();
1652  }
1653 
1656 
1657  for( CONNECTION_SUBGRAPH* subgraph : m_driver_subgraphs )
1658  {
1659  auto key = std::make_pair( subgraph->GetNetName(),
1660  subgraph->m_driver_connection->NetCode() );
1661  m_net_code_to_subgraphs_map[ key ].push_back( subgraph );
1662 
1663  m_net_name_to_subgraphs_map[subgraph->m_driver_connection->Name()].push_back( subgraph );
1664  }
1665 }
1666 
1667 
1669 {
1670  int code;
1671 
1672  auto it = m_net_name_to_code_map.find( aConnection.Name() );
1673 
1674  if( it == m_net_name_to_code_map.end() )
1675  {
1676  code = m_last_net_code++;
1677  m_net_name_to_code_map[ aConnection.Name() ] = code;
1678  }
1679  else
1680  {
1681  code = it->second;
1682  }
1683 
1684  aConnection.SetNetCode( code );
1685 
1686  return code;
1687 }
1688 
1689 
1691 {
1692  auto connections_to_check( aConnection->Members() );
1693 
1694  for( unsigned i = 0; i < connections_to_check.size(); i++ )
1695  {
1696  auto member = connections_to_check[i];
1697 
1698  if( member->IsBus() )
1699  {
1700  connections_to_check.insert( connections_to_check.end(),
1701  member->Members().begin(),
1702  member->Members().end() );
1703  continue;
1704  }
1705 
1706  assignNewNetCode( *member );
1707  }
1708 }
1709 
1710 
1712 {
1713  SCH_CONNECTION* conn = aSubgraph->m_driver_connection;
1714  std::vector<CONNECTION_SUBGRAPH*> search_list;
1715  std::unordered_set<CONNECTION_SUBGRAPH*> visited;
1716  std::vector<SCH_CONNECTION*> stale_bus_members;
1717 
1718  auto visit =
1719  [&]( CONNECTION_SUBGRAPH* aParent )
1720  {
1721  for( SCH_SHEET_PIN* pin : aParent->m_hier_pins )
1722  {
1723  SCH_SHEET_PATH path = aParent->m_sheet;
1724  path.push_back( pin->GetParent() );
1725 
1726  auto it = m_sheet_to_subgraphs_map.find( path );
1727 
1728  if( it == m_sheet_to_subgraphs_map.end() )
1729  continue;
1730 
1731  for( CONNECTION_SUBGRAPH* candidate : it->second )
1732  {
1733  if( !candidate->m_strong_driver
1734  || candidate->m_hier_ports.empty()
1735  || visited.count( candidate ) )
1736  {
1737  continue;
1738  }
1739 
1740  for( SCH_HIERLABEL* label : candidate->m_hier_ports )
1741  {
1742  if( candidate->GetNameForDriver( label ) == aParent->GetNameForDriver( pin ) )
1743  {
1744  wxLogTrace( ConnTrace, wxT( "%lu: found child %lu (%s)" ), aParent->m_code,
1745  candidate->m_code, candidate->m_driver_connection->Name() );
1746 
1747  candidate->m_hier_parent = aParent;
1748 
1749  search_list.push_back( candidate );
1750  break;
1751  }
1752  }
1753  }
1754  }
1755 
1756  for( SCH_HIERLABEL* label : aParent->m_hier_ports )
1757  {
1758  SCH_SHEET_PATH path = aParent->m_sheet;
1759  path.pop_back();
1760 
1761  auto it = m_sheet_to_subgraphs_map.find( path );
1762 
1763  if( it == m_sheet_to_subgraphs_map.end() )
1764  continue;
1765 
1766  for( CONNECTION_SUBGRAPH* candidate : it->second )
1767  {
1768  if( candidate->m_hier_pins.empty()
1769  || visited.count( candidate )
1770  || candidate->m_driver_connection->Type() != aParent->m_driver_connection->Type() )
1771  {
1772  continue;
1773  }
1774 
1775  for( SCH_SHEET_PIN* pin : candidate->m_hier_pins )
1776  {
1777  SCH_SHEET_PATH pin_path = path;
1778  pin_path.push_back( pin->GetParent() );
1779 
1780  if( pin_path != aParent->m_sheet )
1781  continue;
1782 
1783  if( aParent->GetNameForDriver( label ) == candidate->GetNameForDriver( pin ) )
1784  {
1785  wxLogTrace( ConnTrace, wxT( "%lu: found additional parent %lu (%s)" ),
1786  aParent->m_code, candidate->m_code,
1787  candidate->m_driver_connection->Name() );
1788 
1789  search_list.push_back( candidate );
1790  break;
1791  }
1792  }
1793  }
1794  }
1795  };
1796 
1797  auto propagate_bus_neighbors = [&]( CONNECTION_SUBGRAPH* aParentGraph ) {
1798  for( const auto& kv : aParentGraph->m_bus_neighbors )
1799  {
1800  for( CONNECTION_SUBGRAPH* neighbor : kv.second )
1801  {
1802  // May have been absorbed but won't have been deleted
1803  while( neighbor->m_absorbed )
1804  neighbor = neighbor->m_absorbed_by;
1805 
1806  SCH_CONNECTION* parent = aParentGraph->m_driver_connection;
1807 
1808  // Now member may be out of date, since we just cloned the
1809  // connection from higher up in the hierarchy. We need to
1810  // figure out what the actual new connection is.
1811  SCH_CONNECTION* member = matchBusMember( parent, kv.first.get() );
1812 
1813  if( !member )
1814  {
1815  // Try harder: we might match on a secondary driver
1816  for( CONNECTION_SUBGRAPH* sg : kv.second )
1817  {
1818  if( sg->m_multiple_drivers )
1819  {
1820  SCH_SHEET_PATH sheet = sg->m_sheet;
1821 
1822  for( SCH_ITEM* driver : sg->m_drivers )
1823  {
1824  auto c = getDefaultConnection( driver, sg );
1825  member = matchBusMember( parent, c.get() );
1826 
1827  if( member )
1828  break;
1829  }
1830  }
1831 
1832  if( member )
1833  break;
1834  }
1835  }
1836 
1837  // This is bad, probably an ERC error
1838  if( !member )
1839  {
1840  wxLogTrace( ConnTrace, wxT( "Could not match bus member %s in %s" ),
1841  kv.first->Name(), parent->Name() );
1842  continue;
1843  }
1844 
1845  auto neighbor_conn = neighbor->m_driver_connection;
1846  auto neighbor_name = neighbor_conn->Name();
1847 
1848  // Matching name: no update needed
1849  if( neighbor_name == member->Name() )
1850  continue;
1851 
1852  // Was this neighbor already updated from a different sheet? Don't rename it again
1853  if( neighbor_conn->Sheet() != neighbor->m_sheet )
1854  continue;
1855 
1856  // Safety check against infinite recursion
1857  wxASSERT( neighbor_conn->IsNet() );
1858 
1859  wxLogTrace( ConnTrace, wxT( "%lu (%s) connected to bus member %s (local %s)" ),
1860  neighbor->m_code, neighbor_name, member->Name(), member->LocalName() );
1861 
1862  // Take whichever name is higher priority
1865  {
1866  member->Clone( *neighbor_conn );
1867  stale_bus_members.push_back( member );
1868  }
1869  else
1870  {
1871  neighbor_conn->Clone( *member );
1872 
1873  recacheSubgraphName( neighbor, neighbor_name );
1874 
1875  // Recurse onto this neighbor in case it needs to re-propagate
1876  neighbor->m_dirty = true;
1877  propagateToNeighbors( neighbor );
1878  }
1879  }
1880  }
1881  };
1882 
1883  // If we are a bus, we must propagate to local neighbors and then the hierarchy
1884  if( conn->IsBus() )
1885  propagate_bus_neighbors( aSubgraph );
1886 
1887  // If we have both ports and pins, skip processing as we'll be visited by a parent or child.
1888  // If we only have one or the other, process (we can either go bottom-up or top-down depending
1889  // on which subgraph comes up first)
1890  if( !aSubgraph->m_hier_ports.empty() && !aSubgraph->m_hier_pins.empty() )
1891  {
1892  wxLogTrace( ConnTrace, wxT( "%lu (%s) has both hier ports and pins; deferring processing" ),
1893  aSubgraph->m_code, conn->Name() );
1894  return;
1895  }
1896  else if( aSubgraph->m_hier_ports.empty() && aSubgraph->m_hier_pins.empty() )
1897  {
1898  wxLogTrace( ConnTrace, wxT( "%lu (%s) has no hier pins or ports; marking clean" ),
1899  aSubgraph->m_code, conn->Name() );
1900  aSubgraph->m_dirty = false;
1901  return;
1902  }
1903 
1904  visited.insert( aSubgraph );
1905 
1906  wxLogTrace( ConnTrace, wxT( "Propagating %lu (%s) to subsheets" ),
1907  aSubgraph->m_code, aSubgraph->m_driver_connection->Name() );
1908 
1909  visit( aSubgraph );
1910 
1911  for( unsigned i = 0; i < search_list.size(); i++ )
1912  {
1913  auto child = search_list[i];
1914 
1915  visited.insert( child );
1916 
1917  visit( child );
1918 
1919  child->m_dirty = false;
1920  }
1921 
1922  // Now, find the best driver for this chain of subgraphs
1923  CONNECTION_SUBGRAPH* bestDriver = aSubgraph;
1926  bool bestIsStrong = ( highest >= CONNECTION_SUBGRAPH::PRIORITY::HIER_LABEL );
1927  wxString bestName = aSubgraph->m_driver_connection->Name();
1928 
1929  // Check if a subsheet has a higher-priority connection to the same net
1931  {
1932  for( CONNECTION_SUBGRAPH* subgraph : visited )
1933  {
1934  if( subgraph == aSubgraph )
1935  continue;
1936 
1938  CONNECTION_SUBGRAPH::GetDriverPriority( subgraph->m_driver );
1939 
1940  bool candidateStrong = ( priority >= CONNECTION_SUBGRAPH::PRIORITY::HIER_LABEL );
1941  wxString candidateName = subgraph->m_driver_connection->Name();
1942  bool shorterPath = subgraph->m_sheet.size() < bestDriver->m_sheet.size();
1943  bool asGoodPath = subgraph->m_sheet.size() <= bestDriver->m_sheet.size();
1944 
1945  // Pick a better driving subgraph if it:
1946  // a) has a power pin or global driver
1947  // b) is a strong driver and we're a weak driver
1948  // c) is a higher priority strong driver
1949  // d) matches our priority, is a strong driver, and has a shorter path
1950  // e) matches our strength and is at least as short, and is alphabetically lower
1951 
1952  if( ( priority >= CONNECTION_SUBGRAPH::PRIORITY::POWER_PIN ) ||
1953  ( !bestIsStrong && candidateStrong ) ||
1954  ( priority > highest && candidateStrong ) ||
1955  ( priority == highest && candidateStrong && shorterPath ) ||
1956  ( ( bestIsStrong == candidateStrong ) && asGoodPath && ( priority == highest ) &&
1957  ( candidateName < bestName ) ) )
1958  {
1959  bestDriver = subgraph;
1960  highest = priority;
1961  bestIsStrong = candidateStrong;
1962  bestName = candidateName;
1963  }
1964  }
1965  }
1966 
1967  if( bestDriver != aSubgraph )
1968  {
1969  wxLogTrace( ConnTrace, wxT( "%lu (%s) overridden by new driver %lu (%s)" ),
1970  aSubgraph->m_code, aSubgraph->m_driver_connection->Name(), bestDriver->m_code,
1971  bestDriver->m_driver_connection->Name() );
1972  }
1973 
1974  conn = bestDriver->m_driver_connection;
1975 
1976  for( CONNECTION_SUBGRAPH* subgraph : visited )
1977  {
1978  wxString old_name = subgraph->m_driver_connection->Name();
1979 
1980  subgraph->m_driver_connection->Clone( *conn );
1981 
1982  if( old_name != conn->Name() )
1983  recacheSubgraphName( subgraph, old_name );
1984 
1985  if( conn->IsBus() )
1986  propagate_bus_neighbors( subgraph );
1987  }
1988 
1989  // Somewhere along the way, a bus member may have been upgraded to a global or power label.
1990  // Because this can happen anywhere, we need a second pass to update all instances of that bus
1991  // member to have the correct connection info
1992  if( conn->IsBus() && !stale_bus_members.empty() )
1993  {
1994  for( SCH_CONNECTION* stale_member : stale_bus_members )
1995  {
1996  for( CONNECTION_SUBGRAPH* subgraph : visited )
1997  {
1998  SCH_CONNECTION* member = matchBusMember( subgraph->m_driver_connection,
1999  stale_member );
2000 
2001  if( !member )
2002  {
2003  wxLogTrace( ConnTrace, wxT( "WARNING: failed to match stale member %s in %s." ),
2004  stale_member->Name(), subgraph->m_driver_connection->Name() );
2005  continue;
2006  }
2007 
2008  wxLogTrace( ConnTrace, wxT( "Updating %lu (%s) member %s to %s" ), subgraph->m_code,
2009  subgraph->m_driver_connection->Name(), member->LocalName(),
2010  stale_member->Name() );
2011 
2012  member->Clone( *stale_member );
2013 
2014  propagate_bus_neighbors( subgraph );
2015  }
2016  }
2017  }
2018 
2019  aSubgraph->m_dirty = false;
2020 }
2021 
2022 
2023 std::shared_ptr<SCH_CONNECTION> CONNECTION_GRAPH::getDefaultConnection( SCH_ITEM* aItem,
2024  CONNECTION_SUBGRAPH* aSubgraph )
2025 {
2026  std::shared_ptr<SCH_CONNECTION> c = std::shared_ptr<SCH_CONNECTION>( nullptr );
2027 
2028  switch( aItem->Type() )
2029  {
2030  case SCH_PIN_T:
2031  {
2032  SCH_PIN* pin = static_cast<SCH_PIN*>( aItem );
2033 
2034  if( pin->IsPowerConnection() )
2035  c = std::make_shared<SCH_CONNECTION>( aItem, aSubgraph->m_sheet );
2036 
2037  break;
2038  }
2039 
2040  case SCH_GLOBAL_LABEL_T:
2041  case SCH_HIER_LABEL_T:
2042  case SCH_LABEL_T:
2043  {
2044  c = std::make_shared<SCH_CONNECTION>( aItem, aSubgraph->m_sheet );
2045  break;
2046  }
2047 
2048  default:
2049  break;
2050  }
2051 
2052  if( c )
2053  {
2054  c->SetGraph( this );
2055  c->ConfigureFromLabel( aSubgraph->GetNameForDriver( aItem ) );
2056  }
2057 
2058  return c;
2059 }
2060 
2061 
2063  SCH_CONNECTION* aSearch )
2064 {
2065  wxASSERT( aBusConnection->IsBus() );
2066 
2067  SCH_CONNECTION* match = nullptr;
2068 
2069  if( aBusConnection->Type() == CONNECTION_TYPE::BUS )
2070  {
2071  // Vector bus: compare against index, because we allow the name
2072  // to be different
2073 
2074  for( const std::shared_ptr<SCH_CONNECTION>& bus_member : aBusConnection->Members() )
2075  {
2076  if( bus_member->VectorIndex() == aSearch->VectorIndex() )
2077  {
2078  match = bus_member.get();
2079  break;
2080  }
2081  }
2082  }
2083  else
2084  {
2085  // Group bus
2086  for( const std::shared_ptr<SCH_CONNECTION>& c : aBusConnection->Members() )
2087  {
2088  // Vector inside group: compare names, because for bus groups
2089  // we expect the naming to be consistent across all usages
2090  // TODO(JE) explain this in the docs
2091  if( c->Type() == CONNECTION_TYPE::BUS )
2092  {
2093  for( const std::shared_ptr<SCH_CONNECTION>& bus_member : c->Members() )
2094  {
2095  if( bus_member->LocalName() == aSearch->LocalName() )
2096  {
2097  match = bus_member.get();
2098  break;
2099  }
2100  }
2101  }
2102  else if( c->LocalName() == aSearch->LocalName() )
2103  {
2104  match = c.get();
2105  break;
2106  }
2107  }
2108  }
2109 
2110  return match;
2111 }
2112 
2113 
2115  const wxString& aOldName )
2116 {
2117  auto it = m_net_name_to_subgraphs_map.find( aOldName );
2118 
2119  if( it != m_net_name_to_subgraphs_map.end() )
2120  {
2121  std::vector<CONNECTION_SUBGRAPH*>& vec = it->second;
2122  alg::delete_matching( vec, aSubgraph );
2123  }
2124 
2125  wxLogTrace( ConnTrace, wxT( "recacheSubgraphName: %s => %s" ), aOldName,
2126  aSubgraph->m_driver_connection->Name() );
2127 
2128  m_net_name_to_subgraphs_map[aSubgraph->m_driver_connection->Name()].push_back( aSubgraph );
2129 }
2130 
2131 
2132 std::shared_ptr<BUS_ALIAS> CONNECTION_GRAPH::GetBusAlias( const wxString& aName )
2133 {
2134  auto it = m_bus_alias_cache.find( aName );
2135 
2136  return it != m_bus_alias_cache.end() ? it->second : nullptr;
2137 }
2138 
2139 
2140 std::vector<const CONNECTION_SUBGRAPH*> CONNECTION_GRAPH::GetBusesNeedingMigration()
2141 {
2142  std::vector<const CONNECTION_SUBGRAPH*> ret;
2143 
2144  for( CONNECTION_SUBGRAPH* subgraph : m_subgraphs )
2145  {
2146  // Graph is supposed to be up-to-date before calling this
2147  wxASSERT( !subgraph->m_dirty );
2148 
2149  if( !subgraph->m_driver )
2150  continue;
2151 
2152  SCH_CONNECTION* connection = subgraph->m_driver->Connection( &subgraph->m_sheet );
2153 
2154  if( !connection->IsBus() )
2155  continue;
2156 
2157  auto labels = subgraph->GetBusLabels();
2158 
2159  if( labels.size() > 1 )
2160  {
2161  bool different = false;
2162  wxString first = static_cast<SCH_TEXT*>( labels.at( 0 ) )->GetShownText();
2163 
2164  for( unsigned i = 1; i < labels.size(); ++i )
2165  {
2166  if( static_cast<SCH_TEXT*>( labels.at( i ) )->GetShownText() != first )
2167  {
2168  different = true;
2169  break;
2170  }
2171  }
2172 
2173  if( !different )
2174  continue;
2175 
2176  wxLogTrace( ConnTrace, wxT( "SG %ld (%s) has multiple bus labels" ), subgraph->m_code,
2177  connection->Name() );
2178 
2179  ret.push_back( subgraph );
2180  }
2181  }
2182 
2183  return ret;
2184 }
2185 
2186 
2188  const SCH_SHEET_PATH& aPath )
2189 {
2190  auto it = m_net_name_to_subgraphs_map.find( aNetName );
2191 
2192  if( it == m_net_name_to_subgraphs_map.end() )
2193  return nullptr;
2194 
2195  for( CONNECTION_SUBGRAPH* sg : it->second )
2196  {
2197  // Cache is supposed to be valid by now
2198  wxASSERT( sg && !sg->m_absorbed && sg->m_driver_connection );
2199 
2200  if( sg->m_sheet == aPath && sg->m_driver_connection->Name() == aNetName )
2201  return sg;
2202  }
2203 
2204  return nullptr;
2205 }
2206 
2207 
2209 {
2210  auto it = m_net_name_to_subgraphs_map.find( aNetName );
2211 
2212  if( it == m_net_name_to_subgraphs_map.end() )
2213  return nullptr;
2214 
2215  wxASSERT( !it->second.empty() );
2216 
2217  return it->second[0];
2218 }
2219 
2220 
2222 {
2223  auto it = m_item_to_subgraph_map.find( aItem );
2224  CONNECTION_SUBGRAPH* ret = it != m_item_to_subgraph_map.end() ? it->second : nullptr;
2225 
2226  while( ret && ret->m_absorbed )
2227  ret = ret->m_absorbed_by;
2228 
2229  return ret;
2230 }
2231 
2232 
2234 {
2235  int error_count = 0;
2236 
2237  wxCHECK_MSG( m_schematic, true, wxT( "Null m_schematic in CONNECTION_GRAPH::RunERC" ) );
2238 
2239  ERC_SETTINGS& settings = m_schematic->ErcSettings();
2240 
2241  // We don't want to run many ERC checks more than once on a given screen even though it may
2242  // represent multiple sheets with multiple subgraphs. We can tell these apart by drivers.
2243  std::set<SCH_ITEM*> seenDriverInstances;
2244 
2245  for( CONNECTION_SUBGRAPH* subgraph : m_subgraphs )
2246  {
2247  // There shouldn't be any null sub-graph pointers.
2248  wxCHECK2( subgraph, continue );
2249 
2250  // Graph is supposed to be up-to-date before calling RunERC()
2251  wxASSERT( !subgraph->m_dirty );
2252 
2253  if( subgraph->m_absorbed )
2254  continue;
2255 
2256  if( seenDriverInstances.count( subgraph->m_driver ) )
2257  continue;
2258 
2259  if( subgraph->m_driver )
2260  seenDriverInstances.insert( subgraph->m_driver );
2261 
2272  if( settings.IsTestEnabled( ERCE_DRIVER_CONFLICT ) )
2273  {
2274  if( !ercCheckMultipleDrivers( subgraph ) )
2275  error_count++;
2276  }
2277 
2278  subgraph->ResolveDrivers( false );
2279 
2280  if( settings.IsTestEnabled( ERCE_BUS_TO_NET_CONFLICT ) )
2281  {
2282  if( !ercCheckBusToNetConflicts( subgraph ) )
2283  error_count++;
2284  }
2285 
2286  if( settings.IsTestEnabled( ERCE_BUS_ENTRY_CONFLICT ) )
2287  {
2288  if( !ercCheckBusToBusEntryConflicts( subgraph ) )
2289  error_count++;
2290  }
2291 
2292  if( settings.IsTestEnabled( ERCE_BUS_TO_BUS_CONFLICT ) )
2293  {
2294  if( !ercCheckBusToBusConflicts( subgraph ) )
2295  error_count++;
2296  }
2297 
2298  if( settings.IsTestEnabled( ERCE_WIRE_DANGLING ) )
2299  {
2300  if( !ercCheckFloatingWires( subgraph ) )
2301  error_count++;
2302  }
2303 
2304  if( settings.IsTestEnabled( ERCE_NOCONNECT_CONNECTED )
2306  || settings.IsTestEnabled( ERCE_PIN_NOT_CONNECTED ) )
2307  {
2308  if( !ercCheckNoConnects( subgraph ) )
2309  error_count++;
2310  }
2311 
2312  if( settings.IsTestEnabled( ERCE_LABEL_NOT_CONNECTED )
2313  || settings.IsTestEnabled( ERCE_GLOBLABEL ) )
2314  {
2315  if( !ercCheckLabels( subgraph ) )
2316  error_count++;
2317  }
2318  }
2319 
2320  // Hierarchical sheet checking is done at the schematic level
2321  if( settings.IsTestEnabled( ERCE_HIERACHICAL_LABEL )
2322  || settings.IsTestEnabled( ERCE_PIN_NOT_CONNECTED ) )
2323  {
2324  error_count += ercCheckHierSheets();
2325  }
2326 
2327  return error_count;
2328 }
2329 
2330 
2332 {
2333  wxCHECK( aSubgraph, false );
2334  /*
2335  * This was changed late in 6.0 to fix https://gitlab.com/kicad/code/kicad/-/issues/9367
2336  * so I'm going to leave the original code in for just a little while. If anyone comes
2337  * across this in 7.0 development (or later), feel free to delete.
2338  */
2339 #if 0
2340  if( aSubgraph->m_second_driver )
2341  {
2342  SCH_ITEM* primary = aSubgraph->m_first_driver;
2343  SCH_ITEM* secondary = aSubgraph->m_second_driver;
2344 
2345  wxPoint pos = primary->Type() == SCH_PIN_T ?
2346  static_cast<SCH_PIN*>( primary )->GetTransformedPosition() :
2347  primary->GetPosition();
2348 
2349  wxString primaryName = aSubgraph->GetNameForDriver( primary );
2350  wxString secondaryName = aSubgraph->GetNameForDriver( secondary );
2351 
2352  wxString msg = wxString::Format( _( "Both %s and %s are attached to the same "
2353  "items; %s will be used in the netlist" ),
2354  primaryName, secondaryName, primaryName );
2355 
2356  std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_DRIVER_CONFLICT );
2357  ercItem->SetItems( primary, secondary );
2358  ercItem->SetErrorMessage( msg );
2359 
2360  SCH_MARKER* marker = new SCH_MARKER( ercItem, pos );
2361  aSubgraph->m_sheet.LastScreen()->Append( marker );
2362 
2363  return false;
2364  }
2365 #else
2366  if( aSubgraph->m_multiple_drivers )
2367  {
2368  for( SCH_ITEM* driver : aSubgraph->m_drivers )
2369  {
2370  if( driver == aSubgraph->m_driver )
2371  continue;
2372 
2373  if( driver->Type() == SCH_GLOBAL_LABEL_T
2374  || driver->Type() == SCH_HIER_LABEL_T
2375  || driver->Type() == SCH_LABEL_T )
2376  {
2377  wxString primaryName = aSubgraph->GetNameForDriver( aSubgraph->m_driver );
2378  wxString secondaryName = aSubgraph->GetNameForDriver( driver );
2379 
2380  if( primaryName == secondaryName )
2381  continue;
2382 
2383  wxString msg = wxString::Format( _( "Both %s and %s are attached to the same "
2384  "items; %s will be used in the netlist" ),
2385  primaryName, secondaryName, primaryName );
2386 
2387  std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_DRIVER_CONFLICT );
2388  ercItem->SetItems( aSubgraph->m_driver, driver );
2389  ercItem->SetErrorMessage( msg );
2390 
2391  SCH_MARKER* marker = new SCH_MARKER( ercItem, driver->GetPosition() );
2392  aSubgraph->m_sheet.LastScreen()->Append( marker );
2393 
2394  return false;
2395  }
2396  }
2397  }
2398 #endif
2399 
2400  return true;
2401 }
2402 
2403 
2405 {
2406  const SCH_SHEET_PATH& sheet = aSubgraph->m_sheet;
2407  SCH_SCREEN* screen = sheet.LastScreen();
2408 
2409  SCH_ITEM* net_item = nullptr;
2410  SCH_ITEM* bus_item = nullptr;
2411  SCH_CONNECTION conn( this );
2412 
2413  for( SCH_ITEM* item : aSubgraph->m_items )
2414  {
2415  switch( item->Type() )
2416  {
2417  case SCH_LINE_T:
2418  {
2419  if( item->GetLayer() == LAYER_BUS )
2420  bus_item = ( !bus_item ) ? item : bus_item;
2421  else
2422  net_item = ( !net_item ) ? item : net_item;
2423  break;
2424  }
2425 
2426  case SCH_LABEL_T:
2427  case SCH_GLOBAL_LABEL_T:
2428  case SCH_SHEET_PIN_T:
2429  case SCH_HIER_LABEL_T:
2430  {
2431  SCH_TEXT* text = static_cast<SCH_TEXT*>( item );
2432  conn.ConfigureFromLabel( EscapeString( text->GetShownText(), CTX_NETNAME ) );
2433 
2434  if( conn.IsBus() )
2435  bus_item = ( !bus_item ) ? item : bus_item;
2436  else
2437  net_item = ( !net_item ) ? item : net_item;
2438  break;
2439  }
2440 
2441  default:
2442  break;
2443  }
2444  }
2445 
2446  if( net_item && bus_item )
2447  {
2448  std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_BUS_TO_NET_CONFLICT );
2449  ercItem->SetItems( net_item, bus_item );
2450 
2451  SCH_MARKER* marker = new SCH_MARKER( ercItem, net_item->GetPosition() );
2452  screen->Append( marker );
2453 
2454  return false;
2455  }
2456 
2457  return true;
2458 }
2459 
2460 
2462 {
2463  wxString msg;
2464  const SCH_SHEET_PATH& sheet = aSubgraph->m_sheet;
2465  SCH_SCREEN* screen = sheet.LastScreen();
2466 
2467  SCH_ITEM* label = nullptr;
2468  SCH_ITEM* port = nullptr;
2469 
2470  for( auto item : aSubgraph->m_items )
2471  {
2472  switch( item->Type() )
2473  {
2474  case SCH_TEXT_T:
2475  case SCH_GLOBAL_LABEL_T:
2476  {
2477  if( !label && item->Connection( &sheet )->IsBus() )
2478  label = item;
2479  break;
2480  }
2481 
2482  case SCH_SHEET_PIN_T:
2483  case SCH_HIER_LABEL_T:
2484  {
2485  if( !port && item->Connection( &sheet )->IsBus() )
2486  port = item;
2487  break;
2488  }
2489 
2490  default:
2491  break;
2492  }
2493  }
2494 
2495  if( label && port )
2496  {
2497  bool match = false;
2498 
2499  for( const auto& member : label->Connection( &sheet )->Members() )
2500  {
2501  for( const auto& test : port->Connection( &sheet )->Members() )
2502  {
2503  if( test != member && member->Name() == test->Name() )
2504  {
2505  match = true;
2506  break;
2507  }
2508  }
2509 
2510  if( match )
2511  break;
2512  }
2513 
2514  if( !match )
2515  {
2516  std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_BUS_TO_BUS_CONFLICT );
2517  ercItem->SetItems( label, port );
2518 
2519  SCH_MARKER* marker = new SCH_MARKER( ercItem, label->GetPosition() );
2520  screen->Append( marker );
2521 
2522  return false;
2523  }
2524  }
2525 
2526  return true;
2527 }
2528 
2529 
2531 {
2532  bool conflict = false;
2533  const SCH_SHEET_PATH& sheet = aSubgraph->m_sheet;
2534  SCH_SCREEN* screen = sheet.LastScreen();
2535 
2536  SCH_BUS_WIRE_ENTRY* bus_entry = nullptr;
2537  SCH_ITEM* bus_wire = nullptr;
2538  wxString bus_name;
2539 
2540  if( !aSubgraph->m_driver_connection )
2541  {
2542  // Incomplete bus entry. Let the unconnected tests handle it.
2543  return true;
2544  }
2545 
2546  for( SCH_ITEM* item : aSubgraph->m_items )
2547  {
2548  switch( item->Type() )
2549  {
2550  case SCH_BUS_WIRE_ENTRY_T:
2551  {
2552  if( !bus_entry )
2553  bus_entry = static_cast<SCH_BUS_WIRE_ENTRY*>( item );
2554  break;
2555  }
2556 
2557  default:
2558  break;
2559  }
2560  }
2561 
2562  if( bus_entry && bus_entry->m_connected_bus_item )
2563  {
2564  bus_wire = bus_entry->m_connected_bus_item;
2565 
2566  wxASSERT( bus_wire->Type() == SCH_LINE_T );
2567 
2568  // In some cases, the connection list (SCH_CONNECTION*) can be null.
2569  // Skip null connections.
2570  if( bus_entry->Connection( &sheet )
2571  && bus_wire->Type() == SCH_LINE_T
2572  && bus_wire->Connection( &sheet ) )
2573  {
2574  conflict = true; // Assume a conflict; we'll reset if we find it's OK
2575 
2576  bus_name = bus_wire->Connection( &sheet )->Name();
2577 
2578  wxString test_name = bus_entry->Connection( &sheet )->Name();
2579 
2580  for( const auto& member : bus_wire->Connection( &sheet )->Members() )
2581  {
2582  if( member->Type() == CONNECTION_TYPE::BUS )
2583  {
2584  for( const auto& sub_member : member->Members() )
2585  {
2586  if( sub_member->Name() == test_name )
2587  conflict = false;
2588  }
2589  }
2590  else if( member->Name() == test_name )
2591  {
2592  conflict = false;
2593  }
2594  }
2595  }
2596  }
2597 
2598  // Don't report warnings if this bus member has been overridden by a higher priority power pin
2599  // or global label
2600  if( conflict && CONNECTION_SUBGRAPH::GetDriverPriority( aSubgraph->m_driver )
2602  {
2603  conflict = false;
2604  }
2605 
2606  if( conflict )
2607  {
2608  wxString netName = aSubgraph->m_driver_connection->Name();
2609  wxString msg = wxString::Format( _( "Net %s is graphically connected to bus %s but is not a"
2610  " member of that bus" ),
2611  UnescapeString( netName ),
2612  UnescapeString( bus_name ) );
2613  std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_BUS_ENTRY_CONFLICT );
2614  ercItem->SetItems( bus_entry, bus_wire );
2615  ercItem->SetErrorMessage( msg );
2616 
2617  SCH_MARKER* marker = new SCH_MARKER( ercItem, bus_entry->GetPosition() );
2618  screen->Append( marker );
2619 
2620  return false;
2621  }
2622 
2623  return true;
2624 }
2625 
2626 
2627 // TODO(JE) Check sheet pins here too?
2629 {
2630  ERC_SETTINGS& settings = m_schematic->ErcSettings();
2631  wxString msg;
2632  const SCH_SHEET_PATH& sheet = aSubgraph->m_sheet;
2633  SCH_SCREEN* screen = sheet.LastScreen();
2634  bool ok = true;
2635 
2636  if( aSubgraph->m_no_connect != nullptr )
2637  {
2638  bool has_invalid_items = false;
2639  bool has_other_items = false;
2640  SCH_PIN* pin = nullptr;
2641  std::vector<SCH_ITEM*> invalid_items;
2642  wxPoint noConnectPos = aSubgraph->m_no_connect->GetPosition();
2643  double minDist = 0;
2644 
2645  // Any subgraph that contains both a pin and a no-connect should not
2646  // contain any other driving items.
2647 
2648  for( SCH_ITEM* item : aSubgraph->m_items )
2649  {
2650  switch( item->Type() )
2651  {
2652  case SCH_PIN_T:
2653  {
2654  SCH_PIN* candidate = static_cast<SCH_PIN*>( item );
2655  double dist = VECTOR2I( candidate->GetTransformedPosition() - noConnectPos )
2656  .SquaredEuclideanNorm();
2657 
2658  if( !pin || dist < minDist )
2659  {
2660  pin = candidate;
2661  minDist = dist;
2662  }
2663 
2664  has_invalid_items |= has_other_items;
2665  has_other_items = true;
2666  break;
2667  }
2668 
2669  case SCH_LINE_T:
2670  case SCH_JUNCTION_T:
2671  case SCH_NO_CONNECT_T:
2672  break;
2673 
2674  default:
2675  has_invalid_items = true;
2676  has_other_items = true;
2677  invalid_items.push_back( item );
2678  }
2679  }
2680 
2681  if( pin && has_invalid_items && settings.IsTestEnabled( ERCE_NOCONNECT_CONNECTED ) )
2682  {
2683  std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_NOCONNECT_CONNECTED );
2684  ercItem->SetItems( pin );
2685 
2686  SCH_MARKER* marker = new SCH_MARKER( ercItem, pin->GetTransformedPosition() );
2687  screen->Append( marker );
2688 
2689  ok = false;
2690  }
2691 
2692  if( !has_other_items && settings.IsTestEnabled( ERCE_NOCONNECT_NOT_CONNECTED ) )
2693  {
2694  std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_NOCONNECT_NOT_CONNECTED );
2695  ercItem->SetItems( aSubgraph->m_no_connect );
2696 
2697  SCH_MARKER* marker = new SCH_MARKER( ercItem, aSubgraph->m_no_connect->GetPosition() );
2698  screen->Append( marker );
2699 
2700  ok = false;
2701  }
2702  }
2703  else
2704  {
2705  bool has_other_connections = false;
2706  std::vector<SCH_PIN*> pins;
2707 
2708  // Any subgraph that lacks a no-connect and contains a pin should also
2709  // contain at least one other potential driver
2710 
2711  for( SCH_ITEM* item : aSubgraph->m_items )
2712  {
2713  switch( item->Type() )
2714  {
2715  case SCH_PIN_T:
2716  {
2717  if( !pins.empty() )
2718  has_other_connections = true;
2719 
2720  pins.emplace_back( static_cast<SCH_PIN*>( item ) );
2721 
2722  break;
2723  }
2724 
2725  default:
2726  if( aSubgraph->GetDriverPriority( item ) != CONNECTION_SUBGRAPH::PRIORITY::NONE )
2727  has_other_connections = true;
2728 
2729  break;
2730  }
2731  }
2732 
2733  // For many checks, we can just use the first pin
2734  SCH_PIN* pin = pins.empty() ? nullptr : pins[0];
2735 
2736  // Check if invisible power input pins connect to anything else via net name,
2737  // but not for power symbols as the ones in the standard library all have invisible pins
2738  // and we want to throw unconnected errors for those even if they are connected to other
2739  // net items by name, because usually failing to connect them graphically is a mistake
2740  if( pin && !has_other_connections
2741  && pin->GetType() == ELECTRICAL_PINTYPE::PT_POWER_IN
2742  && !pin->IsVisible()
2743  && !pin->GetLibPin()->GetParent()->IsPower() )
2744  {
2745  wxString name = pin->Connection( &sheet )->Name();
2746  wxString local_name = pin->Connection( &sheet )->Name( true );
2747 
2748  if( m_global_label_cache.count( name )
2749  || m_local_label_cache.count( std::make_pair( sheet, local_name ) ) )
2750  {
2751  has_other_connections = true;
2752  }
2753  }
2754 
2755  // Only one pin, and it's not a no-connect pin
2756  if( pin && !has_other_connections
2757  && pin->GetType() != ELECTRICAL_PINTYPE::PT_NC
2758  && pin->GetType() != ELECTRICAL_PINTYPE::PT_NIC
2759  && settings.IsTestEnabled( ERCE_PIN_NOT_CONNECTED ) )
2760  {
2761  std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_PIN_NOT_CONNECTED );
2762  ercItem->SetItems( pin );
2763 
2764  SCH_MARKER* marker = new SCH_MARKER( ercItem, pin->GetTransformedPosition() );
2765  screen->Append( marker );
2766 
2767  ok = false;
2768  }
2769 
2770  // If there are multiple pins in this SG, they might be indirectly connected (by netname)
2771  // rather than directly connected (by wires). We want to flag dangling pins even if they
2772  // join nets with another pin, as it's often a mistake
2773  if( pins.size() > 1 )
2774  {
2775  for( SCH_PIN* testPin : pins )
2776  {
2777  // We only apply this test to power symbols, because other symbols have invisible
2778  // pins that are meant to be dangling, but the KiCad standard library power symbols
2779  // have invisible pins that are *not* meant to be dangling.
2780  if( testPin->GetLibPin()->GetParent()->IsPower()
2781  && testPin->ConnectedItems( sheet ).empty()
2782  && settings.IsTestEnabled( ERCE_PIN_NOT_CONNECTED ) )
2783  {
2784  std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_PIN_NOT_CONNECTED );
2785  ercItem->SetItems( testPin );
2786 
2787  SCH_MARKER* marker = new SCH_MARKER( ercItem,
2788  testPin->GetTransformedPosition() );
2789  screen->Append( marker );
2790 
2791  ok = false;
2792  }
2793  }
2794  }
2795  }
2796 
2797  return ok;
2798 }
2799 
2800 
2802 {
2803  if( aSubgraph->m_driver )
2804  return true;
2805 
2806  std::vector<SCH_ITEM*> wires;
2807 
2808  // We've gotten this far, so we know we have no valid driver. All we need to do is check
2809  // for a wire that we can place the error on.
2810 
2811  for( SCH_ITEM* item : aSubgraph->m_items )
2812  {
2813  if( item->Type() == SCH_LINE_T && item->GetLayer() == LAYER_WIRE )
2814  wires.emplace_back( item );
2815  else if( item->Type() == SCH_BUS_WIRE_ENTRY_T )
2816  wires.emplace_back( item );
2817  }
2818 
2819  if( !wires.empty() )
2820  {
2821  SCH_SCREEN* screen = aSubgraph->m_sheet.LastScreen();
2822 
2823  std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_WIRE_DANGLING );
2824  ercItem->SetItems( wires[0],
2825  wires.size() > 1 ? wires[1] : nullptr,
2826  wires.size() > 2 ? wires[2] : nullptr,
2827  wires.size() > 3 ? wires[3] : nullptr );
2828 
2829  SCH_MARKER* marker = new SCH_MARKER( ercItem, wires[0]->GetPosition() );
2830  screen->Append( marker );
2831 
2832  return false;
2833  }
2834 
2835  return true;
2836 }
2837 
2838 
2840 {
2841  // Label connection rules:
2842  // Local labels are flagged if they don't connect to any pins and don't have a no-connect
2843  // Global labels are flagged if they appear only once, don't connect to any local labels,
2844  // and don't have a no-connect marker
2845 
2846  // So, if there is a no-connect, we will never generate a warning here
2847  if( aSubgraph->m_no_connect )
2848  return true;
2849 
2850  if( !aSubgraph->m_driver_connection )
2851  return true;
2852 
2853  // Buses are excluded from this test: many users create buses with only a single instance
2854  // and it's not really a problem as long as the nets in the bus pass ERC
2855  if( aSubgraph->m_driver_connection->IsBus() )
2856  return true;
2857 
2858  ERC_SETTINGS& settings = m_schematic->ErcSettings();
2859  bool ok = true;
2860  SCH_TEXT* text = nullptr;
2861  bool hasOtherConnections = false;
2862  int pinCount = 0;
2863 
2864  auto hasPins =
2865  []( const CONNECTION_SUBGRAPH* aLocSubgraph )
2866  {
2867  for( const SCH_ITEM* item : aLocSubgraph->m_items )
2868  {
2869  switch( item->Type() )
2870  {
2871  case SCH_PIN_T:
2872  case SCH_SHEET_PIN_T:
2873  return true;
2874 
2875  default: break;
2876  }
2877  }
2878 
2879  return false;
2880  };
2881 
2882  for( SCH_ITEM* item : aSubgraph->m_items )
2883  {
2884  switch( item->Type() )
2885  {
2886  case SCH_LABEL_T:
2887  case SCH_GLOBAL_LABEL_T:
2888  case SCH_HIER_LABEL_T:
2889  {
2890  text = static_cast<SCH_TEXT*>( item );
2891 
2892  // Below, we'll create an ERC if the whole subgraph is unconnected. But, additionally,
2893  // we want to error if an individual label in the subgraph is floating, even if it's
2894  // connected to other valid things by way of another label on the same sheet.
2895 
2896  if( text->IsDangling() && settings.IsTestEnabled( ERCE_LABEL_NOT_CONNECTED ) )
2897  {
2898  std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_LABEL_NOT_CONNECTED );
2899  ercItem->SetItems( text );
2900 
2901  SCH_MARKER* marker = new SCH_MARKER( ercItem, text->GetPosition() );
2902  aSubgraph->m_sheet.LastScreen()->Append( marker );
2903  ok = false;
2904  }
2905 
2906  break;
2907  }
2908 
2909  case SCH_PIN_T:
2910  case SCH_SHEET_PIN_T:
2911  hasOtherConnections = true;
2912  pinCount++;
2913  break;
2914 
2915  default:
2916  break;
2917  }
2918  }
2919 
2920  if( !text )
2921  return true;
2922 
2923  bool isGlobal = text->Type() == SCH_GLOBAL_LABEL_T;
2924  int errCode = isGlobal ? ERCE_GLOBLABEL : ERCE_LABEL_NOT_CONNECTED;
2925 
2926  wxCHECK_MSG( m_schematic, true, wxT( "Null m_schematic in CONNECTION_GRAPH::ercCheckLabels" ) );
2927 
2928  wxString name = EscapeString( text->GetShownText(), CTX_NETNAME );
2929 
2930  if( isGlobal )
2931  {
2932  // This will be set to true if the global is connected to a pin above, but we want to
2933  // reset this to false so that globals get flagged if they only have a single instance
2934  // connected to a single pin
2935  hasOtherConnections = ( pinCount > 1 );
2936 
2937  auto it = m_net_name_to_subgraphs_map.find( name );
2938 
2939  if( it != m_net_name_to_subgraphs_map.end() )
2940  {
2941  if( it->second.size() > 1 || aSubgraph->m_multiple_drivers )
2942  hasOtherConnections = true;
2943  }
2944  }
2945  else if( text->Type() == SCH_HIER_LABEL_T )
2946  {
2947  // For a hier label, check if the parent pin is connected
2948  if( aSubgraph->m_hier_parent
2949  && ( aSubgraph->m_hier_parent->m_strong_driver
2950  || aSubgraph->m_hier_parent->m_drivers.size() > 1) )
2951  {
2952  // For now, a simple check: if there is more than one driver, the parent is probably
2953  // connected elsewhere (because at least one driver will be the hier pin itself)
2954  hasOtherConnections = true;
2955  }
2956  }
2957  else
2958  {
2959  auto pair = std::make_pair( aSubgraph->m_sheet, name );
2960  auto it = m_local_label_cache.find( pair );
2961 
2962  if( it != m_local_label_cache.end() && it->second.size() > 1 )
2963  {
2964  for( const CONNECTION_SUBGRAPH* neighbor : it->second )
2965  {
2966  if( neighbor == aSubgraph )
2967  continue;
2968 
2969  if( hasPins( neighbor ) )
2970  {
2971  hasOtherConnections = true;
2972  break;
2973  }
2974  }
2975  }
2976  }
2977 
2978  if( !hasOtherConnections && settings.IsTestEnabled( errCode ) )
2979  {
2980  std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( errCode );
2981  ercItem->SetItems( text );
2982 
2983  SCH_MARKER* marker = new SCH_MARKER( ercItem, text->GetPosition() );
2984  aSubgraph->m_sheet.LastScreen()->Append( marker );
2985 
2986  return false;
2987  }
2988 
2989  return ok;
2990 }
2991 
2992 
2994 {
2995  int errors = 0;
2996 
2997  ERC_SETTINGS& settings = m_schematic->ErcSettings();
2998 
2999  for( const SCH_SHEET_PATH& sheet : m_sheetList )
3000  {
3001  for( SCH_ITEM* item : sheet.LastScreen()->Items() )
3002  {
3003  if( item->Type() != SCH_SHEET_T )
3004  continue;
3005 
3006  SCH_SHEET* parentSheet = static_cast<SCH_SHEET*>( item );
3007 
3008  std::map<wxString, SCH_SHEET_PIN*> pins;
3009  std::map<wxString, SCH_HIERLABEL*> labels;
3010 
3011  for( SCH_SHEET_PIN* pin : parentSheet->GetPins() )
3012  {
3013  if( settings.IsTestEnabled( ERCE_HIERACHICAL_LABEL ) )
3014  pins[pin->GetText()] = pin;
3015 
3016  if( pin->IsDangling() && settings.IsTestEnabled( ERCE_PIN_NOT_CONNECTED ) )
3017  {
3018  std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_PIN_NOT_CONNECTED );
3019  ercItem->SetItems( pin );
3020 
3021  SCH_MARKER* marker = new SCH_MARKER( ercItem, pin->GetPosition() );
3022  sheet.LastScreen()->Append( marker );
3023 
3024  errors++;
3025  }
3026  }
3027 
3028  if( settings.IsTestEnabled( ERCE_HIERACHICAL_LABEL ) )
3029  {
3030  std::set<wxString> matchedPins;
3031 
3032  for( SCH_ITEM* subItem : parentSheet->GetScreen()->Items() )
3033  {
3034  if( subItem->Type() == SCH_HIER_LABEL_T )
3035  {
3036  SCH_HIERLABEL* label = static_cast<SCH_HIERLABEL*>( subItem );
3037 
3038  if( !pins.count( label->GetText() ) )
3039  labels[label->GetText()] = label;
3040  else
3041  matchedPins.insert( label->GetText() );
3042  }
3043  }
3044 
3045  for( const wxString& matched : matchedPins )
3046  pins.erase( matched );
3047 
3048  for( const std::pair<const wxString, SCH_SHEET_PIN*>& unmatched : pins )
3049  {
3050  wxString msg = wxString::Format( _( "Sheet pin %s has no matching hierarchical "
3051  "label inside the sheet" ),
3052  UnescapeString( unmatched.first ) );
3053 
3054  std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_HIERACHICAL_LABEL );
3055  ercItem->SetItems( unmatched.second );
3056  ercItem->SetErrorMessage( msg );
3057  ercItem->SetIsSheetSpecific();
3058 
3059  SCH_MARKER* marker = new SCH_MARKER( ercItem, unmatched.second->GetPosition() );
3060  sheet.LastScreen()->Append( marker );
3061 
3062  errors++;
3063  }
3064 
3065  for( const std::pair<const wxString, SCH_HIERLABEL*>& unmatched : labels )
3066  {
3067  wxString msg = wxString::Format( _( "Hierarchical label %s has no matching "
3068  "sheet pin in the parent sheet" ),
3069  UnescapeString( unmatched.first ) );
3070 
3071  std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_HIERACHICAL_LABEL );
3072  ercItem->SetItems( unmatched.second );
3073  ercItem->SetErrorMessage( msg );
3074  ercItem->SetIsSheetSpecific();
3075 
3076  SCH_MARKER* marker = new SCH_MARKER( ercItem, unmatched.second->GetPosition() );
3077  parentSheet->GetScreen()->Append( marker );
3078 
3079  errors++;
3080  }
3081  }
3082  }
3083  }
3084 
3085  return errors;
3086 }
CONNECTION_SUBGRAPH * FindSubgraphByName(const wxString &aNetName, const SCH_SHEET_PATH &aPath)
Returns the subgraph for a given net name on a given sheet.
power input (GND, VCC for ICs). Must be connected to a power output.
bool ercCheckMultipleDrivers(const CONNECTION_SUBGRAPH *aSubgraph)
If the subgraph has multiple drivers of equal priority that are graphically connected,...
SCH_SHEET_PATH m_sheet
A container for handling SCH_SHEET_PATH objects in a flattened hierarchy.
EE_TYPE OfType(KICAD_T aType) const
Definition: sch_rtree.h:230
void SetBusCode(int aCode)
SCH_CONNECTION * m_driver_connection
Cache for driver connection.
Pin not connected and not no connect symbol.
Definition: erc_settings.h:41
void propagateToNeighbors(CONNECTION_SUBGRAPH *aSubgraph)
Updates all neighbors of a subgraph with this one's connectivity info.
bool ercCheckBusToBusEntryConflicts(const CONNECTION_SUBGRAPH *aSubgraph)
Checks one subgraph for conflicting bus entry to bus connections.
void Clone(const SCH_CONNECTION &aOther)
Copies connectivity information (but not parent) from another connection.
A connection between bus objects doesn't share at least one net.
Definition: erc_settings.h:59
void resolveAllDrivers()
Finds all subgraphs in the connection graph and calls ResolveDrivers() in parallel.
void buildConnectionGraph()
Generates the connection graph (after all item connectivity has been updated)
std::unordered_map< wxString, std::shared_ptr< BUS_ALIAS > > m_bus_alias_cache
#define kv
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 ...
std::shared_ptr< BUS_ALIAS > GetBusAlias(const wxString &aName)
Returns a bus alias pointer for the given name if it exists (from cache)
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...
void AddConnectionTo(const SCH_SHEET_PATH &aPath, SCH_ITEM *aItem)
Add a connection link between this item and another.
Definition: sch_item.cpp:183
void Absorb(CONNECTION_SUBGRAPH *aOther)
Combines another subgraph on the same sheet into this one.
SCH_ITEM * m_driver
The SCH_ITEM that drives this connection's net.
static std::shared_ptr< ERC_ITEM > Create(int aErrorCode)
Constructs an ERC_ITEM for the given error code.
Definition: erc_item.cpp:194
This item represents a bus group.
void push_back(SCH_SHEET *aSheet)
Forwarded method from std::vector.
SCH_ITEM * m_second_driver
Used for multiple drivers ERC message; stores the second possible driver (or nullptr)
bool m_absorbed
True if this subgraph has been absorbed into another. No pointers here are safe if so!
virtual bool ConnectionPropagatesTo(const EDA_ITEM *aItem) const
Return true if this item should propagate connection info to aItem.
Definition: sch_item.h:411
bool IsTestEnabled(int aErrorCode) const
Definition: erc_settings.h:123
std::unordered_map< SCH_SHEET_PATH, std::vector< CONNECTION_SUBGRAPH * > > m_sheet_to_subgraphs_map
A bus wire is graphically connected to a net port/pin (or vice versa).
Definition: erc_settings.h:61
static PRIORITY GetDriverPriority(SCH_ITEM *aDriver)
Return the priority (higher is more important) of a candidate driver.
bool ercCheckFloatingWires(const CONNECTION_SUBGRAPH *aSubgraph)
Checks one subgraph for floating wires.
std::vector< SCH_SHEET_PIN * > m_hier_pins
bool m_local_driver
True if the driver is a local (i.e. non-global) type.
std::vector< CONNECTION_SUBGRAPH * > m_subgraphs
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.
void ConfigureFromLabel(const wxString &aLabel)
Configures the connection given a label.
void SetDriver(SCH_ITEM *aItem)
Some wires are not connected to anything else.
Definition: erc_settings.h:65
void SetName(const wxString &aName)
NET_MAP m_net_code_to_subgraphs_map
void Recalculate(const SCH_SHEET_LIST &aSheetList, bool aUnconditional=false, std::function< void(SCH_ITEM *)> *aChangedItemHandler=nullptr)
Updates the connection graph for the given list of sheets.
std::vector< SCH_ITEM * > m_items
Label not connected to anything.
Definition: erc_settings.h:49
SCHEMATIC * m_schematic
The schematic this graph represents.
virtual wxPoint GetPosition() const
Definition: eda_item.h:251
wxString LocalName() const
void buildItemSubGraphs()
Generates individual item subgraphs on a per-sheet basis.
VECTOR2< int > VECTOR2I
Definition: vector2d.h:622
SCH_SCREEN * GetScreen() const
Definition: sch_sheet.h:102
std::unordered_map< wxString, std::vector< CONNECTION_SUBGRAPH * > > m_net_name_to_subgraphs_map
A subgraph is a set of items that are electrically connected on a single sheet.
void updateItemConnectivity(const SCH_SHEET_PATH &aSheet, const std::vector< SCH_ITEM * > &aItemList)
Updates the graphical connectivity between items (i.e.
int ercCheckHierSheets()
Checks that a hierarchical sheet has at least one matching label inside the sheet for each port on th...
static const wxChar ConnTrace[]
bool m_multiple_drivers
True if this subgraph contains more than one driver that should be shorted together in the netlist.
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:138
#define CANDIDATE
flag indicating that the structure is connected
std::vector< SCH_ITEM * > SCH_ITEM_SET
Definition: sch_item.h:134
not internally connected (may be connected to anything)
void recacheSubgraphName(CONNECTION_SUBGRAPH *aSubgraph, const wxString &aOldName)
bool ercCheckBusToNetConflicts(const CONNECTION_SUBGRAPH *aSubgraph)
Checks one subgraph for conflicting connections between net and bus labels.
CONNECTION_GRAPH * m_graph
wxString Name(bool aIgnoreSheet=false) const
const wxString & GetNameForDriver(SCH_ITEM *aItem)
Returns the candidate net name for a driver.
int assignNewNetCode(SCH_CONNECTION &aConnection)
Helper to assign a new net code to a connection.
size_t size() const
Forwarded method from std::vector.
void SetSubgraphCode(int aCode)
SCH_LINE * GetBus(const wxPoint &aPosition, int aAccuracy=0, SCH_LINE_TEST_T aSearchType=ENTIRE_LENGTH_T) const
Definition: sch_screen.h:429
bool ercCheckNoConnects(const CONNECTION_SUBGRAPH *aSubgraph)
Checks one subgraph for proper presence or absence of no-connect symbols.
std::vector< SCH_ITEM * > m_items
double msecs(bool aSinceLast=false)
Definition: profile.h:147
void SetLayer(SCH_LAYER_ID aLayer)
Set the layer this item is on.
Definition: sch_item.h:266
bool ResolveDrivers(bool aCheckMultipleDrivers=false)
Determines which potential driver should drive the subgraph.
std::vector< CONNECTION_SUBGRAPH * > m_driver_subgraphs
void AddItem(SCH_ITEM *aItem)
Adds a new item to the subgraph.
int SubgraphCode() const
Functions to provide common constants and other functions to assist in making a consistent UI.
#define _(s)
bool m_strong_driver
True if the driver is "strong": a label or power object.
std::vector< SCH_ITEM * > m_drivers
Define a sheet pin (label) used in sheets to create hierarchical schematics.
Definition: sch_sheet_pin.h:65
Container for ERC settings.
Definition: erc_settings.h:106
Handle access to a stack of flattened SCH_SHEET objects by way of a path for creating a flattened sch...
long VectorIndex() const
void generateInvisiblePinSubGraphs()
Iterate through the invisible power pins to collect the global labels as drivers.
A wire connected to a bus doesn't match the bus.
Definition: erc_settings.h:57
CONNECTION_SUBGRAPH * FindFirstSubgraphByName(const wxString &aNetName)
Retrieves a subgraph for the given net name, if one exists.
bool IsDriver() const
Checks if the SCH_ITEM this connection is attached to can drive connections Drivers can be labels,...
std::vector< SCH_HIERLABEL * > m_hier_ports
A small class to help profiling.
Definition: profile.h:46
A no connect symbol is not connected to anything.
Definition: erc_settings.h:48
std::map< wxString, std::vector< const CONNECTION_SUBGRAPH * > > m_global_label_cache
void assignNetCodesToBus(SCH_CONNECTION *aConnection)
Ensures all members of the bus connection have a valid net code assigned.
wxString UnescapeString(const wxString &aSource)
void Stop()
Save the time when this function was called, and set the counter stane to stop.
Definition: profile.h:86
This item represents a net.
SCH_ITEM * m_first_driver
Stores the primary driver for the multiple drivers ERC check.
Sheet symbol placed in a schematic, and is the entry point for a sub schematic.
Definition: sch_sheet.h:54
static const wxChar ConnProfileMask[]
std::vector< std::pair< SCH_SHEET_PATH, SCH_PIN * > > m_invisible_power_pins
SCH_LAYER_ID GetLayer() const
Return the layer this item is on.
Definition: sch_item.h:259
std::vector< SCH_SHEET_PIN * > & GetPins()
Definition: sch_sheet.h:183
void Format(OUTPUTFORMATTER *out, int aNestLevel, int aCtl, const CPTREE &aTree)
Output a PTREE into s-expression format via an OUTPUTFORMATTER derivative.
Definition: ptree.cpp:200
static bool m_allowRealTime
A global label is unique.
Definition: erc_settings.h:63
A no connect symbol is connected to more than 1 pin.
Definition: erc_settings.h:47
SCH_SHEET_LIST GetSheets() const override
Builds and returns an updated schematic hierarchy TODO: can this be cached?
Definition: schematic.h:87
bool IsNet() const
void processSubGraphs()
Process all subgraphs to assign netcodes and merge subgraphs based on labels.
void Show(std::ostream &aStream=std::cerr)
Print the elapsed time (in a suitable unit) to a stream.
Definition: profile.h:103
CONNECTION_SUBGRAPH * m_absorbed_by
If this subgraph is absorbed, points to the absorbing (and valid) subgraph.
Schematic symbol object.
Definition: sch_symbol.h:78
SCH_SCREEN * LastScreen()
const char * name
Definition: DXF_plotter.cpp:56
Segment description base class to describe items which have 2 end points (track, wire,...
Definition: sch_line.h:37
Mismatch between hierarchical labels and pins sheets.
Definition: erc_settings.h:46
SCH_ITEM_SET & ConnectedItems(const SCH_SHEET_PATH &aPath)
Retrieve the set of items connected to this item on the given sheet.
Definition: sch_item.cpp:177
void Append(SCH_ITEM *aItem)
Definition: sch_screen.cpp:146
Each graphical item can have a SCH_CONNECTION describing its logical connection (to a bus or net).
SCH_SHEET_LIST m_sheetList
std::shared_ptr< SCH_CONNECTION > getDefaultConnection(SCH_ITEM *aItem, CONNECTION_SUBGRAPH *aSubgraph)
Builds a new default connection for the given item based on its properties.
std::vector< SCH_ITEM * > GetBusLabels() const
Returns all the bus labels attached to this subgraph (if any)
virtual wxString GetSelectMenuText(EDA_UNITS aUnits) const
Return the text to display to be used in the selection clarification context menu when multiple items...
Definition: eda_item.cpp:109
EE_RTREE & Items()
Gets the full RTree, usually for iterating.
Definition: sch_screen.h:110
std::map< wxString, int > m_bus_name_to_code_map
The common library.
wxString driverName(SCH_ITEM *aItem) const
std::map< wxString, int > m_net_name_to_code_map
CONNECTION_SUBGRAPH * m_hier_parent
std::vector< std::shared_ptr< SCH_CONNECTION > > & Members()
Class for a wire to bus entry.
static const ADVANCED_CFG & GetCfg()
Get the singleton instance's config, which is shared by all consumers.
void collectAllDriverValues()
Maps the driver values for each subgraph.
CONNECTION_SUBGRAPH * GetSubgraphForItem(SCH_ITEM *aItem)
bool IsPowerConnection() const
Definition: sch_pin.h:140
ERC_SETTINGS & ErcSettings() const
Definition: schematic.cpp:174
wxString EscapeString(const wxString &aSource, ESCAPE_CONTEXT aContext)
The Escape/Unescape routines use HTML-entity-reference-style encoding to handle characters which are:...
void SetType(CONNECTION_TYPE aType)
SCH_ITEM * m_no_connect
No-connect item in graph, if any.
void delete_matching(_Container &__c, _Value __value)
Covers for the horrifically named std::remove and std::remove_if (neither of which remove anything).
Definition: kicad_algo.h:164
std::map< std::pair< SCH_SHEET_PATH, wxString >, std::vector< const CONNECTION_SUBGRAPH * > > m_local_label_cache
Definition of the SCH_SHEET_PATH and SCH_SHEET_LIST classes for Eeschema.
not connected (must be left open)
bool IsBus() const
bool IsSubsetOf(SCH_CONNECTION *aOther) const
Returns true if this connection is contained within aOther (but not the same as aOther)
Conflicting drivers (labels, etc) on a subgraph.
Definition: erc_settings.h:56
std::vector< const CONNECTION_SUBGRAPH * > GetBusesNeedingMigration()
Determines which subgraphs have more than one conflicting bus label.
CONNECTION_TYPE Type() const
bool ercCheckLabels(const CONNECTION_SUBGRAPH *aSubgraph)
Checks one subgraph for proper connection of labels.
Base class for any item which can be embedded within the SCHEMATIC container class,...
Definition: sch_item.h:182
std::map< SCH_ITEM *, CONNECTION_SUBGRAPH * > m_item_to_subgraph_map
void delete_if(_Container &__c, _Function &&__f)
Deletes all values from __c for which __f returns true.
Definition: kicad_algo.h:173
virtual const wxString & GetText() const
Return the string associated with the text object.
Definition: eda_text.h:154
int RunERC()
Runs electrical rule checks on the connectivity graph.
void SetNetCode(int aCode)
wxString GetNetName() const
Returns the fully-qualified net name for this subgraph (if one exists)
PRIORITY GetDriverPriority()
std::vector< SCH_PIN * > GetPins(const SCH_SHEET_PATH *aSheet=nullptr) const
Retrieve a list of the SCH_PINs for the given sheet path.
Definition: sch_symbol.cpp:896
This item represents a bus vector.
static SCH_CONNECTION * matchBusMember(SCH_CONNECTION *aBusConnection, SCH_CONNECTION *aSearch)
Search for a matching bus member inside a bus connection.
PINSHEETLABEL_SHAPE GetShape() const
Definition: sch_text.h:161
bool ercCheckBusToBusConflicts(const CONNECTION_SUBGRAPH *aSubgraph)
Checks one subgraph for conflicting connections between two bus items.
KICAD_T Type() const
Returns the type of object.
Definition: eda_item.h:112
std::unordered_map< SCH_ITEM *, wxString > m_driver_name_cache
A cache of escaped netnames from schematic items.
wxPoint GetPosition() const override
wxPoint GetTransformedPosition() const
Definition: sch_pin.cpp:294
void UpdateItemConnections()
Updates all items to match the driver connection.