KiCad PCB EDA Suite
Loading...
Searching...
No Matches
conn_engine_erc.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 The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20#include "conn_engine.h"
21#include <wx/thread.h>
22#include <algorithm>
23#include <iterator>
24#include <ranges>
25#include <stdexcept>
26#include <string_utils.h>
27
28namespace SCH_CONNECTIVITY
29{
30namespace
31{
32struct NC_SOURCE
33{
35 VECTOR2I position;
36 bool pin = false;
37 bool nc = false;
38 bool powerFlag = false;
39};
40
41bool isLabel( KICAD_T aType )
42{
43 return aType == SCH_LABEL_T || aType == SCH_GLOBAL_LABEL_T || aType == SCH_HIER_LABEL_T;
44}
45
46bool isLabelOrSheetPin( KICAD_T aType )
47{
48 return isLabel( aType ) || aType == SCH_SHEET_PIN_T;
49}
50
51// Screen item facts are sorted by id
52const ITEM_FACT* findFact( const std::vector<ITEM_FACT>& aFacts, const KIID& aId )
53{
54 const auto it = std::ranges::lower_bound( aFacts, aId, []( const KIID& a, const KIID& b ) { return a < b; },
56 return it != aFacts.end() && it->id == aId ? &*it : nullptr;
57}
58
59template <typename MAP, typename BUILD>
60const typename MAP::mapped_type& cached( MAP& aCache, const typename MAP::key_type& aKey, BUILD&& aBuild )
61{
62 if( const auto it = aCache.find( aKey ); it != aCache.end() )
63 return it->second;
64
65 return aCache.emplace( aKey, aBuild() ).first->second;
66}
67
68template <typename MAP>
69std::vector<typename MAP::mapped_type> moveValues( MAP& aMap )
70{
71 std::vector<typename MAP::mapped_type> result;
72 result.reserve( aMap.size() );
73
74 for( auto& [key, value] : aMap )
75 result.push_back( std::move( value ) );
76
77 return result;
78}
79
80std::vector<INST_ID> islandInstances( const AUXILIARY::ISLANDS& aIslands )
81{
82 std::set<INST_ID> seen;
83 std::vector<INST_ID> result;
84
85 for( const auto& [key, island] : aIslands )
86 {
87 if( seen.insert( key.inst ).second )
88 result.push_back( key.inst );
89 }
90
91 return result;
92}
93
94std::map<KIID, VECTOR2I> itemPositions( const SCREEN_FACTS& aFacts )
95{
96 std::map<KIID, VECTOR2I> result;
97
98 for( const ITEM_FACT& fact : aFacts.items )
99 {
100 if( !fact.ports.empty() )
101 result.emplace( fact.id, fact.ports.front().position );
102
103 for( const PIN_FACT& pin : fact.pins )
104 result.emplace( pin.id, pin.position );
105 }
106
107 return result;
108}
109
110// Pins are inserted before their group fact, which shares the smallest pin id
111std::map<KIID, NC_SOURCE> noConnectSources( const SCREEN_FACTS& aFacts )
112{
113 std::map<KIID, NC_SOURCE> result;
114
115 for( const ITEM_FACT& fact : aFacts.items )
116 {
117 for( const PIN_FACT& pin : fact.pins )
118 {
119 result.emplace( pin.id, NC_SOURCE{ SCH_PIN_T, pin.position, true, pin.type == ELECTRICAL_PINTYPE::PT_NC,
120 ( pin.globalPowerParent || pin.localPowerParent )
121 && pin.type == ELECTRICAL_PINTYPE::PT_POWER_OUT } );
122 }
123
124 if( !fact.ports.empty() )
125 result.emplace( fact.id, NC_SOURCE{ fact.type, fact.ports.front().position } );
126 }
127
128 return result;
129}
130
131std::set<std::pair<NODE_ID, INST_ID>> busNoConnectMembers( const PUBLICATION& aPublished )
132{
133 std::set<std::pair<NODE_ID, INST_ID>> result;
134
135 for( const auto& [key, island] : aPublished.Auxiliary().Islands() )
136 {
137 if( !island.value.atoms.busNoConnect )
138 continue;
139
140 const auto& row = aPublished.Rows().at( { *island.value.atoms.noConnect, key.inst } );
141 const auto& bundle = *aPublished.Components().at( row.component ).content;
142
143 for( const SLOT_KEY& slot : bundle.slots )
144 result.emplace( aPublished.SlotComponents().at( slot ), key.inst );
145 }
146
147 return result;
148}
149} // namespace
150
151std::vector<INST_ID> ENGINE::instancesInPageOrder() const
152{
153 std::vector<INST_ID> result;
154 std::ranges::copy( std::views::keys( m_inputs.Instances().Entries() ), std::back_inserter( result ) );
155 std::ranges::sort( result, {}, [&]( INST_ID id )
156 {
157 return std::tie( m_inputs.FindInstance( id )->value.pageOrder, m_keys.Instance( id ) );
158 } );
159 return result;
160}
161
162std::set<RECORD_KEY, KEY_LESS> ENGINE::ercIslands() const
163{
164 std::map<INST_ID, size_t> rank;
165
166 for( INST_ID instance : instancesInPageOrder() )
167 rank.emplace( instance, rank.size() );
168
169 std::map<std::pair<SCREEN_ID, KIID>, RECORD_KEY> driven;
170 std::set<RECORD_KEY, KEY_LESS> result( KEY_LESS{ m_keys } );
171
172 for( const auto& [key, island] : m_published.Auxiliary().Islands() )
173 {
174 const auto& claims = m_records.Records().Entries().at( key )->value.claims;
175
176 if( claims.empty() )
177 {
178 result.insert( key );
179 continue;
180 }
181
182 const std::pair<SCREEN_ID, KIID> driver{ m_inputs.InstanceScreen( key.inst ), claims.front().source.item };
183 const auto [first, inserted] = driven.try_emplace( driver, key );
184
185 if( !inserted && rank.at( key.inst ) < rank.at( first->second.inst ) )
186 first->second = key;
187 }
188
189 for( const auto& [driver, key] : driven )
190 result.insert( key );
191
192 return result;
193}
194
195std::vector<DRIVER_CONFLICT> ENGINE::DriverConflicts() const
196{
197 wxASSERT( wxThread::IsMain() );
198 std::vector<DRIVER_CONFLICT> result;
199 std::map<INST_ID, std::map<KIID, VECTOR2I>> positions;
200 const auto reported = ercIslands();
201
202 for( const auto& [key, island] : m_published.Auxiliary().Islands() )
203 {
204 if( island.value.atoms.strongNames.size() < 2 || !reported.contains( key ) )
205 continue;
206
207 const auto& claims = m_records.Records().Entries().at( key )->value.claims;
208 const CLAIM& first = claims.front();
209 const auto second = std::ranges::find_if( claims,
210 [&]( const CLAIM& claim ) { return claim.Strong() && claim.name != first.name; } );
211
212 if( second == claims.end() )
213 continue;
214
215 const auto& points = cached( positions, key.inst,
216 [&] { return itemPositions( m_inputs.InstanceScreenFacts( key.inst ) ); } );
217
218 result.push_back( { m_keys.Instance( key.inst ), first.source.item, second->source.item,
219 m_keys.Name( first.name ), m_keys.Name( second->name ),
220 points.at( second->source.item ) } );
221 }
222
223 return result;
224}
225
226std::vector<WIRE_ENDPOINT> ENGINE::DanglingWireEndpoints() const
227{
228 wxASSERT( wxThread::IsMain() );
229 std::vector<WIRE_ENDPOINT> result;
230 const auto reported = ercIslands();
231
232 for( const auto& [key, island] : m_published.Auxiliary().Islands() )
233 {
234 if( !reported.contains( key ) )
235 continue;
236
237 const auto& facts = m_inputs.InstanceScreenFacts( key.inst ).items;
238
239 for( const auto& [item, dangling] : island.value.dangling )
240 {
241 const ITEM_FACT* fact = dangling ? findFact( facts, item ) : nullptr;
242
243 if( !fact )
244 continue;
245
246 const bool entry = fact->type == SCH_BUS_WIRE_ENTRY_T;
247 const bool wire = fact->type == SCH_LINE_T && !fact->ports.empty()
248 && fact->ports.front().kind == PORT_KIND::WIRE;
249
250 if( !entry && !wire )
251 continue;
252
253 for( size_t endpoint = 0; endpoint < fact->ports.size(); ++endpoint )
254 {
255 if( !( dangling & ( 1U << endpoint ) ) )
256 continue;
257
258 // Coincident ends have the same marker identity
259 if( endpoint != 0 && ( dangling & 1 )
260 && fact->ports[endpoint].position == fact->ports.front().position )
261 {
262 continue;
263 }
264
265 result.push_back( { m_keys.Instance( key.inst ), item, fact->ports[endpoint].position, entry } );
266 }
267 }
268 }
269
270 return result;
271}
272
273std::vector<FLOATING_WIRE> ENGINE::FloatingWires() const
274{
275 wxASSERT( wxThread::IsMain() );
276 std::vector<FLOATING_WIRE> result;
277
278 for( const auto& [key, island] : m_published.Auxiliary().Islands() )
279 {
280 const auto& row = m_published.Rows().at( { island.value.items.front(), key.inst } );
281
282 if( m_published.Components().at( row.component ).content->best )
283 continue;
284
285 const auto& facts = m_inputs.InstanceScreenFacts( key.inst ).items;
286 FLOATING_WIRE group{ m_keys.Instance( key.inst ), {}, {} };
287
288 for( const KIID& item : island.value.items )
289 {
290 const ITEM_FACT* fact = findFact( facts, item );
291
292 if( !fact || fact->ports.empty() )
293 continue;
294
295 const bool wire = fact->type == SCH_LINE_T && fact->ports.front().kind == PORT_KIND::WIRE;
296
297 if( !wire && fact->type != SCH_BUS_WIRE_ENTRY_T )
298 continue;
299
300 if( group.items.empty() )
301 group.position = fact->ports.front().position;
302
303 group.items.push_back( item );
304
305 if( group.items.size() == 4 )
306 break;
307 }
308
309 if( !group.items.empty() )
310 result.push_back( std::move( group ) );
311 }
312
313 return result;
314}
315
316std::vector<BUS_NET_CONFLICT> ENGINE::BusNetConflicts() const
317{
318 wxASSERT( wxThread::IsMain() );
319 std::vector<BUS_NET_CONFLICT> result;
320 const auto reported = ercIslands();
321
322 for( const auto& [key, island] : m_published.Auxiliary().Islands() )
323 {
324 if( !island.value.atoms.kindConflict || !reported.contains( key ) )
325 continue;
326
327 const auto& facts = m_inputs.InstanceScreenFacts( key.inst ).items;
328 const auto& claims = m_records.Records().Entries().at( key )->value.claims;
329 const ITEM_FACT* net = nullptr;
330 const ITEM_FACT* bus = nullptr;
331
332 for( const KIID& id : island.value.items )
333 {
334 const ITEM_FACT* fact = findFact( facts, id );
335
336 if( !fact || fact->ports.empty() )
337 continue;
338
339 bool isBus = false;
340
341 if( fact->type == SCH_LINE_T )
342 {
343 isBus = fact->ports.front().kind != PORT_KIND::WIRE;
344 }
345 else if( isLabelOrSheetPin( fact->type ) )
346 {
347 const auto claim = std::ranges::find_if( claims,
348 [&]( const CLAIM& candidate ) { return candidate.source.item == id; } );
349 isBus = claim != claims.end() && bool( claim->schema );
350 }
351 else
352 {
353 continue;
354 }
355
356 if( isBus && !bus )
357 bus = fact;
358 else if( !isBus && !net )
359 net = fact;
360
361 if( net && bus )
362 break;
363 }
364
365 if( net && bus )
366 result.push_back( { m_keys.Instance( key.inst ), net->id, bus->id, net->ports.front().position } );
367 }
368
369 return result;
370}
371
372std::vector<BUS_BUS_CONFLICT> ENGINE::BusBusConflicts() const
373{
374 wxASSERT( wxThread::IsMain() );
375 std::vector<BUS_BUS_CONFLICT> result;
376 std::map<INST_ID, std::map<KIID, VECTOR2I>> positions;
377 const auto reported = ercIslands();
378
379 for( const auto& [key, island] : m_published.Auxiliary().Islands() )
380 {
381 if( !reported.contains( key ) )
382 continue;
383
384 const auto& record = m_records.Records().Entries().at( key )->value;
385
386 if( record.atoms.mixedBusShapes )
387 {
388 const auto first = std::ranges::find_if( record.claims,
389 []( const CLAIM& claim ) { return bool( claim.schema ); } );
390 const auto second = std::find_if( first, record.claims.end(),
391 [&]( const CLAIM& claim )
392 {
393 return claim.schema && claim.schema->shape != first->schema->shape;
394 } );
395 wxCHECK2( first != record.claims.end() && second != record.claims.end(), continue );
396 const auto& points = cached( positions, key.inst,
397 [&] { return itemPositions( m_inputs.InstanceScreenFacts( key.inst ) ); } );
398
399 result.push_back( { m_keys.Instance( key.inst ), first->source.item, second->source.item,
400 points.at( first->source.item ), true } );
401 continue;
402 }
403
404 if( record.kind != KIND::BUNDLE || record.claims.size() < 2 )
405 continue;
406
407 const auto& facts = m_inputs.InstanceScreenFacts( key.inst ).items;
408 const CLAIM* canonical = nullptr;
409 VECTOR2I position;
410 std::set<wxString> leaves;
411
412 for( const CLAIM& claim : record.claims )
413 {
414 const ITEM_FACT* fact = claim.schema ? findFact( facts, claim.source.item ) : nullptr;
415
416 if( !fact || fact->ports.empty() || !isLabelOrSheetPin( fact->type ) )
417 continue;
418
419 if( !canonical )
420 {
421 canonical = &claim;
422 position = fact->ports.front().position;
423
424 for( const auto& leaf : claim.schema->leaves )
425 leaves.insert( leaf.name );
426
427 continue;
428 }
429
430 if( std::ranges::any_of( claim.schema->leaves,
431 [&]( const BUS_SCHEMA::LEAF& leaf ) { return leaves.contains( leaf.name ); } ) )
432 {
433 continue;
434 }
435
436 result.push_back( { m_keys.Instance( key.inst ), canonical->source.item, claim.source.item, position } );
437 break;
438 }
439 }
440
441 return result;
442}
443
444std::vector<BUS_ENTRY_CONFLICT> ENGINE::BusEntryConflicts() const
445{
446 wxASSERT( wxThread::IsMain() );
447 std::vector<BUS_ENTRY_CONFLICT> result;
448 const auto& rows = m_published.Rows();
449 const auto& islandOf = m_published.Auxiliary().IslandOf();
450 const auto reported = ercIslands();
451 const auto name = [&]( NAME_ID id ) { return id == INVALID_ID ? wxString() : m_keys.Name( id ); };
452
453 for( const auto& [key, island] : m_published.Auxiliary().Islands() )
454 {
455 if( !reported.contains( key ) )
456 continue;
457
458 KIID previous = niluuid;
459 const auto& facts = m_inputs.InstanceScreenFacts( key.inst ).items;
460
461 for( const auto& [entry, bus] : island.value.busEntryLinks )
462 {
463 // Each contact is published on both islands; report from the entry side only
464 if( islandOf.at( { entry, key.inst } ) != key || entry == previous )
465 continue;
466
467 const ITEM_FACT* busFact = findFact( facts, bus );
468
469 if( !busFact || busFact->type != SCH_LINE_T || busFact->ports.empty()
470 || busFact->ports.front().kind != PORT_KIND::BUS )
471 {
472 continue;
473 }
474
475 previous = entry;
476 const auto signalRow = rows.find( { entry, key.inst } );
477 const auto busRow = rows.find( { bus, key.inst } );
478
479 if( signalRow == rows.end() || busRow == rows.end()
480 || signalRow->second.kind != KIND::SIGNAL || busRow->second.kind != KIND::BUNDLE )
481 {
482 continue;
483 }
484
485 const auto& signal = *m_published.Components().at( signalRow->second.component ).content;
486 const auto& bundle = *m_published.Components().at( busRow->second.component ).content;
487
488 if( !signal.best || signal.best->priority >= PRIORITY::GLOBAL_POWER_PIN )
489 continue;
490
491 // Slot membership includes alternate strong names and hierarchy-renamed members
492 const bool member = std::ranges::any_of( bundle.members,
493 [&]( const SLOT_KEY& slot )
494 {
495 return m_published.SlotComponents().at( slot ) == signalRow->second.component;
496 } );
497
498 if( member )
499 continue;
500
501 const ITEM_FACT* fact = findFact( facts, entry );
502
503 if( !fact || fact->type != SCH_BUS_WIRE_ENTRY_T || fact->ports.empty() )
504 continue;
505
506 result.push_back( { m_keys.Instance( key.inst ), entry, bus, name( signalRow->second.name ),
507 name( busRow->second.name ), fact->ports.front().position } );
508 }
509 }
510
511 return result;
512}
513
514std::vector<NO_CONNECT_PIN_CONFLICT> ENGINE::NoConnectPinConflicts() const
515{
516 wxASSERT( wxThread::IsMain() );
517 struct GROUP
518 {
519 std::set<KIID> pins;
520 std::set<KIID> others;
521 };
522 std::map<INST_ID, std::map<KIID, NC_SOURCE>> sources;
523 std::map<std::pair<KIID_PATH, VECTOR2I>, GROUP, SHEET_POSITION_LESS> groups;
524
525 for( const auto& [key, island] : m_published.Auxiliary().Islands() )
526 {
527 if( island.value.ncContacts.empty() )
528 continue;
529
530 const auto& items = cached( sources, key.inst,
531 [&] { return noConnectSources( m_inputs.InstanceScreenFacts( key.inst ) ); } );
532
533 for( const auto& [first, second] : island.value.ncContacts )
534 {
535 const NC_SOURCE& a = items.at( first );
536 const NC_SOURCE& b = items.at( second );
537
538 if( a.nc == b.nc || a.powerFlag || b.powerFlag )
539 continue;
540
541 const NC_SOURCE& pin = a.nc ? a : b;
542 const NC_SOURCE& other = a.nc ? b : a;
543
544 if( other.type == SCH_NO_CONNECT_T )
545 continue;
546
547 auto& group = groups[{ m_keys.Instance( key.inst ), pin.position }];
548 group.pins.insert( a.nc ? first : second );
549 group.others.insert( a.nc ? second : first );
550 }
551 }
552
553 std::vector<NO_CONNECT_PIN_CONFLICT> result;
554
555 for( const auto& [key, group] : groups )
556 {
557 result.push_back( { key.first, { group.pins.begin(), group.pins.end() },
558 { group.others.begin(), group.others.end() }, key.second } );
559 }
560
561 return result;
562}
563
564std::vector<NO_CONNECT_FLAG_ERROR> ENGINE::NoConnectFlagErrors() const
565{
566 wxASSERT( wxThread::IsMain() );
567 struct NET
568 {
569 uint32_t pins = 0;
570 bool label = false;
571 };
572 std::map<INST_ID, std::map<KIID, NC_SOURCE>> sources;
573 std::map<NODE_ID, NET> nets;
574 const auto source = [&]( INST_ID inst, const KIID& id ) -> const NC_SOURCE&
575 {
576 return cached( sources, inst, [&] { return noConnectSources( m_inputs.InstanceScreenFacts( inst ) ); } )
577 .at( id );
578 };
579 const auto pinWitness = []( const NC_SOURCE& item ) { return item.pin && !item.powerFlag; };
580 const auto& islands = m_published.Auxiliary().Islands();
581 const auto reported = ercIslands();
582 std::vector<NO_CONNECT_FLAG_ERROR> result;
583
584 for( const auto& [key, island] : islands )
585 {
586 if( !island.value.atoms.noConnect || island.value.atoms.busNoConnect || !reported.contains( key ) )
587 continue;
588
589 const KIID& flag = *island.value.atoms.noConnect;
590 const VECTOR2I position = source( key.inst, flag ).position;
591 bool hierarchy = false;
592 bool clean = true;
593 bool attached = false;
594 KIID pin = niluuid;
595
596 for( const KIID& id : island.value.items )
597 {
598 const NC_SOURCE& item = source( key.inst, id );
599 const bool hier = item.type == SCH_SHEET_PIN_T || item.type == SCH_HIER_LABEL_T;
600 hierarchy |= hier;
601 attached |= hier && item.position == position;
602 clean &= !pinWitness( item ) && item.type != SCH_LABEL_T && item.type != SCH_GLOBAL_LABEL_T
603 && item.type != SCH_DIRECTIVE_LABEL_T;
604
605 if( pinWitness( item ) && ( pin == niluuid || id < pin ) )
606 pin = id;
607 }
608
609 for( const auto& [first, second] : island.value.ncContacts )
610 {
611 if( first == flag || second == flag )
612 attached |= source( key.inst, first == flag ? second : first ).nc;
613 }
614
615 if( attached || ( hierarchy && clean ) )
616 continue;
617
618 const auto& row = m_published.Rows().at( { flag, key.inst } );
619 const NET& net = cached( nets, row.component, [&]
620 {
621 NET value;
622 const auto& component = *m_published.Components().at( row.component ).content;
623
624 for( const RECORD_KEY& record : component.records )
625 value.pins += islands.at( record ).value.atoms.pinCount;
626
627 for( const ITEM_KEY& id : component.items )
628 value.label |= isLabel( source( id.inst, id.item ).type );
629
630 return value;
631 } );
632
633 const bool connected = net.pins > 1;
634
635 if( connected || ( net.pins == 0 && !net.label ) )
636 {
637 result.push_back( { m_keys.Instance( key.inst ), flag, connected ? pin : niluuid,
638 connected && pin != niluuid ? source( key.inst, pin ).position : position,
639 connected } );
640 }
641 }
642
643 return result;
644}
645
646std::vector<UNCONNECTED_PIN> ENGINE::UnconnectedPins() const
647{
648 wxASSERT( wxThread::IsMain() );
649 struct SOURCE
650 {
651 const PIN_FACT* pin = nullptr;
652 KICAD_T type = TYPE_NOT_INIT;
653 KIID owner;
654 KIID group;
656
657 bool PowerSymbol() const { return pin && ( pin->globalPowerParent || pin->localPowerParent ); }
658 bool PowerFlag() const { return PowerSymbol() && pin->type == ELECTRICAL_PINTYPE::PT_POWER_OUT; }
659 bool Label() const { return isLabelOrSheetPin( type ); }
660 };
661 const auto samePin = []( const SOURCE& a, const SOURCE& b )
662 {
663 if( a.group == b.group )
664 return true;
665
666 if( !a.pin || !b.pin || a.owner != b.owner || a.pin->position != b.pin->position
667 || a.pin->shownName != b.pin->shownName )
668 return false;
669
670 const auto passive = []( ELECTRICAL_PINTYPE type )
671 {
673 };
674 return a.pin->type == b.pin->type || passive( a.pin->type ) || passive( b.pin->type );
675 };
676 std::map<INST_ID, std::map<KIID, SOURCE>> sources;
677 std::map<NODE_ID, std::vector<CLAIM>> drivers;
678 std::map<NODE_ID, bool> powerConnections;
679 const auto source = [&]( INST_ID inst, const KIID& id ) -> const SOURCE&
680 {
681 return cached( sources, inst, [&]
682 {
683 std::map<KIID, SOURCE> items;
684
685 for( const ITEM_FACT& fact : m_inputs.InstanceScreenFacts( inst ).items )
686 {
687 for( const PIN_FACT& pin : fact.pins )
688 items.emplace( pin.id, SOURCE{ &pin, SCH_PIN_T, fact.owner, fact.id } );
689
690 if( !fact.ports.empty() )
691 {
692 items.emplace( fact.id,
693 SOURCE{ nullptr, fact.type, fact.owner, fact.id, fact.ports.front().kind } );
694 }
695 }
696
697 return items;
698 } ).at( id );
699 };
700 const auto candidates = [&]( NODE_ID component ) -> const std::vector<CLAIM>&
701 {
702 return cached( drivers, component, [&] { return DriverCandidates( component ); } );
703 };
704 const auto& auxiliary = m_published.Auxiliary();
705 const auto busNoConnect = busNoConnectMembers( m_published );
706
707 const auto reported = ercIslands();
708 std::vector<UNCONNECTED_PIN> result;
709
710 for( const auto& [key, island] : auxiliary.Islands() )
711 {
712 if( island.value.atoms.noConnect || !island.value.atoms.hasSymbolPin || !reported.contains( key ) )
713 continue;
714
715 const auto& row = m_published.Rows().at( { island.value.items.front(), key.inst } );
716
717 if( busNoConnect.contains( { row.component, key.inst } ) )
718 continue;
719
720 std::vector<const SOURCE*> pins;
721 bool connected = false;
722
723 for( const KIID& id : island.value.items )
724 {
725 const SOURCE& item = source( key.inst, id );
726 connected |= item.Label();
727
728 if( item.pin && !item.PowerFlag() )
729 pins.push_back( &item );
730 }
731
732 const SOURCE* selected = nullptr;
733
734 for( const SOURCE* item : pins )
735 {
736 const PIN_FACT& pin = *item->pin;
737
739 continue;
740
741 if( !item->PowerSymbol() )
742 {
743 if( !selected || ( selected->pin->invisible && !pin.invisible )
744 || ( pin.invisible == selected->pin->invisible
746 && selected->pin->type != ELECTRICAL_PINTYPE::PT_POWER_IN ) )
747 selected = item;
748
749 continue;
750 }
751
752 bool attached = false;
753 const auto contact = [&]( const KIID& otherId )
754 {
755 const SOURCE& other = source( key.inst, otherId );
756
757 if( other.pin )
758 return !other.PowerFlag() && other.owner != item->owner;
759
760 return other.Label() || other.type == SCH_DIRECTIVE_LABEL_T || other.type == SCH_JUNCTION_T
761 || other.type == SCH_NO_CONNECT_T
762 || ( other.type == SCH_LINE_T && other.port == PORT_KIND::WIRE );
763 };
764
765 if( island.value.dangling.at( pin.id ) == 0 )
766 {
767 if( const auto found = auxiliary.NeighborsOf().find( { pin.id, key.inst } );
768 found != auxiliary.NeighborsOf().end() )
769 attached = std::ranges::any_of( found->second, contact );
770
771 for( const auto& [first, second] : island.value.ncContacts )
772 {
773 if( first == pin.id || second == pin.id )
774 attached |= contact( first == pin.id ? second : first );
775 }
776 }
777
778 const bool electrical = cached( powerConnections, row.component, [&]
779 {
780 bool value = false;
781 const auto& component = *m_published.Components().at( row.component ).content;
782
783 for( const ITEM_KEY& member : component.items )
784 {
785 const SOURCE& other = source( member.inst, member.item );
786 value |= other.Label() || ( other.pin && !other.PowerSymbol() );
787 }
788
789 for( const CLAIM& claim : candidates( row.component ) )
790 value |= source( claim.source.inst, claim.source.item ).Label();
791
792 return value;
793 } );
794
795 if( !attached || !electrical )
796 result.push_back( { m_keys.Instance( key.inst ), pin.id, pin.position } );
797 }
798
799 if( !selected || connected )
800 continue;
801
802 for( size_t first = 0; first < pins.size() && !connected; ++first )
803 {
804 if( pins[first]->PowerSymbol() )
805 continue;
806
807 connected = std::any_of( pins.begin() + first + 1, pins.end(),
808 [&]( const SOURCE* b ) { return !b->PowerSymbol() && !samePin( *pins[first], *b ); } );
809 }
810
811 if( !connected )
812 {
813 connected = std::ranges::any_of( candidates( row.component ),
814 [&]( const CLAIM& claim )
815 {
816 if( claim.priority < PRIORITY::HIER_LABEL )
817 return false;
818
819 const SOURCE& driver = source( claim.source.inst, claim.source.item );
820 return !driver.PowerFlag()
821 && ( claim.source.inst != key.inst || !samePin( driver, *selected ) );
822 } );
823 }
824
825 if( !connected )
826 result.push_back( { m_keys.Instance( key.inst ), selected->pin->id, selected->pin->position } );
827 }
828
829 return result;
830}
831
832std::vector<LABEL_WIRE_CONFLICT> ENGINE::LabelWireConflicts() const
833{
834 wxASSERT( wxThread::IsMain() );
835 std::map<std::pair<KIID_PATH, VECTOR2I>, LABEL_WIRE_CONFLICT, SHEET_POSITION_LESS> locations;
836
837 for( const auto& [key, neighbors] : m_published.Auxiliary().NeighborsOf() )
838 {
839 const auto& facts = m_inputs.InstanceScreenFacts( key.inst ).items;
840 const ITEM_FACT* label = findFact( facts, key.item );
841
842 if( !label || !isLabel( label->type ) || label->ports.empty() )
843 continue;
844
845 const VECTOR2I position = label->ports.front().position;
846 std::vector<KIID> wires;
847
848 for( const KIID& id : neighbors )
849 {
850 const ITEM_FACT* wire = findFact( facts, id );
851
852 if( wire && wire->type == SCH_LINE_T && wire->segment
853 && position != wire->segment->A && position != wire->segment->B )
854 {
855 wires.push_back( id );
856 }
857 }
858
859 if( wires.size() < 2 )
860 continue;
861
862 std::sort( wires.begin(), wires.end() );
863 const KIID_PATH& sheet = m_keys.Instance( key.inst );
864 auto [entry, inserted] = locations.try_emplace( std::pair{ sheet, position },
865 LABEL_WIRE_CONFLICT{ sheet, key.item, position, std::move( wires ) } );
866
867 if( !inserted && key.item < entry->second.label )
868 entry->second.label = key.item;
869 }
870
871 return moveValues( locations );
872}
873
874std::vector<FOUR_WAY_JUNCTION> ENGINE::FourWayJunctions() const
875{
876 wxASSERT( wxThread::IsMain() );
877 struct CONTACTS
878 {
879 std::map<KIID, const PIN_FACT*> pins;
880 std::vector<KIID> lines;
881 std::vector<KIID> members;
882 };
883 std::vector<FOUR_WAY_JUNCTION> result;
884
885 for( INST_ID inst : islandInstances( m_published.Auxiliary().Islands() ) )
886 {
887 const auto* instance = m_inputs.FindInstance( inst );
888
889 if( !instance )
890 throw std::logic_error( "Missing instance inputs in four-way connectivity query" );
891
892 const auto& units = instance->value.units;
893 std::map<VECTOR2I, CONTACTS> locations;
894
895 for( const ITEM_FACT& fact : m_inputs.InstanceScreenFacts( inst ).items )
896 {
897 if( fact.type == SCH_PIN_T )
898 {
899 const auto unit = std::lower_bound( units.begin(), units.end(), fact.owner,
900 []( const auto& entry, const KIID& owner ) { return entry.first < owner; } );
901
902 if( unit == units.end() || unit->first != fact.owner )
903 throw std::invalid_argument( "Missing symbol unit in connectivity geometry" );
904
905 for( const PIN_FACT& pin : fact.pins )
906 {
907 if( pin.unit != 0 && unit->second != 0 && pin.unit != unit->second )
908 continue;
909
910 auto& contacts = locations[pin.position];
911 contacts.members.push_back( pin.id );
912 auto& selected = contacts.pins[fact.owner];
913
914 if( !selected || std::tie( pin.invisible, pin.id )
915 < std::tie( selected->invisible, selected->id ) )
916 selected = &pin;
917 }
918 }
919 else if( fact.type == SCH_LINE_T )
920 {
921 for( const PORT_FACT& port : fact.ports )
922 {
923 auto& contacts = locations[port.position];
924 contacts.lines.push_back( fact.id );
925 contacts.members.push_back( fact.id );
926 }
927 }
928 }
929
930 for( auto& [position, contacts] : locations )
931 {
932 if( contacts.pins.size() + contacts.lines.size() < 4 )
933 continue;
934
935 std::sort( contacts.members.begin(), contacts.members.end() );
936 contacts.members.erase( std::unique( contacts.members.begin(), contacts.members.end() ),
937 contacts.members.end() );
938 FOUR_WAY_JUNCTION junction{ m_keys.Instance( inst ), position, {}, std::move( contacts.members ) };
939
940 for( const auto& [owner, pin] : contacts.pins )
941 junction.items.push_back( pin->id );
942
943 std::sort( junction.items.begin(), junction.items.end() );
944 std::sort( contacts.lines.begin(), contacts.lines.end() );
945 junction.items.insert( junction.items.end(), contacts.lines.begin(), contacts.lines.end() );
946 result.push_back( std::move( junction ) );
947 }
948 }
949
950 std::ranges::sort( result, {}, []( const auto& e ) { return std::tie( e.sheet, e.position.x, e.position.y ); } );
951 return result;
952}
953
954std::vector<NETCLASS_REFERENCE> ENGINE::NetclassReferences() const
955{
956 wxASSERT( wxThread::IsMain() );
957 std::vector<NETCLASS_REFERENCE> result;
958
959 for( const auto& [instance, entry] : m_inputs.Instances().Entries() )
960 std::ranges::copy( entry->value.netclassReferences, std::back_inserter( result ) );
961
962 std::ranges::sort( result, {}, []( const auto& e ) { return std::tie( e.sheet, e.item, e.name ); } );
963 return result;
964}
965
966std::vector<UNMAPPED_PIN_CANDIDATE> ENGINE::UnmappedPinCandidates() const
967{
968 wxASSERT( wxThread::IsMain() );
969 std::vector<UNMAPPED_PIN_CANDIDATE> result;
970 const auto& auxiliary = m_published.Auxiliary();
971
972 for( INST_ID inst : instancesInPageOrder() )
973 {
974 const auto* entry = m_inputs.FindInstance( inst );
975
976 for( const auto& pin : entry->value.pinMapCandidates )
977 {
978 const auto island = auxiliary.IslandOf().find( { pin.id, inst } );
979
980 if( island == auxiliary.IslandOf().end() )
981 continue;
982
983 if( !pin.ignoresDangling && auxiliary.Islands().at( island->second ).value.dangling.at( pin.id ) )
984 continue;
985
986 result.push_back( { m_keys.Instance( inst ), pin.id, pin.position, pin.number, pin.footprint } );
987 }
988 }
989
990 return result;
991}
992
993std::vector<PIN_MAP_FACT> ENGINE::PinMapSymbols( SCREEN_ID aScreen ) const
994{
995 wxASSERT( wxThread::IsMain() );
996 const auto* screen = m_inputs.FindScreen( aScreen );
997 return screen ? screen->value.pinMaps : std::vector<PIN_MAP_FACT>();
998}
999
1000std::vector<LIBRARY_SYMBOL_FACT> ENGINE::LibrarySymbols( const KIID_PATH& aPath ) const
1001{
1002 wxASSERT( wxThread::IsMain() );
1003 const auto id = m_keys.FindInstance( aPath );
1004 const auto* entry = id ? m_inputs.Instances().Find( *id ) : nullptr;
1005 return entry ? m_inputs.InstanceScreenFacts( *id ).LibrarySymbols()
1006 : std::vector<LIBRARY_SYMBOL_FACT>();
1007}
1008
1009std::vector<VARIANT_SYMBOL_FACT> ENGINE::VariantSymbols( const KIID_PATH& aPath ) const
1010{
1011 wxASSERT( wxThread::IsMain() );
1012 const auto id = m_keys.FindInstance( aPath );
1013 const auto* entry = id ? m_inputs.Instances().Find( *id ) : nullptr;
1014 return entry ? entry->value.variantSymbols : std::vector<VARIANT_SYMBOL_FACT>();
1015}
1016
1017std::vector<FOOTPRINT_SOURCE> ENGINE::FootprintSources() const
1018{
1019 wxASSERT( wxThread::IsMain() );
1020 std::vector<FOOTPRINT_SOURCE> result;
1021
1022 for( INST_ID id : instancesInPageOrder() )
1023 {
1024 const auto* entry = m_inputs.FindInstance( id );
1025 const auto& sources = m_inputs.InstanceScreenFacts( id ).footprints;
1026 const auto& footprints = entry->value.footprints;
1027 const KIID_PATH& sheet = m_keys.Instance( id );
1028
1029 if( sources.size() != footprints.size() )
1030 throw std::runtime_error( "Footprint instance facts do not match screen facts" );
1031
1032 for( size_t i = 0; i < footprints.size(); ++i )
1033 {
1034 const auto& source = sources[i];
1035 const auto& [item, footprint] = footprints[i];
1036
1037 if( source.id != item )
1038 throw std::runtime_error( "Footprint instance facts do not match screen facts" );
1039
1040 result.push_back( { sheet, item, source.position, footprint, source.filters } );
1041 }
1042 }
1043
1044 return result;
1045}
1046
1047std::vector<MULTI_UNIT_GROUP> ENGINE::MultiUnitSymbols() const
1048{
1049 wxASSERT( wxThread::IsMain() );
1050 std::map<wxString, MULTI_UNIT_GROUP> groups;
1051
1052 for( INST_ID id : instancesInPageOrder() )
1053 {
1054 std::map<KIID, const MULTI_UNIT_FACT*> sources;
1055
1056 for( const MULTI_UNIT_FACT& source : m_inputs.InstanceScreenFacts( id ).multiUnits )
1057 sources.emplace( source.id, &source );
1058
1059 for( const MULTI_UNIT_REFERENCE& reference : m_inputs.FindInstance( id )->value.multiUnits )
1060 {
1061 if( reference.reference.IsEmpty() || reference.reference.EndsWith( "?" ) )
1062 continue;
1063
1064 const auto& source = *sources.at( reference.id );
1065 auto [group, inserted] = groups.try_emplace( reference.reference );
1066
1067 if( inserted )
1068 {
1069 group->second.reference = reference.reference;
1070 group->second.units = source.units;
1071 }
1072
1073 group->second.instances.push_back( { m_keys.Instance( id ), source.id, source.position,
1074 reference.name, reference.footprint, reference.unit } );
1075 }
1076 }
1077
1078 return moveValues( groups );
1079}
1080
1081std::vector<DUPLICATE_SHEET_ERROR> ENGINE::DuplicateSheetNames() const
1082{
1083 wxASSERT( wxThread::IsMain() );
1084 std::vector<DUPLICATE_SHEET_ERROR> result;
1085
1086 for( const auto& [instance, entry] : m_inputs.Instances().Entries() )
1087 {
1088 const auto& children = entry->value.childSheets;
1089
1090 for( size_t i = 0; i < children.size(); ++i )
1091 {
1092 for( size_t j = i + 1; j < children.size(); ++j )
1093 {
1094 if( children[i].name.IsSameAs( children[j].name, false ) )
1095 {
1096 result.push_back( { m_keys.Instance( instance ), children[i].id,
1097 children[j].id, children[i].position } );
1098 }
1099 }
1100 }
1101 }
1102
1103 std::ranges::sort( result, {}, []( const auto& e ) { return std::tie( e.sheet, e.main, e.auxiliary ); } );
1104 return result;
1105}
1106
1107std::vector<FIELD_NAME_ERROR> ENGINE::InvalidFieldNames() const
1108{
1109 wxASSERT( wxThread::IsMain() );
1110 std::vector<FIELD_NAME_ERROR> result;
1111
1112 for( const auto& [instance, entry] : m_inputs.Instances().Entries() )
1113 {
1114 for( const FIELD_NAME_FACT& field : m_inputs.InstanceScreenFacts( instance ).invalidFieldNames )
1115 result.push_back( { m_keys.Instance( instance ), field } );
1116 }
1117
1118 std::ranges::sort( result, {}, []( const auto& e ) { return std::tie( e.sheet, e.field.owner, e.field.field ); } );
1119 return result;
1120}
1121
1122std::vector<SOURCE_LOCATION> ENGINE::EmptyLabels() const
1123{
1124 wxASSERT( wxThread::IsMain() );
1125 std::vector<SOURCE_LOCATION> result;
1126
1127 for( INST_ID instance : instancesInPageOrder() )
1128 {
1129 for( const ITEM_FACT& fact : m_inputs.InstanceScreenFacts( instance ).items )
1130 {
1131 if( !isLabel( fact.type ) )
1132 continue;
1133
1134 wxString text = fact.rawText;
1135
1136 if( text.Trim( false ).Trim( true ).IsEmpty() )
1137 result.push_back( { m_keys.Instance( instance ), fact.id, fact.ports.front().position } );
1138 }
1139 }
1140
1141 return result;
1142}
1143
1144std::vector<SOURCE_LOCATION> ENGINE::EmptyPowerValues() const
1145{
1146 wxASSERT( wxThread::IsMain() );
1147 std::vector<SOURCE_LOCATION> result;
1148
1149 for( INST_ID instance : instancesInPageOrder() )
1150 {
1151 for( const ITEM_FACT& fact : m_inputs.InstanceScreenFacts( instance ).items )
1152 {
1153 for( const PIN_FACT& pin : fact.pins )
1154 {
1155 // Only a power pin owned by a power symbol takes its net name from the value.
1156 // A hidden power input on an ordinary symbol is named from the pin instead.
1157 if( ( !pin.globalPower && !pin.localPower ) || ( !pin.globalPowerParent && !pin.localPowerParent ) )
1158 {
1159 continue;
1160 }
1161
1162 if( const auto* text = m_inputs.Text( { pin.id, instance } ) )
1163 {
1164 wxString name = text->value.name;
1165
1166 if( name.Trim( false ).Trim( true ).IsEmpty() )
1167 {
1168 result.push_back( { m_keys.Instance( instance ), pin.id, pin.position } );
1169 break;
1170 }
1171 }
1172 }
1173 }
1174 }
1175
1176 return result;
1177}
1178
1179std::vector<SOURCE_LOCATION> ENGINE::WiredImplicitPowerPins() const
1180{
1181 wxASSERT( wxThread::IsMain() );
1182 std::vector<SOURCE_LOCATION> result;
1183 const auto& auxiliary = m_published.Auxiliary();
1184
1185 for( INST_ID instance : instancesInPageOrder() )
1186 {
1187 for( const ITEM_FACT& fact : m_inputs.InstanceScreenFacts( instance ).items )
1188 {
1189 for( const PIN_FACT& pin : fact.pins )
1190 {
1191 const ITEM_KEY key{ pin.id, instance };
1192 const auto* text = m_inputs.Text( key );
1193
1194 if( !text || !text->value.canDrive || !pin.globalPower || !pin.invisible
1196 || pin.globalPowerParent || pin.localPowerParent )
1197 continue;
1198
1199 const auto& island = auxiliary.Islands().at( auxiliary.IslandOf().at( key ) );
1200
1201 if( island.value.atoms.invisiblePowerWired )
1202 {
1203 result.push_back( { m_keys.Instance( instance ), pin.id, pin.position } );
1204 break;
1205 }
1206 }
1207 }
1208 }
1209
1210 return result;
1211}
1212
1213std::vector<SOURCE_LOCATION> ENGINE::InvalidPinNotation() const
1214{
1215 wxASSERT( wxThread::IsMain() );
1216 std::vector<SOURCE_LOCATION> result;
1217
1218 for( INST_ID instance : instancesInPageOrder() )
1219 {
1220 for( const ITEM_FACT& fact : m_inputs.InstanceScreenFacts( instance ).items )
1221 {
1222 for( const PIN_FACT& pin : fact.pins )
1223 {
1224 if( !m_inputs.Text( { pin.id, instance } ) )
1225 continue;
1226
1227 bool valid = false;
1228 ExpandStackedPinNotation( pin.shownNumber, &valid );
1229
1230 if( !valid )
1231 result.push_back( { m_keys.Instance( instance ), pin.id, pin.position } );
1232 }
1233 }
1234 }
1235
1236 return result;
1237}
1238
1239std::vector<OFF_GRID_ENDPOINT> ENGINE::OffGridEndpoints( int aGrid ) const
1240{
1241 wxASSERT( wxThread::IsMain() );
1242 std::vector<OFF_GRID_ENDPOINT> result;
1243 const auto offGrid = [aGrid]( const VECTOR2I& point )
1244 {
1245 return point.x % aGrid != 0 || point.y % aGrid != 0;
1246 };
1247
1248 std::set<SCREEN_ID> screens;
1249
1250 for( INST_ID instance : instancesInPageOrder() )
1251 {
1252 // Screen geometry is the same on every instance, so only the first one in page order reports
1253 if( !screens.insert( m_inputs.InstanceScreen( instance ) ).second )
1254 continue;
1255
1256 const KIID_PATH& path = m_keys.Instance( instance );
1257 std::map<KIID, OFF_GRID_ENDPOINT> symbols;
1258
1259 for( const ITEM_FACT& fact : m_inputs.InstanceScreenFacts( instance ).items )
1260 {
1261 if( fact.type == SCH_LINE_T && fact.segment )
1262 {
1263 if( offGrid( fact.segment->A ) )
1264 result.push_back( { path, fact.id, fact.segment->A, {} } );
1265 else if( offGrid( fact.segment->B ) )
1266 result.push_back( { path, fact.id, fact.segment->B, {} } );
1267 }
1268 else if( fact.type == SCH_BUS_WIRE_ENTRY_T )
1269 {
1270 for( const PORT_FACT& port : fact.ports )
1271 {
1272 if( offGrid( port.position ) )
1273 result.push_back( { path, fact.id, port.position, {} } );
1274 }
1275 }
1276
1277 for( const PIN_FACT& pin : fact.pins )
1278 {
1279 if( pin.type == ELECTRICAL_PINTYPE::PT_NC || !m_inputs.Text( { pin.id, instance } )
1280 || !offGrid( pin.position ) )
1281 continue;
1282
1283 auto [found, inserted] = symbols.try_emplace( fact.owner );
1284 OFF_GRID_ENDPOINT& diagnostic = found->second;
1285
1286 if( inserted || pin.id < diagnostic.item )
1287 {
1288 diagnostic.sheet = path;
1289 diagnostic.item = pin.id;
1290 diagnostic.position = pin.position;
1291 }
1292
1293 diagnostic.equivalentPins.emplace_back( pin.id, pin.position );
1294 }
1295 }
1296
1297 std::ranges::move( std::views::values( symbols ), std::back_inserter( result ) );
1298 }
1299
1300 return result;
1301}
1302
1303std::vector<GROUND_PIN_ERROR> ENGINE::GroundPinErrors() const
1304{
1305 wxASSERT( wxThread::IsMain() );
1306 const auto isGround = []( const wxString& name )
1307 {
1308 const wxString upper = name.Upper();
1309 return upper.Contains( wxS( "GND" ) ) || upper == wxS( "EARTH" ) || upper.StartsWith( wxS( "EARTH_" ) )
1310 || upper == wxS( "VSS" ) || upper == wxS( "VSSA" );
1311 };
1312 struct SYMBOL_PINS
1313 {
1314 bool hasGroundNet = false;
1315 std::vector<GROUND_PIN_ERROR> mismatched;
1316 };
1317 std::vector<GROUND_PIN_ERROR> result;
1318
1319 for( INST_ID inst : islandInstances( m_published.Auxiliary().Islands() ) )
1320 {
1321 std::map<KIID, SYMBOL_PINS> symbols;
1322
1323 for( const ITEM_FACT& fact : m_inputs.InstanceScreenFacts( inst ).items )
1324 {
1325 for( const PIN_FACT& pin : fact.pins )
1326 {
1328 || !m_inputs.Text( { pin.id, inst } ) )
1329 continue;
1330
1331 wxString name;
1332 const auto row = m_published.Rows().find( { pin.id, inst } );
1333
1334 if( row != m_published.Rows().end() )
1335 {
1336 const auto& component = m_published.Components().at( row->second.component );
1337
1338 if( component.name != INVALID_ID )
1339 {
1340 name = m_keys.Name( component.name );
1341
1342 if( component.content->best && component.content->best->path != INVALID_ID )
1343 name = name.Mid( m_keys.Name( component.content->best->path ).length() );
1344 }
1345 }
1346
1347 auto& symbol = symbols[fact.owner];
1348 const bool ground = isGround( name );
1349 symbol.hasGroundNet |= ground;
1350
1351 if( isGround( pin.shownName ) && !ground )
1352 symbol.mismatched.push_back( { m_keys.Instance( inst ), pin.id, pin.position, pin.shownName } );
1353 }
1354 }
1355
1356 for( auto& [owner, symbol] : symbols )
1357 {
1358 if( symbol.hasGroundNet )
1359 std::ranges::move( symbol.mismatched, std::back_inserter( result ) );
1360 }
1361 }
1362
1363 std::ranges::sort( result, {}, []( const auto& e ) { return std::tie( e.sheet, e.pin ); } );
1364 return result;
1365}
1366
1367std::vector<LABEL_CONNECTION_ERROR> ENGINE::LabelConnectionErrors() const
1368{
1369 wxASSERT( wxThread::IsMain() );
1370 struct CONNECTIONS
1371 {
1372 size_t count = 0;
1373 bool noConnect = false;
1374 std::map<INST_ID, size_t> slots;
1375 size_t slotCount = 0;
1376 size_t pins = 0;
1377 std::map<INST_ID, size_t> localPins;
1378 std::map<INST_ID, size_t> hierarchy;
1379 bool busMember = false;
1380 };
1381 std::map<NODE_ID, CONNECTIONS> connections;
1382 const auto& auxiliary = m_published.Auxiliary();
1383 const auto busNoConnect = busNoConnectMembers( m_published );
1384 std::map<SCREEN_ID, std::set<KIID>> screenPinIds;
1385
1386 const auto screenPins = [&]( INST_ID aInstance ) -> const std::set<KIID>&
1387 {
1388 return cached( screenPinIds, m_inputs.InstanceScreen( aInstance ), [&]
1389 {
1390 std::set<KIID> ids;
1391
1392 for( const ITEM_FACT& item : m_inputs.InstanceScreenFacts( aInstance ).items )
1393 {
1394 for( const PIN_FACT& pin : item.pins )
1395 ids.insert( pin.id );
1396 }
1397
1398 return ids;
1399 } );
1400 };
1401
1402 const auto netConnections = [&]( NODE_ID id ) -> const CONNECTIONS&
1403 {
1404 return cached( connections, id, [&]
1405 {
1406 CONNECTIONS net;
1407 const auto& component = *m_published.Components().at( id ).content;
1408 std::set<std::pair<INST_ID, wxString>> ports;
1409 std::set<std::pair<INST_ID, wxString>> hierLabels;
1410 net.busMember = !component.slots.empty();
1411
1412 for( const RECORD_KEY& key : component.records )
1413 {
1414 const auto& island = auxiliary.Islands().at( key ).value;
1415 const auto& facts = m_inputs.InstanceScreenFacts( key.inst ).items;
1416 const auto& pins = screenPins( key.inst );
1417 net.count += island.atoms.pinCount;
1418 net.noConnect |= island.atoms.noConnect.has_value();
1419
1420 for( const KIID& item : island.items )
1421 {
1422 // Power symbol pins count here even though they are not naming witnesses
1423 if( pins.contains( item ) )
1424 {
1425 ++net.pins;
1426 ++net.localPins[key.inst];
1427 continue;
1428 }
1429
1430 const ITEM_FACT* source = findFact( facts, item );
1431
1432 if( !source )
1433 continue;
1434
1435 if( source->type == SCH_SHEET_PIN_T )
1436 {
1437 ++net.count;
1438 ++net.hierarchy[key.inst];
1439 }
1440 else if( source->type == SCH_HIER_LABEL_T )
1441 {
1442 const wxString& name = m_inputs.Text( { item, key.inst } )->value.name;
1443 hierLabels.emplace( key.inst, name );
1444
1445 if( m_keys.Instance( key.inst ).size() > 1 )
1446 ports.emplace( key.inst, name );
1447 }
1448 }
1449 }
1450
1451 net.count += ports.size();
1452
1453 for( const auto& [instance, name] : hierLabels )
1454 ++net.hierarchy[instance];
1455
1456 std::set<NAME_KEY, KEY_LESS> neighbors( KEY_LESS{ m_keys } );
1457
1458 for( const SLOT_KEY& slot : component.slots )
1459 {
1460 for( const NAME_KEY& edge : m_slots.Slots().Entries().at( slot )->value.edges )
1461 {
1462 if( edge.scope == SCOPE::SHEET )
1463 neighbors.insert( edge );
1464 }
1465 }
1466
1467 for( const NAME_KEY& neighbor : neighbors )
1468 {
1469 ++net.slots[neighbor.inst];
1470 ++net.slotCount;
1471 }
1472
1473 return net;
1474 } );
1475 };
1476 const auto reported = ercIslands();
1477 std::vector<LABEL_CONNECTION_ERROR> result;
1478
1479 for( const auto& [key, island] : auxiliary.Islands() )
1480 {
1481 if( !reported.contains( key ) )
1482 continue;
1483
1484 const auto& facts = m_inputs.InstanceScreenFacts( key.inst ).items;
1485
1486 for( const KIID& id : island.value.items )
1487 {
1488 const ITEM_FACT* label = findFact( facts, id );
1489
1490 if( !label || !isLabel( label->type ) )
1491 continue;
1492
1493 const auto& row = m_published.Rows().at( { id, key.inst } );
1494
1495 if( row.kind == KIND::BUNDLE )
1496 continue;
1497
1498 const CONNECTIONS& net = netConnections( row.component );
1499 const auto localSlots = net.slots.find( key.inst );
1500 const size_t count = net.count + net.slotCount
1501 - ( localSlots == net.slots.end() ? 0 : localSlots->second );
1502 const auto hierarchy = net.hierarchy.find( key.inst );
1503 const bool busNc = busNoConnect.contains( { row.component, key.inst } );
1504
1505 // A local label without pins on its sheet may still route hierarchy or join a net flagged elsewhere
1506 const bool routesHierarchy = hierarchy != net.hierarchy.end()
1507 && hierarchy->second > ( net.busMember ? 0U : 1U );
1508 const bool orphan = label->type == SCH_LABEL_T && !net.localPins.contains( key.inst ) && net.pins > 1
1509 && !net.noConnect && !busNc && !routesHierarchy;
1510 const bool unconnected = island.value.dangling.at( id ) != 0 || count == 0 || orphan;
1511 const bool singlePin = count == 1 && !net.noConnect && !busNc;
1512
1513 if( unconnected )
1514 result.push_back( { m_keys.Instance( key.inst ), id, label->ports.front().position, false } );
1515
1516 if( singlePin )
1517 result.push_back( { m_keys.Instance( key.inst ), id, label->ports.front().position, true } );
1518 }
1519 }
1520
1521 return result;
1522}
1523
1524std::vector<LABEL_LOCATION> ENGINE::DanglingDirectives() const
1525{
1526 wxASSERT( wxThread::IsMain() );
1527 std::vector<LABEL_LOCATION> result;
1528
1529 for( const auto& [key, island] : m_published.Auxiliary().Islands() )
1530 {
1531 const auto& facts = m_inputs.InstanceScreenFacts( key.inst ).items;
1532
1533 for( const auto& [id, dangling] : island.value.dangling )
1534 {
1535 const ITEM_FACT* fact = dangling ? findFact( facts, id ) : nullptr;
1536
1537 if( fact && fact->type == SCH_DIRECTIVE_LABEL_T )
1538 result.push_back( { m_keys.Instance( key.inst ), id, fact->ports.front().position } );
1539 }
1540 }
1541
1542 return result;
1543}
1544
1545std::vector<ERC_PIN_NET> ENGINE::PinNets() const
1546{
1547 wxASSERT( wxThread::IsMain() );
1548 std::map<NAME_ID, ERC_PIN_NET, NAME_LESS> nets( NAME_LESS{ &m_keys } );
1549 const auto netFor = [&]( const ITEM_KEY& key ) -> ERC_PIN_NET*
1550 {
1551 // Active pins and no-connect flags are all island members and must have published rows
1552 const auto& row = m_published.Rows().at( key );
1553
1554 if( row.name == INVALID_ID )
1555 return nullptr;
1556
1557 auto [it, inserted] = nets.try_emplace( row.name );
1558
1559 if( inserted )
1560 it->second.name = m_keys.Name( row.name );
1561
1562 return &it->second;
1563 };
1564
1565 for( INST_ID inst : islandInstances( m_published.Auxiliary().Islands() ) )
1566 {
1567 for( const ITEM_FACT& fact : m_inputs.InstanceScreenFacts( inst ).items )
1568 {
1569 if( fact.type == SCH_NO_CONNECT_T )
1570 {
1571 if( auto* net = netFor( { fact.id, inst } ) )
1572 net->noConnect = true;
1573 }
1574
1575 for( const PIN_FACT& pin : fact.pins )
1576 {
1577 const auto* text = m_inputs.Text( { pin.id, inst } );
1578 auto* net = text ? netFor( { pin.id, inst } ) : nullptr;
1579
1580 if( !net )
1581 continue;
1582
1583 net->noConnect |= pin.type == ELECTRICAL_PINTYPE::PT_NC;
1584 net->powerDriven |= pin.type == ELECTRICAL_PINTYPE::PT_POWER_OUT;
1585 net->pins.push_back( { m_keys.Instance( inst ), fact.owner, text->value.reference, pin } );
1586 }
1587 }
1588 }
1589
1590 return moveValues( nets );
1591}
1592
1593std::vector<MULTI_UNIT_CONFLICT> ENGINE::MultiUnitPinConflicts() const
1594{
1595 wxASSERT( wxThread::IsMain() );
1596 struct PIN_ON_NET
1597 {
1599 NAME_ID net;
1600 };
1601 std::map<std::pair<std::string, std::string>, std::vector<PIN_ON_NET>> groups;
1602
1603 for( INST_ID inst : islandInstances( m_published.Auxiliary().Islands() ) )
1604 {
1605 for( const ITEM_FACT& fact : m_inputs.InstanceScreenFacts( inst ).items )
1606 {
1607 if( !fact.multiUnit )
1608 continue;
1609
1610 for( const PIN_FACT& pin : fact.pins )
1611 {
1612 const auto* text = m_inputs.Text( { pin.id, inst } );
1613
1614 if( !text )
1615 continue;
1616
1617 const auto& row = m_published.Rows().at( { pin.id, inst } );
1618
1619 if( row.name == INVALID_ID )
1620 continue;
1621
1622 groups[{ text->value.reference.utf8_string(), pin.shownNumber.utf8_string() }].push_back(
1623 { { m_keys.Instance( inst ), pin.id, pin.position }, row.name } );
1624 }
1625 }
1626 }
1627
1628 std::vector<MULTI_UNIT_CONFLICT> result;
1629
1630 for( auto& [key, pins] : groups )
1631 {
1632 std::ranges::sort( pins, [&]( const PIN_ON_NET& a, const PIN_ON_NET& b )
1633 {
1634 if( a.net != b.net )
1635 return m_keys.NameLess( a.net, b.net );
1636
1637 if( a.location.item != b.location.item )
1638 return a.location.item < b.location.item;
1639
1640 // Tuple comparison would select vector's lexical ordering for KIID_PATH
1641 return a.location.sheet < b.location.sheet;
1642 } );
1643 const PIN_ON_NET& first = pins.front();
1644 const auto other = std::ranges::find_if( pins, [&]( const PIN_ON_NET& pin ) { return pin.net != first.net; } );
1645
1646 if( other != pins.end() )
1647 {
1648 result.push_back( { first.location, other->location, wxString::FromUTF8( key.second ),
1649 m_keys.Name( first.net ), m_keys.Name( other->net ) } );
1650 }
1651 }
1652
1653 return result;
1654}
1655
1656std::vector<NAMED_ITEM> ENGINE::NamedItems() const
1657{
1658 wxASSERT( wxThread::IsMain() );
1659 std::vector<NAMED_ITEM> result;
1660 const auto unescapeNetName = []( wxString name )
1661 {
1662 // CTX_NETNAME only escapes slashes; other brace tokens are literal name text
1663 name.Replace( wxS( "{slash}" ), wxS( "/" ) );
1664 return name;
1665 };
1666
1667 for( INST_ID inst : islandInstances( m_published.Auxiliary().Islands() ) )
1668 {
1669 const KIID_PATH& path = m_keys.Instance( inst );
1670
1671 for( const ITEM_FACT& fact : m_inputs.InstanceScreenFacts( inst ).items )
1672 {
1673 if( isLabel( fact.type ) )
1674 {
1675 const auto* text = m_inputs.Text( { fact.id, inst } );
1676 result.push_back( { path, fact.id, fact.ports.front().position,
1677 unescapeNetName( text->value.name ), fact.type, fact.type == SCH_GLOBAL_LABEL_T } );
1678 }
1679
1680 for( const PIN_FACT& pin : fact.pins )
1681 {
1682 if( !pin.globalPower && !pin.localPower )
1683 continue;
1684
1685 // Pins from unselected units have no per-instance text entry
1686 if( const auto* text = m_inputs.Text( { pin.id, inst } ) )
1687 {
1688 result.push_back( { path, pin.id, pin.position, unescapeNetName( text->value.name ),
1689 SCH_PIN_T, pin.globalPower } );
1690 }
1691 }
1692 }
1693 }
1694
1695 return result;
1696}
1697
1698std::vector<LABEL_LOCATION> ENGINE::SingleGlobalLabels() const
1699{
1700 wxASSERT( wxThread::IsMain() );
1701 std::map<std::string, std::optional<LABEL_LOCATION>> labels;
1702
1703 for( INST_ID inst : islandInstances( m_published.Auxiliary().Islands() ) )
1704 {
1705 for( const ITEM_FACT& fact : m_inputs.InstanceScreenFacts( inst ).items )
1706 {
1707 if( fact.type != SCH_GLOBAL_LABEL_T )
1708 continue;
1709
1710 const wxString& name = m_inputs.Text( { fact.id, inst } )->value.name;
1711 auto [entry, inserted] = labels.try_emplace( name.utf8_string(),
1712 LABEL_LOCATION{ m_keys.Instance( inst ), fact.id, fact.ports.front().position } );
1713
1714 if( !inserted )
1715 entry->second.reset();
1716 }
1717 }
1718
1719 std::vector<LABEL_LOCATION> result;
1720
1721 for( const auto& [name, label] : labels )
1722 {
1723 if( label )
1724 result.push_back( *label );
1725 }
1726
1727 return result;
1728}
1729
1730std::vector<HIERARCHY_ERROR> ENGINE::HierarchyErrors() const
1731{
1732 wxASSERT( wxThread::IsMain() );
1733 using ERROR_KIND = HIERARCHY_ERROR::KIND;
1734 struct PORTS
1735 {
1736 std::map<wxString, std::vector<const ITEM_FACT*>> labels;
1737 std::map<std::pair<KIID, wxString>, std::vector<const ITEM_FACT*>> pins;
1738 };
1739 std::map<INST_ID, PORTS> instances;
1740 std::vector<HIERARCHY_ERROR> result;
1741 const auto& auxiliary = m_published.Auxiliary();
1742
1743 for( INST_ID inst : islandInstances( auxiliary.Islands() ) )
1744 {
1745 PORTS& ports = instances[inst];
1746
1747 for( const ITEM_FACT& fact : m_inputs.InstanceScreenFacts( inst ).items )
1748 {
1749 if( fact.type != SCH_HIER_LABEL_T && fact.type != SCH_SHEET_PIN_T )
1750 continue;
1751
1752 const wxString& name = m_inputs.Text( { fact.id, inst } )->value.name;
1753
1754 if( fact.type == SCH_HIER_LABEL_T )
1755 {
1756 ports.labels[name].push_back( &fact );
1757 }
1758 else
1759 {
1760 ports.pins[{ fact.owner, name }].push_back( &fact );
1761 const auto& record = auxiliary.IslandOf().at( { fact.id, inst } );
1762
1763 if( auxiliary.Islands().at( record ).value.dangling.at( fact.id ) != 0 )
1764 {
1765 result.push_back( { m_keys.Instance( inst ), fact.id, fact.ports.front().position,
1766 name, ERROR_KIND::DANGLING_PIN, {} } );
1767 }
1768 }
1769 }
1770 }
1771
1772 const auto mismatch = [&]( const KIID_PATH& path, const std::vector<const ITEM_FACT*>& items,
1773 const wxString& name, ERROR_KIND kind )
1774 {
1775 const ITEM_FACT& first = *items.front();
1776 HIERARCHY_ERROR error{ path, first.id, first.ports.front().position, name, kind, {} };
1777
1778 for( const ITEM_FACT* item : items )
1779 error.equivalentItems.push_back( item->id );
1780
1781 result.push_back( std::move( error ) );
1782 };
1783
1784 for( const auto& [inst, ports] : instances )
1785 {
1786 const KIID_PATH& path = m_keys.Instance( inst );
1787 KIID_PATH parentPath = path;
1788 const KIID owner = parentPath.back();
1789 parentPath.pop_back();
1790 const auto parent = m_keys.FindInstance( parentPath );
1791 const bool hasParent = parent && m_inputs.FindInstance( *parent );
1792
1793 const auto parentPorts = hasParent ? instances.find( *parent ) : instances.end();
1794
1795 for( const auto& [name, labels] : ports.labels )
1796 {
1797 if( !hasParent )
1798 {
1799 for( const ITEM_FACT* label : labels )
1800 {
1801 result.push_back( { path, label->id, label->ports.front().position, name,
1802 ERROR_KIND::ROOT_LABEL, {} } );
1803 }
1804 }
1805 else if( parentPorts == instances.end() || !parentPorts->second.pins.contains( { owner, name } ) )
1806 {
1807 mismatch( path, labels, name, ERROR_KIND::MISSING_PIN );
1808 }
1809 }
1810
1811 for( const auto& [key, pins] : ports.pins )
1812 {
1813 KIID_PATH childPath = path;
1814 childPath.push_back( key.first );
1815 const auto child = m_keys.FindInstance( childPath );
1816 const auto found = child ? instances.find( *child ) : instances.end();
1817
1818 if( found == instances.end() || !found->second.labels.contains( key.second ) )
1819 mismatch( path, pins, key.second, ERROR_KIND::MISSING_LABEL );
1820 }
1821 }
1822
1823 std::ranges::sort( result, []( const auto& a, const auto& b )
1824 {
1825 if( a.sheet != b.sheet )
1826 return a.sheet < b.sheet;
1827
1828 // Keep KIID_PATH out of tuple comparisons, which select vector's lexical ordering
1829 return std::tie( a.item, a.kind ) < std::tie( b.item, b.kind );
1830 } );
1831 return result;
1832}
1833} // namespace SCH_CONNECTIVITY
const char * name
Definition kiid.h:46
std::map< RECORD_KEY, ISLAND_ENTRY, KEY_LESS > ISLANDS
std::vector< NO_CONNECT_PIN_CONFLICT > NoConnectPinConflicts() const
No-connect pins that touch other items, grouped by instance and position.
std::vector< MULTI_UNIT_GROUP > MultiUnitSymbols() const
Annotated multi-unit symbols grouped by reference, with instances in page order.
std::vector< ERC_PIN_NET > PinNets() const
Active pins for each named net, in net name order.
std::vector< NO_CONNECT_FLAG_ERROR > NoConnectFlagErrors() const
No-connect flags on reported islands that connect to many pins or to nothing.
std::vector< OFF_GRID_ENDPOINT > OffGridEndpoints(int aGrid) const
Off-grid endpoints on the first instance of each screen; one pin for each symbol.
std::vector< LABEL_LOCATION > SingleGlobalLabels() const
Global labels whose name occurs only once in the hierarchy.
std::vector< DUPLICATE_SHEET_ERROR > DuplicateSheetNames() const
Child sheet pairs whose names differ only in case, sorted by instance and pair.
std::vector< GROUND_PIN_ERROR > GroundPinErrors() const
Ground-named power pins off a ground net, sorted by instance and pin.
std::vector< PIN_MAP_FACT > PinMapSymbols(SCREEN_ID aScreen) const
Captured pin maps of one screen; empty for an unknown screen.
std::vector< BUS_BUS_CONFLICT > BusBusConflicts() const
Reported bus islands with mixed bus shapes, or with two claims that share no member.
std::vector< BUS_ENTRY_CONFLICT > BusEntryConflicts() const
Bus wire entries on reported islands whose net is not a member of the touched bus.
std::vector< UNCONNECTED_PIN > UnconnectedPins() const
Unconnected symbol pins on reported islands; each power symbol pin reports on its own.
std::vector< DRIVER_CONFLICT > DriverConflicts() const
Islands with two different strong names; one result for each reported island.
std::vector< WIRE_ENDPOINT > DanglingWireEndpoints() const
Dangling ends of wires and bus wire entries on reported islands.
std::vector< MULTI_UNIT_CONFLICT > MultiUnitPinConflicts() const
One conflict for each multi-unit reference and pin number with more than one net.
std::vector< VARIANT_SYMBOL_FACT > VariantSymbols(const KIID_PATH &aPath) const
Captured variant symbol overrides of one instance; empty for an unknown path.
std::vector< NAMED_ITEM > NamedItems() const
Every label and power pin with its resolved name, on every instance.
std::set< RECORD_KEY, KEY_LESS > ercIslands() const
Islands that ERC reports.
std::vector< FOUR_WAY_JUNCTION > FourWayJunctions() const
Positions with four or more contacts, sorted by instance and position.
std::vector< BUS_NET_CONFLICT > BusNetConflicts() const
Reported islands whose ERC_ATOMS::kindConflict is set and that hold both a net and a bus item.
std::vector< FOOTPRINT_SOURCE > FootprintSources() const
Resolved footprint and filters of every symbol on every instance.
std::vector< LIBRARY_SYMBOL_FACT > LibrarySymbols(const KIID_PATH &aPath) const
Captured library links of the symbols on the screen of one instance; empty for an unknown path.
std::vector< INST_ID > instancesInPageOrder() const
All captured instances, sorted by page order and then by instance path.
std::vector< SOURCE_LOCATION > EmptyPowerValues() const
std::vector< NETCLASS_REFERENCE > NetclassReferences() const
Netclass field references of every instance, sorted by instance, item and name.
std::vector< LABEL_LOCATION > DanglingDirectives() const
Dangling directive labels on every instance.
std::vector< FLOATING_WIRE > FloatingWires() const
Wires and bus entries of each island whose net has no driver, on every instance.
std::vector< HIERARCHY_ERROR > HierarchyErrors() const
Hierarchical label and sheet pin mismatches, sorted by instance, item and kind.
std::vector< SOURCE_LOCATION > InvalidPinNotation() const
Pins whose shown number looks like stacked pin notation but does not parse.
std::vector< FIELD_NAME_ERROR > InvalidFieldNames() const
Field names with leading or trailing white space, sorted by instance, owner and field.
std::vector< SOURCE_LOCATION > WiredImplicitPowerPins() const
Hidden global power input pins that a wire also joins; one pin for each symbol.
std::vector< SOURCE_LOCATION > EmptyLabels() const
Labels whose raw text is empty or only white space, on every instance.
std::vector< LABEL_CONNECTION_ERROR > LabelConnectionErrors() const
Unconnected and single-pin labels on reported islands.
std::vector< UNMAPPED_PIN_CANDIDATE > UnmappedPinCandidates() const
Pins that need a pad check.
std::vector< LABEL_WIRE_CONFLICT > LabelWireConflicts() const
Labels that touch the interior of two or more wires; one result for each position.
std::vector< CLAIM > DriverCandidates(NODE_ID aComponent) const
Owned eligible claims of one published component, strongest first.
Main-thread publication.
std::string source
KIID niluuid(0)
Value keys and the key session of the schematic connectivity engine.
uint32_t INST_ID
Session handle of a sheet instance KIID_PATH.
Definition conn_keys.h:41
uint32_t NAME_ID
Session handle of a name, ordered by UTF-8 value through NAME_LESS.
Definition conn_keys.h:42
uint64_t SCREEN_ID
Process-local SCH_SCREEN::ConnectivityId(), never a file UUID.
Definition conn_keys.h:44
SOURCE_LOCATION LABEL_LOCATION
uint32_t NODE_ID
Session handle of a NODE_KEY graph node.
Definition conn_keys.h:43
constexpr uint32_t INVALID_ID
Marks an unset handle.
Definition conn_keys.h:47
@ GLOBAL_POWER_PIN
Power input pin of a global power symbol, or a hidden power input pin.
Definition conn_claims.h:45
ELECTRICAL_PINTYPE
The symbol library pin object electrical types used in ERC tests.
Definition pin_type.h:32
@ PT_NC
not connected (must be left open)
Definition pin_type.h:46
@ PT_NIC
not internally connected (may be connected to anything)
Definition pin_type.h:40
@ PT_POWER_OUT
output of a regulator: intended to be connected to power input pins
Definition pin_type.h:43
@ PT_POWER_IN
power input (GND, VCC for ICs). Must be connected to a power output.
Definition pin_type.h:42
@ PT_PASSIVE
pin for passive symbols: must be connected, and can be connected to any pin.
Definition pin_type.h:39
@ NET
This item represents a net.
std::vector< wxString > ExpandStackedPinNotation(const wxString &aPinName, bool *aValid)
Expand stacked pin notation like [1,2,3], [1-4], [A1-A4], or [AA1-AA3,AB4,CD12-CD14] into individual ...
The claim of one item for the name of its island.
Definition conn_claims.h:53
ITEM_KEY source
The claiming item, the last tie-break.
Definition conn_claims.h:60
bool Strong() const
True for a name that the user placed.
Definition conn_claims.h:69
NAME_ID name
Item text without the sheet prefix.
Definition conn_claims.h:57
std::shared_ptr< const BUS_SCHEMA > schema
Parsed bus, or null for a signal.
Definition conn_claims.h:65
All active pins that the publication places on one named net.
Up to four wires and bus entries of one island that has no driver.
Four or more contacts at one position.
std::vector< KIID > items
One pin for each symbol, then the lines; ERC reports four.
A hierarchical label or sheet pin with no partner, or a dangling sheet pin.
std::vector< KIID > equivalentItems
For a missing partner, all same-named labels or sheet pins.
A value copy of one connectable item, or of one group of pins that share a number and a position.
Definition conn_facts.h:101
KIID id
The item KIID, or the smallest member KIID of a pin group.
Definition conn_facts.h:102
std::optional< SEG > segment
Definition conn_facts.h:106
std::vector< PORT_FACT > ports
Definition conn_facts.h:105
One item or pin in one sheet instance.
Definition conn_keys.h:77
KIID item
The item or pin KIID.
Definition conn_keys.h:78
Orders keys by value through SESSION_KEYS::Less().
Definition conn_keys.h:255
A label that sits on the interior of two or more wires.
Orders name handles by UTF-8 value.
Definition conn_keys.h:246
A wire end, a bus entry end or a symbol pin that is not on the connection grid.
std::vector< std::pair< KIID, VECTOR2I > > equivalentPins
All off-grid pins of the symbol.
One connection point of an item.
Definition conn_facts.h:68
One island in one sheet instance.
Definition conn_keys.h:87
INST_ID inst
The sheet instance of the record.
Definition conn_keys.h:88
The value copy of one screen.
Definition conn_facts.h:251
One member position of a bus.
Definition conn_keys.h:127
std::vector< DATABASE_CONNECTION::ROW > rows
std::string path
KIBIS_PIN * pin
static std::vector< int > candidates(const SEGMENT_INDEX &aIndex, const SEG &aQuery, int aPadding)
VECTOR2I location
wxString result
Test unit parsing edge cases and error handling.
KICAD_T
The set of class identification values stored in EDA_ITEM::m_structType.
Definition typeinfo.h:70
@ SCH_LINE_T
Definition typeinfo.h:159
@ SCH_NO_CONNECT_T
Definition typeinfo.h:156
@ TYPE_NOT_INIT
Definition typeinfo.h:73
@ SCH_DIRECTIVE_LABEL_T
Definition typeinfo.h:167
@ SCH_LABEL_T
Definition typeinfo.h:163
@ SCH_HIER_LABEL_T
Definition typeinfo.h:165
@ SCH_SHEET_PIN_T
Definition typeinfo.h:170
@ SCH_BUS_WIRE_ENTRY_T
Definition typeinfo.h:157
@ SCH_GLOBAL_LABEL_T
Definition typeinfo.h:164
@ SCH_JUNCTION_T
Definition typeinfo.h:155
@ SCH_PIN_T
Definition typeinfo.h:149
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708