KiCad PCB EDA Suite
Loading...
Searching...
No Matches
sch_io_pads.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2025 KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 3
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
24
26
27#include <lib_symbol.h>
28#include <page_info.h>
29#include <sch_junction.h>
30#include <sch_label.h>
31#include <sch_line.h>
32#include <sch_pin.h>
33#include <sch_screen.h>
34#include <sch_shape.h>
35#include <sch_sheet.h>
36#include <sch_sheet_path.h>
37#include <sch_symbol.h>
38#include <sch_text.h>
39#include <schematic.h>
40#include <schematic_settings.h>
42
43#include <math/util.h>
44#include <stroke_params.h>
45
46#include <advanced_config.h>
47#include <io/pads/pads_common.h>
48#include <locale_io.h>
49#include <progress_reporter.h>
50#include <reporter.h>
51
52#include <fstream>
53#include <map>
54#include <set>
55#include <wx/filename.h>
56#include <wx/log.h>
57
58
64static std::string extractConnectorPinNumber( const std::string& aRef )
65{
66 size_t sepPos = aRef.rfind( '-' );
67
68 if( sepPos == std::string::npos )
69 sepPos = aRef.rfind( '.' );
70
71 if( sepPos != std::string::npos && sepPos + 1 < aRef.size()
72 && std::isdigit( static_cast<unsigned char>( aRef[sepPos + 1] ) ) )
73 {
74 return aRef.substr( sepPos + 1 );
75 }
76
77 return "";
78}
79
80
86static std::string extractConnectorBaseRef( const std::string& aRef )
87{
88 size_t sepPos = aRef.rfind( '-' );
89
90 if( sepPos == std::string::npos )
91 sepPos = aRef.rfind( '.' );
92
93 if( sepPos != std::string::npos && sepPos + 1 < aRef.size()
94 && std::isdigit( static_cast<unsigned char>( aRef[sepPos + 1] ) ) )
95 {
96 return aRef.substr( 0, sepPos );
97 }
98
99 return aRef;
100}
101
102
107static std::string stripGateSuffix( const std::string& aRef )
108{
109 size_t sepPos = aRef.rfind( '-' );
110
111 if( sepPos == std::string::npos )
112 sepPos = aRef.rfind( '.' );
113
114 if( sepPos != std::string::npos && sepPos + 1 < aRef.size()
115 && std::isalpha( static_cast<unsigned char>( aRef[sepPos + 1] ) ) )
116 {
117 return aRef.substr( 0, sepPos );
118 }
119
120 return aRef;
121}
122
123
124static SCH_TEXT* createSchText( const PADS_SCH::TEXT_ITEM& aText, const VECTOR2I& aPos )
125{
126 SCH_TEXT* schText = new SCH_TEXT( aPos, wxString::FromUTF8( aText.content ) );
127
128 if( aText.height > 0 )
129 {
130 int scaledSize = schIUScale.MilsToIU( aText.height );
131 int charHeight = static_cast<int>( scaledSize
133 int charWidth = static_cast<int>( scaledSize
135 schText->SetTextSize( VECTOR2I( charWidth, charHeight ) );
136 }
137
138 if( aText.width_factor > 0 )
139 schText->SetTextThickness( schIUScale.MilsToIU( aText.width_factor ) );
140
141 // PADS justification: value = vertical_offset + horizontal_code
142 // Vertical offsets: bottom=0, top=2, middle=8
143 // Horizontal codes: left=0, right=1, center=4
144 int justVal = aText.justification;
145 int hCode = 0;
146 int vGroup = 0;
147
148 if( justVal >= 8 )
149 {
150 vGroup = 2; // middle
151 hCode = justVal - 8;
152 }
153 else if( justVal >= 2 )
154 {
155 vGroup = 1; // top
156 hCode = justVal - 2;
157 }
158 else
159 {
160 vGroup = 0; // bottom
161 hCode = justVal;
162 }
163
164 switch( hCode )
165 {
166 default:
167 case 0: schText->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT ); break;
168 case 1: schText->SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT ); break;
169 case 4: schText->SetHorizJustify( GR_TEXT_H_ALIGN_CENTER ); break;
170 }
171
172 switch( vGroup )
173 {
174 default:
175 case 0: schText->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM ); break;
176 case 1: schText->SetVertJustify( GR_TEXT_V_ALIGN_TOP ); break;
177 case 2: schText->SetVertJustify( GR_TEXT_V_ALIGN_CENTER ); break;
178 }
179
180 if( aText.rotation != 0 )
181 schText->SetTextAngleDegrees( aText.rotation * 90.0 );
182
183 return schText;
184}
185
186
194static int computePowerOrientation( const std::string& aOpcId,
195 const std::vector<PADS_SCH::SCH_SIGNAL>& aSignals,
196 const VECTOR2I& aOpcPos, bool aPinUp, int aPageHeightIU )
197{
198 // Find the wire endpoint matching this OPC and get the adjacent vertex
199 std::string opcRef = "@@@O" + aOpcId;
200 VECTOR2I adjPos = aOpcPos;
201 bool found = false;
202
203 for( const auto& signal : aSignals )
204 {
205 for( const auto& wire : signal.wires )
206 {
207 if( wire.vertices.size() < 2 )
208 continue;
209
210 if( wire.endpoint_a == opcRef )
211 {
212 adjPos = VECTOR2I(
213 schIUScale.MilsToIU( KiROUND( wire.vertices[1].x ) ),
214 aPageHeightIU
215 - schIUScale.MilsToIU( KiROUND( wire.vertices[1].y ) ) );
216 found = true;
217 break;
218 }
219
220 if( wire.endpoint_b == opcRef )
221 {
222 size_t last = wire.vertices.size() - 1;
223 adjPos = VECTOR2I(
224 schIUScale.MilsToIU( KiROUND( wire.vertices[last - 1].x ) ),
225 aPageHeightIU
226 - schIUScale.MilsToIU(
227 KiROUND( wire.vertices[last - 1].y ) ) );
228 found = true;
229 break;
230 }
231 }
232
233 if( found )
234 break;
235 }
236
237 if( !found )
239
240 // Wire goes from aOpcPos toward adjPos
241 int dx = adjPos.x - aOpcPos.x;
242 int dy = adjPos.y - aOpcPos.y;
243
244 // Determine which direction the wire approaches from (relative to OPC position).
245 // The symbol body should face AWAY from the wire.
246 // In KiCad Y-down coordinates: dy > 0 means wire goes down from OPC.
247
248 if( std::abs( dx ) >= std::abs( dy ) )
249 {
250 // Horizontal wire
251 if( dx > 0 )
252 {
253 // Wire goes right → body should face left
256 }
257 else
258 {
259 // Wire goes left → body should face right
262 }
263 }
264 else
265 {
266 // Vertical wire
267 if( dy > 0 )
268 {
269 // Wire goes down → body should face up
272 }
273 else
274 {
275 // Wire goes up → body should face down
278 }
279 }
280}
281
282
283SCH_IO_PADS::SCH_IO_PADS() : SCH_IO( wxS( "PADS Logic" ) )
284{
285}
286
287
291
292
293bool SCH_IO_PADS::CanReadSchematicFile( const wxString& aFileName ) const
294{
295 if( !SCH_IO::CanReadSchematicFile( aFileName ) )
296 return false;
297
298 return checkFileHeader( aFileName );
299}
300
301
302bool SCH_IO_PADS::CanReadLibrary( const wxString& aFileName ) const
303{
304 if( !SCH_IO::CanReadLibrary( aFileName ) )
305 return false;
306
307 return checkFileHeader( aFileName );
308}
309
310
311SCH_SHEET* SCH_IO_PADS::LoadSchematicFile( const wxString& aFileName,
312 SCHEMATIC* aSchematic,
313 SCH_SHEET* aAppendToMe,
314 const std::map<std::string, UTF8>* aProperties )
315{
316 wxCHECK( !aFileName.IsEmpty() && aSchematic, nullptr );
317
318 LOCALE_IO setlocale;
319
320 SCH_SHEET* rootSheet = nullptr;
321
322 if( aAppendToMe )
323 {
324 wxCHECK_MSG( aSchematic->IsValid(), nullptr, "Can't append to a schematic with no root!" );
325 rootSheet = aAppendToMe;
326 }
327 else
328 {
329 rootSheet = new SCH_SHEET( aSchematic );
330 rootSheet->SetFileName( aFileName );
331 aSchematic->SetTopLevelSheets( { rootSheet } );
332 }
333
334 if( !rootSheet->GetScreen() )
335 {
336 SCH_SCREEN* screen = new SCH_SCREEN( aSchematic );
337 screen->SetFileName( aFileName );
338 rootSheet->SetScreen( screen );
339
340 const_cast<KIID&>( rootSheet->m_Uuid ) = screen->GetUuid();
341 }
342
343 SCH_SHEET_PATH rootPath;
344 rootPath.push_back( rootSheet );
345
346 SCH_SCREEN* rootScreen = rootSheet->GetScreen();
347 wxCHECK( rootScreen, nullptr );
348
349 SCH_SHEET_INSTANCE sheetInstance;
350 sheetInstance.m_Path = rootPath.Path();
351 sheetInstance.m_PageNumber = wxT( "#" );
352 rootScreen->m_sheetInstances.emplace_back( sheetInstance );
353
355 m_progressReporter->SetNumPhases( 3 );
356
358 std::string filename( aFileName.ToUTF8() );
359
360 if( !parser.Parse( filename ) )
361 {
362 THROW_IO_ERROR( wxString::Format( wxT( "Failed to parse PADS file: %s" ), aFileName ) );
363 }
364
366 m_progressReporter->BeginPhase( 1 );
367
368 const PADS_SCH::PARAMETERS& params = parser.GetParameters();
369 PADS_SCH::PADS_SCH_SYMBOL_BUILDER symbolBuilder( params );
370 PADS_SCH::PADS_SCH_SCHEMATIC_BUILDER schBuilder( params, aSchematic );
371
372 // Detect gate suffix separator from multi-gate part references (e.g. U17-A → '-')
373 for( const auto& part : parser.GetPartPlacements() )
374 {
375 const std::string& ref = part.reference;
376 size_t dashPos = ref.rfind( '-' );
377 size_t dotPos = ref.rfind( '.' );
378 size_t sepPos = std::string::npos;
379
380 if( dashPos != std::string::npos )
381 sepPos = dashPos;
382 else if( dotPos != std::string::npos )
383 sepPos = dotPos;
384
385 if( sepPos != std::string::npos && sepPos + 1 < ref.size()
386 && std::isalpha( static_cast<unsigned char>( ref[sepPos + 1] ) ) )
387 {
388 aSchematic->Settings().m_SubpartIdSeparator = static_cast<int>( ref[sepPos] );
389 aSchematic->Settings().m_SubpartFirstId = 'A';
390 break;
391 }
392 }
393
394 // Set KiCad page size to match the PADS drawing sheet
395 PAGE_INFO pageInfo;
396
397 if( !params.sheet_size.name.empty() )
398 pageInfo.SetType( wxString::FromUTF8( params.sheet_size.name ) );
399 else
400 pageInfo.SetType( PAGE_SIZE_TYPE::A );
401
402 // PADS Y-up to KiCad Y-down: Y_kicad = pageHeight - Y_pads
403 const int pageHeightIU = pageInfo.GetHeightIU( schIUScale.IU_PER_MILS );
404
405 // Build LIB_SYMBOL objects from all CAEDECAL definitions
406 for( const PADS_SCH::SYMBOL_DEF& symDef : parser.GetSymbolDefs() )
407 symbolBuilder.GetOrCreateSymbol( symDef );
408
409 std::set<int> sheetNumbers = parser.GetSheetNumbers();
410
411 if( sheetNumbers.empty() )
412 sheetNumbers.insert( 1 );
413
414 bool isSingleSheet = ( sheetNumbers.size() == 1 );
415
416 // Map sheet number -> (SCH_SHEET*, SCH_SCREEN*, SCH_SHEET_PATH)
417 struct SheetContext
418 {
419 SCH_SHEET* sheet = nullptr;
420 SCH_SCREEN* screen = nullptr;
422 };
423
424 std::map<int, SheetContext> sheetContexts;
425
426 if( isSingleSheet )
427 {
428 int sheetNum = *sheetNumbers.begin();
429 SheetContext ctx;
430 ctx.sheet = rootSheet;
431 ctx.screen = rootScreen;
432 ctx.path = rootPath;
433 ctx.screen->SetPageSettings( pageInfo );
434 sheetContexts[sheetNum] = ctx;
435 }
436 else
437 {
438 // Multi-sheet: root is a container with sub-sheets
439 int totalSheets = static_cast<int>( sheetNumbers.size() );
440
441 for( int sheetNum : sheetNumbers )
442 {
443 SCH_SHEET* subSheet = schBuilder.CreateHierarchicalSheet(
444 sheetNum, totalSheets, rootSheet, aFileName );
445
446 if( !subSheet )
447 continue;
448
449 // Find the sheet name from parser headers
450 for( const PADS_SCH::SHEET_HEADER& hdr : parser.GetSheetHeaders() )
451 {
452 if( hdr.sheet_num == sheetNum && !hdr.sheet_name.empty() )
453 {
455 wxString::FromUTF8( hdr.sheet_name ) );
456
457 break;
458 }
459 }
460
461 SCH_SHEET_PATH subPath;
462 subPath.push_back( rootSheet );
463 subPath.push_back( subSheet );
464
465 wxString pageNo = wxString::Format( wxT( "%d" ), sheetNum );
466 subPath.SetPageNumber( pageNo );
467
468 SCH_SHEET_INSTANCE subInstance;
469 subInstance.m_Path = subPath.Path();
470 subInstance.m_PageNumber = pageNo;
471 subSheet->GetScreen()->m_sheetInstances.emplace_back( subInstance );
472
473 SheetContext ctx;
474 ctx.sheet = subSheet;
475 ctx.screen = subSheet->GetScreen();
476 ctx.path = subPath;
477 ctx.screen->SetPageSettings( pageInfo );
478 sheetContexts[sheetNum] = ctx;
479 }
480 }
481
483 m_progressReporter->BeginPhase( 2 );
484
485 // Track connector base references for wire-endpoint label creation
486 std::set<std::string> connectorBaseRefs;
487
488 // Pre-scan connector placements to group pins by base reference.
489 // Each group becomes one multi-unit connector symbol in KiCad.
490 struct ConnectorGroup
491 {
492 std::vector<std::string> pinNumbers;
493 std::map<std::string, int> pinToUnit;
494 std::string partType;
495 };
496
497 std::map<std::string, ConnectorGroup> connectorGroups;
498
499 for( const auto& [sheetNum, ctx] : sheetContexts )
500 {
501 std::vector<PADS_SCH::PART_PLACEMENT> sheetParts = parser.GetPartsOnSheet( sheetNum );
502
503 for( const PADS_SCH::PART_PLACEMENT& part : sheetParts )
504 {
505 auto ptIt = parser.GetPartTypes().find( part.part_type );
506
507 if( ptIt == parser.GetPartTypes().end() || !ptIt->second.is_connector )
508 continue;
509
510 std::string pinNum = extractConnectorPinNumber( part.reference );
511
512 if( pinNum.empty() )
513 continue;
514
515 std::string baseRef = extractConnectorBaseRef( part.reference );
516 ConnectorGroup& group = connectorGroups[baseRef];
517 group.partType = part.part_type;
518 group.pinNumbers.push_back( pinNum );
519 }
520 }
521
522 for( auto& [baseRef, group] : connectorGroups )
523 {
524 std::sort( group.pinNumbers.begin(), group.pinNumbers.end(),
525 []( const std::string& a, const std::string& b )
526 {
527 return std::stoi( a ) < std::stoi( b );
528 } );
529
530 for( size_t i = 0; i < group.pinNumbers.size(); i++ )
531 group.pinToUnit[group.pinNumbers[i]] = static_cast<int>( i + 1 );
532 }
533
534 // Place symbols on each sheet
535 for( auto& [sheetNum, ctx] : sheetContexts )
536 {
537 std::vector<PADS_SCH::PART_PLACEMENT> parts = parser.GetPartsOnSheet( sheetNum );
538
539 for( const PADS_SCH::PART_PLACEMENT& part : parts )
540 {
541 auto ptIt = parser.GetPartTypes().find( part.part_type );
542
543 LIB_SYMBOL* libSymbol = nullptr;
544 bool isMultiGate = false;
545 bool isConnector = false;
546 bool isPower = false;
547 std::string libItemName;
548 std::string connectorPinNumber;
549
550 if( ptIt != parser.GetPartTypes().end() )
551 {
552 const PADS_SCH::PARTTYPE_DEF& ptDef = ptIt->second;
553
554 if( ptDef.gates.size() > 1 )
555 {
556 // Multi-gate PARTTYPE: composite multi-unit symbol
557 libSymbol = symbolBuilder.GetOrCreateMultiUnitSymbol(
558 ptDef, parser.GetSymbolDefs() );
559 libItemName = ptDef.name;
560 isMultiGate = true;
561 }
562 else if( !ptDef.gates.empty() )
563 {
564 const PADS_SCH::GATE_DEF& gate = ptDef.gates[0];
565 int idx = std::max( 0, part.gate_index );
566 std::string decalName;
567
568 if( idx < static_cast<int>( gate.decal_names.size() ) )
569 decalName = gate.decal_names[idx];
570 else if( !gate.decal_names.empty() )
571 decalName = gate.decal_names[0];
572
573 const PADS_SCH::SYMBOL_DEF* symDef = parser.GetSymbolDef( decalName );
574
575 connectorPinNumber = ptDef.is_connector
576 ? extractConnectorPinNumber( part.reference )
577 : std::string();
578
579 if( symDef && !connectorPinNumber.empty() )
580 {
581 // Multi-unit connector placement (e.g. J12-15 → unit of J12).
582 // All pins of the same connector share one multi-unit symbol.
583 std::string baseRef = extractConnectorBaseRef( part.reference );
584 auto groupIt = connectorGroups.find( baseRef );
585
586 if( groupIt != connectorGroups.end() )
587 {
588 std::string cacheKey = ptDef.name + ":conn:" + baseRef;
589
590 libSymbol = symbolBuilder.GetOrCreateMultiUnitConnectorSymbol(
591 ptDef, *symDef, groupIt->second.pinNumbers, cacheKey );
592 libItemName = ptDef.name + "_" + baseRef;
593 isConnector = true;
594 isMultiGate = true;
595
596 connectorBaseRefs.insert( baseRef );
597 }
598 }
599 else if( symDef )
600 {
601 libSymbol = symbolBuilder.GetOrCreatePartTypeSymbol( ptDef, *symDef );
602 libItemName = decalName;
603 }
604 }
605 else if( !ptDef.special_variants.empty() )
606 {
607 // Power/ground symbols
608 int idx = std::max( 0, part.gate_index );
609 idx = std::min( idx, static_cast<int>( ptDef.special_variants.size() ) - 1 );
610 std::string decalName = ptDef.special_variants[idx].decal_name;
611
612 const PADS_SCH::SYMBOL_DEF* symDef = parser.GetSymbolDef( decalName );
613
614 if( symDef )
615 {
616 libSymbol = symbolBuilder.GetOrCreateSymbol( *symDef );
617 libItemName = decalName;
618 }
619 }
620
621 if( !ptDef.special_keyword.empty() && ptDef.special_keyword != "OFF" )
622 isPower = true;
623 }
624
625 // Fallback: resolve directly by CAEDECAL name
626 if( !libSymbol )
627 {
628 const PADS_SCH::SYMBOL_DEF* symDef = parser.GetSymbolDef( part.symbol_name );
629
630 if( !symDef )
631 {
632 m_errorMessages.emplace(
633 wxString::Format( wxT( "PADS Import: symbol '%s' not found,"
634 " part '%s' skipped" ),
635 wxString::FromUTF8( part.symbol_name ),
636 wxString::FromUTF8( part.reference ) ),
638 continue;
639 }
640
641 libSymbol = symbolBuilder.GetOrCreateSymbol( *symDef );
642 libItemName = symDef->name;
643 }
644
645 if( !libSymbol )
646 continue;
647
648 if( ptIt != parser.GetPartTypes().end() && !ptIt->second.sigpins.empty() )
649 symbolBuilder.AddHiddenPowerPins( libSymbol, ptIt->second.sigpins );
650
651 if( !isPower )
652 isPower = PADS_SCH::PADS_SCH_SYMBOL_BUILDER::IsPowerSymbol( part.part_type );
653
654 // Resolve power symbol style. Prefer the PARTTYPE variant decal style
655 // (e.g. +BUBBLE → +VDC) which preserves the original PADS symbol shape,
656 // falling back to net-name matching (e.g. GND → GND, +5V → +5V).
657 std::string powerStyle;
658
659 if( isPower && ptIt != parser.GetPartTypes().end()
660 && !ptIt->second.special_variants.empty() )
661 {
662 int varIdx = std::max( 0, part.gate_index );
663 varIdx = std::min(
664 varIdx,
665 static_cast<int>( ptIt->second.special_variants.size() ) - 1 );
666 const auto& variant = ptIt->second.special_variants[varIdx];
667
669 variant.decal_name, variant.pin_type );
670 }
671
672 if( isPower && powerStyle.empty() )
673 {
674 std::string rawNetName = part.power_net_name.empty()
675 ? part.symbol_name
676 : part.power_net_name;
677
678 auto powerLibId =
680
681 if( powerLibId )
682 powerStyle = std::string( powerLibId->GetLibItemName().c_str() );
683 }
684
685 auto symbolPtr = std::make_unique<SCH_SYMBOL>();
686 SCH_SYMBOL* symbol = symbolPtr.get();
687 LIB_SYMBOL* instanceSymbol = nullptr;
688
689 if( isPower && !powerStyle.empty() )
690 {
691 instanceSymbol = symbolBuilder.BuildKiCadPowerSymbol( powerStyle );
692
693 LIB_ID libId;
694 libId.SetLibNickname( wxT( "power" ) );
695 libId.SetLibItemName( wxString::FromUTF8( powerStyle ) );
696 symbol->SetLibId( libId );
697 }
698 else
699 {
700 LIB_ID libId;
701 libId.SetLibNickname( wxT( "pads_import" ) );
702 libId.SetLibItemName( wxString::FromUTF8( libItemName ) );
703 symbol->SetLibId( libId );
704
705 instanceSymbol = new LIB_SYMBOL( *libSymbol );
706
707 if( isPower )
708 instanceSymbol->SetGlobalPower();
709 }
710
711 symbol->SetLibSymbol( instanceSymbol );
712 symbol->SetPosition( VECTOR2I(
713 schIUScale.MilsToIU( KiROUND( part.position.x ) ),
714 pageHeightIU - schIUScale.MilsToIU( KiROUND( part.position.y ) ) ) );
715
716 int orientation = SYMBOL_ORIENTATION_T::SYM_ORIENT_0;
717
718 if( part.rotation == 90.0 )
720 else if( part.rotation == 180.0 )
722 else if( part.rotation == 270.0 )
724
725 if( part.mirror_flags & 1 )
727
728 if( part.mirror_flags & 2 )
730
731 symbol->SetOrientation( orientation );
732
733 if( isConnector && !connectorPinNumber.empty() )
734 {
735 std::string baseRef = extractConnectorBaseRef( part.reference );
736 auto groupIt = connectorGroups.find( baseRef );
737
738 if( groupIt != connectorGroups.end() )
739 {
740 auto unitIt = groupIt->second.pinToUnit.find( connectorPinNumber );
741
742 if( unitIt != groupIt->second.pinToUnit.end() )
743 symbol->SetUnit( unitIt->second );
744 else
745 symbol->SetUnit( 1 );
746 }
747 else
748 {
749 symbol->SetUnit( 1 );
750 }
751 }
752 else if( isMultiGate )
753 {
754 symbol->SetUnit( part.gate_index + 1 );
755 }
756 else
757 {
758 symbol->SetUnit( 1 );
759 }
760
761 // Assign deterministic UUID so PCB cross-probe can match footprints
762 // to symbols. Only the primary gate (index 0) or the first connector
763 // pin gets the deterministic UUID since one footprint maps to one
764 // symbol instance.
765 bool isPrimaryUnit = isConnector
766 ? ( symbol->GetUnit() == 1 )
767 : ( !isMultiGate || part.gate_index == 0 );
768
769 if( !isPower && isPrimaryUnit )
770 {
771 std::string baseRef = isConnector
772 ? extractConnectorBaseRef( part.reference )
773 : stripGateSuffix( part.reference );
774
775 const_cast<KIID&>( symbol->m_Uuid ) =
777 }
778
779 symbol->SetRef( &ctx.path, wxString::FromUTF8( part.reference ) );
780
781 schBuilder.ApplyPartAttributes( symbol, part );
782 schBuilder.CreateCustomFields( symbol, part );
783
784 // For connectors, override reference to the base (e.g. "J12" not "J12-1").
785 // Must happen after ApplyPartAttributes which only strips alpha suffixes.
786 if( isConnector )
787 {
788 std::string baseRef = extractConnectorBaseRef( part.reference );
789 symbol->SetRef( &ctx.path, wxString::FromUTF8( baseRef ) );
790 }
791
792 // For multi-gate parts, strip the alpha gate suffix (e.g. "U1-A" → "U1")
793 // so KiCad recognizes all units as belonging to the same part.
794 if( isMultiGate && !isConnector )
795 {
796 std::string baseRef = stripGateSuffix( part.reference );
797 symbol->SetRef( &ctx.path, wxString::FromUTF8( baseRef ) );
798 }
799
800 // For passive components, override Value with VALUE1 parametric value
801 // so that e.g. C10 shows "0.1uF" instead of the generic "CAPMF0805".
802 // Also apply the VALUE1 attribute position.
803 if( ptIt != parser.GetPartTypes().end() )
804 {
805 const std::string& cat = ptIt->second.category;
806
807 if( cat == "CAP" || cat == "RES" || cat == "IND" )
808 {
809 auto valIt = part.attr_overrides.find( "VALUE" );
810
811 if( valIt == part.attr_overrides.end() )
812 valIt = part.attr_overrides.find( "VALUE1" );
813
814 if( valIt != part.attr_overrides.end() && !valIt->second.empty() )
815 {
816 symbol->SetValueFieldText( wxString::FromUTF8( valIt->second ) );
817
818 for( const auto& attr : part.attributes )
819 {
820 if( attr.name == "VALUE" || attr.name == "VALUE1"
821 || attr.name == "Value1" )
822 {
823 SCH_FIELD* valField = symbol->GetField( FIELD_T::VALUE );
824 int fx = schIUScale.MilsToIU( KiROUND( attr.position.x ) );
825
826 if( part.mirror_flags & 1 )
827 fx = -fx;
828
829 VECTOR2I fieldPos( fx,
830 -schIUScale.MilsToIU(
831 KiROUND( attr.position.y ) ) );
832 valField->SetPosition( symbol->GetPosition() + fieldPos );
833
834 int fieldTextSize = schIUScale.MilsToIU( 50 );
835 valField->SetTextSize(
836 VECTOR2I( fieldTextSize, fieldTextSize ) );
839 break;
840 }
841 }
842 }
843 }
844 }
845
846 if( isPower )
847 {
848 symbol->GetField( FIELD_T::REFERENCE )->SetVisible( false );
849
850 wxString netName = part.power_net_name.empty()
851 ? wxString::FromUTF8( part.symbol_name )
852 : wxString::FromUTF8( part.power_net_name );
853
854 if( netName.StartsWith( wxT( "/" ) ) )
855 netName = wxT( "~{" ) + netName.Mid( 1 ) + wxT( "}" );
856
857 symbol->GetField( FIELD_T::VALUE )->SetText( netName );
858 symbol->GetField( FIELD_T::VALUE )->SetVisible( true );
859 }
860
861 {
862 std::string hierRef;
863
864 if( isConnector )
865 hierRef = extractConnectorBaseRef( part.reference );
866 else if( isMultiGate )
867 hierRef = stripGateSuffix( part.reference );
868 else
869 hierRef = part.reference;
870
871 symbol->AddHierarchicalReference( ctx.path.Path(),
872 wxString::FromUTF8( hierRef ),
873 symbol->GetUnit() );
874 }
875
876 symbol->ClearFlags();
877
878 // For connector pins, create a local label at the pin position
879 // before transferring ownership to the screen.
880 // The matching label at the wire endpoint creates the electrical connection.
881 if( isConnector && !connectorPinNumber.empty() )
882 {
883 std::string baseRef = extractConnectorBaseRef( part.reference );
884 wxString labelText = wxString::Format( wxT( "%s.%s" ),
885 wxString::FromUTF8( baseRef ),
886 wxString::FromUTF8( connectorPinNumber ) );
887
888 VECTOR2I pinPos = symbol->GetPosition();
889 std::vector<SCH_PIN*> pins = symbol->GetPins();
890
891 if( !pins.empty() )
892 pinPos = pins[0]->GetPosition();
893
894 SCH_LABEL* label = new SCH_LABEL( pinPos, labelText );
895 int labelSize = schIUScale.MilsToIU( 50 );
896 label->SetTextSize( VECTOR2I( labelSize, labelSize ) );
898 label->SetFlags( IS_NEW );
899 ctx.screen->Append( label );
900 }
901
902 ctx.screen->Append( symbolPtr.release() );
903 }
904 }
905
906 // Build set of power signal names so we can suppress duplicate global labels
907 // where a power symbol is placed instead. Non-power signal labels are handled
908 // by CreateNetLabels which places them at dangling wire endpoints.
909 std::set<std::string> powerSignalNames;
910
911 for( const PADS_SCH::OFF_PAGE_CONNECTOR& opc : parser.GetOffPageConnectors() )
912 {
913 if( opc.signal_name.empty() )
914 continue;
915
916 auto ptIt = parser.GetPartTypes().find( opc.symbol_lib );
917
918 if( ptIt != parser.GetPartTypes().end()
919 && !ptIt->second.special_keyword.empty() && ptIt->second.special_keyword != "OFF"
920 && !ptIt->second.special_variants.empty() )
921 {
922 int idx = std::max( 0, opc.flags2 );
923 idx = std::min( idx,
924 static_cast<int>( ptIt->second.special_variants.size() ) - 1 );
925 const auto& variant = ptIt->second.special_variants[idx];
926
928 variant.decal_name, variant.pin_type ).empty() )
929 {
930 powerSignalNames.insert( opc.signal_name );
931 }
932 }
933 }
934
935 // Build set of OPC reference IDs for non-power signal OPCs. Each entry
936 // corresponds to a wire endpoint reference like "@@@O48" that should receive
937 // its own global label with orientation derived from the wire direction.
938 std::set<std::string> signalOpcIds;
939
940 for( const PADS_SCH::OFF_PAGE_CONNECTOR& opc : parser.GetOffPageConnectors() )
941 {
942 if( opc.signal_name.empty() || powerSignalNames.count( opc.signal_name ) )
943 continue;
944
945 signalOpcIds.insert( "@@@O" + std::to_string( opc.id ) );
946 }
947
948 // Create wires and connectivity on each sheet
949 for( auto& [sheetNum, ctx] : sheetContexts )
950 {
951 std::vector<PADS_SCH::SCH_SIGNAL> sheetSignals = parser.GetSignalsOnSheet( sheetNum );
952
953 // Create wire segments from vertex data
954 for( const PADS_SCH::SCH_SIGNAL& signal : sheetSignals )
955 {
956 for( const PADS_SCH::WIRE_SEGMENT& wire : signal.wires )
957 {
958 if( wire.vertices.size() < 2 )
959 continue;
960
961 // Each consecutive pair of vertices becomes a wire segment
962 for( size_t v = 0; v + 1 < wire.vertices.size(); v++ )
963 {
964 VECTOR2I start(
965 schIUScale.MilsToIU( KiROUND( wire.vertices[v].x ) ),
966 pageHeightIU
967 - schIUScale.MilsToIU( KiROUND( wire.vertices[v].y ) ) );
969 schIUScale.MilsToIU( KiROUND( wire.vertices[v + 1].x ) ),
970 pageHeightIU
971 - schIUScale.MilsToIU( KiROUND( wire.vertices[v + 1].y ) ) );
972
973 if( start == end )
974 continue;
975
976 SCH_LINE* line = new SCH_LINE( start, SCH_LAYER_ID::LAYER_WIRE );
977 line->SetEndPoint( end );
978 line->SetConnectivityDirty();
979 ctx.screen->Append( line );
980 }
981 }
982 }
983
984 // Create local labels at wire endpoints that reference connector pins
985 for( const PADS_SCH::SCH_SIGNAL& signal : sheetSignals )
986 {
987 for( const PADS_SCH::WIRE_SEGMENT& wire : signal.wires )
988 {
989 if( wire.vertices.size() < 2 )
990 continue;
991
992 // Check endpoint_a for connector pin reference
993 if( wire.endpoint_a.find( '.' ) != std::string::npos
994 && wire.endpoint_a.find( "@@@" ) == std::string::npos )
995 {
996 size_t dotPos = wire.endpoint_a.find( '.' );
997 std::string ref = wire.endpoint_a.substr( 0, dotPos );
998
999 if( connectorBaseRefs.count( ref ) )
1000 {
1001 const auto& vtx = wire.vertices.front();
1002 VECTOR2I pos(
1003 schIUScale.MilsToIU( KiROUND( vtx.x ) ),
1004 pageHeightIU - schIUScale.MilsToIU( KiROUND( vtx.y ) ) );
1005
1006 // Compute label orientation from adjacent vertex
1007 const auto& adj = wire.vertices[1];
1008 VECTOR2I adjPos(
1009 schIUScale.MilsToIU( KiROUND( adj.x ) ),
1010 pageHeightIU - schIUScale.MilsToIU( KiROUND( adj.y ) ) );
1011
1012 int dx = adjPos.x - pos.x;
1013 int dy = adjPos.y - pos.y;
1015
1016 if( std::abs( dx ) >= std::abs( dy ) )
1017 orient = ( dx > 0 ) ? SPIN_STYLE::LEFT : SPIN_STYLE::RIGHT;
1018 else
1019 orient = ( dy > 0 ) ? SPIN_STYLE::UP : SPIN_STYLE::BOTTOM;
1020
1021 wxString labelText = wxString::FromUTF8( wire.endpoint_a );
1022 SCH_LABEL* label = new SCH_LABEL( pos, labelText );
1023 int labelSize = schIUScale.MilsToIU( 50 );
1024 label->SetTextSize( VECTOR2I( labelSize, labelSize ) );
1025 label->SetSpinStyle( orient );
1026 label->SetFlags( IS_NEW );
1027 ctx.screen->Append( label );
1028 }
1029 }
1030
1031 // Check endpoint_b for connector pin reference
1032 if( wire.endpoint_b.find( '.' ) != std::string::npos
1033 && wire.endpoint_b.find( "@@@" ) == std::string::npos )
1034 {
1035 size_t dotPos = wire.endpoint_b.find( '.' );
1036 std::string ref = wire.endpoint_b.substr( 0, dotPos );
1037
1038 if( connectorBaseRefs.count( ref ) )
1039 {
1040 const auto& vtx = wire.vertices.back();
1041 VECTOR2I pos(
1042 schIUScale.MilsToIU( KiROUND( vtx.x ) ),
1043 pageHeightIU - schIUScale.MilsToIU( KiROUND( vtx.y ) ) );
1044
1045 // Compute label orientation from adjacent vertex
1046 size_t lastIdx = wire.vertices.size() - 1;
1047 const auto& adj = wire.vertices[lastIdx - 1];
1048 VECTOR2I adjPos(
1049 schIUScale.MilsToIU( KiROUND( adj.x ) ),
1050 pageHeightIU - schIUScale.MilsToIU( KiROUND( adj.y ) ) );
1051
1052 int dx = adjPos.x - pos.x;
1053 int dy = adjPos.y - pos.y;
1055
1056 if( std::abs( dx ) >= std::abs( dy ) )
1057 orient = ( dx > 0 ) ? SPIN_STYLE::LEFT : SPIN_STYLE::RIGHT;
1058 else
1059 orient = ( dy > 0 ) ? SPIN_STYLE::UP : SPIN_STYLE::BOTTOM;
1060
1061 wxString labelText = wxString::FromUTF8( wire.endpoint_b );
1062 SCH_LABEL* label = new SCH_LABEL( pos, labelText );
1063 int labelSize = schIUScale.MilsToIU( 50 );
1064 label->SetTextSize( VECTOR2I( labelSize, labelSize ) );
1065 label->SetSpinStyle( orient );
1066 label->SetFlags( IS_NEW );
1067 ctx.screen->Append( label );
1068 }
1069 }
1070 }
1071 }
1072
1073 // Create junctions from TIEDOTS for this sheet
1074 for( const PADS_SCH::TIED_DOT& dot : parser.GetTiedDots() )
1075 {
1076 if( dot.sheet_number != sheetNum )
1077 continue;
1078
1079 VECTOR2I pos( schIUScale.MilsToIU( KiROUND( dot.position.x ) ),
1080 pageHeightIU - schIUScale.MilsToIU( KiROUND( dot.position.y ) ) );
1081
1082 SCH_JUNCTION* junction = new SCH_JUNCTION( pos );
1083 ctx.screen->Append( junction );
1084 }
1085
1086 // Create net labels, skipping power nets that get dedicated symbols
1087 schBuilder.CreateNetLabels( sheetSignals, ctx.screen, signalOpcIds,
1088 powerSignalNames );
1089
1090 // Place off-page connectors: power/ground types become SCH_SYMBOL with
1091 // KiCad standard power graphics; signal types become SCH_GLOBALLABEL.
1092 int pwrIndex = 1;
1093
1094 for( const PADS_SCH::OFF_PAGE_CONNECTOR& opc : parser.GetOffPageConnectors() )
1095 {
1096 if( opc.source_sheet != sheetNum )
1097 continue;
1098
1099 if( opc.signal_name.empty() )
1100 continue;
1101
1102 VECTOR2I pos( schIUScale.MilsToIU( KiROUND( opc.position.x ) ),
1103 pageHeightIU
1104 - schIUScale.MilsToIU( KiROUND( opc.position.y ) ) );
1105
1106 // Resolve power style from the PARTTYPE variant definition
1107 std::string powerStyle;
1108 auto opcPtIt = parser.GetPartTypes().find( opc.symbol_lib );
1109
1110 if( opcPtIt != parser.GetPartTypes().end()
1111 && !opcPtIt->second.special_keyword.empty() && opcPtIt->second.special_keyword != "OFF"
1112 && !opcPtIt->second.special_variants.empty() )
1113 {
1114 int idx = std::max( 0, opc.flags2 );
1115 idx = std::min( idx,
1116 static_cast<int>(
1117 opcPtIt->second.special_variants.size() ) - 1 );
1118 const auto& variant = opcPtIt->second.special_variants[idx];
1119
1121 variant.decal_name, variant.pin_type );
1122 }
1123
1124 if( !powerStyle.empty() )
1125 {
1126 LIB_SYMBOL* pwrSym = symbolBuilder.BuildKiCadPowerSymbol( powerStyle );
1127
1128 if( pwrSym )
1129 {
1130 auto symbolPtr = std::make_unique<SCH_SYMBOL>();
1131 SCH_SYMBOL* symbol = symbolPtr.get();
1132
1133 LIB_ID libId;
1134 libId.SetLibNickname( wxT( "power" ) );
1135 libId.SetLibItemName( wxString::FromUTF8( powerStyle ) );
1136 symbol->SetLibId( libId );
1137 symbol->SetLibSymbol( pwrSym );
1138 symbol->SetPosition( pos );
1139 symbol->SetUnit( 1 );
1140
1141 // VCC and PWR_TRIANGLE have pin pointing up (body above pin).
1142 // All others (GND, GNDD, PWR_BAR, VEE, Earth) have pin pointing down.
1143 bool pinUp = ( powerStyle == "VCC" || powerStyle == "PWR_TRIANGLE" );
1144 int orient = computePowerOrientation(
1145 std::to_string( opc.id ), sheetSignals, pos, pinUp,
1146 pageHeightIU );
1147
1148 symbol->SetOrientation( orient );
1149
1150 wxString netName = wxString::FromUTF8( opc.signal_name );
1151
1152 if( netName.StartsWith( wxT( "/" ) ) )
1153 netName = wxT( "~{" ) + netName.Mid( 1 ) + wxT( "}" );
1154
1155 symbol->GetField( FIELD_T::VALUE )->SetText( netName );
1156 symbol->GetField( FIELD_T::VALUE )->SetVisible( true );
1157
1158 wxString pwrRef = wxString::Format( wxT( "#PWR%03d" ), pwrIndex++ );
1159 symbol->SetRef( &ctx.path, pwrRef );
1160 symbol->GetField( FIELD_T::REFERENCE )->SetVisible( false );
1161
1162 symbol->ClearFlags();
1163 ctx.screen->Append( symbolPtr.release() );
1164 continue;
1165 }
1166 }
1167
1168 // Non-power signal OPCs don't create labels here. CreateNetLabels
1169 // handles all signal net labels, placing them at dangling wire endpoints
1170 // rather than at OPC positions (which may not land on a wire).
1171 }
1172 }
1173
1174 // Place free text items from *TEXT* section on the first (or only) sheet
1175 if( !sheetContexts.empty() )
1176 {
1177 SCH_SCREEN* textScreen = sheetContexts.begin()->second.screen;
1178
1179 for( const PADS_SCH::TEXT_ITEM& textItem : parser.GetTextItems() )
1180 {
1181 if( textItem.content.empty() )
1182 continue;
1183
1184 VECTOR2I pos( schIUScale.MilsToIU( KiROUND( textItem.position.x ) ),
1185 pageHeightIU
1186 - schIUScale.MilsToIU( KiROUND( textItem.position.y ) ) );
1187
1188 textScreen->Append( createSchText( textItem, pos ) );
1189 }
1190 }
1191
1192 // Place graphic lines from *LINES* section (skip the border template)
1193 if( !sheetContexts.empty() )
1194 {
1195 SCH_SCREEN* linesScreen = sheetContexts.begin()->second.screen;
1196
1197 for( const PADS_SCH::LINES_ITEM& linesItem : parser.GetLinesItems() )
1198 {
1199 if( linesItem.name == params.border_template )
1200 continue;
1201
1202 double ox = linesItem.origin.x;
1203 double oy = linesItem.origin.y;
1204
1205 for( const PADS_SCH::SYMBOL_GRAPHIC& prim : linesItem.primitives )
1206 {
1207 int strokeWidth = prim.line_width > 0.0
1208 ? schIUScale.MilsToIU( KiROUND( prim.line_width ) )
1209 : 0;
1210
1212
1214 {
1216 schIUScale.MilsToIU( KiROUND( ox + prim.center.x ) ),
1217 pageHeightIU
1218 - schIUScale.MilsToIU( KiROUND( oy + prim.center.y ) ) );
1219 int radius = schIUScale.MilsToIU( KiROUND( prim.radius ) );
1220
1222 circle->SetStart( center );
1223 circle->SetEnd( VECTOR2I( center.x + radius, center.y ) );
1224 circle->SetStroke( STROKE_PARAMS( strokeWidth, lineStyle ) );
1225
1226 if( prim.filled )
1227 circle->SetFillMode( FILL_T::FILLED_SHAPE );
1228
1229 linesScreen->Append( circle );
1230 }
1232 && prim.points.size() == 2 )
1233 {
1234 VECTOR2I pos(
1235 schIUScale.MilsToIU( KiROUND( ox + prim.points[0].coord.x ) ),
1236 pageHeightIU
1237 - schIUScale.MilsToIU(
1238 KiROUND( oy + prim.points[0].coord.y ) ) );
1239 VECTOR2I end(
1240 schIUScale.MilsToIU( KiROUND( ox + prim.points[1].coord.x ) ),
1241 pageHeightIU
1242 - schIUScale.MilsToIU(
1243 KiROUND( oy + prim.points[1].coord.y ) ) );
1244
1245 SCH_SHAPE* rect = new SCH_SHAPE( SHAPE_T::RECTANGLE );
1246 rect->SetPosition( pos );
1247 rect->SetEnd( end );
1248 rect->SetStroke( STROKE_PARAMS( strokeWidth, lineStyle ) );
1249
1250 if( prim.filled )
1252
1253 linesScreen->Append( rect );
1254 }
1255 else if( prim.points.size() >= 2 )
1256 {
1257 for( size_t p = 0; p + 1 < prim.points.size(); p++ )
1258 {
1259 VECTOR2I start(
1260 schIUScale.MilsToIU(
1261 KiROUND( ox + prim.points[p].coord.x ) ),
1262 pageHeightIU
1263 - schIUScale.MilsToIU(
1264 KiROUND( oy + prim.points[p].coord.y ) ) );
1265 VECTOR2I end(
1266 schIUScale.MilsToIU(
1267 KiROUND( ox + prim.points[p + 1].coord.x ) ),
1268 pageHeightIU
1269 - schIUScale.MilsToIU(
1270 KiROUND( oy + prim.points[p + 1].coord.y ) ) );
1271
1272 if( start == end )
1273 continue;
1274
1275 if( prim.points[p].arc.has_value() )
1276 {
1277 const PADS_SCH::ARC_DATA& ad = *prim.points[p].arc;
1278 double cx = ( ad.bbox_x1 + ad.bbox_x2 ) / 2.0;
1279 double cy = ( ad.bbox_y1 + ad.bbox_y2 ) / 2.0;
1281 schIUScale.MilsToIU( KiROUND( ox + cx ) ),
1282 pageHeightIU
1283 - schIUScale.MilsToIU( KiROUND( oy + cy ) ) );
1284
1285 double sx = start.x - center.x;
1286 double sy = start.y - center.y;
1287 double ex = end.x - center.x;
1288 double ey = end.y - center.y;
1289 double radius = std::sqrt( sx * sx + sy * sy );
1290
1291 double mx = sx + ex;
1292 double my = sy + ey;
1293 double mlen = std::sqrt( mx * mx + my * my );
1294
1295 VECTOR2I midPt;
1296
1297 if( mlen > 0.001 )
1298 {
1299 midPt.x = center.x
1300 + static_cast<int>( radius * mx / mlen );
1301 midPt.y = center.y
1302 + static_cast<int>( radius * my / mlen );
1303 }
1304 else
1305 {
1306 midPt.x = center.x
1307 + static_cast<int>( -sy * radius
1308 / std::max( radius, 1.0 ) );
1309 midPt.y = center.y
1310 + static_cast<int>( sx * radius
1311 / std::max( radius, 1.0 ) );
1312 }
1313
1314 if( ad.angle < 0 )
1315 {
1316 midPt.x = 2 * center.x - midPt.x;
1317 midPt.y = 2 * center.y - midPt.y;
1318 }
1319
1320 SCH_SHAPE* arc = new SCH_SHAPE( SHAPE_T::ARC );
1321 arc->SetArcGeometry( start, midPt, end );
1322 arc->SetStroke( STROKE_PARAMS( strokeWidth, lineStyle ) );
1323
1324 if( prim.filled )
1326
1327 linesScreen->Append( arc );
1328 }
1329 else
1330 {
1331 SCH_LINE* line = new SCH_LINE(
1333 line->SetEndPoint( end );
1334 line->SetStroke( STROKE_PARAMS( strokeWidth, lineStyle ) );
1335 linesScreen->Append( line );
1336 }
1337 }
1338 }
1339 }
1340
1341 // Render text items within this LINES group
1342 for( const PADS_SCH::TEXT_ITEM& textItem : linesItem.texts )
1343 {
1344 if( textItem.content.empty() )
1345 continue;
1346
1347 VECTOR2I pos(
1348 schIUScale.MilsToIU( KiROUND( ox + textItem.position.x ) ),
1349 pageHeightIU
1350 - schIUScale.MilsToIU( KiROUND( oy + textItem.position.y ) ) );
1351
1352 linesScreen->Append( createSchText( textItem, pos ) );
1353 }
1354 }
1355 }
1356
1357 // Set title block from parsed parameters
1358 schBuilder.CreateTitleBlock( rootScreen );
1359
1360 // Finalize all sheets
1361 SCH_SCREENS allSheets( rootSheet );
1362 allSheets.UpdateSymbolLinks();
1363 allSheets.ClearEditFlags();
1364
1365 if( m_reporter )
1366 {
1367 for( const auto& [msg, severity] : m_errorMessages )
1368 m_reporter->Report( msg, severity );
1369 }
1370
1371 m_errorMessages.clear();
1372
1373 return rootSheet;
1374}
1375
1376
1377void SCH_IO_PADS::EnumerateSymbolLib( wxArrayString& aSymbolNameList,
1378 const wxString& aLibraryPath,
1379 const std::map<std::string, UTF8>* aProperties )
1380{
1381 ensureLoadedLibrary( aLibraryPath );
1382
1383 bool powerSymbolsOnly = aProperties
1384 && aProperties->contains( SYMBOL_LIBRARY_ADAPTER::PropPowerSymsOnly );
1385
1386 for( const auto& [name, symbol] : m_librarySymbols )
1387 {
1388 if( powerSymbolsOnly && !symbol->IsPower() )
1389 continue;
1390
1391 aSymbolNameList.Add( name );
1392 }
1393}
1394
1395
1396void SCH_IO_PADS::EnumerateSymbolLib( std::vector<LIB_SYMBOL*>& aSymbolList,
1397 const wxString& aLibraryPath,
1398 const std::map<std::string, UTF8>* aProperties )
1399{
1400 ensureLoadedLibrary( aLibraryPath );
1401
1402 bool powerSymbolsOnly = aProperties
1403 && aProperties->contains( SYMBOL_LIBRARY_ADAPTER::PropPowerSymsOnly );
1404
1405 for( const auto& [name, symbol] : m_librarySymbols )
1406 {
1407 if( powerSymbolsOnly && !symbol->IsPower() )
1408 continue;
1409
1410 aSymbolList.push_back( symbol.get() );
1411 }
1412}
1413
1414
1415LIB_SYMBOL* SCH_IO_PADS::LoadSymbol( const wxString& aLibraryPath, const wxString& aPartName,
1416 const std::map<std::string, UTF8>* aProperties )
1417{
1418 ensureLoadedLibrary( aLibraryPath );
1419
1420 auto it = m_librarySymbols.find( aPartName );
1421
1422 if( it != m_librarySymbols.end() )
1423 return it->second.get();
1424
1425 return nullptr;
1426}
1427
1428
1429long long SCH_IO_PADS::getLibraryTimestamp( const wxString& aLibraryPath ) const
1430{
1431 wxFileName fn( aLibraryPath );
1432
1433 if( fn.IsFileReadable() && fn.GetModificationTime().IsValid() )
1434 return fn.GetModificationTime().GetValue().GetValue();
1435
1436 return 0;
1437}
1438
1439
1440void SCH_IO_PADS::ensureLoadedLibrary( const wxString& aLibraryPath )
1441{
1442 long long timestamp = getLibraryTimestamp( aLibraryPath );
1443
1444 if( m_libraryCacheValid && aLibraryPath == m_cachedLibraryPath
1445 && timestamp == m_cachedLibraryTimestamp )
1446 {
1447 return;
1448 }
1449
1450 m_librarySymbols.clear();
1451 m_libraryCacheValid = false;
1452 m_cachedLibraryPath = aLibraryPath;
1453 m_cachedLibraryTimestamp = timestamp;
1454
1455 if( !checkFileHeader( aLibraryPath ) )
1456 {
1457 THROW_IO_ERROR( wxString::Format( _( "'%s' is not a PADS Logic ASCII file." ),
1458 aLibraryPath ) );
1459 }
1460
1461 LOCALE_IO setlocale;
1462
1464 std::string filename( aLibraryPath.ToUTF8() );
1465
1466 if( !parser.Parse( filename ) )
1467 {
1469 wxString::Format( _( "Failed to parse PADS Logic file '%s'." ), aLibraryPath ) );
1470 }
1471
1472 const PADS_SCH::PARAMETERS& params = parser.GetParameters();
1473 PADS_SCH::PADS_SCH_SYMBOL_BUILDER symbolBuilder( params );
1474
1475 std::set<std::string> referencedDecals;
1476
1477 // Build a LIB_SYMBOL per PARTTYPE: multi-gate parts become multi-unit symbols,
1478 // single-gate parts apply PARTTYPE pin overrides to the CAEDECAL graphics,
1479 // and power/ground special variants are skipped (they map to KiCad power lib).
1480 for( const auto& [ptName, ptDef] : parser.GetPartTypes() )
1481 {
1482 if( !ptDef.special_keyword.empty() && ptDef.special_keyword != "OFF" )
1483 continue;
1484
1485 LIB_SYMBOL* built = nullptr;
1486 wxString libName = wxString::FromUTF8( ptDef.name );
1487
1488 if( ptDef.gates.size() > 1 )
1489 {
1490 built = symbolBuilder.BuildMultiUnitSymbol( ptDef, parser.GetSymbolDefs() );
1491
1492 for( const PADS_SCH::GATE_DEF& gate : ptDef.gates )
1493 {
1494 for( const std::string& decalName : gate.decal_names )
1495 referencedDecals.insert( decalName );
1496 }
1497 }
1498 else if( !ptDef.gates.empty() )
1499 {
1500 const PADS_SCH::GATE_DEF& gate = ptDef.gates[0];
1501 std::string decalName;
1502
1503 if( !gate.decal_names.empty() )
1504 decalName = gate.decal_names[0];
1505
1506 const PADS_SCH::SYMBOL_DEF* symDef = parser.GetSymbolDef( decalName );
1507
1508 if( symDef && ptDef.is_connector && !gate.pins.empty() )
1509 {
1510 // Connectors declare one CAEDECAL shared by every pin. Build a
1511 // multi-unit library symbol so each PARTTYPE pin is representable
1512 // without assuming a particular schematic placement grouping.
1513 std::vector<std::string> pinNumbers;
1514 pinNumbers.reserve( gate.pins.size() );
1515
1516 for( const PADS_SCH::PARTTYPE_PIN& pin : gate.pins )
1517 pinNumbers.push_back( pin.pin_id );
1518
1519 built = symbolBuilder.BuildMultiUnitConnectorSymbol( ptDef, *symDef, pinNumbers );
1520 referencedDecals.insert( decalName );
1521 }
1522 else if( symDef )
1523 {
1524 // GetOrCreatePartTypeSymbol caches inside the builder and returns a
1525 // non-owning pointer; clone it so the library owns its own copy.
1526 LIB_SYMBOL* cached = symbolBuilder.GetOrCreatePartTypeSymbol( ptDef, *symDef );
1527
1528 if( cached )
1529 built = new LIB_SYMBOL( *cached );
1530
1531 referencedDecals.insert( decalName );
1532 }
1533 }
1534
1535 if( !built )
1536 continue;
1537
1538 built->SetName( libName );
1539
1540 if( !ptDef.sigpins.empty() )
1541 symbolBuilder.AddHiddenPowerPins( built, ptDef.sigpins );
1542
1543 m_librarySymbols[libName] = std::unique_ptr<LIB_SYMBOL>( built );
1544 }
1545
1546 // Also expose any CAEDECAL entries that no PARTTYPE referenced, so the user
1547 // still sees orphan decal graphics that ship with the PADS library.
1548 for( const PADS_SCH::SYMBOL_DEF& symDef : parser.GetSymbolDefs() )
1549 {
1550 if( referencedDecals.count( symDef.name ) )
1551 continue;
1552
1553 wxString libName = wxString::FromUTF8( symDef.name );
1554
1555 if( libName.IsEmpty() || m_librarySymbols.count( libName ) )
1556 continue;
1557
1558 LIB_SYMBOL* built = symbolBuilder.BuildSymbol( symDef );
1559
1560 if( !built )
1561 continue;
1562
1563 m_librarySymbols[libName] = std::unique_ptr<LIB_SYMBOL>( built );
1564 }
1565
1566 m_libraryCacheValid = true;
1567}
1568
1569
1570bool SCH_IO_PADS::checkFileHeader( const wxString& aFileName ) const
1571{
1572 try
1573 {
1574 std::ifstream file( aFileName.fn_str() );
1575
1576 if( !file.is_open() )
1577 return false;
1578
1579 std::string line;
1580
1581 if( std::getline( file, line ) )
1582 {
1583 if( line.find( "*PADS-POWERLOGIC" ) != std::string::npos )
1584 return true;
1585
1586 if( line.find( "*PADS-LOGIC" ) != std::string::npos )
1587 return true;
1588 }
1589 }
1590 catch( ... )
1591 {
1592 }
1593
1594 return false;
1595}
const char * name
constexpr EDA_IU_SCALE schIUScale
Definition base_units.h:127
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:990
static const ADVANCED_CFG & GetCfg()
Get the singleton instance's config, which is shared by all consumers.
void SetFlags(EDA_ITEM_FLAGS aMask)
Definition eda_item.h:149
const KIID m_Uuid
Definition eda_item.h:528
void ClearFlags(EDA_ITEM_FLAGS aMask=EDA_ITEM_ALL_FLAGS)
Definition eda_item.h:151
void SetEnd(const VECTOR2I &aEnd)
Definition eda_shape.h:236
void SetArcGeometry(const VECTOR2I &aStart, const VECTOR2I &aMid, const VECTOR2I &aEnd)
Set the three controlling points for an arc.
void SetFillMode(FILL_T aFill)
void SetTextSize(VECTOR2I aNewSize, bool aEnforceMinTextSize=true)
Definition eda_text.cpp:532
void SetVertJustify(GR_TEXT_V_ALIGN_T aType)
Definition eda_text.cpp:416
virtual void SetVisible(bool aVisible)
Definition eda_text.cpp:385
void SetTextThickness(int aWidth)
The TextThickness is that set by the user.
Definition eda_text.cpp:283
void SetTextAngleDegrees(double aOrientation)
Definition eda_text.h:175
void SetHorizJustify(GR_TEXT_H_ALIGN_T aType)
Definition eda_text.cpp:408
REPORTER * m_reporter
Reporter to log errors/warnings to, may be nullptr.
Definition io_base.h:237
PROGRESS_REPORTER * m_progressReporter
Progress reporter to track the progress of the operation, may be nullptr.
Definition io_base.h:240
virtual bool CanReadLibrary(const wxString &aFileName) const
Checks if this IO object can read the specified library file/directory.
Definition io_base.cpp:71
Definition kiid.h:48
A logical library item identifier and consists of various portions much like a URI.
Definition lib_id.h:49
int SetLibItemName(const UTF8 &aLibItemName)
Override the library item name portion of the LIB_ID to aLibItemName.
Definition lib_id.cpp:111
int SetLibNickname(const UTF8 &aLibNickname)
Override the logical library name portion of the LIB_ID to aLibNickname.
Definition lib_id.cpp:100
Define a library symbol object.
Definition lib_symbol.h:83
void SetGlobalPower()
virtual void SetName(const wxString &aName)
Instantiate the current locale within a scope in which you are expecting exceptions to be thrown.
Definition locale_io.h:41
Parser for PADS Logic schematic design export files.
const std::vector< TEXT_ITEM > & GetTextItems() const
const std::vector< TIED_DOT > & GetTiedDots() const
const std::vector< OFF_PAGE_CONNECTOR > & GetOffPageConnectors() const
bool Parse(const std::string &aFileName)
const std::vector< SHEET_HEADER > & GetSheetHeaders() const
const std::vector< LINES_ITEM > & GetLinesItems() const
std::vector< SCH_SIGNAL > GetSignalsOnSheet(int aSheetNumber) const
std::set< int > GetSheetNumbers() const
const std::vector< SYMBOL_DEF > & GetSymbolDefs() const
const std::vector< PART_PLACEMENT > & GetPartPlacements() const
const std::map< std::string, PARTTYPE_DEF > & GetPartTypes() const
std::vector< PART_PLACEMENT > GetPartsOnSheet(int aSheetNumber) const
const SYMBOL_DEF * GetSymbolDef(const std::string &aName) const
const PARAMETERS & GetParameters() const
Builder class to create KiCad schematic elements from parsed PADS data.
int CreateNetLabels(const std::vector< SCH_SIGNAL > &aSignals, SCH_SCREEN *aScreen, const std::set< std::string > &aSignalOpcIds, const std::set< std::string > &aSkipSignals={})
Create net labels for named signals.
void ApplyPartAttributes(SCH_SYMBOL *aSymbol, const PART_PLACEMENT &aPlacement)
Apply part attributes to a symbol instance.
void CreateTitleBlock(SCH_SCREEN *aScreen)
Create title block from parsed PADS parameters.
int CreateCustomFields(SCH_SYMBOL *aSymbol, const PART_PLACEMENT &aPlacement)
Create custom fields from non-standard PADS attributes.
SCH_SHEET * CreateHierarchicalSheet(int aSheetNumber, int aTotalSheets, SCH_SHEET *aParentSheet, const wxString &aBaseFilename)
Create hierarchical sheet for a sub-schematic page.
Builder class to convert PADS symbol definitions to KiCad LIB_SYMBOL objects.
LIB_SYMBOL * BuildMultiUnitSymbol(const PARTTYPE_DEF &aPartType, const std::vector< SYMBOL_DEF > &aSymbolDefs)
Build a composite multi-unit LIB_SYMBOL from a multi-gate PARTTYPE.
void AddHiddenPowerPins(LIB_SYMBOL *aSymbol, const std::vector< PARTTYPE_DEF::SIGPIN > &aSigpins)
Add hidden power pins from PARTTYPE SIGPIN entries to an existing symbol.
LIB_SYMBOL * GetOrCreateMultiUnitSymbol(const PARTTYPE_DEF &aPartType, const std::vector< SYMBOL_DEF > &aSymbolDefs)
Get or create a multi-unit symbol for the given PARTTYPE.
static std::optional< LIB_ID > GetKiCadPowerSymbolId(const std::string &aPadsName)
Get KiCad power library symbol ID for a PADS power symbol.
LIB_SYMBOL * BuildKiCadPowerSymbol(const std::string &aKiCadName)
Build a power symbol using hard-coded KiCad-standard graphics.
static bool IsPowerSymbol(const std::string &aName)
Check if a symbol name indicates a power symbol.
static std::string GetPowerStyleFromVariant(const std::string &aDecalName, const std::string &aPinType)
Map a PADS special_variant to a power symbol style name.
LIB_SYMBOL * GetOrCreateSymbol(const SYMBOL_DEF &aSymbolDef)
Get or create a symbol for the given definition.
LIB_SYMBOL * GetOrCreateMultiUnitConnectorSymbol(const PARTTYPE_DEF &aPartType, const SYMBOL_DEF &aSymbolDef, const std::vector< std::string > &aPinNumbers, const std::string &aCacheKey)
Get or create a multi-unit connector symbol, cached by base reference.
LIB_SYMBOL * BuildMultiUnitConnectorSymbol(const PARTTYPE_DEF &aPartType, const SYMBOL_DEF &aSymbolDef, const std::vector< std::string > &aPinNumbers)
Build a multi-unit connector symbol where each unit represents one pin.
LIB_SYMBOL * BuildSymbol(const SYMBOL_DEF &aSymbolDef)
Build a KiCad LIB_SYMBOL from a PADS symbol definition.
LIB_SYMBOL * GetOrCreatePartTypeSymbol(const PARTTYPE_DEF &aPartType, const SYMBOL_DEF &aSymbolDef)
Get or create a single-gate symbol with PARTTYPE-specific pin remapping.
Describe the page size and margins of a paper page on which to eventually print or plot.
Definition page_info.h:79
int GetHeightIU(double aIUScale) const
Gets the page height in IU.
Definition page_info.h:168
bool SetType(PAGE_SIZE_TYPE aPageSize, bool aIsPortrait=false)
Set the name of the page type and also the sizes and margins commonly associated with that type name.
Holds all the data relating to one schematic.
Definition schematic.h:89
SCHEMATIC_SETTINGS & Settings() const
bool IsValid() const
A simple test if the schematic is loaded, not a complete one.
Definition schematic.h:173
void SetTopLevelSheets(const std::vector< SCH_SHEET * > &aSheets)
void SetPosition(const VECTOR2I &aPosition) override
void SetText(const wxString &aText) override
std::map< wxString, std::unique_ptr< LIB_SYMBOL > > m_librarySymbols
Definition sch_io_pads.h:99
LIB_SYMBOL * LoadSymbol(const wxString &aLibraryPath, const wxString &aPartName, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Load a LIB_SYMBOL object having aPartName from the aLibraryPath containing a library format that this...
void EnumerateSymbolLib(wxArrayString &aSymbolNameList, const wxString &aLibraryPath, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Populate a list of LIB_SYMBOL alias names contained within the library aLibraryPath.
long long getLibraryTimestamp(const wxString &aLibraryPath) const
bool CanReadLibrary(const wxString &aFileName) const override
Checks if this IO object can read the specified library file/directory.
bool checkFileHeader(const wxString &aFileName) const
Check if the file header indicates a PADS Logic schematic file.
void ensureLoadedLibrary(const wxString &aLibraryPath)
Parse the PADS Logic ASCII file and populate the library symbol cache.
wxString m_cachedLibraryPath
Definition sch_io_pads.h:96
bool CanReadSchematicFile(const wxString &aFileName) const override
Checks if this SCH_IO can read the specified schematic file.
long long m_cachedLibraryTimestamp
Definition sch_io_pads.h:97
std::unordered_map< wxString, SEVERITY > m_errorMessages
Definition sch_io_pads.h:94
SCH_SHEET * LoadSchematicFile(const wxString &aFileName, SCHEMATIC *aSchematic, SCH_SHEET *aAppendToMe=nullptr, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Load information from some input file format that this SCH_IO implementation knows about,...
bool m_libraryCacheValid
Definition sch_io_pads.h:98
virtual bool CanReadSchematicFile(const wxString &aFileName) const
Checks if this SCH_IO can read the specified schematic file.
Definition sch_io.cpp:45
SCH_IO(const wxString &aName)
Definition sch_io.h:375
int GetUnit() const
Definition sch_item.h:239
void SetConnectivityDirty(bool aDirty=true)
Definition sch_item.h:593
virtual void SetUnit(int aUnit)
Definition sch_item.h:238
virtual void SetSpinStyle(SPIN_STYLE aSpinStyle)
Segment description base class to describe items which have 2 end points (track, wire,...
Definition sch_line.h:42
virtual void SetStroke(const STROKE_PARAMS &aStroke) override
Definition sch_line.h:202
void SetEndPoint(const VECTOR2I &aPosition)
Definition sch_line.h:149
Container class that holds multiple SCH_SCREEN objects in a hierarchy.
Definition sch_screen.h:750
void UpdateSymbolLinks(REPORTER *aReporter=nullptr)
Initialize the LIB_SYMBOL reference for each SCH_SYMBOL found in the full schematic.
void ClearEditFlags()
std::vector< SCH_SHEET_INSTANCE > m_sheetInstances
Definition sch_screen.h:728
void Append(SCH_ITEM *aItem, bool aUpdateLibSymbol=true)
const KIID & GetUuid() const
Definition sch_screen.h:533
void SetFileName(const wxString &aFileName)
Set the file name for this screen to aFileName.
void SetPosition(const VECTOR2I &aPos) override
Definition sch_shape.h:89
void SetStroke(const STROKE_PARAMS &aStroke) override
Handle access to a stack of flattened SCH_SHEET objects by way of a path for creating a flattened sch...
KIID_PATH Path() const
Get the sheet path as an KIID_PATH.
void SetPageNumber(const wxString &aPageNumber)
Set the sheet instance user definable page number.
void push_back(SCH_SHEET *aSheet)
Forwarded method from std::vector.
Sheet symbol placed in a schematic, and is the entry point for a sub schematic.
Definition sch_sheet.h:48
void SetFileName(const wxString &aFilename)
Definition sch_sheet.h:380
SCH_FIELD * GetField(FIELD_T aFieldType)
Return a mandatory field in this sheet.
SCH_SCREEN * GetScreen() const
Definition sch_sheet.h:143
void SetScreen(SCH_SCREEN *aScreen)
Set the SCH_SCREEN associated with this sheet to aScreen.
Schematic symbol object.
Definition sch_symbol.h:76
void SetLibId(const LIB_ID &aName)
void SetPosition(const VECTOR2I &aPosition) override
Definition sch_symbol.h:872
std::vector< const SCH_PIN * > GetPins(const SCH_SHEET_PATH *aSheet) const
Retrieve a list of the SCH_PINs for the given sheet path.
void SetRef(const SCH_SHEET_PATH *aSheet, const wxString &aReference)
Set the reference for the given sheet path for this symbol.
void SetOrientation(int aOrientation)
Compute the new transform matrix based on aOrientation for the symbol which is applied to the current...
void AddHierarchicalReference(const KIID_PATH &aPath, const wxString &aRef, int aUnit)
Add a full hierarchical reference to this symbol.
VECTOR2I GetPosition() const override
Definition sch_symbol.h:871
void SetValueFieldText(const wxString &aValue, const SCH_SHEET_PATH *aInstance=nullptr, const wxString &aVariantName=wxEmptyString)
void SetLibSymbol(LIB_SYMBOL *aLibSymbol)
Set this schematic symbol library symbol reference to aLibSymbol.
SCH_FIELD * GetField(FIELD_T aFieldType)
Return a mandatory field in this symbol.
Simple container to manage line stroke parameters.
static const char * PropPowerSymsOnly
#define _(s)
#define IS_NEW
New item, just created.
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:49
@ FILLED_SHAPE
Fill with object color.
Definition eda_shape.h:61
double m_PadsSchTextWidthScale
PADS text width scale factor for schematic imports.
double m_PadsSchTextHeightScale
PADS text height scale factor for schematic imports.
#define THROW_IO_ERROR(msg)
macro which captures the "call site" values of FILE_, __FUNCTION & LINE
@ LAYER_WIRE
Definition layer_ids.h:454
@ LAYER_NOTES
Definition layer_ids.h:469
LINE_STYLE PadsLineStyleToKiCad(int aPadsStyle)
Convert a PADS line style integer to a KiCad LINE_STYLE enum value.
KIID GenerateDeterministicUuid(const std::string &aIdentifier)
Generate a deterministic KIID from a PADS component identifier.
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
Common utilities and types for parsing PADS file formats.
@ RPT_SEVERITY_WARNING
static int computePowerOrientation(const std::string &aOpcId, const std::vector< PADS_SCH::SCH_SIGNAL > &aSignals, const VECTOR2I &aOpcPos, bool aPinUp, int aPageHeightIU)
Determine the orientation for a power symbol at an OPC position based on the wire direction at that p...
static std::string extractConnectorBaseRef(const std::string &aRef)
Extract the base reference from a connector reference designator.
static SCH_TEXT * createSchText(const PADS_SCH::TEXT_ITEM &aText, const VECTOR2I &aPos)
static std::string extractConnectorPinNumber(const std::string &aRef)
Extract the numeric connector pin suffix from a reference designator.
static std::string stripGateSuffix(const std::string &aRef)
Strip any alphabetic gate suffix (e.g.
Definition of the SCH_SHEET_PATH and SCH_SHEET_LIST classes for Eeschema.
LINE_STYLE
Dashed line types.
Gate definition within a PARTTYPE.
std::vector< std::string > decal_names
std::vector< PARTTYPE_PIN > pins
Graphical line/shape item from LINES section.
Off-page reference from OFFPAGE REFS section.
General schematic parameters from SCH and FIELDS sections.
std::string border_template
Part type definition from PARTTYPE section.
std::vector< GATE_DEF > gates
std::vector< SPECIAL_VARIANT > special_variants
Pin definition within a PARTTYPE GATE.
Part instance from PART section.
Signal (net) definition from CONNECTION and SIGNAL sections.
Sheet header from SHT section.
Symbol definition from CAEDECAL section.
Graphic primitive from CAEDECAL or LINES sections (OPEN, CLOSED, CIRCLE, COPCLS).
std::vector< GRAPHIC_POINT > points
Free text item from TEXT section.
Junction dot from TIEDOTS section.
Wire segment connecting two endpoints through coordinate vertices.
std::vector< POINT > vertices
A simple container for sheet instance information.
@ SYM_ORIENT_270
Definition symbol.h:42
@ SYM_MIRROR_Y
Definition symbol.h:44
@ SYM_ORIENT_180
Definition symbol.h:41
@ SYM_MIRROR_X
Definition symbol.h:43
@ SYM_ORIENT_90
Definition symbol.h:40
@ SYM_ORIENT_0
Definition symbol.h:39
@ REFERENCE
Field Reference of part, i.e. "IC21".
@ VALUE
Field Value of part, i.e. "3.3K".
std::string path
KIBIS_PIN * pin
VECTOR2I center
int radius
VECTOR2I end
SHAPE_CIRCLE circle(c.m_circle_center, c.m_circle_radius)
@ GR_TEXT_H_ALIGN_CENTER
@ GR_TEXT_H_ALIGN_RIGHT
@ GR_TEXT_H_ALIGN_LEFT
@ GR_TEXT_V_ALIGN_BOTTOM
@ GR_TEXT_V_ALIGN_CENTER
@ GR_TEXT_V_ALIGN_TOP
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:687
Definition of file extensions used in Kicad.