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::WiredImplicitPowerPins() const
1145{
1146 wxASSERT( wxThread::IsMain() );
1147 std::vector<SOURCE_LOCATION> result;
1148 const auto& auxiliary = m_published.Auxiliary();
1149
1150 for( INST_ID instance : instancesInPageOrder() )
1151 {
1152 for( const ITEM_FACT& fact : m_inputs.InstanceScreenFacts( instance ).items )
1153 {
1154 for( const PIN_FACT& pin : fact.pins )
1155 {
1156 const ITEM_KEY key{ pin.id, instance };
1157 const auto* text = m_inputs.Text( key );
1158
1159 if( !text || !text->value.canDrive || !pin.globalPower || !pin.invisible
1161 || pin.globalPowerParent || pin.localPowerParent )
1162 continue;
1163
1164 const auto& island = auxiliary.Islands().at( auxiliary.IslandOf().at( key ) );
1165
1166 if( island.value.atoms.invisiblePowerWired )
1167 {
1168 result.push_back( { m_keys.Instance( instance ), pin.id, pin.position } );
1169 break;
1170 }
1171 }
1172 }
1173 }
1174
1175 return result;
1176}
1177
1178std::vector<SOURCE_LOCATION> ENGINE::InvalidPinNotation() const
1179{
1180 wxASSERT( wxThread::IsMain() );
1181 std::vector<SOURCE_LOCATION> result;
1182
1183 for( INST_ID instance : instancesInPageOrder() )
1184 {
1185 for( const ITEM_FACT& fact : m_inputs.InstanceScreenFacts( instance ).items )
1186 {
1187 for( const PIN_FACT& pin : fact.pins )
1188 {
1189 if( !m_inputs.Text( { pin.id, instance } ) )
1190 continue;
1191
1192 bool valid = false;
1193 ExpandStackedPinNotation( pin.shownNumber, &valid );
1194
1195 if( !valid )
1196 result.push_back( { m_keys.Instance( instance ), pin.id, pin.position } );
1197 }
1198 }
1199 }
1200
1201 return result;
1202}
1203
1204std::vector<OFF_GRID_ENDPOINT> ENGINE::OffGridEndpoints( int aGrid ) const
1205{
1206 wxASSERT( wxThread::IsMain() );
1207 std::vector<OFF_GRID_ENDPOINT> result;
1208 const auto offGrid = [aGrid]( const VECTOR2I& point )
1209 {
1210 return point.x % aGrid != 0 || point.y % aGrid != 0;
1211 };
1212
1213 std::set<SCREEN_ID> screens;
1214
1215 for( INST_ID instance : instancesInPageOrder() )
1216 {
1217 // Screen geometry is the same on every instance, so only the first one in page order reports
1218 if( !screens.insert( m_inputs.InstanceScreen( instance ) ).second )
1219 continue;
1220
1221 const KIID_PATH& path = m_keys.Instance( instance );
1222 std::map<KIID, OFF_GRID_ENDPOINT> symbols;
1223
1224 for( const ITEM_FACT& fact : m_inputs.InstanceScreenFacts( instance ).items )
1225 {
1226 if( fact.type == SCH_LINE_T && fact.segment )
1227 {
1228 if( offGrid( fact.segment->A ) )
1229 result.push_back( { path, fact.id, fact.segment->A, {} } );
1230 else if( offGrid( fact.segment->B ) )
1231 result.push_back( { path, fact.id, fact.segment->B, {} } );
1232 }
1233 else if( fact.type == SCH_BUS_WIRE_ENTRY_T )
1234 {
1235 for( const PORT_FACT& port : fact.ports )
1236 {
1237 if( offGrid( port.position ) )
1238 result.push_back( { path, fact.id, port.position, {} } );
1239 }
1240 }
1241
1242 for( const PIN_FACT& pin : fact.pins )
1243 {
1244 if( pin.type == ELECTRICAL_PINTYPE::PT_NC || !m_inputs.Text( { pin.id, instance } )
1245 || !offGrid( pin.position ) )
1246 continue;
1247
1248 auto [found, inserted] = symbols.try_emplace( fact.owner );
1249 OFF_GRID_ENDPOINT& diagnostic = found->second;
1250
1251 if( inserted || pin.id < diagnostic.item )
1252 {
1253 diagnostic.sheet = path;
1254 diagnostic.item = pin.id;
1255 diagnostic.position = pin.position;
1256 }
1257
1258 diagnostic.equivalentPins.emplace_back( pin.id, pin.position );
1259 }
1260 }
1261
1262 std::ranges::move( std::views::values( symbols ), std::back_inserter( result ) );
1263 }
1264
1265 return result;
1266}
1267
1268std::vector<GROUND_PIN_ERROR> ENGINE::GroundPinErrors() const
1269{
1270 wxASSERT( wxThread::IsMain() );
1271 const auto isGround = []( const wxString& name )
1272 {
1273 const wxString upper = name.Upper();
1274 return upper.Contains( wxS( "GND" ) ) || upper == wxS( "EARTH" ) || upper.StartsWith( wxS( "EARTH_" ) )
1275 || upper == wxS( "VSS" ) || upper == wxS( "VSSA" );
1276 };
1277 struct SYMBOL_PINS
1278 {
1279 bool hasGroundNet = false;
1280 std::vector<GROUND_PIN_ERROR> mismatched;
1281 };
1282 std::vector<GROUND_PIN_ERROR> result;
1283
1284 for( INST_ID inst : islandInstances( m_published.Auxiliary().Islands() ) )
1285 {
1286 std::map<KIID, SYMBOL_PINS> symbols;
1287
1288 for( const ITEM_FACT& fact : m_inputs.InstanceScreenFacts( inst ).items )
1289 {
1290 for( const PIN_FACT& pin : fact.pins )
1291 {
1293 || !m_inputs.Text( { pin.id, inst } ) )
1294 continue;
1295
1296 wxString name;
1297 const auto row = m_published.Rows().find( { pin.id, inst } );
1298
1299 if( row != m_published.Rows().end() )
1300 {
1301 const auto& component = m_published.Components().at( row->second.component );
1302
1303 if( component.name != INVALID_ID )
1304 {
1305 name = m_keys.Name( component.name );
1306
1307 if( component.content->best && component.content->best->path != INVALID_ID )
1308 name = name.Mid( m_keys.Name( component.content->best->path ).length() );
1309 }
1310 }
1311
1312 auto& symbol = symbols[fact.owner];
1313 const bool ground = isGround( name );
1314 symbol.hasGroundNet |= ground;
1315
1316 if( isGround( pin.shownName ) && !ground )
1317 symbol.mismatched.push_back( { m_keys.Instance( inst ), pin.id, pin.position, pin.shownName } );
1318 }
1319 }
1320
1321 for( auto& [owner, symbol] : symbols )
1322 {
1323 if( symbol.hasGroundNet )
1324 std::ranges::move( symbol.mismatched, std::back_inserter( result ) );
1325 }
1326 }
1327
1328 std::ranges::sort( result, {}, []( const auto& e ) { return std::tie( e.sheet, e.pin ); } );
1329 return result;
1330}
1331
1332std::vector<LABEL_CONNECTION_ERROR> ENGINE::LabelConnectionErrors() const
1333{
1334 wxASSERT( wxThread::IsMain() );
1335 struct CONNECTIONS
1336 {
1337 size_t count = 0;
1338 bool noConnect = false;
1339 std::map<INST_ID, size_t> slots;
1340 size_t slotCount = 0;
1341 size_t pins = 0;
1342 std::map<INST_ID, size_t> localPins;
1343 std::map<INST_ID, size_t> hierarchy;
1344 bool busMember = false;
1345 };
1346 std::map<NODE_ID, CONNECTIONS> connections;
1347 const auto& auxiliary = m_published.Auxiliary();
1348 const auto busNoConnect = busNoConnectMembers( m_published );
1349 std::map<SCREEN_ID, std::set<KIID>> screenPinIds;
1350
1351 const auto screenPins = [&]( INST_ID aInstance ) -> const std::set<KIID>&
1352 {
1353 return cached( screenPinIds, m_inputs.InstanceScreen( aInstance ), [&]
1354 {
1355 std::set<KIID> ids;
1356
1357 for( const ITEM_FACT& item : m_inputs.InstanceScreenFacts( aInstance ).items )
1358 {
1359 for( const PIN_FACT& pin : item.pins )
1360 ids.insert( pin.id );
1361 }
1362
1363 return ids;
1364 } );
1365 };
1366
1367 const auto netConnections = [&]( NODE_ID id ) -> const CONNECTIONS&
1368 {
1369 return cached( connections, id, [&]
1370 {
1371 CONNECTIONS net;
1372 const auto& component = *m_published.Components().at( id ).content;
1373 std::set<std::pair<INST_ID, wxString>> ports;
1374 std::set<std::pair<INST_ID, wxString>> hierLabels;
1375 net.busMember = !component.slots.empty();
1376
1377 for( const RECORD_KEY& key : component.records )
1378 {
1379 const auto& island = auxiliary.Islands().at( key ).value;
1380 const auto& facts = m_inputs.InstanceScreenFacts( key.inst ).items;
1381 const auto& pins = screenPins( key.inst );
1382 net.count += island.atoms.pinCount;
1383 net.noConnect |= island.atoms.noConnect.has_value();
1384
1385 for( const KIID& item : island.items )
1386 {
1387 // Power symbol pins count here even though they are not naming witnesses
1388 if( pins.contains( item ) )
1389 {
1390 ++net.pins;
1391 ++net.localPins[key.inst];
1392 continue;
1393 }
1394
1395 const ITEM_FACT* source = findFact( facts, item );
1396
1397 if( !source )
1398 continue;
1399
1400 if( source->type == SCH_SHEET_PIN_T )
1401 {
1402 ++net.count;
1403 ++net.hierarchy[key.inst];
1404 }
1405 else if( source->type == SCH_HIER_LABEL_T )
1406 {
1407 const wxString& name = m_inputs.Text( { item, key.inst } )->value.name;
1408 hierLabels.emplace( key.inst, name );
1409
1410 if( m_keys.Instance( key.inst ).size() > 1 )
1411 ports.emplace( key.inst, name );
1412 }
1413 }
1414 }
1415
1416 net.count += ports.size();
1417
1418 for( const auto& [instance, name] : hierLabels )
1419 ++net.hierarchy[instance];
1420
1421 std::set<NAME_KEY, KEY_LESS> neighbors( KEY_LESS{ m_keys } );
1422
1423 for( const SLOT_KEY& slot : component.slots )
1424 {
1425 for( const NAME_KEY& edge : m_slots.Slots().Entries().at( slot )->value.edges )
1426 {
1427 if( edge.scope == SCOPE::SHEET )
1428 neighbors.insert( edge );
1429 }
1430 }
1431
1432 for( const NAME_KEY& neighbor : neighbors )
1433 {
1434 ++net.slots[neighbor.inst];
1435 ++net.slotCount;
1436 }
1437
1438 return net;
1439 } );
1440 };
1441 const auto reported = ercIslands();
1442 std::vector<LABEL_CONNECTION_ERROR> result;
1443
1444 for( const auto& [key, island] : auxiliary.Islands() )
1445 {
1446 if( !reported.contains( key ) )
1447 continue;
1448
1449 const auto& facts = m_inputs.InstanceScreenFacts( key.inst ).items;
1450
1451 for( const KIID& id : island.value.items )
1452 {
1453 const ITEM_FACT* label = findFact( facts, id );
1454
1455 if( !label || !isLabel( label->type ) )
1456 continue;
1457
1458 const auto& row = m_published.Rows().at( { id, key.inst } );
1459
1460 if( row.kind == KIND::BUNDLE )
1461 continue;
1462
1463 const CONNECTIONS& net = netConnections( row.component );
1464 const auto localSlots = net.slots.find( key.inst );
1465 const size_t count = net.count + net.slotCount
1466 - ( localSlots == net.slots.end() ? 0 : localSlots->second );
1467 const auto hierarchy = net.hierarchy.find( key.inst );
1468 const bool busNc = busNoConnect.contains( { row.component, key.inst } );
1469
1470 // A local label without pins on its sheet may still route hierarchy or join a net flagged elsewhere
1471 const bool routesHierarchy = hierarchy != net.hierarchy.end()
1472 && hierarchy->second > ( net.busMember ? 0U : 1U );
1473 const bool orphan = label->type == SCH_LABEL_T && !net.localPins.contains( key.inst ) && net.pins > 1
1474 && !net.noConnect && !busNc && !routesHierarchy;
1475 const bool unconnected = island.value.dangling.at( id ) != 0 || count == 0 || orphan;
1476 const bool singlePin = count == 1 && !net.noConnect && !busNc;
1477
1478 if( unconnected )
1479 result.push_back( { m_keys.Instance( key.inst ), id, label->ports.front().position, false } );
1480
1481 if( singlePin )
1482 result.push_back( { m_keys.Instance( key.inst ), id, label->ports.front().position, true } );
1483 }
1484 }
1485
1486 return result;
1487}
1488
1489std::vector<LABEL_LOCATION> ENGINE::DanglingDirectives() const
1490{
1491 wxASSERT( wxThread::IsMain() );
1492 std::vector<LABEL_LOCATION> result;
1493
1494 for( const auto& [key, island] : m_published.Auxiliary().Islands() )
1495 {
1496 const auto& facts = m_inputs.InstanceScreenFacts( key.inst ).items;
1497
1498 for( const auto& [id, dangling] : island.value.dangling )
1499 {
1500 const ITEM_FACT* fact = dangling ? findFact( facts, id ) : nullptr;
1501
1502 if( fact && fact->type == SCH_DIRECTIVE_LABEL_T )
1503 result.push_back( { m_keys.Instance( key.inst ), id, fact->ports.front().position } );
1504 }
1505 }
1506
1507 return result;
1508}
1509
1510std::vector<ERC_PIN_NET> ENGINE::PinNets() const
1511{
1512 wxASSERT( wxThread::IsMain() );
1513 std::map<NAME_ID, ERC_PIN_NET, NAME_LESS> nets( NAME_LESS{ &m_keys } );
1514 const auto netFor = [&]( const ITEM_KEY& key ) -> ERC_PIN_NET*
1515 {
1516 // Active pins and no-connect flags are all island members and must have published rows
1517 const auto& row = m_published.Rows().at( key );
1518
1519 if( row.name == INVALID_ID )
1520 return nullptr;
1521
1522 auto [it, inserted] = nets.try_emplace( row.name );
1523
1524 if( inserted )
1525 it->second.name = m_keys.Name( row.name );
1526
1527 return &it->second;
1528 };
1529
1530 for( INST_ID inst : islandInstances( m_published.Auxiliary().Islands() ) )
1531 {
1532 for( const ITEM_FACT& fact : m_inputs.InstanceScreenFacts( inst ).items )
1533 {
1534 if( fact.type == SCH_NO_CONNECT_T )
1535 {
1536 if( auto* net = netFor( { fact.id, inst } ) )
1537 net->noConnect = true;
1538 }
1539
1540 for( const PIN_FACT& pin : fact.pins )
1541 {
1542 const auto* text = m_inputs.Text( { pin.id, inst } );
1543 auto* net = text ? netFor( { pin.id, inst } ) : nullptr;
1544
1545 if( !net )
1546 continue;
1547
1548 net->noConnect |= pin.type == ELECTRICAL_PINTYPE::PT_NC;
1549 net->powerDriven |= pin.type == ELECTRICAL_PINTYPE::PT_POWER_OUT;
1550 net->pins.push_back( { m_keys.Instance( inst ), fact.owner, text->value.reference, pin } );
1551 }
1552 }
1553 }
1554
1555 return moveValues( nets );
1556}
1557
1558std::vector<MULTI_UNIT_CONFLICT> ENGINE::MultiUnitPinConflicts() const
1559{
1560 wxASSERT( wxThread::IsMain() );
1561 struct PIN_ON_NET
1562 {
1564 NAME_ID net;
1565 };
1566 std::map<std::pair<std::string, std::string>, std::vector<PIN_ON_NET>> groups;
1567
1568 for( INST_ID inst : islandInstances( m_published.Auxiliary().Islands() ) )
1569 {
1570 for( const ITEM_FACT& fact : m_inputs.InstanceScreenFacts( inst ).items )
1571 {
1572 if( !fact.multiUnit )
1573 continue;
1574
1575 for( const PIN_FACT& pin : fact.pins )
1576 {
1577 const auto* text = m_inputs.Text( { pin.id, inst } );
1578
1579 if( !text )
1580 continue;
1581
1582 const auto& row = m_published.Rows().at( { pin.id, inst } );
1583
1584 if( row.name == INVALID_ID )
1585 continue;
1586
1587 groups[{ text->value.reference.utf8_string(), pin.shownNumber.utf8_string() }].push_back(
1588 { { m_keys.Instance( inst ), pin.id, pin.position }, row.name } );
1589 }
1590 }
1591 }
1592
1593 std::vector<MULTI_UNIT_CONFLICT> result;
1594
1595 for( auto& [key, pins] : groups )
1596 {
1597 std::ranges::sort( pins, [&]( const PIN_ON_NET& a, const PIN_ON_NET& b )
1598 {
1599 if( a.net != b.net )
1600 return m_keys.NameLess( a.net, b.net );
1601
1602 if( a.location.item != b.location.item )
1603 return a.location.item < b.location.item;
1604
1605 // Tuple comparison would select vector's lexical ordering for KIID_PATH
1606 return a.location.sheet < b.location.sheet;
1607 } );
1608 const PIN_ON_NET& first = pins.front();
1609 const auto other = std::ranges::find_if( pins, [&]( const PIN_ON_NET& pin ) { return pin.net != first.net; } );
1610
1611 if( other != pins.end() )
1612 {
1613 result.push_back( { first.location, other->location, wxString::FromUTF8( key.second ),
1614 m_keys.Name( first.net ), m_keys.Name( other->net ) } );
1615 }
1616 }
1617
1618 return result;
1619}
1620
1621std::vector<NAMED_ITEM> ENGINE::NamedItems() const
1622{
1623 wxASSERT( wxThread::IsMain() );
1624 std::vector<NAMED_ITEM> result;
1625 const auto unescapeNetName = []( wxString name )
1626 {
1627 // CTX_NETNAME only escapes slashes; other brace tokens are literal name text
1628 name.Replace( wxS( "{slash}" ), wxS( "/" ) );
1629 return name;
1630 };
1631
1632 for( INST_ID inst : islandInstances( m_published.Auxiliary().Islands() ) )
1633 {
1634 const KIID_PATH& path = m_keys.Instance( inst );
1635
1636 for( const ITEM_FACT& fact : m_inputs.InstanceScreenFacts( inst ).items )
1637 {
1638 if( isLabel( fact.type ) )
1639 {
1640 const auto* text = m_inputs.Text( { fact.id, inst } );
1641 result.push_back( { path, fact.id, fact.ports.front().position,
1642 unescapeNetName( text->value.name ), fact.type, fact.type == SCH_GLOBAL_LABEL_T } );
1643 }
1644
1645 for( const PIN_FACT& pin : fact.pins )
1646 {
1647 if( !pin.globalPower && !pin.localPower )
1648 continue;
1649
1650 // Pins from unselected units have no per-instance text entry
1651 if( const auto* text = m_inputs.Text( { pin.id, inst } ) )
1652 {
1653 result.push_back( { path, pin.id, pin.position, unescapeNetName( text->value.name ),
1654 SCH_PIN_T, pin.globalPower } );
1655 }
1656 }
1657 }
1658 }
1659
1660 return result;
1661}
1662
1663std::vector<LABEL_LOCATION> ENGINE::SingleGlobalLabels() const
1664{
1665 wxASSERT( wxThread::IsMain() );
1666 std::map<std::string, std::optional<LABEL_LOCATION>> labels;
1667
1668 for( INST_ID inst : islandInstances( m_published.Auxiliary().Islands() ) )
1669 {
1670 for( const ITEM_FACT& fact : m_inputs.InstanceScreenFacts( inst ).items )
1671 {
1672 if( fact.type != SCH_GLOBAL_LABEL_T )
1673 continue;
1674
1675 const wxString& name = m_inputs.Text( { fact.id, inst } )->value.name;
1676 auto [entry, inserted] = labels.try_emplace( name.utf8_string(),
1677 LABEL_LOCATION{ m_keys.Instance( inst ), fact.id, fact.ports.front().position } );
1678
1679 if( !inserted )
1680 entry->second.reset();
1681 }
1682 }
1683
1684 std::vector<LABEL_LOCATION> result;
1685
1686 for( const auto& [name, label] : labels )
1687 {
1688 if( label )
1689 result.push_back( *label );
1690 }
1691
1692 return result;
1693}
1694
1695std::vector<HIERARCHY_ERROR> ENGINE::HierarchyErrors() const
1696{
1697 wxASSERT( wxThread::IsMain() );
1698 using ERROR_KIND = HIERARCHY_ERROR::KIND;
1699 struct PORTS
1700 {
1701 std::map<wxString, std::vector<const ITEM_FACT*>> labels;
1702 std::map<std::pair<KIID, wxString>, std::vector<const ITEM_FACT*>> pins;
1703 };
1704 std::map<INST_ID, PORTS> instances;
1705 std::vector<HIERARCHY_ERROR> result;
1706 const auto& auxiliary = m_published.Auxiliary();
1707
1708 for( INST_ID inst : islandInstances( auxiliary.Islands() ) )
1709 {
1710 PORTS& ports = instances[inst];
1711
1712 for( const ITEM_FACT& fact : m_inputs.InstanceScreenFacts( inst ).items )
1713 {
1714 if( fact.type != SCH_HIER_LABEL_T && fact.type != SCH_SHEET_PIN_T )
1715 continue;
1716
1717 const wxString& name = m_inputs.Text( { fact.id, inst } )->value.name;
1718
1719 if( fact.type == SCH_HIER_LABEL_T )
1720 {
1721 ports.labels[name].push_back( &fact );
1722 }
1723 else
1724 {
1725 ports.pins[{ fact.owner, name }].push_back( &fact );
1726 const auto& record = auxiliary.IslandOf().at( { fact.id, inst } );
1727
1728 if( auxiliary.Islands().at( record ).value.dangling.at( fact.id ) != 0 )
1729 {
1730 result.push_back( { m_keys.Instance( inst ), fact.id, fact.ports.front().position,
1731 name, ERROR_KIND::DANGLING_PIN, {} } );
1732 }
1733 }
1734 }
1735 }
1736
1737 const auto mismatch = [&]( const KIID_PATH& path, const std::vector<const ITEM_FACT*>& items,
1738 const wxString& name, ERROR_KIND kind )
1739 {
1740 const ITEM_FACT& first = *items.front();
1741 HIERARCHY_ERROR error{ path, first.id, first.ports.front().position, name, kind, {} };
1742
1743 for( const ITEM_FACT* item : items )
1744 error.equivalentItems.push_back( item->id );
1745
1746 result.push_back( std::move( error ) );
1747 };
1748
1749 for( const auto& [inst, ports] : instances )
1750 {
1751 const KIID_PATH& path = m_keys.Instance( inst );
1752 KIID_PATH parentPath = path;
1753 const KIID owner = parentPath.back();
1754 parentPath.pop_back();
1755 const auto parent = m_keys.FindInstance( parentPath );
1756 const bool hasParent = parent && m_inputs.FindInstance( *parent );
1757
1758 const auto parentPorts = hasParent ? instances.find( *parent ) : instances.end();
1759
1760 for( const auto& [name, labels] : ports.labels )
1761 {
1762 if( !hasParent )
1763 {
1764 for( const ITEM_FACT* label : labels )
1765 {
1766 result.push_back( { path, label->id, label->ports.front().position, name,
1767 ERROR_KIND::ROOT_LABEL, {} } );
1768 }
1769 }
1770 else if( parentPorts == instances.end() || !parentPorts->second.pins.contains( { owner, name } ) )
1771 {
1772 mismatch( path, labels, name, ERROR_KIND::MISSING_PIN );
1773 }
1774 }
1775
1776 for( const auto& [key, pins] : ports.pins )
1777 {
1778 KIID_PATH childPath = path;
1779 childPath.push_back( key.first );
1780 const auto child = m_keys.FindInstance( childPath );
1781 const auto found = child ? instances.find( *child ) : instances.end();
1782
1783 if( found == instances.end() || !found->second.labels.contains( key.second ) )
1784 mismatch( path, pins, key.second, ERROR_KIND::MISSING_LABEL );
1785 }
1786 }
1787
1788 std::ranges::sort( result, []( const auto& a, const auto& b )
1789 {
1790 if( a.sheet != b.sheet )
1791 return a.sheet < b.sheet;
1792
1793 // Keep KIID_PATH out of tuple comparisons, which select vector's lexical ordering
1794 return std::tie( a.item, a.kind ) < std::tie( b.item, b.kind );
1795 } );
1796 return result;
1797}
1798} // 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< 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.
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::string path
KIBIS_PIN * pin
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:683