KiCad PCB EDA Suite
Loading...
Searching...
No Matches
sch_io_pcad.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) 2007, 2008 Lubo Racko <[email protected]>
5 * Copyright (C) 2012-2013 Alexander Lunev <[email protected]>
6 * Copyright (C) 2017 Eldar Khayrullin <[email protected]>
7 * Copyright (C) 2025 KiCad Developers, see AUTHORS.txt for contributors.
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License
11 * as published by the Free Software Foundation; either version 2
12 * of the License, or (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License along
20 * with this program. If not, see <http://www.gnu.org/licenses/>.
21 */
22
25
27
28#include <lib_id.h>
29#include <lib_symbol.h>
30#include <page_info.h>
31#include <pin_type.h>
32#include <sch_bus_entry.h>
33#include <sch_field.h>
34#include <sch_junction.h>
35#include <sch_label.h>
36#include <sch_line.h>
37#include <sch_pin.h>
38#include <sch_screen.h>
39#include <sch_shape.h>
40#include <sch_sheet.h>
41#include <sch_sheet_path.h>
42#include <sch_symbol.h>
43#include <sch_text.h>
44#include <schematic.h>
45#include <stroke_params.h>
46#include <title_block.h>
48
49#include <wx/filename.h>
50#include <wx/regex.h>
51
52#include <cmath>
53#include <memory>
54#include <set>
55
56
57// All library symbols built from one parsed file. Definition names are the
58// unique lookup keys; the LIB_SYMBOL names are de-duplicated display names.
60{
61 std::map<wxString, std::unique_ptr<LIB_SYMBOL>> byCompDef;
62 std::map<wxString, std::unique_ptr<LIB_SYMBOL>> bySymbolDef;
63};
64
65
66namespace
67{
68
69using namespace PCAD_SCH;
70
71// P-CAD stroke and TrueType fonts are specified by cell height; the KiCad text
72// size is the glyph height. These ratios come from measuring P-CAD output.
73constexpr double STROKE_HEIGHT_TO_SIZE = 0.656;
74constexpr double TRUETYPE_HEIGHT_TO_SIZE = 0.585;
75
76constexpr double BUS_ENTRY_SIZE_MILS = 100.0;
77
78
79// ---------------------------------------------------------------------------
80// Coordinate helpers. P-CAD uses Y-up; KiCad uses Y-down.
81// ---------------------------------------------------------------------------
82
83int toIU( double aMils )
84{
85 return schIUScale.MilsToIU( aMils );
86}
87
88
89int schX( double aPcadX )
90{
91 return toIU( aPcadX );
92}
93
94
95int schY( double aPcadY, double aPageHeightMils )
96{
97 return toIU( aPageHeightMils - aPcadY );
98}
99
100
101int symX( double aPcadX )
102{
103 return toIU( aPcadX );
104}
105
106
107int symY( double aPcadY )
108{
109 return -toIU( aPcadY );
110}
111
112
113int normalizeDeg( double aRotDeg )
114{
115 int rot = static_cast<int>( std::round( aRotDeg ) ) % 360;
116
117 if( rot < 0 )
118 rot += 360;
119
120 return rot;
121}
122
123
124// P-CAD auto-generated net names are either purely numeric or NETnnnn;
125// anything else is a user-assigned name worth a label.
126bool isAutoNetName( const wxString& aName )
127{
128 if( aName.IsEmpty() )
129 return true;
130
131 static wxRegEx autoNet( wxT( "^(NET)?[0-9]+$" ), wxRE_ICASE );
132
133 return autoNet.Matches( aName );
134}
135
136
137bool isAutoBusName( const wxString& aName )
138{
139 if( aName.IsEmpty() )
140 return true;
141
142 static wxRegEx autoBus( wxT( "^BUS[0-9]+$" ), wxRE_ICASE );
143
144 return autoBus.Matches( aName );
145}
146
147
148// ---------------------------------------------------------------------------
149// Text formatting
150// ---------------------------------------------------------------------------
151
152void applyJustify( EDA_TEXT* aText, JUSTIFY aJustify, bool aFlipped )
153{
154 GR_TEXT_H_ALIGN_T hAlign;
155 GR_TEXT_V_ALIGN_T vAlign;
156
157 switch( aJustify )
158 {
161 case JUSTIFY::LEFT:
162 hAlign = GR_TEXT_H_ALIGN_LEFT;
163 break;
166 case JUSTIFY::RIGHT:
167 hAlign = GR_TEXT_H_ALIGN_RIGHT;
168 break;
169 default:
170 hAlign = GR_TEXT_H_ALIGN_CENTER;
171 break;
172 }
173
174 switch( aJustify )
175 {
179 vAlign = GR_TEXT_V_ALIGN_BOTTOM;
180 break;
184 vAlign = GR_TEXT_V_ALIGN_TOP;
185 break;
186 default:
187 vAlign = GR_TEXT_V_ALIGN_CENTER;
188 break;
189 }
190
191 if( aFlipped )
192 {
193 if( hAlign == GR_TEXT_H_ALIGN_LEFT )
194 hAlign = GR_TEXT_H_ALIGN_RIGHT;
195 else if( hAlign == GR_TEXT_H_ALIGN_RIGHT )
196 hAlign = GR_TEXT_H_ALIGN_LEFT;
197 }
198
199 aText->SetHorizJustify( hAlign );
200 aText->SetVertJustify( vAlign );
201}
202
203
204// P-CAD font cell height to KiCad glyph height, with a legible fallback for
205// degenerate styles.
206int textSizeFromStyle( const PCAD_SCH::TEXT_STYLE* aStyle, int aDefault )
207{
208 if( !aStyle )
209 return aDefault;
210
211 const PCAD_SCH::FONT& font = aStyle->EffectiveFont();
213 int size = toIU( font.height * ratio );
214
215 return size > 0 ? size : aDefault;
216}
217
218
219// Apply a P-CAD text style (font size, weight, slant) and the per-item
220// justification and rotation to a KiCad text object.
221void applyTextStyle( EDA_TEXT* aText, const PCAD_SCH::TEXT_ITEM& aItem,
222 const PCAD_SCH::SCHEMATIC& aPcad )
223{
224 const PCAD_SCH::TEXT_STYLE* style = aPcad.FindTextStyle( aItem.styleRef );
225
226 double widthMils = 10.0;
227 bool isBold = false;
228 bool isItalic = false;
229
230 if( style )
231 {
232 const PCAD_SCH::FONT& font = style->EffectiveFont();
233
234 widthMils = font.strokeWidth;
235 isBold = font.isBold;
236 isItalic = font.isItalic;
237 }
238
239 int size = textSizeFromStyle( style, toIU( 100.0 * STROKE_HEIGHT_TO_SIZE ) );
240
241 aText->SetTextSize( VECTOR2I( size, size ) );
242
243 if( !isBold && widthMils > 0 )
244 aText->SetTextThickness( toIU( widthMils ) );
245
246 aText->SetBold( isBold );
247 aText->SetItalic( isItalic );
248 aText->SetVisible( aItem.isVisible );
249
250 int rot = normalizeDeg( aItem.rotation );
251
252 // KiCad schematic text is horizontal or vertical; 180 and 270 fold onto
253 // the base angle with mirrored justification.
254 if( rot == 90 || rot == 270 )
255 aText->SetTextAngle( EDA_ANGLE( 90.0, DEGREES_T ) );
256 else
257 aText->SetTextAngle( EDA_ANGLE( 0.0, DEGREES_T ) );
258
259 applyJustify( aText, aItem.justify, aItem.isFlipped );
260
261 if( rot == 180 || rot == 270 )
262 {
265 }
266}
267
268
269// ---------------------------------------------------------------------------
270// Pin mapping
271// ---------------------------------------------------------------------------
272
273// Map P-CAD pin rotation to KiCad PIN_ORIENTATION. P-Cad rotation is the
274// direction the pin exits the body toward the connection point (CCW, Y-up).
275PIN_ORIENTATION pinOrientation( double aRotDeg )
276{
277 switch( normalizeDeg( aRotDeg ) )
278 {
279 case 90: return PIN_ORIENTATION::PIN_DOWN;
280 case 180: return PIN_ORIENTATION::PIN_RIGHT;
281 case 270: return PIN_ORIENTATION::PIN_UP;
282 default: return PIN_ORIENTATION::PIN_LEFT; // 0 degrees
283 }
284}
285
286
287ELECTRICAL_PINTYPE mapPinType( const wxString& aPcadType )
288{
289 // "Pasive" is the historical P-CAD spelling; Altium exports write "Passive".
290 if( aPcadType == wxT( "Passive" ) || aPcadType == wxT( "Pasive" ) )
292
293 if( aPcadType == wxT( "Input" ) )
295
296 if( aPcadType == wxT( "Output" ) )
298
299 if( aPcadType == wxT( "Bidirectional" ) )
301
302 if( aPcadType == wxT( "Power" ) )
304
305 if( aPcadType == wxT( "ThreeState" ) )
307
308 if( aPcadType == wxT( "OpenH" ) )
310
311 if( aPcadType == wxT( "OpenL" ) )
313
315}
316
317
318GRAPHIC_PINSHAPE mapPinShape( const PIN& aPin )
319{
320 bool inverted = ( aPin.outsideEdgeStyle == wxT( "Dot" ) );
321 bool clock = ( aPin.outsideEdgeStyle == wxT( "Clock" )
322 || aPin.insideEdgeStyle == wxT( "Clock" ) );
323
324 if( inverted && clock )
326
327 if( inverted )
329
330 if( clock )
332
334}
335
336
337// ---------------------------------------------------------------------------
338// IEEE symbol synthesis. P-CAD qualifier symbols have no KiCad primitive;
339// draw them as polylines scaled by the symbol height.
340// ---------------------------------------------------------------------------
341
342const VECTOR2D IEEE_BREAK( -1e9, -1e9 );
343
344std::vector<VECTOR2D> ieeePoints( const IEEE_SYMBOL& aSym )
345{
346 const double h = aSym.height;
347 const double half = h / 2.0;
348 const double quarter = h / 4.0;
349 const double third = h / 3.0;
350
351 switch( aSym.kind )
352 {
353 case IEEE_KIND::ADDER:
354 return { { h, h - quarter }, { h, h }, { 0, h }, { half, half }, { 0, 0 }, { h, 0 },
355 { h, quarter } };
356
358 return { { 0, 0 }, { h, half }, { 0, h }, { 0, 0 } };
359
361 return { { 0, 0 }, { half, 0 }, { half, h }, { h, h }, { h, 0 }, { h + half, 0 },
362 { h + half, h }, { 2 * h, h }, { 2 * h, 0 }, { 2 * h + half, 0 } };
363
365 return { { 0, 0 }, { h, 0 }, IEEE_BREAK, { half, 0 }, { half, h }, IEEE_BREAK,
366 { 0, h }, { h, h } };
367
369 return { { 0, 0 }, { half, 0 }, { half, h }, { h, h }, { h, 0 }, { h + half, 0 } };
370
372 return { { 0, 0 }, { half + h, 0 }, { half + h, h }, { half, h }, { half, 0 },
373 IEEE_BREAK, { half + h, h }, { 2 * h, h } };
374
376 return { { 0, h }, { h, h }, IEEE_BREAK, { third, 0 }, { third, h }, IEEE_BREAK,
377 { 2 * third, 0 }, { 2 * third, h } };
378
379 default:
380 return {};
381 }
382}
383
384
385VECTOR2D ieeeTransform( const VECTOR2D& aPoint, const IEEE_SYMBOL& aSym )
386{
387 double rad = aSym.rotation * M_PI / 180.0;
388 double x = aPoint.x * std::cos( rad ) - aPoint.y * std::sin( rad );
389 double y = aPoint.x * std::sin( rad ) + aPoint.y * std::cos( rad );
390
391 if( aSym.isFlipped )
392 x = -x;
393
394 return { x + aSym.x, y + aSym.y };
395}
396
397
398// Emit an IEEE symbol as polyline shapes through a caller-provided
399// point transform (symbol space or sheet space).
400template <typename XFORM>
401std::vector<SCH_SHAPE*> buildIeeeShapes( const IEEE_SYMBOL& aSym, int aWidthIU, SCH_LAYER_ID aLayer,
402 XFORM aXform )
403{
404 std::vector<SCH_SHAPE*> shapes;
405 std::vector<VECTOR2D> points = ieeePoints( aSym );
406
407 if( points.empty() )
408 return shapes;
409
410 SCH_SHAPE* poly = nullptr;
411
412 for( const VECTOR2D& pt : points )
413 {
414 if( pt == IEEE_BREAK )
415 {
416 poly = nullptr;
417 continue;
418 }
419
420 VECTOR2D world = ieeeTransform( pt, aSym );
421
422 if( !poly )
423 {
424 poly = new SCH_SHAPE( SHAPE_T::POLY, aLayer );
425 poly->SetStroke( STROKE_PARAMS( aWidthIU, LINE_STYLE::SOLID ) );
427 shapes.push_back( poly );
428 }
429
430 poly->AddPoint( aXform( world.x, world.y ) );
431 }
432
433 // A Complex qualifier also carries a small circle at its centre.
434 if( aSym.kind == IEEE_KIND::COMPLEX )
435 {
436 IEEE_SYMBOL centre = aSym;
437 VECTOR2D c = ieeeTransform( { aSym.height / 2.0, aSym.height / 2.0 }, centre );
438
439 auto* circle = new SCH_SHAPE( SHAPE_T::CIRCLE, aLayer );
440 circle->SetStroke( STROKE_PARAMS( aWidthIU, LINE_STYLE::SOLID ) );
441 circle->SetFillMode( FILL_T::NO_FILL );
442 circle->SetCenter( aXform( c.x, c.y ) );
443 circle->SetEnd( aXform( c.x + aSym.height / 8.0, c.y ) );
444 shapes.push_back( circle );
445 }
446
447 return shapes;
448}
449
450
451// Both the symbol and sheet paths build arcs the same way; the Y-flip in the
452// caller's transform mirrors the angles.
453template <typename XFORM>
454SCH_SHAPE* buildArcShape( const PCAD_SCH::ARC& aArc, int aWidthIU, SCH_LAYER_ID aLayer,
455 XFORM aXform )
456{
457 auto* shape = new SCH_SHAPE( aArc.sweepAngle >= 360.0 ? SHAPE_T::CIRCLE : SHAPE_T::ARC,
458 aLayer );
459 int radius = toIU( aArc.radius );
460 VECTOR2I center = aXform( aArc.x, aArc.y );
461
462 if( shape->GetShape() == SHAPE_T::CIRCLE )
463 {
464 shape->SetCenter( center );
465 shape->SetEnd( center + VECTOR2I( radius, 0 ) );
466 }
467 else
468 {
469 double startRad = -aArc.startAngle * M_PI / 180.0;
470 VECTOR2I start( center.x + KiROUND( radius * std::cos( startRad ) ),
471 center.y + KiROUND( radius * std::sin( startRad ) ) );
472 shape->SetCenter( center );
473 shape->SetStart( start );
474 shape->SetArcAngleAndEnd( EDA_ANGLE( -aArc.sweepAngle, DEGREES_T ) );
475 }
476
477 shape->SetFillMode( FILL_T::NO_FILL );
478 shape->SetStroke( STROKE_PARAMS( aWidthIU, LINE_STYLE::SOLID ) );
479
480 return shape;
481}
482
483
484LINE_STYLE mapLineStyle( LINE_KIND aKind )
485{
486 switch( aKind )
487 {
490 default: return LINE_STYLE::SOLID;
491 }
492}
493
494
495// ---------------------------------------------------------------------------
496// LIB_SYMBOL construction
497// ---------------------------------------------------------------------------
498
499const PCAD_SCH::SYMBOL_DEF* findSymbolDef( const PCAD_SCH::SCHEMATIC& aPcad,
500 const wxString& aName )
501{
502 auto it = aPcad.symbolDefsByName.find( aName );
503
504 return it == aPcad.symbolDefsByName.end() ? nullptr : it->second;
505}
506
507
508const PCAD_SCH::COMP_PIN* findCompPin( const PCAD_SCH::COMP_DEF& aDef, int aPartNum,
509 const wxString& aSymPinNum )
510{
511 for( const COMP_PIN& cp : aDef.compPins )
512 {
513 if( cp.partNum == aPartNum && cp.symPinNum == aSymPinNum )
514 return &cp;
515 }
516
517 return nullptr;
518}
519
520
521// Add one symbolDef's geometry and pins to a LIB_SYMBOL as the given unit.
522// aCompDef may be null when building directly from a bare symbolDef.
523void addSymbolDefToLibSymbol( LIB_SYMBOL* aSymbol, const PCAD_SCH::SYMBOL_DEF& aDef,
524 const PCAD_SCH::COMP_DEF* aCompDef, int aUnit,
525 const PCAD_SCH::SCHEMATIC& aPcad,
526 bool* aShowPinDes, bool* aShowPinName )
527{
528 const int defaultWidth = toIU( 10.0 );
529
530 for( const LINE& ln : aDef.lines )
531 {
532 if( ln.pts.size() < 2 )
533 continue;
534
535 auto* shape = new SCH_SHAPE( SHAPE_T::POLY, LAYER_DEVICE );
536
537 for( const auto& [x, y] : ln.pts )
538 shape->AddPoint( VECTOR2I( symX( x ), symY( y ) ) );
539
540 shape->SetFillMode( FILL_T::NO_FILL );
541 shape->SetStroke( STROKE_PARAMS( ln.width > 0 ? toIU( ln.width ) : defaultWidth,
542 mapLineStyle( ln.style ) ) );
543 shape->SetUnit( aUnit );
544 aSymbol->AddDrawItem( shape );
545 }
546
547 for( const ARC& arc : aDef.arcs )
548 {
549 SCH_SHAPE* shape = buildArcShape( arc, arc.width > 0 ? toIU( arc.width ) : defaultWidth,
551 []( double x, double y )
552 {
553 return VECTOR2I( symX( x ), symY( y ) );
554 } );
555 shape->SetUnit( aUnit );
556 aSymbol->AddDrawItem( shape );
557 }
558
559 for( const POLY& poly : aDef.polys )
560 {
561 if( poly.pts.size() < 3 )
562 continue;
563
564 auto* shape = new SCH_SHAPE( SHAPE_T::POLY, LAYER_DEVICE );
565
566 for( const auto& [x, y] : poly.pts )
567 shape->AddPoint( VECTOR2I( symX( x ), symY( y ) ) );
568
569 // close the outline
570 shape->AddPoint( VECTOR2I( symX( poly.pts[0].first ), symY( poly.pts[0].second ) ) );
572 shape->SetStroke( STROKE_PARAMS( defaultWidth, LINE_STYLE::SOLID ) );
573 shape->SetUnit( aUnit );
574 aSymbol->AddDrawItem( shape );
575 }
576
577 for( const TEXT_ITEM& text : aDef.texts )
578 {
579 if( text.text.IsEmpty() )
580 continue;
581
582 auto* schText = new SCH_TEXT( VECTOR2I( symX( text.x ), symY( text.y ) ), text.text,
583 LAYER_DEVICE );
584 applyTextStyle( schText, text, aPcad );
585 schText->SetUnit( aUnit );
586 aSymbol->AddDrawItem( schText );
587 }
588
589 for( const IEEE_SYMBOL& ieee : aDef.ieeeSymbols )
590 {
591 for( SCH_SHAPE* shape : buildIeeeShapes( ieee, defaultWidth, LAYER_DEVICE,
592 []( double x, double y )
593 {
594 return VECTOR2I( symX( x ), symY( y ) );
595 } ) )
596 {
597 shape->SetUnit( aUnit );
598 aSymbol->AddDrawItem( shape );
599 }
600 }
601
602 const int pinTextSize = toIU( 50.0 );
603
604 for( const PIN& pin : aDef.pins )
605 {
606 // The wire connection point is the body attachment displaced by the
607 // pin length along the exit direction.
608 int rot = normalizeDeg( pin.rotation );
609 double extX = pin.x;
610 double extY = pin.y;
611
612 switch( rot )
613 {
614 case 0: extX += pin.pinLength; break;
615 case 90: extY += pin.pinLength; break;
616 case 180: extX -= pin.pinLength; break;
617 case 270: extY -= pin.pinLength; break;
618 default:
619 extX += pin.pinLength * std::cos( rot * M_PI / 180.0 );
620 extY += pin.pinLength * std::sin( rot * M_PI / 180.0 );
621 break;
622 }
623
624 auto* schPin = new SCH_PIN( aSymbol );
625
626 const COMP_PIN* compPin = aCompDef ? findCompPin( *aCompDef, aUnit, pin.pinNum )
627 : nullptr;
628
629 wxString number;
630 wxString name;
631
632 if( compPin )
633 {
634 number = compPin->padDes;
635 name = compPin->pinName;
636 }
637
638 if( number.IsEmpty() )
639 number = !pin.defaultPinDes.IsEmpty() ? pin.defaultPinDes : pin.pinDesText.text;
640
641 if( number.IsEmpty() )
642 number = pin.pinNum;
643
644 if( name.IsEmpty() )
645 name = pin.pinNameText.text;
646
647 schPin->SetNumber( number );
648 schPin->SetName( name );
649 schPin->SetPosition( VECTOR2I( symX( extX ), symY( extY ) ) );
650 schPin->SetLength( toIU( pin.pinLength ) );
651 schPin->SetOrientation( pinOrientation( pin.rotation ) );
652 schPin->SetShape( mapPinShape( pin ) );
653 schPin->SetUnit( aUnit );
654
655 if( aCompDef && aCompDef->isPower )
656 {
657 schPin->SetType( ELECTRICAL_PINTYPE::PT_POWER_IN );
658 schPin->SetVisible( false );
659 }
660 else
661 {
662 schPin->SetType( compPin ? mapPinType( compPin->pinType )
664 }
665
666 schPin->SetNumberTextSize(
667 textSizeFromStyle( aPcad.FindTextStyle( pin.pinDesText.styleRef ),
668 pinTextSize ) );
669 schPin->SetNameTextSize(
670 textSizeFromStyle( aPcad.FindTextStyle( pin.pinNameText.styleRef ),
671 pinTextSize ) );
672
673 if( aShowPinDes )
674 *aShowPinDes |= pin.showPinDes;
675
676 if( aShowPinName )
677 *aShowPinName |= pin.showPinName;
678
679 aSymbol->AddDrawItem( schPin );
680 }
681
682 // Reference and value placement from (attr "RefDes" ...) and (attr "Type" ...).
683 // Only the first unit positions the shared fields.
684 if( aUnit == 1 )
685 {
686 for( const ATTR& attr : aDef.attrs )
687 {
688 SCH_FIELD* field = nullptr;
689
690 if( attr.name == wxT( "RefDes" ) )
691 field = &aSymbol->GetReferenceField();
692 else if( attr.name == wxT( "Type" ) || attr.name == wxT( "Value" ) )
693 field = &aSymbol->GetValueField();
694
695 if( field )
696 {
697 field->SetPosition( VECTOR2I( symX( attr.placement.x ),
698 symY( attr.placement.y ) ) );
699 applyTextStyle( field, attr.placement, aPcad );
700 field->SetVisible( attr.placement.isVisible );
701 }
702 }
703 }
704}
705
706
707wxString libSymbolName( const PCAD_SCH::COMP_DEF& aDef )
708{
709 wxString name = !aDef.originalName.IsEmpty() ? aDef.originalName : aDef.name;
710 name.Replace( wxT( " " ), wxT( "_" ) );
711 return name;
712}
713
714
715// Component and symbol definitions can share display names (e.g. merged
716// libraries reusing an originalName); make each placed name unique.
717wxString uniqueSymbolName( const wxString& aName, std::set<wxString>& aUsedNames )
718{
719 wxString name = aName;
720 int suffix = 2;
721
722 while( !aUsedNames.insert( name ).second )
723 name = wxString::Format( wxT( "%s_%d" ), aName, suffix++ );
724
725 return name;
726}
727
728
729// Build a complete LIB_SYMBOL (all units) from a component definition.
730LIB_SYMBOL* buildLibSymbolFromCompDef( const PCAD_SCH::COMP_DEF& aDef,
731 const PCAD_SCH::SCHEMATIC& aPcad,
732 const wxString& aLibName,
733 const wxString& aSymbolName )
734{
735 auto* sym = new LIB_SYMBOL( aSymbolName );
736
737 sym->SetLibId( LIB_ID( aLibName, aSymbolName ) );
738 sym->SetUnitCount( aDef.numParts > 0 ? aDef.numParts : 1, false );
739
740 bool showPinDes = false;
741 bool showPinName = false;
742
743 for( int unit = 1; unit <= aDef.numParts; unit++ )
744 {
745 wxString symName;
746
747 if( unit < static_cast<int>( aDef.attachedSymbols.size() ) )
748 symName = aDef.attachedSymbols[unit];
749
750 const SYMBOL_DEF* symDef = symName.IsEmpty() ? nullptr
751 : findSymbolDef( aPcad, symName );
752
753 if( symDef )
754 addSymbolDefToLibSymbol( sym, *symDef, &aDef, unit, aPcad, &showPinDes,
755 &showPinName );
756 }
757
758 sym->SetShowPinNumbers( showPinDes );
759 sym->SetShowPinNames( showPinName );
760
761 if( !aDef.refDesPrefix.IsEmpty() )
762 sym->GetReferenceField().SetText( aDef.refDesPrefix );
763
764 if( !aDef.description.IsEmpty() )
765 sym->SetDescription( aDef.description );
766
767 if( !aDef.attachedPattern.IsEmpty() )
768 sym->GetFootprintField().SetText( aDef.attachedPattern );
769
770 if( aDef.isPower )
771 {
772 sym->SetGlobalPower();
773 sym->GetReferenceField().SetText( wxT( "#PWR" ) );
774 sym->GetReferenceField().SetVisible( false );
775 }
776
777 return sym;
778}
779
780
781wxString symbolDefDisplayName( const PCAD_SCH::SYMBOL_DEF& aDef )
782{
783 wxString name = !aDef.originalName.IsEmpty() ? aDef.originalName : aDef.name;
784 name.Replace( wxT( " " ), wxT( "_" ) );
785 return name;
786}
787
788
789// Fallback for files that carry symbolDefs without component definitions.
790LIB_SYMBOL* buildLibSymbolFromSymbolDef( const PCAD_SCH::SYMBOL_DEF& aDef,
791 const PCAD_SCH::SCHEMATIC& aPcad,
792 const wxString& aLibName,
793 const wxString& aSymbolName )
794{
795 auto* sym = new LIB_SYMBOL( aSymbolName );
796 sym->SetLibId( LIB_ID( aLibName, aSymbolName ) );
797
798 bool showPinDes = false;
799 bool showPinName = false;
800
801 addSymbolDefToLibSymbol( sym, aDef, nullptr, 1, aPcad, &showPinDes, &showPinName );
802
803 sym->SetShowPinNumbers( showPinDes );
804 sym->SetShowPinNames( showPinName );
805
806 return sym;
807}
808
809
810// ---------------------------------------------------------------------------
811// Sheet items
812// ---------------------------------------------------------------------------
813
814// Map P-CAD symbol instance rotation to KiCad orientation (both CCW once the
815// Y-flip is applied).
816SYMBOL_ORIENTATION_T symOrientation( double aRotDeg )
817{
818 switch( normalizeDeg( aRotDeg ) )
819 {
820 case 90: return SYM_ORIENT_90;
821 case 180: return SYM_ORIENT_180;
822 case 270: return SYM_ORIENT_270;
823 default: return SYM_ORIENT_0;
824 }
825}
826
827
828LIB_SYMBOL_STORE buildLibSymbols( const PCAD_SCH::SCHEMATIC& aPcad, const wxString& aLibName )
829{
830 LIB_SYMBOL_STORE store;
831 std::set<wxString> usedNames;
832 std::set<wxString> attachedNames;
833
834 for( const PCAD_SCH::COMP_DEF& cd : aPcad.compDefs )
835 {
836 for( const wxString& attached : cd.attachedSymbols )
837 {
838 if( !attached.IsEmpty() )
839 attachedNames.insert( attached );
840 }
841
842 if( store.byCompDef.count( cd.name ) )
843 continue;
844
845 wxString name = uniqueSymbolName( libSymbolName( cd ), usedNames );
846
847 store.byCompDef[cd.name].reset( buildLibSymbolFromCompDef( cd, aPcad, aLibName, name ) );
848 }
849
850 // symbolDefs never referenced by a compDef still need a library symbol so
851 // netlist-less files import
852 for( const PCAD_SCH::SYMBOL_DEF& sd : aPcad.symbolDefs )
853 {
854 if( attachedNames.count( sd.name ) || attachedNames.count( sd.originalName ) )
855 continue;
856
857 if( store.bySymbolDef.count( sd.name ) )
858 continue;
859
860 wxString name = uniqueSymbolName( symbolDefDisplayName( sd ), usedNames );
861
862 store.bySymbolDef[sd.name].reset(
863 buildLibSymbolFromSymbolDef( sd, aPcad, aLibName, name ) );
864 }
865
866 return store;
867}
868
869
870SPIN_STYLE spinFromRotation( double aRotDeg, bool aFlipped, bool aVertical )
871{
872 int rot = normalizeDeg( aRotDeg );
873
874 if( rot == 0 && aVertical )
875 rot = 90;
876
878
879 switch( rot )
880 {
881 case 90: spin = SPIN_STYLE::UP; break;
882 case 180: spin = SPIN_STYLE::LEFT; break;
883 case 270: spin = SPIN_STYLE::BOTTOM; break;
884 default: spin = SPIN_STYLE::RIGHT; break;
885 }
886
887 if( aFlipped )
888 {
889 if( spin == SPIN_STYLE::RIGHT )
890 spin = SPIN_STYLE::LEFT;
891 else if( spin == SPIN_STYLE::LEFT )
892 spin = SPIN_STYLE::RIGHT;
893 }
894
895 return spin;
896}
897
898
899LABEL_FLAG_SHAPE mapPortShape( const wxString& aPortType )
900{
901 if( aPortType.Contains( wxT( "Dbl" ) ) )
903
904 if( aPortType.StartsWith( wxT( "LeftAngle" ) ) || aPortType.StartsWith( wxT( "RightAngle" ) ) )
906
908}
909
910} // anonymous namespace
911
912
913// ---------------------------------------------------------------------------
914// SCH_IO_PCAD
915// ---------------------------------------------------------------------------
916
917SCH_IO_PCAD::SCH_IO_PCAD() : SCH_IO( wxS( "P-CAD" ) )
918{
919}
920
921
925
926
927bool SCH_IO_PCAD::CanReadSchematicFile( const wxString& aFileName ) const
928{
929 // P-CAD ASCII saves are extension-agnostic, and boards share the same
930 // ACCEL_ASCII container, so detect by content.
931 return PCAD2KICAD::FileMatchesFormat( aFileName, "schematicDesign" );
932}
933
934
935wxString SCH_IO_PCAD::getLibName( const ::SCHEMATIC* aSchematic, const wxString& aFileName )
936{
937 wxString libName;
938
939 if( aSchematic )
940 libName = aSchematic->Project().GetProjectName();
941
942 if( libName.IsEmpty() )
943 libName = wxFileName( aFileName ).GetName();
944
945 if( libName.IsEmpty() )
946 libName = wxT( "noname" );
947
948 libName += wxT( "-pcad-import" );
949
950 return LIB_ID::FixIllegalChars( libName, true ).wx_str();
951}
952
953
955 const PCAD_SCH::SCHEMATIC& aPcad, double aPageH,
956 const LIB_SYMBOL_STORE& aLibSymbols,
957 const wxString& aLibName )
958{
959 auto xform =
960 [aPageH]( double x, double y )
961 {
962 return VECTOR2I( schX( x ), schY( y, aPageH ) );
963 };
964
965 // Wires; a P-CAD wire line may be a polyline.
966 for( const WIRE& w : aSheet.wires )
967 {
968 for( size_t i = 0; i + 1 < w.pts.size(); i++ )
969 {
970 auto* line = new SCH_LINE( xform( w.pts[i].first, w.pts[i].second ), LAYER_WIRE );
971 line->SetEndPoint( xform( w.pts[i + 1].first, w.pts[i + 1].second ) );
972 aScreen->Append( line );
973 }
974 }
975
976 for( const BUS& b : aSheet.buses )
977 {
978 for( size_t i = 0; i + 1 < b.pts.size(); i++ )
979 {
980 auto* line = new SCH_LINE( xform( b.pts[i].first, b.pts[i].second ), LAYER_BUS );
981 line->SetEndPoint( xform( b.pts[i + 1].first, b.pts[i + 1].second ) );
982 aScreen->Append( line );
983 }
984
985 if( ( b.dispName || !isAutoBusName( b.name ) ) && !b.name.IsEmpty() )
986 {
987 VECTOR2I pos;
988 const TEXT_ITEM& label = b.label;
989
990 if( label.x != 0 || label.y != 0 )
991 pos = xform( label.x, label.y );
992 else
993 pos = xform( b.pts[0].first, b.pts[0].second );
994
995 auto* schLabel = new SCH_LABEL( pos, b.name );
996 applyTextStyle( schLabel, label, aPcad );
997 schLabel->SetSpinStyle( spinFromRotation( label.rotation, label.isFlipped, false ) );
998 aScreen->Append( schLabel );
999 }
1000 }
1001
1002 // Bus entries. The P-CAD node holds the wire-side end and the direction
1003 // toward the bus; the diagonal's vertical slant is chosen so the entry
1004 // lands on a segment of the referenced bus when one can be found.
1005 for( const BUS_ENTRY& e : aSheet.busEntries )
1006 {
1007 VECTOR2I pos = xform( e.x, e.y );
1008 int step = toIU( BUS_ENTRY_SIZE_MILS );
1009 VECTOR2I size;
1010 bool towardBusIsX;
1011
1012 // orient gives only the toward-bus axis; the slant along the other
1013 // axis is not stored, so try both diagonals against the bus geometry
1014 if( e.orient == wxT( "Left" ) )
1015 {
1016 size = VECTOR2I( -step, step );
1017 towardBusIsX = true;
1018 }
1019 else if( e.orient == wxT( "Right" ) )
1020 {
1021 size = VECTOR2I( step, step );
1022 towardBusIsX = true;
1023 }
1024 else if( e.orient == wxT( "Up" ) )
1025 {
1026 // P-CAD Y-up "Up" is -Y after the KiCad flip
1027 size = VECTOR2I( step, -step );
1028 towardBusIsX = false;
1029 }
1030 else
1031 {
1032 size = VECTOR2I( step, step );
1033 towardBusIsX = false;
1034 }
1035
1036 const BUS* bus = nullptr;
1037
1038 for( const BUS& b : aSheet.buses )
1039 {
1040 if( b.name == e.busNameRef )
1041 {
1042 bus = &b;
1043 break;
1044 }
1045 }
1046
1047 if( bus )
1048 {
1049 // Try both slants; prefer the one whose far end touches the bus.
1050 auto touchesBus =
1051 [&]( const VECTOR2I& aEnd ) -> bool
1052 {
1053 for( size_t i = 0; i + 1 < bus->pts.size(); i++ )
1054 {
1055 VECTOR2I a = xform( bus->pts[i].first, bus->pts[i].second );
1056 VECTOR2I c = xform( bus->pts[i + 1].first, bus->pts[i + 1].second );
1057
1058 if( a.x == c.x && aEnd.x == a.x
1059 && aEnd.y >= std::min( a.y, c.y ) && aEnd.y <= std::max( a.y, c.y ) )
1060 return true;
1061
1062 if( a.y == c.y && aEnd.y == a.y
1063 && aEnd.x >= std::min( a.x, c.x ) && aEnd.x <= std::max( a.x, c.x ) )
1064 return true;
1065 }
1066
1067 return false;
1068 };
1069
1070 VECTOR2I altSize = towardBusIsX ? VECTOR2I( size.x, -size.y )
1071 : VECTOR2I( -size.x, size.y );
1072
1073 if( !touchesBus( pos + size ) && touchesBus( pos + altSize ) )
1074 size = altSize;
1075 }
1076
1077 auto* entry = new SCH_BUS_WIRE_ENTRY( pos );
1078 entry->SetSize( size );
1079 aScreen->Append( entry );
1080 }
1081
1082 for( const JUNCTION& j : aSheet.junctions )
1083 aScreen->Append( new SCH_JUNCTION( xform( j.x, j.y ) ) );
1084
1085 // P-CAD ports connect nets across sheets, which is KiCad global label
1086 // semantics.
1087 for( const PORT& p : aSheet.ports )
1088 {
1089 if( p.netNameRef.IsEmpty() )
1090 continue;
1091
1092 auto* label = new SCH_GLOBALLABEL( xform( p.x, p.y ), p.netNameRef );
1093 label->SetShape( mapPortShape( p.portType ) );
1094 label->SetSpinStyle( spinFromRotation( p.rotation, p.isFlipped,
1095 p.portType.EndsWith( wxT( "_Vert" ) ) ) );
1096 label->SetTextSize( VECTOR2I( toIU( 50.0 ), toIU( 50.0 ) ) );
1097 aScreen->Append( label );
1098 }
1099
1100 // Wire net-name labels where P-CAD displays them
1101 std::set<wxString> labelledNets;
1102
1103 for( const WIRE& w : aSheet.wires )
1104 {
1105 if( !w.dispName || w.netName.IsEmpty() || w.pts.empty() )
1106 continue;
1107
1108 VECTOR2I pos;
1109
1110 if( w.label.x != 0 || w.label.y != 0 )
1111 pos = xform( w.label.x, w.label.y );
1112 else
1113 pos = xform( w.pts[0].first, w.pts[0].second );
1114
1115 auto* label = new SCH_LABEL( pos, w.netName );
1116 applyTextStyle( label, w.label, aPcad );
1117 label->SetSpinStyle( spinFromRotation( w.label.rotation, w.label.isFlipped, false ) );
1118 aScreen->Append( label );
1119
1120 labelledNets.insert( w.netName );
1121 }
1122
1123 // Preserve user-assigned net names that are never displayed with a label
1124 // at the first junction of the net. Auto-generated names are dropped.
1125 for( const JUNCTION& j : aSheet.junctions )
1126 {
1127 if( j.netName.IsEmpty() || labelledNets.count( j.netName ) )
1128 continue;
1129
1130 if( isAutoNetName( j.netName ) )
1131 continue;
1132
1133 auto* label = new SCH_LABEL( xform( j.x, j.y ), j.netName );
1134 label->SetTextSize( VECTOR2I( toIU( 50.0 ), toIU( 50.0 ) ) );
1135 aScreen->Append( label );
1136
1137 labelledNets.insert( j.netName );
1138 }
1139
1140 // Placed title-block fields render as plain text with the field value
1141 for( const FIELD_PLACEMENT& f : aSheet.fields )
1142 {
1143 auto it = aPcad.titleSheet.fields.find( f.name );
1144
1145 if( it == aPcad.titleSheet.fields.end() || it->second.IsEmpty() )
1146 continue;
1147
1148 auto* text = new SCH_TEXT( xform( f.placement.x, f.placement.y ), it->second );
1149 applyTextStyle( text, f.placement, aPcad );
1150 aScreen->Append( text );
1151 }
1152
1153 for( const TEXT_ITEM& t : aSheet.texts )
1154 {
1155 if( t.text.IsEmpty() )
1156 continue;
1157
1158 auto* text = new SCH_TEXT( xform( t.x, t.y ), t.text );
1159 applyTextStyle( text, t, aPcad );
1160 aScreen->Append( text );
1161 }
1162
1163 for( const LINE& ln : aSheet.lines )
1164 {
1165 if( ln.pts.size() < 2 )
1166 continue;
1167
1168 auto* shape = new SCH_SHAPE( SHAPE_T::POLY, LAYER_NOTES );
1169
1170 for( const auto& [x, y] : ln.pts )
1171 shape->AddPoint( xform( x, y ) );
1172
1173 shape->SetFillMode( FILL_T::NO_FILL );
1174 shape->SetStroke( STROKE_PARAMS( toIU( ln.width ), mapLineStyle( ln.style ) ) );
1175 aScreen->Append( shape );
1176 }
1177
1178 for( const ARC& arc : aSheet.arcs )
1179 {
1180 aScreen->Append( buildArcShape( arc,
1181 arc.width > 0 ? toIU( arc.width ) : toIU( 10.0 ),
1182 LAYER_NOTES, xform ) );
1183 }
1184
1185 for( const POLY& poly : aSheet.polys )
1186 {
1187 auto* shape = new SCH_SHAPE( SHAPE_T::POLY, LAYER_NOTES );
1188
1189 for( const auto& [x, y] : poly.pts )
1190 shape->AddPoint( xform( x, y ) );
1191
1192 shape->AddPoint( xform( poly.pts[0].first, poly.pts[0].second ) );
1194 shape->SetStroke( STROKE_PARAMS( toIU( 10.0 ), LINE_STYLE::SOLID ) );
1195 aScreen->Append( shape );
1196 }
1197
1198 for( const IEEE_SYMBOL& ieee : aSheet.ieeeSymbols )
1199 {
1200 for( SCH_SHAPE* shape : buildIeeeShapes( ieee, toIU( 10.0 ), LAYER_NOTES, xform ) )
1201 aScreen->Append( shape );
1202 }
1203
1204 for( const SYMBOL_INST& inst : aSheet.symbols )
1205 {
1206 const COMP_INST* compInst = nullptr;
1207 const COMP_DEF* compDef = nullptr;
1208
1209 auto ciIt = aPcad.compInstsByRef.find( inst.refDesRef );
1210
1211 if( ciIt != aPcad.compInstsByRef.end() )
1212 {
1213 compInst = ciIt->second;
1214
1215 wxString compRef = compInst->compRef;
1216 auto aliasIt = aPcad.compAliases.find( compRef );
1217
1218 if( aliasIt != aPcad.compAliases.end() )
1219 compRef = aliasIt->second;
1220
1221 auto cdIt = aPcad.compDefsByName.find( compRef );
1222
1223 if( cdIt != aPcad.compDefsByName.end() )
1224 compDef = cdIt->second;
1225 }
1226
1227 const LIB_SYMBOL* libSym = nullptr;
1228
1229 if( compDef )
1230 {
1231 auto libIt = aLibSymbols.byCompDef.find( compDef->name );
1232
1233 if( libIt != aLibSymbols.byCompDef.end() )
1234 libSym = libIt->second.get();
1235 }
1236
1237 if( !libSym )
1238 {
1239 // without a netlist entry the raw symbolDef is the only reference
1240 if( const SYMBOL_DEF* symDef = findSymbolDef( aPcad, inst.symbolRef ) )
1241 {
1242 auto libIt = aLibSymbols.bySymbolDef.find( symDef->name );
1243
1244 if( libIt != aLibSymbols.bySymbolDef.end() )
1245 libSym = libIt->second.get();
1246 }
1247 }
1248
1249 if( !libSym )
1250 {
1251 if( m_reporter )
1252 {
1253 m_reporter->Report( wxString::Format(
1254 _( "Symbol definition '%s' for '%s' not found." ),
1256 }
1257
1258 continue;
1259 }
1260
1261 auto* symbol = new SCH_SYMBOL();
1262 symbol->SetLibId( libSym->GetLibId() );
1263 symbol->SetUnit( inst.partNum );
1264 symbol->SetLibSymbol( new LIB_SYMBOL( *libSym ) );
1265 symbol->SetPosition( xform( inst.x, inst.y ) );
1266 symbol->SetOrientation( symOrientation( inst.rotation ) );
1267
1268 if( inst.isFlipped )
1269 symbol->SetMirrorY( true );
1270
1271 // Initialise field positions from the library symbol
1272 symbol->UpdateFields( nullptr,
1273 true, /* update style */
1274 false, /* update ref text */
1275 false, /* update other field text */
1276 false, /* reset ref */
1277 false /* reset other fields */ );
1278
1279 // Per-instance attribute placements override the library defaults
1280 for( const ATTR& attr : inst.attrs )
1281 {
1282 SCH_FIELD* field = nullptr;
1283
1284 if( attr.name == wxT( "RefDes" ) )
1285 field = symbol->GetField( FIELD_T::REFERENCE );
1286 else if( attr.name == wxT( "Value" ) || attr.name == wxT( "Type" ) )
1287 field = symbol->GetField( FIELD_T::VALUE );
1288
1289 if( field && ( attr.placement.x != 0 || attr.placement.y != 0 ) )
1290 {
1291 field->SetPosition( xform( attr.placement.x, attr.placement.y ) );
1292 applyTextStyle( field, attr.placement, aPcad );
1293 field->SetVisible( attr.placement.isVisible );
1294 }
1295 }
1296
1297 if( compInst )
1298 {
1299 symbol->GetField( FIELD_T::REFERENCE )->SetText( compInst->refDes );
1300
1301 wxString val = !compInst->value.IsEmpty() ? compInst->value
1302 : compInst->originalName;
1303
1304 if( !val.IsEmpty() )
1305 symbol->GetField( FIELD_T::VALUE )->SetText( val );
1306 }
1307 else
1308 {
1309 symbol->GetField( FIELD_T::REFERENCE )->SetText( inst.refDesRef );
1310 }
1311
1312 if( compDef )
1313 {
1314 if( !compDef->attachedPattern.IsEmpty() )
1315 symbol->GetField( FIELD_T::FOOTPRINT )->SetText( compDef->attachedPattern );
1316
1317 if( compDef->isPower )
1318 symbol->GetField( FIELD_T::REFERENCE )->SetVisible( false );
1319 }
1320
1321 aScreen->Append( symbol );
1322 }
1323}
1324
1325
1326SCH_SHEET* SCH_IO_PCAD::LoadSchematicFile( const wxString& aFileName, ::SCHEMATIC* aSchematic,
1327 SCH_SHEET* aAppendToMe,
1328 const std::map<std::string, UTF8>* aProperties )
1329{
1330 wxASSERT( !aFileName.IsEmpty() && aSchematic != nullptr );
1331
1334 parser.LoadFromFile( aFileName, pcad );
1335
1336 // --- Root sheet -----------------------------------------------------------
1337
1338 SCH_SHEET* rootSheet = nullptr;
1339
1340 wxFileName newFilename( aFileName );
1341 newFilename.SetExt( FILEEXT::KiCadSchematicFileExtension );
1342
1343 if( aAppendToMe )
1344 {
1345 wxCHECK_MSG( aSchematic->IsValid(), nullptr,
1346 wxT( "Can't append to a schematic with no root!" ) );
1347 rootSheet = aAppendToMe;
1348 }
1349 else
1350 {
1351 // The content root carries a real UUID so SetTopLevelSheets() accepts
1352 // it as a top-level sheet; a nil UUID marks the virtual root, which it
1353 // skips.
1354 rootSheet = new SCH_SHEET( aSchematic );
1355 rootSheet->SetFileName( newFilename.GetFullPath() );
1356
1357 SCH_SCREEN* screen = new SCH_SCREEN( aSchematic );
1358 screen->SetFileName( newFilename.GetFullPath() );
1359 rootSheet->SetScreen( screen );
1360
1361 aSchematic->SetTopLevelSheets( { rootSheet } );
1362 }
1363
1364 if( !rootSheet->GetScreen() )
1365 {
1366 SCH_SCREEN* screen = new SCH_SCREEN( aSchematic );
1367 screen->SetFileName( newFilename.GetFullPath() );
1368 rootSheet->SetScreen( screen );
1369 }
1370
1371 // --- Page setup -----------------------------------------------------------
1372
1373 PAGE_INFO page;
1374 double wIn = pcad.workspaceWidth / 1000.0;
1375 double hIn = pcad.workspaceHeight / 1000.0;
1376
1377 if( std::abs( wIn - 17.0 ) < 0.5 && std::abs( hIn - 11.0 ) < 0.5 )
1378 {
1379 page.SetType( wxT( "B" ) );
1380 }
1381 else if( std::abs( wIn - 11.0 ) < 0.5 && std::abs( hIn - 8.5 ) < 0.5 )
1382 {
1383 page.SetType( wxT( "A" ) );
1384 }
1385 else
1386 {
1387 page.SetType( wxT( "User" ) );
1388 page.SetWidthMils( KiROUND( pcad.workspaceWidth ) );
1389 page.SetHeightMils( KiROUND( pcad.workspaceHeight ) );
1390 }
1391
1392 // The Y-flip must use the height of the page actually selected so items
1393 // stay put relative to the sheet frame when the size snaps to B or A.
1394 const double pageH = page.GetHeightMils();
1395
1396 // --- Library symbols -------------------------------------------------------
1397
1398 const wxString libName = getLibName( aSchematic, aFileName );
1399
1400 LIB_SYMBOL_STORE libSymbols = buildLibSymbols( pcad, libName );
1401
1402 // --- Sheets ----------------------------------------------------------------
1403
1404 std::vector<SCH_SCREEN*> screens;
1405
1406 if( pcad.sheets.empty() )
1407 {
1408 screens.push_back( rootSheet->GetScreen() );
1409 }
1410 else
1411 {
1412 // First P-CAD sheet lands on the root; further sheets become subsheets.
1413 screens.push_back( rootSheet->GetScreen() );
1414
1415 if( !pcad.sheets[0].name.IsEmpty() )
1416 rootSheet->SetName( pcad.sheets[0].name );
1417
1418 const int sheetSymbolStep = toIU( 1500.0 );
1419 int sheetIndex = 1;
1420
1421 std::set<wxString> usedFileNames;
1422
1423 for( size_t i = 1; i < pcad.sheets.size(); i++ )
1424 {
1425 const PCAD_SCH::SHEET& psheet = pcad.sheets[i];
1426
1427 auto* subSheet = new SCH_SHEET( aSchematic );
1428 auto* subScreen = new SCH_SCREEN( aSchematic );
1429
1430 wxString sheetName = psheet.name;
1431
1432 if( sheetName.IsEmpty() )
1433 sheetName = wxString::Format( wxT( "Sheet%d" ), psheet.sheetNum );
1434
1435 // sheet names can repeat and can carry path separators; the screen
1436 // file name must be a unique plain file name
1437 wxString fileBase;
1438
1439 for( wxUniChar c : sheetName )
1440 fileBase += wxIsalnum( c ) ? c : wxUniChar( '_' );
1441
1442 fileBase = uniqueSymbolName( fileBase, usedFileNames );
1443
1444 wxFileName subFilename( newFilename );
1445 subFilename.SetName( subFilename.GetName() + wxT( "-" ) + fileBase );
1446
1447 subScreen->SetFileName( subFilename.GetFullPath() );
1448 subSheet->SetScreen( subScreen );
1449 subSheet->SetName( sheetName );
1450 subSheet->SetFileName( subFilename.GetFullName() );
1451
1452 subSheet->SetPosition( VECTOR2I( toIU( 1000.0 ),
1453 toIU( 1000.0 ) + sheetIndex * sheetSymbolStep ) );
1454 subSheet->SetSize( VECTOR2I( toIU( 1000.0 ), toIU( 500.0 ) ) );
1455
1456 rootSheet->GetScreen()->Append( subSheet );
1457
1458 screens.push_back( subScreen );
1459 sheetIndex++;
1460 }
1461 }
1462
1463 TITLE_BLOCK titleBlock;
1464
1465 {
1466 const std::map<wxString, wxString>& fields = pcad.titleSheet.fields;
1467 int commentIdx = 0;
1468
1469 auto get =
1470 [&fields]( const wxString& aName ) -> wxString
1471 {
1472 auto it = fields.find( aName );
1473 return it == fields.end() ? wxString() : it->second;
1474 };
1475
1476 titleBlock.SetTitle( get( wxT( "Title" ) ) );
1477 titleBlock.SetDate( get( wxT( "Date" ) ) );
1478 titleBlock.SetRevision( get( wxT( "Revision" ) ) );
1479
1480 wxString company = get( wxT( "Company Name" ) );
1481
1482 if( company.IsEmpty() )
1483 company = get( wxT( "Organization" ) );
1484
1485 titleBlock.SetCompany( company );
1486
1487 for( const wxChar* name : { wxT( "Author" ), wxT( "Drawn By" ), wxT( "Checked By" ),
1488 wxT( "Approved By" ), wxT( "Engineer" ),
1489 wxT( "Drawing Number" ) } )
1490 {
1491 wxString value = get( name );
1492
1493 if( !value.IsEmpty() && commentIdx < 9 )
1494 titleBlock.SetComment( commentIdx++, wxString( name ) + wxT( ": " ) + value );
1495 }
1496 }
1497
1498 for( size_t i = 0; i < screens.size(); i++ )
1499 {
1500 screens[i]->SetPageSettings( page );
1501 screens[i]->SetTitleBlock( titleBlock );
1502
1503 if( i < pcad.sheets.size() )
1504 populateScreen( screens[i], pcad.sheets[i], pcad, pageH, libSymbols, libName );
1505 }
1506
1507 // --- Hierarchy bookkeeping for headless and CLI consumers -------------------
1508
1509 aSchematic->RefreshHierarchy();
1510
1511 if( !aAppendToMe )
1512 {
1513 wxString projectName = aSchematic->Project().GetProjectName();
1514
1515 if( projectName.IsEmpty() )
1516 projectName = wxFileName( aFileName ).GetName();
1517
1518 SCH_SHEET_LIST sheets = aSchematic->BuildUnorderedSheetList();
1519 sheets.AddNewSymbolInstances( SCH_SHEET_PATH(), projectName );
1520 sheets.AddNewSheetInstances( SCH_SHEET_PATH(), 0 );
1521
1522 if( sheets.AllSheetPageNumbersEmpty() )
1523 sheets.SetInitialPageNumbers();
1524 }
1525
1526 return rootSheet;
1527}
1528
1529
1530void SCH_IO_PCAD::ensureLoadedLibrary( const wxString& aLibraryPath )
1531{
1532 wxFileName fn( aLibraryPath );
1533 long long timestamp = fn.FileExists() ? fn.GetModificationTime().GetValue().GetValue() : 0;
1534
1535 if( m_cachePath == aLibraryPath && m_cacheTimestamp == timestamp )
1536 return;
1537
1538 m_libCache.clear();
1539 m_cachePath.clear();
1540
1543 parser.LoadFromFile( aLibraryPath, pcad );
1544
1545 LIB_SYMBOL_STORE store = buildLibSymbols( pcad, getLibName( nullptr, aLibraryPath ) );
1546
1547 for( auto& [key, sym] : store.byCompDef )
1548 m_libCache[sym->GetName()] = std::move( sym );
1549
1550 for( auto& [key, sym] : store.bySymbolDef )
1551 m_libCache[sym->GetName()] = std::move( sym );
1552
1553 m_cachePath = aLibraryPath;
1554 m_cacheTimestamp = timestamp;
1555 m_modifyHash++;
1556}
1557
1558
1559bool SCH_IO_PCAD::CanReadLibrary( const wxString& aFileName ) const
1560{
1561 // no extension gate; like the schematic check, detection is by content
1562 // because P-CAD saves are extension-agnostic. Any non-board ACCEL file
1563 // can serve as a symbol source.
1564 return PCAD2KICAD::FileMatchesFormat( aFileName, "" )
1565 && !PCAD2KICAD::FileMatchesFormat( aFileName, "pcbDesign" );
1566}
1567
1568
1569void SCH_IO_PCAD::EnumerateSymbolLib( wxArrayString& aSymbolNameList,
1570 const wxString& aLibraryPath,
1571 const std::map<std::string, UTF8>* aProperties )
1572{
1573 ensureLoadedLibrary( aLibraryPath );
1574
1575 for( const auto& [name, sym] : m_libCache )
1576 aSymbolNameList.Add( name );
1577}
1578
1579
1580void SCH_IO_PCAD::EnumerateSymbolLib( std::vector<LIB_SYMBOL*>& aSymbolList,
1581 const wxString& aLibraryPath,
1582 const std::map<std::string, UTF8>* aProperties )
1583{
1584 ensureLoadedLibrary( aLibraryPath );
1585
1586 for( const auto& [name, sym] : m_libCache )
1587 aSymbolList.push_back( sym.get() );
1588}
1589
1590
1591LIB_SYMBOL* SCH_IO_PCAD::LoadSymbol( const wxString& aLibraryPath, const wxString& aPartName,
1592 const std::map<std::string, UTF8>* aProperties )
1593{
1594 ensureLoadedLibrary( aLibraryPath );
1595
1596 auto it = m_libCache.find( aPartName );
1597
1598 return it == m_libCache.end() ? nullptr : it->second.get();
1599}
const char * name
constexpr EDA_IU_SCALE schIUScale
Definition base_units.h:123
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:986
void SetFillMode(FILL_T aFill)
A mix-in class (via multiple inheritance) that handles texts such as labels, parts,...
Definition eda_text.h:89
virtual void SetTextSize(VECTOR2I aNewSize, bool aEnforceMinTextSize=true)
Definition eda_text.cpp:532
void SetVertJustify(GR_TEXT_V_ALIGN_T aType)
Definition eda_text.cpp:412
GR_TEXT_H_ALIGN_T GetHorizJustify() const
Definition eda_text.h:221
virtual void SetVisible(bool aVisible)
Definition eda_text.cpp:381
virtual void SetTextThickness(int aWidth)
The TextThickness is that set by the user.
Definition eda_text.cpp:279
void SetBold(bool aBold)
Set the text to be bold - this will also update the font if needed.
Definition eda_text.cpp:330
GR_TEXT_V_ALIGN_T GetVertJustify() const
Definition eda_text.h:224
virtual void SetTextAngle(const EDA_ANGLE &aAngle)
Definition eda_text.cpp:294
void SetItalic(bool aItalic)
Set the text to be italic - this will also update the font if needed.
Definition eda_text.cpp:302
void SetHorizJustify(GR_TEXT_H_ALIGN_T aType)
Definition eda_text.cpp:404
REPORTER * m_reporter
Reporter to log errors/warnings to, may be nullptr.
Definition io_base.h:237
A logical library item identifier and consists of various portions much like a URI.
Definition lib_id.h:45
static UTF8 FixIllegalChars(const UTF8 &aLibItemName, bool aLib)
Replace illegal LIB_ID item name characters with underscores '_'.
Definition lib_id.cpp:205
Define a library symbol object.
Definition lib_symbol.h:80
const LIB_ID & GetLibId() const override
Definition lib_symbol.h:149
SCH_FIELD & GetValueField()
Return reference to the value field.
Definition lib_symbol.h:378
void AddDrawItem(SCH_ITEM *aItem, bool aSort=true)
Add a new draw aItem to the draw object list and sort according to aSort.
SCH_FIELD & GetReferenceField()
Return reference to the reference designator field.
Definition lib_symbol.h:382
Describe the page size and margins of a paper page on which to eventually print or plot.
Definition page_info.h:75
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.
void SetHeightMils(double aHeightInMils)
double GetHeightMils() const
Definition page_info.h:143
void SetWidthMils(double aWidthInMils)
void LoadFromFile(const wxString &aFilename, SCHEMATIC &aSchematic)
Class for a wire to bus entry.
void SetPosition(const VECTOR2I &aPosition) override
wxString m_cachePath
Definition sch_io_pcad.h:99
bool CanReadLibrary(const wxString &aFileName) const override
Checks if this IO object can read the specified library file/directory.
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...
static wxString getLibName(const ::SCHEMATIC *aSchematic, const wxString &aFileName)
long long m_cacheTimestamp
void ensureLoadedLibrary(const wxString &aLibraryPath)
Parse the library file once and serve cache-owned symbols, invalidated by path or file timestamp chan...
bool CanReadSchematicFile(const wxString &aFileName) const override
Checks if this SCH_IO can read the specified schematic file.
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,...
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.
void populateScreen(SCH_SCREEN *aScreen, const PCAD_SCH::SHEET &aSheet, const PCAD_SCH::SCHEMATIC &aPcad, double aPageH, const LIB_SYMBOL_STORE &aLibSymbols, const wxString &aLibName)
std::map< wxString, std::unique_ptr< LIB_SYMBOL > > m_libCache
SCH_IO(const wxString &aName)
Definition sch_io.h:375
virtual void SetUnit(int aUnit)
Definition sch_item.h:232
Segment description base class to describe items which have 2 end points (track, wire,...
Definition sch_line.h:38
void Append(SCH_ITEM *aItem, bool aUpdateLibSymbol=true)
void SetFileName(const wxString &aFileName)
Set the file name for this screen to aFileName.
void SetStroke(const STROKE_PARAMS &aStroke) override
Definition sch_shape.cpp:98
void AddPoint(const VECTOR2I &aPosition)
A container for handling SCH_SHEET_PATH objects in a flattened hierarchy.
void AddNewSymbolInstances(const SCH_SHEET_PATH &aPrefixSheetPath, const wxString &aProjectName)
Attempt to add new symbol instances for all symbols in this list of sheet paths prefixed with aPrefix...
void SetInitialPageNumbers()
Set initial sheet page numbers.
bool AllSheetPageNumbersEmpty() const
Check all of the sheet instance for empty page numbers.
void AddNewSheetInstances(const SCH_SHEET_PATH &aPrefixSheetPath, int aLastVirtualPageNumber)
Handle access to a stack of flattened SCH_SHEET objects by way of a path for creating a flattened sch...
Sheet symbol placed in a schematic, and is the entry point for a sub schematic.
Definition sch_sheet.h:44
void SetFileName(const wxString &aFilename)
Definition sch_sheet.h:376
void SetName(const wxString &aName)
Definition sch_sheet.h:137
SCH_SCREEN * GetScreen() const
Definition sch_sheet.h:139
void SetScreen(SCH_SCREEN *aScreen)
Set the SCH_SCREEN associated with this sheet to aScreen.
Schematic symbol object.
Definition sch_symbol.h:69
Simple container to manage line stroke parameters.
Hold the information shown in the lower right corner of a plot, printout, or editing view.
Definition title_block.h:37
void SetRevision(const wxString &aRevision)
Definition title_block.h:77
void SetComment(int aIdx, const wxString &aComment)
Definition title_block.h:97
void SetTitle(const wxString &aTitle)
Definition title_block.h:54
void SetCompany(const wxString &aCompany)
Definition title_block.h:87
void SetDate(const wxString &aDate)
Set the date field, and defaults to the current time and date.
Definition title_block.h:67
wxString wx_str() const
Definition utf8.cpp:41
#define _(s)
@ DEGREES_T
Definition eda_angle.h:31
@ NO_FILL
Definition eda_shape.h:60
@ FILLED_SHAPE
Fill with object color.
Definition eda_shape.h:61
static const std::string KiCadSchematicFileExtension
SCH_LAYER_ID
Eeschema drawing layers.
Definition layer_ids.h:447
@ LAYER_DEVICE
Definition layer_ids.h:464
@ LAYER_WIRE
Definition layer_ids.h:450
@ LAYER_NOTES
Definition layer_ids.h:465
@ LAYER_BUS
Definition layer_ids.h:451
bool FileMatchesFormat(const wxString &aFileName, const std::string &aSection)
Check that a file is ACCEL_ASCII and, when aSection is not empty, that it contains the given section ...
const double TRUETYPE_HEIGHT_TO_SIZE
const double STROKE_HEIGHT_TO_SIZE
std::chrono::steady_clock clock
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
ELECTRICAL_PINTYPE
The symbol library pin object electrical types used in ERC tests.
Definition pin_type.h:32
@ PT_INPUT
usual pin input: must be connected
Definition pin_type.h:33
@ PT_OUTPUT
usual output
Definition pin_type.h:34
@ PT_TRISTATE
tri state bus pin
Definition pin_type.h:36
@ PT_BIDI
input or output (like port for a microprocessor)
Definition pin_type.h:35
@ PT_OPENEMITTER
pin type open emitter
Definition pin_type.h:45
@ PT_OPENCOLLECTOR
pin type open collector
Definition pin_type.h:44
@ PT_POWER_IN
power input (GND, VCC for ICs). Must be connected to a power output.
Definition pin_type.h:42
@ PT_UNSPECIFIED
unknown electrical properties: creates always a warning when connected
Definition pin_type.h:41
@ PT_PASSIVE
pin for passive symbols: must be connected, and can be connected to any pin.
Definition pin_type.h:39
PIN_ORIENTATION
The symbol library pin object orientations.
Definition pin_type.h:101
@ PIN_UP
The pin extends upwards from the connection point: Probably on the bottom side of the symbol.
Definition pin_type.h:123
@ PIN_RIGHT
The pin extends rightwards from the connection point.
Definition pin_type.h:107
@ PIN_LEFT
The pin extends leftwards from the connection point: Probably on the right side of the symbol.
Definition pin_type.h:114
@ PIN_DOWN
The pin extends downwards from the connection: Probably on the top side of the symbol.
Definition pin_type.h:131
GRAPHIC_PINSHAPE
Definition pin_type.h:80
@ RPT_SEVERITY_WARNING
LABEL_FLAG_SHAPE
Definition sch_label.h:97
@ L_BIDI
Definition sch_label.h:100
@ L_UNSPECIFIED
Definition sch_label.h:102
@ L_INPUT
Definition sch_label.h:98
Definition of the SCH_SHEET_PATH and SCH_SHEET_LIST classes for Eeschema.
LINE_STYLE
Dashed line types.
std::map< wxString, std::unique_ptr< LIB_SYMBOL > > bySymbolDef
std::map< wxString, std::unique_ptr< LIB_SYMBOL > > byCompDef
Center/radius/angles form; triplePointArc is converted at parse time.
wxString orient
double x
double y
wxString busNameRef
std::vector< std::pair< double, double > > pts
std::vector< wxString > attachedSymbols
std::vector< COMP_PIN > compPins
A (compPin "padDes" ...) inside a compDef, mapping a symbol pin ordinal to the physical pad designato...
A placed title-block field whose name references the fieldDef holding the displayed text.
One font description inside a (textStyleDef ...).
std::vector< std::pair< double, double > > pts
wxString insideEdgeStyle
wxString outsideEdgeStyle
std::vector< std::pair< double, double > > pts
std::map< wxString, const COMP_DEF * > compDefsByName
std::map< wxString, const COMP_INST * > compInstsByRef
std::vector< SHEET > sheets
std::vector< SYMBOL_DEF > symbolDefs
const TEXT_STYLE * FindTextStyle(const wxString &aName) const
std::map< wxString, wxString > compAliases
compAlias name -> compDef name
std::vector< COMP_DEF > compDefs
std::map< wxString, const SYMBOL_DEF * > symbolDefsByName
std::vector< BUS > buses
std::vector< PORT > ports
std::vector< BUS_ENTRY > busEntries
std::vector< WIRE > wires
std::vector< JUNCTION > junctions
std::vector< POLY > polys
std::vector< TEXT_ITEM > texts
std::vector< LINE > lines
std::vector< FIELD_PLACEMENT > fields
std::vector< IEEE_SYMBOL > ieeeSymbols
std::vector< SYMBOL_INST > symbols
std::vector< ARC > arcs
std::vector< POLY > polys
std::vector< ATTR > attrs
std::vector< PIN > pins
std::vector< TEXT_ITEM > texts
std::vector< IEEE_SYMBOL > ieeeSymbols
std::vector< LINE > lines
std::vector< ARC > arcs
std::vector< ATTR > attrs
A positioned text.
const FONT & EffectiveFont() const
std::map< wxString, wxString > fields
std::vector< std::pair< double, double > > pts
SYMBOL_ORIENTATION_T
enum used in RotationMiroir()
Definition symbol.h:31
@ SYM_ORIENT_270
Definition symbol.h:38
@ SYM_ORIENT_180
Definition symbol.h:37
@ SYM_ORIENT_90
Definition symbol.h:36
@ SYM_ORIENT_0
Definition symbol.h:35
@ FOOTPRINT
Field Name Module PCB, i.e. "16DIP300".
@ REFERENCE
Field Reference of part, i.e. "IC21".
@ VALUE
Field Value of part, i.e. "3.3K".
KIBIS_PIN * pin
VECTOR2I center
int radius
SHAPE_CIRCLE circle(c.m_circle_center, c.m_circle_radius)
GR_TEXT_H_ALIGN_T
This is API surface mapped to common.types.HorizontalAlignment.
@ GR_TEXT_H_ALIGN_CENTER
@ GR_TEXT_H_ALIGN_RIGHT
@ GR_TEXT_H_ALIGN_LEFT
GR_TEXT_V_ALIGN_T
This is API surface mapped to common.types.VertialAlignment.
@ GR_TEXT_V_ALIGN_BOTTOM
@ GR_TEXT_V_ALIGN_CENTER
@ GR_TEXT_V_ALIGN_TOP
constexpr GR_TEXT_H_ALIGN_T GetFlippedAlignment(GR_TEXT_H_ALIGN_T aAlign)
Get the reverse alignment: left-right are swapped, others are unchanged.
#define M_PI
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683
VECTOR2< double > VECTOR2D
Definition vector2d.h:682
Definition of file extensions used in Kicad.