KiCad PCB EDA Suite
Loading...
Searching...
No Matches
pcb_io_kicad_sexpr_parser.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) 2012 CERN
5 * Copyright (C) 2012-2024 KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, you may find one here:
19 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
20 * or you may search the http://www.gnu.org website for the version 2 license,
21 * or you may write to the Free Software Foundation, Inc.,
22 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23 */
24
30#include <cerrno>
31#include <charconv>
32#include <confirm.h>
33#include <macros.h>
34#include <fmt/format.h>
35#include <title_block.h>
36#include <trigo.h>
37
38#include <board.h>
41#include <font/fontconfig.h>
42#include <magic_enum.hpp>
43#include <pcb_dimension.h>
44#include <pcb_shape.h>
45#include <pcb_reference_image.h>
46#include <pcb_group.h>
47#include <pcb_generator.h>
48#include <pcb_target.h>
49#include <pcb_track.h>
50#include <pcb_textbox.h>
51#include <pcb_table.h>
52#include <pad.h>
53#include <generators_mgr.h>
54#include <zone.h>
55#include <footprint.h>
57#include <font/font.h>
58#include <core/ignore.h>
59#include <netclass.h>
62#include <pcb_plot_params.h>
63#include <locale_io.h>
64#include <zones.h>
66#include <convert_basic_shapes_to_polygon.h> // for RECT_CHAMFER_POSITIONS definition
67#include <math/util.h> // KiROUND, Clamp
68#include <string_utils.h>
70#include <wx/log.h>
71#include <progress_reporter.h>
73#include <pgm_base.h>
74
75// For some reason wxWidgets is built with wxUSE_BASE64 unset so expose the wxWidgets
76// base64 code. Needed for PCB_REFERENCE_IMAGE
77#define wxUSE_BASE64 1
78#include <wx/base64.h>
79#include <wx/log.h>
80#include <wx/mstream.h>
81
82// We currently represent board units as integers. Any values that are
83// larger or smaller than those board units represent undefined behavior for
84// the system. We limit values to the largest usable
85// i.e. std::numeric_limits<int>::max().
86// However to avoid issues in comparisons, use a slightly smaller value
87// Note also the usable limits are much smaller to avoid overflows in intermediate
88// calculations.
89constexpr double INT_LIMIT = std::numeric_limits<int>::max() - 10;
90
91using namespace PCB_KEYS_T;
92
93
95{
98 m_tooRecent = false;
100 m_layerIndices.clear();
101 m_layerMasks.clear();
102 m_resetKIIDMap.clear();
103
104 // Add untranslated default (i.e. English) layernames.
105 // Some may be overridden later if parsing a board rather than a footprint.
106 // The English name will survive if parsing only a footprint.
107 for( int layer = 0; layer < PCB_LAYER_ID_COUNT; ++layer )
108 {
109 std::string untranslated = TO_UTF8( LSET::Name( PCB_LAYER_ID( layer ) ) );
110
111 m_layerIndices[untranslated] = PCB_LAYER_ID( layer );
112 m_layerMasks[untranslated] = LSET( { PCB_LAYER_ID( layer ) } );
113 }
114
115 m_layerMasks[ "*.Cu" ] = LSET::AllCuMask();
116 m_layerMasks[ "*In.Cu" ] = LSET::InternalCuMask();
117 m_layerMasks[ "F&B.Cu" ] = LSET( { F_Cu, B_Cu } );
118 m_layerMasks[ "*.Adhes" ] = LSET( { B_Adhes, F_Adhes } );
119 m_layerMasks[ "*.Paste" ] = LSET( { B_Paste, F_Paste } );
120 m_layerMasks[ "*.Mask" ] = LSET( { B_Mask, F_Mask } );
121 m_layerMasks[ "*.SilkS" ] = LSET( { B_SilkS, F_SilkS } );
122 m_layerMasks[ "*.Fab" ] = LSET( { B_Fab, F_Fab } );
123 m_layerMasks[ "*.CrtYd" ] = LSET( { B_CrtYd, F_CrtYd } );
124
125 // This is for the first pretty & *.kicad_pcb formats, which had
126 // Inner1_Cu - Inner14_Cu with the numbering sequence
127 // reversed from the subsequent format's In1_Cu - In30_Cu numbering scheme.
128 // The newer format brought in an additional 16 Cu layers and flipped the cu stack but
129 // kept the gap between one of the outside layers and the last cu internal.
130
131 for( int i=1; i<=14; ++i )
132 {
133 std::string key = StrPrintf( "Inner%d.Cu", i );
134
135 m_layerMasks[key] = LSET( { PCB_LAYER_ID( In15_Cu - 2 * i ) } );
136 }
137}
138
139
141{
143 {
144 TIME_PT curTime = CLOCK::now();
145 unsigned curLine = reader->LineNumber();
146 auto delta = std::chrono::duration_cast<TIMEOUT>( curTime - m_lastProgressTime );
147
148 if( delta > std::chrono::milliseconds( 250 ) )
149 {
150 m_progressReporter->SetCurrentProgress( ( (double) curLine )
151 / std::max( 1U, m_lineCount ) );
152
154 THROW_IO_ERROR( _( "Open cancelled by user." ) );
155
156 m_lastProgressTime = curTime;
157 }
158 }
159}
160
161
163{
164 int curr_level = 0;
165 T token;
166
167 while( ( token = NextTok() ) != T_EOF )
168 {
169 if( token == T_LEFT )
170 curr_level--;
171
172 if( token == T_RIGHT )
173 {
174 curr_level++;
175
176 if( curr_level > 0 )
177 return;
178 }
179 }
180}
181
182
184{
185 // Add aValue in netcode mapping (m_netCodes) at index aNetCode
186 // ensure there is room in m_netCodes for that, and add room if needed.
187
188 if( (int)m_netCodes.size() <= aIndex )
189 m_netCodes.resize( static_cast<std::size_t>( aIndex ) + 1 );
190
191 m_netCodes[aIndex] = aValue;
192}
193
194
196{
197 // There should be no major rounding issues here, since the values in
198 // the file are in mm and get converted to nano-meters.
199 // See test program tools/test-nm-biu-to-ascii-mm-round-tripping.cpp
200 // to confirm or experiment. Use a similar strategy in both places, here
201 // and in the test program. Make that program with:
202 // $ make test-nm-biu-to-ascii-mm-round-tripping
203 auto retval = parseDouble() * pcbIUScale.IU_PER_MM;
204
205 // N.B. we currently represent board units as integers. Any values that are
206 // larger or smaller than those board units represent undefined behavior for
207 // the system. We limit values to the largest that is visible on the screen
208 return KiROUND( std::clamp( retval, -INT_LIMIT, INT_LIMIT ) );
209}
210
211
213{
214 auto retval = parseDouble( aExpected ) * pcbIUScale.IU_PER_MM;
215
216 // N.B. we currently represent board units as integers. Any values that are
217 // larger or smaller than those board units represent undefined behavior for
218 // the system. We limit values to the largest that is visible on the screen
219 return KiROUND( std::clamp( retval, -INT_LIMIT, INT_LIMIT ) );
220}
221
222
224{
225 T token = NextTok();
226
227 if( token == T_yes )
228 return true;
229 else if( token == T_no )
230 return false;
231 else
232 Expecting( "yes or no" );
233
234 return false;
235}
236
237
238/*
239 * e.g. "hide", "hide)", "(hide yes)"
240 */
242{
243 bool ret = aDefaultValue;
244
245 if( PrevTok() == T_LEFT )
246 {
247 T token = NextTok();
248
249 // "hide)"
250 if( static_cast<int>( token ) == DSN_RIGHT )
251 return aDefaultValue;
252
253 if( token == T_yes || token == T_true )
254 ret = true;
255 else if( token == T_no || token == T_false )
256 ret = false;
257 else
258 Expecting( "yes or no" );
259
260 NeedRIGHT();
261 }
262 else
263 {
264 // "hide"
265 return aDefaultValue;
266 }
267
268 return ret;
269}
270
271
273{
274 int year, month, day;
275
276 year = m_requiredVersion / 10000;
277 month = ( m_requiredVersion / 100 ) - ( year * 100 );
278 day = m_requiredVersion - ( year * 10000 ) - ( month * 100 );
279
280 // wx throws an assertion, not a catchable exception, when the date is invalid.
281 // User input shouldn't give wx asserts, so check manually and throw a proper
282 // error instead
283 if( day <= 0 || month <= 0 || month > 12 ||
284 day > wxDateTime::GetNumberOfDays( (wxDateTime::Month)( month - 1 ), year ) )
285 {
286 wxString err;
287 err.Printf( _( "Cannot interpret date code %d" ), m_requiredVersion );
288 THROW_PARSE_ERROR( err, CurSource(), CurLine(), CurLineNumber(), CurOffset() );
289 }
290
291 wxDateTime date( day, (wxDateTime::Month)( month - 1 ), year, 0, 0, 0, 0 );
292 return date.FormatDate();
293}
294
295
297{
298 if( CurTok() != T_LEFT )
299 NeedLEFT();
300
301 VECTOR2I pt;
302 T token = NextTok();
303
304 if( token != T_xy )
305 Expecting( T_xy );
306
307 pt.x = parseBoardUnits( "X coordinate" );
308 pt.y = parseBoardUnits( "Y coordinate" );
309
310 NeedRIGHT();
311
312 return pt;
313}
314
315
317{
318 if( CurTok() != T_LEFT )
319 NeedLEFT();
320
321 T token = NextTok();
322
323 switch( token )
324 {
325 case T_xy:
326 {
327 int x = parseBoardUnits( "X coordinate" );
328 int y = parseBoardUnits( "Y coordinate" );
329
330 NeedRIGHT();
331
332 aPoly.Append( x, y );
333 break;
334 }
335 case T_arc:
336 {
337 bool has_start = false;
338 bool has_mid = false;
339 bool has_end = false;
340
341 VECTOR2I arc_start, arc_mid, arc_end;
342
343 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
344 {
345 if( token != T_LEFT )
346 Expecting( T_LEFT );
347
348 token = NextTok();
349
350 switch( token )
351 {
352 case T_start:
353 arc_start.x = parseBoardUnits( "start x" );
354 arc_start.y = parseBoardUnits( "start y" );
355 has_start = true;
356 break;
357
358 case T_mid:
359 arc_mid.x = parseBoardUnits( "mid x" );
360 arc_mid.y = parseBoardUnits( "mid y" );
361 has_mid = true;
362 break;
363
364 case T_end:
365 arc_end.x = parseBoardUnits( "end x" );
366 arc_end.y = parseBoardUnits( "end y" );
367 has_end = true;
368 break;
369
370 default:
371 Expecting( "start, mid or end" );
372 }
373
374 NeedRIGHT();
375 }
376
377 if( !has_start )
378 Expecting( "start" );
379
380 if( !has_mid )
381 Expecting( "mid" );
382
383 if( !has_end )
384 Expecting( "end" );
385
386 SHAPE_ARC arc( arc_start, arc_mid, arc_end, 0 );
387
388 aPoly.Append( arc );
389
390 if( token != T_RIGHT )
391 Expecting( T_RIGHT );
392
393 break;
394 }
395 default:
396 Expecting( "xy or arc" );
397 }
398}
399
400
402{
403 VECTOR2I pt = parseXY();
404
405 if( aX )
406 *aX = pt.x;
407
408 if( aY )
409 *aY = pt.y;
410}
411
412
413void PCB_IO_KICAD_SEXPR_PARSER::parseMargins( int& aLeft, int& aTop, int& aRight, int& aBottom )
414{
415 aLeft = parseBoardUnits( "left margin" );
416 aTop = parseBoardUnits( "top margin" );
417 aRight = parseBoardUnits( "right margin" );
418 aBottom = parseBoardUnits( "bottom margin" );
419}
420
421
423{
424 wxString pName;
425 wxString pValue;
426
427 NeedSYMBOL();
428 pName = FromUTF8();
429 NeedSYMBOL();
430 pValue = FromUTF8();
431 NeedRIGHT();
432
433 return { pName, pValue };
434}
435
436
438{
439 tdParams->m_Enabled = false;
440 tdParams->m_AllowUseTwoTracks = false;
441 tdParams->m_TdOnPadsInZones = true;
442
443 for( T token = NextTok(); token != T_RIGHT; token = NextTok() )
444 {
445 if( token == T_LEFT )
446 token = NextTok();
447
448 switch( token )
449 {
450 case T_enabled:
451 tdParams->m_Enabled = parseMaybeAbsentBool( true );
452 break;
453
454 case T_allow_two_segments:
455 tdParams->m_AllowUseTwoTracks = parseMaybeAbsentBool( true );
456 break;
457
458 case T_prefer_zone_connections:
459 tdParams->m_TdOnPadsInZones = !parseMaybeAbsentBool( false );
460 break;
461
462 case T_best_length_ratio:
463 tdParams->m_BestLengthRatio = parseDouble( "teardrop best length ratio" );
464 NeedRIGHT();
465 break;
466
467 case T_max_length:
468 tdParams->m_TdMaxLen = parseBoardUnits( "teardrop max length" );
469 NeedRIGHT();
470 break;
471
472 case T_best_width_ratio:
473 tdParams->m_BestWidthRatio = parseDouble( "teardrop best width ratio" );
474 NeedRIGHT();
475 break;
476
477 case T_max_width:
478 tdParams->m_TdMaxWidth = parseBoardUnits( "teardrop max width" );
479 NeedRIGHT();
480 break;
481
482 case T_curve_points:
483 tdParams->m_CurveSegCount = parseInt( "teardrop curve points count" );
484 NeedRIGHT();
485 break;
486
487 case T_filter_ratio:
488 tdParams->m_WidthtoSizeFilterRatio = parseDouble( "teardrop filter ratio" );
489 NeedRIGHT();
490 break;
491
492 default:
493 Expecting( "enabled, allow_two_segments, prefer_zone_connections, best_length_ratio, "
494 "max_length, best_width_ratio, max_width, curve_points or filter_ratio" );
495 }
496 }
497}
498
499
501{
502 wxCHECK_RET( CurTok() == T_effects,
503 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as EDA_TEXT." ) );
504
505 // These are not written out if center/center and/or no mirror,
506 // so we have to make sure we start that way.
507 // (these parameters will be set in T_justify section, when existing)
510 aText->SetMirrored( false );
511
512 // In version 20210606 the notation for overbars was changed from `~...~` to `~{...}`.
513 // We need to convert the old syntax to the new one.
514 if( m_requiredVersion < 20210606 )
515 aText->SetText( ConvertToNewOverbarNotation( aText->GetText() ) );
516
517 T token;
518
519 // Prior to v5.0 text size was omitted from file format if equal to 60mils
520 // Now, it is always explicitly written to file
521 bool foundTextSize = false;
522 wxString faceName;
523
524 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
525 {
526 if( token == T_LEFT )
527 token = NextTok();
528
529 switch( token )
530 {
531 case T_font:
532 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
533 {
534 if( token == T_LEFT )
535 continue;
536
537 switch( token )
538 {
539 case T_face:
540 NeedSYMBOL();
541 faceName = FromUTF8();
542 NeedRIGHT();
543 break;
544
545 case T_size:
546 {
547 VECTOR2I sz;
548 sz.y = parseBoardUnits( "text height" );
549 sz.x = parseBoardUnits( "text width" );
550 aText->SetTextSize( sz );
551 NeedRIGHT();
552
553 foundTextSize = true;
554 break;
555 }
556
557 case T_line_spacing:
558 aText->SetLineSpacing( parseDouble( "line spacing" ) );
559 NeedRIGHT();
560 break;
561
562 case T_thickness:
563 aText->SetTextThickness( parseBoardUnits( "text thickness" ) );
564 NeedRIGHT();
565 break;
566
567 case T_bold:
568 {
569 bool value = parseMaybeAbsentBool( true );
570 aText->SetBoldFlag( value );
571 }
572 break;
573
574 case T_italic:
575 {
576 bool value = parseMaybeAbsentBool( true );
577 aText->SetItalicFlag( value );
578 }
579 break;
580
581 default:
582 Expecting( "face, size, line_spacing, thickness, bold, or italic" );
583 }
584 }
585
586 if( !faceName.IsEmpty() )
587 {
588 m_fontTextMap[aText] = { faceName, aText->IsBold(), aText->IsItalic() };
589 }
590
591 break;
592
593 case T_justify:
594 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
595 {
596 if( token == T_LEFT )
597 continue;
598
599 switch( token )
600 {
601 case T_left:
603 break;
604
605 case T_right:
607 break;
608
609 case T_top:
611 break;
612
613 case T_bottom:
615 break;
616
617 case T_mirror:
618 aText->SetMirrored( true );
619 break;
620
621 default:
622 Expecting( "left, right, top, bottom, or mirror" );
623 }
624
625 }
626
627 break;
628
629 case T_hide:
630 aText->SetVisible( !parseMaybeAbsentBool( true ) );
631 break;
632
633 default:
634 Expecting( "font, justify, or hide" );
635 }
636 }
637
638 // Text size was not specified in file, force legacy default units
639 // 60mils is 1.524mm
640 if( !foundTextSize )
641 {
642 const double defaultTextSize = 1.524 * pcbIUScale.IU_PER_MM;
643
644 aText->SetTextSize( VECTOR2I( defaultTextSize, defaultTextSize ) );
645 }
646}
647
648
650{
651 T token;
652
653 NeedSYMBOLorNUMBER();
654 wxString cacheText = From_UTF8( CurText() );
655 EDA_ANGLE cacheAngle( parseDouble( "render cache angle" ), DEGREES_T );
656
657 text->SetupRenderCache( cacheText, text->GetFont(), cacheAngle, { 0, 0 } );
658
659 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
660 {
661 if( token != T_LEFT )
662 Expecting( T_LEFT );
663
664 token = NextTok();
665
666 if( token != T_polygon )
667 Expecting( T_polygon );
668
669 SHAPE_POLY_SET poly;
670
671 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
672 {
673 if( token != T_LEFT )
674 Expecting( T_LEFT );
675
676 token = NextTok();
677
678 if( token != T_pts )
679 Expecting( T_pts );
680
681 SHAPE_LINE_CHAIN lineChain;
682
683 while( (token = NextTok() ) != T_RIGHT )
684 parseOutlinePoints( lineChain );
685
686 lineChain.SetClosed( true );
687
688 if( poly.OutlineCount() == 0 )
689 poly.AddOutline( lineChain );
690 else
691 poly.AddHole( lineChain );
692 }
693
694 text->AddRenderCacheGlyph( poly );
695 }
696}
697
698
700{
701 wxCHECK_MSG( CurTok() == T_model, nullptr,
702 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as FP_3DMODEL." ) );
703
704 T token;
705
706 FP_3DMODEL* n3D = new FP_3DMODEL;
707 NeedSYMBOLorNUMBER();
708 n3D->m_Filename = FromUTF8();
709
710 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
711 {
712 if( token == T_LEFT )
713 token = NextTok();
714
715 switch( token )
716 {
717 case T_at:
718 NeedLEFT();
719 token = NextTok();
720
721 if( token != T_xyz )
722 Expecting( T_xyz );
723
724 /* Note:
725 * Prior to KiCad v5, model offset was designated by "at",
726 * and the units were in inches.
727 * Now we use mm, but support reading of legacy files
728 */
729
730 n3D->m_Offset.x = parseDouble( "x value" ) * 25.4f;
731 n3D->m_Offset.y = parseDouble( "y value" ) * 25.4f;
732 n3D->m_Offset.z = parseDouble( "z value" ) * 25.4f;
733
734 NeedRIGHT(); // xyz
735 NeedRIGHT(); // at
736 break;
737
738 case T_hide:
739 {
740 bool hide = parseMaybeAbsentBool( true );
741 n3D->m_Show = !hide;
742 break;
743 }
744
745 case T_opacity:
746 n3D->m_Opacity = parseDouble( "opacity value" );
747 NeedRIGHT();
748 break;
749
750 case T_offset:
751 NeedLEFT();
752 token = NextTok();
753
754 if( token != T_xyz )
755 Expecting( T_xyz );
756
757 /*
758 * 3D model offset is in mm
759 */
760 n3D->m_Offset.x = parseDouble( "x value" );
761 n3D->m_Offset.y = parseDouble( "y value" );
762 n3D->m_Offset.z = parseDouble( "z value" );
763
764 NeedRIGHT(); // xyz
765 NeedRIGHT(); // offset
766 break;
767
768 case T_scale:
769 NeedLEFT();
770 token = NextTok();
771
772 if( token != T_xyz )
773 Expecting( T_xyz );
774
775 n3D->m_Scale.x = parseDouble( "x value" );
776 n3D->m_Scale.y = parseDouble( "y value" );
777 n3D->m_Scale.z = parseDouble( "z value" );
778
779 NeedRIGHT(); // xyz
780 NeedRIGHT(); // scale
781 break;
782
783 case T_rotate:
784 NeedLEFT();
785 token = NextTok();
786
787 if( token != T_xyz )
788 Expecting( T_xyz );
789
790 n3D->m_Rotation.x = parseDouble( "x value" );
791 n3D->m_Rotation.y = parseDouble( "y value" );
792 n3D->m_Rotation.z = parseDouble( "z value" );
793
794 NeedRIGHT(); // xyz
795 NeedRIGHT(); // rotate
796 break;
797
798 default:
799 Expecting( "at, hide, opacity, offset, scale, or rotate" );
800 }
801
802 }
803
804 return n3D;
805}
806
807
809{
810 LOCALE_IO toggle;
811
812 m_groupInfos.clear();
813
814 // See Parse() - FOOTPRINTS can be prefixed with an initial block of single line comments,
815 // eventually BOARD might be the same
816 ReadCommentLines();
817
818 if( CurTok() != T_LEFT )
819 return false;
820
821 if( NextTok() != T_kicad_pcb)
822 return false;
823
824 return true;
825}
826
827
829{
830 T token;
831 BOARD_ITEM* item;
832 LOCALE_IO toggle;
833
834 m_groupInfos.clear();
835
836 // FOOTPRINTS can be prefixed with an initial block of single line comments and these are
837 // kept for Format() so they round trip in s-expression form. BOARDs might eventually do
838 // the same, but currently do not.
839 std::unique_ptr<wxArrayString> initial_comments( ReadCommentLines() );
840
841 token = CurTok();
842
843 if( token == -1 ) // EOF
844 Unexpected( token );
845
846 if( token != T_LEFT )
847 Expecting( T_LEFT );
848
849 switch( NextTok() )
850 {
851 case T_kicad_pcb:
852 if( m_board == nullptr )
853 m_board = new BOARD();
854
855 item = (BOARD_ITEM*) parseBOARD();
856 break;
857
858 case T_module: // legacy token
859 case T_footprint:
860 item = (BOARD_ITEM*) parseFOOTPRINT( initial_comments.release() );
861
862 // Locking a footprint has no meaning outside of a board.
863 item->SetLocked( false );
864 break;
865
866 default:
867 wxString err;
868 err.Printf( _( "Unknown token '%s'" ), FromUTF8() );
869 THROW_PARSE_ERROR( err, CurSource(), CurLine(), CurLineNumber(), CurOffset() );
870 }
871
872 // Assign the fonts after we have parsed any potential embedded fonts from the board
873 // or footprint. This also ensures that the embedded fonts are cached
874 for( auto& [text, params] : m_fontTextMap )
875 {
876 const std::vector<wxString>* embeddedFonts = item->GetEmbeddedFiles()->UpdateFontFiles();
877
878 text->SetFont( KIFONT::FONT::GetFont( std::get<0>( params ), std::get<1>( params ),
879 std::get<2>( params ),
880 embeddedFonts ) );
881 }
882
883 resolveGroups( item );
884
885 return item;
886}
887
888
890{
891 try
892 {
893 return parseBOARD_unchecked();
894 }
895 catch( const PARSE_ERROR& parse_error )
896 {
897 if( m_tooRecent )
898 throw FUTURE_FORMAT_ERROR( parse_error, GetRequiredVersion() );
899 else
900 throw;
901 }
902}
903
904
906{
907 T token;
908 std::map<wxString, wxString> properties;
909
910 parseHeader();
911
912 auto checkVersion =
913 [&]()
914 {
916 {
917 throw FUTURE_FORMAT_ERROR( fmt::format( "{}", m_requiredVersion ),
919 }
920 };
921
922 std::vector<BOARD_ITEM*> bulkAddedItems;
923 BOARD_ITEM* item = nullptr;
924
925 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
926 {
927 checkpoint();
928
929 if( token != T_LEFT )
930 Expecting( T_LEFT );
931
932 token = NextTok();
933
934 if( token == T_page && m_requiredVersion <= 20200119 )
935 token = T_paper;
936
937 switch( token )
938 {
939 case T_host: // legacy token
940 NeedSYMBOL();
941 m_board->SetGenerator( FromUTF8() );
942
943 // Older formats included build data
945 NeedSYMBOL();
946
947 NeedRIGHT();
948 break;
949
950 case T_generator:
951 NeedSYMBOL();
952 m_board->SetGenerator( FromUTF8() );
953 NeedRIGHT();
954 break;
955
956 case T_generator_version:
957 {
958 NeedSYMBOL();
959 m_generatorVersion = FromUTF8();
960 NeedRIGHT();
961
962 // If the format includes a generator version, by this point we have enough info to
963 // do the version check here
964 checkVersion();
965
966 break;
967 }
968
969 case T_general:
970 // Do another version check here, for older files that do not include generator_version
971 checkVersion();
972
974 break;
975
976 case T_paper:
978 break;
979
980 case T_title_block:
982 break;
983
984 case T_layers:
985 parseLayers();
986 break;
987
988 case T_setup:
989 parseSetup();
990 break;
991
992 case T_property:
993 properties.insert( parseBoardProperty() );
994 break;
995
996 case T_net:
998 break;
999
1000 case T_net_class:
1001 parseNETCLASS();
1003 break;
1004
1005 case T_gr_arc:
1006 case T_gr_curve:
1007 case T_gr_line:
1008 case T_gr_poly:
1009 case T_gr_circle:
1010 case T_gr_rect:
1011 item = parsePCB_SHAPE( m_board );
1012 m_board->Add( item, ADD_MODE::BULK_APPEND, true );
1013 bulkAddedItems.push_back( item );
1014 break;
1015
1016 case T_image:
1018 m_board->Add( item, ADD_MODE::BULK_APPEND, true );
1019 bulkAddedItems.push_back( item );
1020 break;
1021
1022 case T_gr_text:
1023 item = parsePCB_TEXT( m_board );
1024 m_board->Add( item, ADD_MODE::BULK_APPEND, true );
1025 bulkAddedItems.push_back( item );
1026 break;
1027
1028 case T_gr_text_box:
1029 item = parsePCB_TEXTBOX( m_board );
1030 m_board->Add( item, ADD_MODE::BULK_APPEND, true );
1031 bulkAddedItems.push_back( item );
1032 break;
1033
1034 case T_table:
1035 item = parsePCB_TABLE( m_board );
1036 m_board->Add( item, ADD_MODE::BULK_APPEND, true );
1037 bulkAddedItems.push_back( item );
1038 break;
1039
1040 case T_dimension:
1041 item = parseDIMENSION( m_board );
1042 m_board->Add( item, ADD_MODE::BULK_APPEND, true );
1043 bulkAddedItems.push_back( item );
1044 break;
1045
1046 case T_module: // legacy token
1047 case T_footprint:
1048 item = parseFOOTPRINT();
1049 m_board->Add( item, ADD_MODE::BULK_APPEND, true );
1050 bulkAddedItems.push_back( item );
1051 break;
1052
1053 case T_segment:
1054 item = parsePCB_TRACK();
1055 m_board->Add( item, ADD_MODE::BULK_APPEND, true );
1056 bulkAddedItems.push_back( item );
1057 break;
1058
1059 case T_arc:
1060 item = parseARC();
1061 m_board->Add( item, ADD_MODE::BULK_APPEND, true );
1062 bulkAddedItems.push_back( item );
1063 break;
1064
1065 case T_group:
1067 break;
1068
1069 case T_generated:
1071 break;
1072
1073 case T_via:
1074 item = parsePCB_VIA();
1075 m_board->Add( item, ADD_MODE::BULK_APPEND, true );
1076 bulkAddedItems.push_back( item );
1077 break;
1078
1079 case T_zone:
1080 item = parseZONE( m_board );
1081 m_board->Add( item, ADD_MODE::BULK_APPEND, true );
1082 bulkAddedItems.push_back( item );
1083 break;
1084
1085 case T_target:
1086 item = parsePCB_TARGET();
1087 m_board->Add( item, ADD_MODE::BULK_APPEND, true );
1088 bulkAddedItems.push_back( item );
1089 break;
1090
1091 case T_embedded_fonts:
1092 {
1094 NeedRIGHT();
1095 break;
1096 }
1097
1098 case T_embedded_files:
1099 {
1100 EMBEDDED_FILES_PARSER embeddedFilesParser( reader );
1101 embeddedFilesParser.SyncLineReaderWith( *this );
1102
1103 try
1104 {
1105 embeddedFilesParser.ParseEmbedded( m_board->GetEmbeddedFiles() );
1106 }
1107 catch( const PARSE_ERROR& e )
1108 {
1109 wxLogError( e.What() );
1110 }
1111
1112 SyncLineReaderWith( embeddedFilesParser );
1113 break;
1114 }
1115
1116 default:
1117 wxString err;
1118 err.Printf( _( "Unknown token '%s'" ), FromUTF8() );
1119 THROW_PARSE_ERROR( err, CurSource(), CurLine(), CurLineNumber(), CurOffset() );
1120 }
1121 }
1122
1123 if( bulkAddedItems.size() > 0 )
1124 m_board->FinalizeBulkAdd( bulkAddedItems );
1125
1126 m_board->SetProperties( properties );
1127
1128 if( m_undefinedLayers.size() > 0 )
1129 {
1130 PCB_LAYER_ID destLayer = Cmts_User;
1131 wxString msg, undefinedLayerNames, destLayerName;
1132
1133 for( const wxString& layerName : m_undefinedLayers )
1134 {
1135 if( !undefinedLayerNames.IsEmpty() )
1136 undefinedLayerNames += wxT( ", " );
1137
1138 undefinedLayerNames += layerName;
1139 }
1140
1141 destLayerName = m_board->GetLayerName( destLayer );
1142
1143 if( Pgm().IsGUI() && m_queryUserCallback )
1144 {
1145 msg.Printf( _( "Items found on undefined layers (%s).\n"
1146 "Do you wish to rescue them to the %s layer?\n"
1147 "\n"
1148 "Zones will need to be refilled." ),
1149 undefinedLayerNames, destLayerName );
1150
1151 if( !m_queryUserCallback( _( "Undefined Layers Warning" ), wxICON_WARNING, msg,
1152 _( "Rescue" ) ) )
1153 {
1154 THROW_IO_ERROR( wxT( "CANCEL" ) );
1155 }
1156
1157 // Make sure the destination layer is enabled, even if not in the file
1159
1160 const auto visitItem = [&]( BOARD_ITEM& curr_item )
1161 {
1162 LSET layers = curr_item.GetLayerSet();
1163
1164 if( layers.test( Rescue ) )
1165 {
1166 layers.set( destLayer );
1167 layers.reset( Rescue );
1168 }
1169
1170 curr_item.SetLayerSet( layers );
1171 };
1172
1173 for( PCB_TRACK* track : m_board->Tracks() )
1174 {
1175 if( track->Type() == PCB_VIA_T )
1176 {
1177 PCB_VIA* via = static_cast<PCB_VIA*>( track );
1178 PCB_LAYER_ID top_layer, bottom_layer;
1179
1180 if( via->GetViaType() == VIATYPE::THROUGH )
1181 continue;
1182
1183 via->LayerPair( &top_layer, &bottom_layer );
1184
1185 if( top_layer == Rescue || bottom_layer == Rescue )
1186 {
1187 if( top_layer == Rescue )
1188 top_layer = F_Cu;
1189
1190 if( bottom_layer == Rescue )
1191 bottom_layer = B_Cu;
1192
1193 via->SetLayerPair( top_layer, bottom_layer );
1194 }
1195 }
1196 else
1197 {
1198 visitItem( *track );
1199 }
1200 }
1201
1202 for( BOARD_ITEM* zone : m_board->Zones() )
1203 visitItem( *zone );
1204
1205 for( BOARD_ITEM* drawing : m_board->Drawings() )
1206 visitItem( *drawing );
1207
1208 for( FOOTPRINT* fp : m_board->Footprints() )
1209 {
1210 for( BOARD_ITEM* drawing : fp->GraphicalItems() )
1211 visitItem( *drawing );
1212
1213 for( BOARD_ITEM* zone : fp->Zones() )
1214 visitItem( *zone );
1215 }
1216
1217 m_undefinedLayers.clear();
1218 }
1219 else
1220 {
1221 THROW_IO_ERROR( wxT( "One or more undefined undefinedLayerNames was found; "
1222 "open the board in the PCB Editor to resolve." ) );
1223 }
1224 }
1225
1226 // Clear unused zone data
1227 {
1228 LSET layers = m_board->GetEnabledLayers();
1229
1230 for( BOARD_ITEM* zone : m_board->Zones() )
1231 {
1232 ZONE* z = static_cast<ZONE*>( zone );
1233
1234 z->SetLayerSet( z->GetLayerSet() & layers );
1235 }
1236 }
1237
1238 // Ensure all footprints have their embedded data from the board
1240
1241 return m_board;
1242}
1243
1244
1246{
1247 auto getItem =
1248 [&]( const KIID& aId )
1249 {
1250 BOARD_ITEM* aItem = nullptr;
1251
1252 if( BOARD* board = dynamic_cast<BOARD*>( aParent ) )
1253 {
1254 aItem = board->GetItem( aId );
1255 }
1256 else if( FOOTPRINT* footprint = dynamic_cast<FOOTPRINT*>( aParent ) )
1257 {
1258 footprint->RunOnChildren(
1259 [&]( BOARD_ITEM* child )
1260 {
1261 if( child->m_Uuid == aId )
1262 aItem = child;
1263 } );
1264 }
1265
1266 return aItem;
1267 };
1268
1269 // Now that we've parsed the other Uuids in the file we can resolve the uuids referred
1270 // to in the group declarations we saw.
1271 //
1272 // First add all group objects so subsequent GetItem() calls for nested groups work.
1273
1274 std::vector<const GROUP_INFO*> groupTypeObjects;
1275
1276 for( const GROUP_INFO& groupInfo : m_groupInfos )
1277 groupTypeObjects.emplace_back( &groupInfo );
1278
1279 for( const GENERATOR_INFO& genInfo : m_generatorInfos )
1280 groupTypeObjects.emplace_back( &genInfo );
1281
1282 for( const GROUP_INFO* groupInfo : groupTypeObjects )
1283 {
1284 PCB_GROUP* group = nullptr;
1285
1286 if( const GENERATOR_INFO* genInfo = dynamic_cast<const GENERATOR_INFO*>( groupInfo ) )
1287 {
1289
1290 PCB_GENERATOR* gen;
1291 group = gen = mgr.CreateFromType( genInfo->genType );
1292
1293 if( !gen )
1294 {
1295 THROW_IO_ERROR( wxString::Format(
1296 _( "Cannot create generated object of type '%s'" ), genInfo->genType ) );
1297 }
1298
1299 gen->SetLayer( genInfo->layer );
1300 gen->SetProperties( genInfo->properties );
1301 }
1302 else
1303 {
1304 group = new PCB_GROUP( groupInfo->parent );
1305 group->SetName( groupInfo->name );
1306 }
1307
1308 const_cast<KIID&>( group->m_Uuid ) = groupInfo->uuid;
1309
1310 if( groupInfo->locked )
1311 group->SetLocked( true );
1312
1313 if( groupInfo->parent->Type() == PCB_FOOTPRINT_T )
1314 static_cast<FOOTPRINT*>( groupInfo->parent )->Add( group, ADD_MODE::INSERT, true );
1315 else
1316 static_cast<BOARD*>( groupInfo->parent )->Add( group, ADD_MODE::INSERT, true );
1317 }
1318
1319 wxString error;
1320
1321 for( const GROUP_INFO* groupInfo : groupTypeObjects )
1322 {
1323 if( PCB_GROUP* group = dynamic_cast<PCB_GROUP*>( getItem( groupInfo->uuid ) ) )
1324 {
1325 for( const KIID& aUuid : groupInfo->memberUuids )
1326 {
1327 BOARD_ITEM* item = nullptr;
1328
1329 if( m_appendToExisting )
1330 item = getItem( m_resetKIIDMap[ aUuid.AsString() ] );
1331 else
1332 item = getItem( aUuid );
1333
1334 if( !item || item->Type() == NOT_USED )
1335 {
1336 // This is the deleted item singleton, which means we didn't find the uuid.
1337 continue;
1338 }
1339
1340 // We used to allow fp items in non-footprint groups. It was a mistake. Check
1341 // to make sure they the item and group are owned by the same parent (will both
1342 // be nullptr in the board case).
1343 if( item->GetParentFootprint() == group->GetParentFootprint() )
1344 group->AddItem( item );
1345 }
1346 }
1347 }
1348
1349 // Don't allow group cycles
1350 if( m_board )
1351 m_board->GroupsSanityCheck( true );
1352}
1353
1354
1356{
1357 wxCHECK_RET( CurTok() == T_kicad_pcb,
1358 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a header." ) );
1359
1360 NeedLEFT();
1361
1362 T tok = NextTok();
1363
1364 if( tok == T_version )
1365 {
1366 m_requiredVersion = parseInt( FromUTF8().mb_str( wxConvUTF8 ) );
1367 NeedRIGHT();
1368 }
1369 else
1370 {
1371 m_requiredVersion = 20201115; // Last version before we started writing version #s
1372 // in footprint files as well as board files.
1373 }
1374
1376
1378}
1379
1380
1382{
1383 wxCHECK_RET( CurTok() == T_general,
1384 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) +
1385 wxT( " as a general section." ) );
1386
1387 T token;
1388
1389 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
1390 {
1391 if( token != T_LEFT )
1392 Expecting( T_LEFT );
1393
1394 token = NextTok();
1395
1396 switch( token )
1397 {
1398 case T_thickness:
1400 NeedRIGHT();
1401 break;
1402
1403 case T_legacy_teardrops:
1405 break;
1406
1407 default: // Skip everything else.
1408 while( ( token = NextTok() ) != T_RIGHT )
1409 {
1410 if( !IsSymbol( token ) && token != T_NUMBER )
1411 Expecting( "symbol or number" );
1412 }
1413 }
1414 }
1415}
1416
1417
1419{
1420 wxCHECK_RET( ( CurTok() == T_page && m_requiredVersion <= 20200119 ) || CurTok() == T_paper,
1421 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a PAGE_INFO." ) );
1422
1423 T token;
1424 PAGE_INFO pageInfo;
1425
1426 NeedSYMBOL();
1427
1428 wxString pageType = FromUTF8();
1429
1430 if( !pageInfo.SetType( pageType ) )
1431 {
1432 wxString err;
1433 err.Printf( _( "Page type '%s' is not valid." ), FromUTF8() );
1434 THROW_PARSE_ERROR( err, CurSource(), CurLine(), CurLineNumber(), CurOffset() );
1435 }
1436
1437 if( pageType == PAGE_INFO::Custom )
1438 {
1439 double width = parseDouble( "width" ); // width in mm
1440
1441 // Perform some controls to avoid crashes if the size is edited by hands
1442 if( width < MIN_PAGE_SIZE_MM )
1443 width = MIN_PAGE_SIZE_MM;
1444 else if( width > MAX_PAGE_SIZE_PCBNEW_MM )
1446
1447 double height = parseDouble( "height" ); // height in mm
1448
1449 if( height < MIN_PAGE_SIZE_MM )
1450 height = MIN_PAGE_SIZE_MM;
1451 else if( height > MAX_PAGE_SIZE_PCBNEW_MM )
1452 height = MAX_PAGE_SIZE_PCBNEW_MM;
1453
1454 pageInfo.SetWidthMM( width );
1455 pageInfo.SetHeightMM( height );
1456 }
1457
1458 token = NextTok();
1459
1460 if( token == T_portrait )
1461 {
1462 pageInfo.SetPortrait( true );
1463 NeedRIGHT();
1464 }
1465 else if( token != T_RIGHT )
1466 {
1467 Expecting( "portrait|)" );
1468 }
1469
1470 m_board->SetPageSettings( pageInfo );
1471}
1472
1473
1475{
1476 wxCHECK_RET( CurTok() == T_title_block,
1477 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as TITLE_BLOCK." ) );
1478
1479 T token;
1480 TITLE_BLOCK titleBlock;
1481
1482 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
1483 {
1484 if( token != T_LEFT )
1485 Expecting( T_LEFT );
1486
1487 token = NextTok();
1488
1489 switch( token )
1490 {
1491 case T_title:
1492 NextTok();
1493 titleBlock.SetTitle( FromUTF8() );
1494 break;
1495
1496 case T_date:
1497 NextTok();
1498 titleBlock.SetDate( FromUTF8() );
1499 break;
1500
1501 case T_rev:
1502 NextTok();
1503 titleBlock.SetRevision( FromUTF8() );
1504 break;
1505
1506 case T_company:
1507 NextTok();
1508 titleBlock.SetCompany( FromUTF8() );
1509 break;
1510
1511 case T_comment:
1512 {
1513 int commentNumber = parseInt( "comment" );
1514
1515 switch( commentNumber )
1516 {
1517 case 1:
1518 NextTok();
1519 titleBlock.SetComment( 0, FromUTF8() );
1520 break;
1521
1522 case 2:
1523 NextTok();
1524 titleBlock.SetComment( 1, FromUTF8() );
1525 break;
1526
1527 case 3:
1528 NextTok();
1529 titleBlock.SetComment( 2, FromUTF8() );
1530 break;
1531
1532 case 4:
1533 NextTok();
1534 titleBlock.SetComment( 3, FromUTF8() );
1535 break;
1536
1537 case 5:
1538 NextTok();
1539 titleBlock.SetComment( 4, FromUTF8() );
1540 break;
1541
1542 case 6:
1543 NextTok();
1544 titleBlock.SetComment( 5, FromUTF8() );
1545 break;
1546
1547 case 7:
1548 NextTok();
1549 titleBlock.SetComment( 6, FromUTF8() );
1550 break;
1551
1552 case 8:
1553 NextTok();
1554 titleBlock.SetComment( 7, FromUTF8() );
1555 break;
1556
1557 case 9:
1558 NextTok();
1559 titleBlock.SetComment( 8, FromUTF8() );
1560 break;
1561
1562 default:
1563 wxString err;
1564 err.Printf( wxT( "%d is not a valid title block comment number" ), commentNumber );
1565 THROW_PARSE_ERROR( err, CurSource(), CurLine(), CurLineNumber(), CurOffset() );
1566 }
1567
1568 break;
1569 }
1570
1571 default:
1572 Expecting( "title, date, rev, company, or comment" );
1573 }
1574
1575 NeedRIGHT();
1576 }
1577
1578 m_board->SetTitleBlock( titleBlock );
1579}
1580
1581
1583{
1584 T token;
1585
1586 std::string name;
1587 std::string userName;
1588 std::string type;
1589 bool isVisible = true;
1590
1591 aLayer->clear();
1592
1593 if( CurTok() != T_LEFT )
1594 Expecting( T_LEFT );
1595
1596 // this layer_num is not used, we DO depend on LAYER_T however.
1597 int layer_num = parseInt( "layer index" );
1598
1599 NeedSYMBOLorNUMBER();
1600 name = CurText();
1601
1602 NeedSYMBOL();
1603 type = CurText();
1604
1605 token = NextTok();
1606
1607 // @todo Figure out why we are looking for a hide token in the layer definition.
1608 if( token == T_hide )
1609 {
1610 isVisible = false;
1611 NeedRIGHT();
1612 }
1613 else if( token == T_STRING )
1614 {
1615 userName = CurText();
1616 NeedRIGHT();
1617 }
1618 else if( token != T_RIGHT )
1619 {
1620 Expecting( "hide, user defined name, or )" );
1621 }
1622
1623 aLayer->m_name = From_UTF8( name.c_str() );
1624 aLayer->m_type = LAYER::ParseType( type.c_str() );
1625 aLayer->m_number = layer_num;
1626 aLayer->m_visible = isVisible;
1627
1628 if( !userName.empty() )
1629 aLayer->m_userName = From_UTF8( userName.c_str() );
1630
1631 // The canonical name will get reset back to the default for copper layer on the next
1632 // save. The user defined name is now a separate optional layer token from the canonical
1633 // name.
1634 if( aLayer->m_name != LSET::Name( static_cast<PCB_LAYER_ID>( aLayer->m_number ) ) )
1635 aLayer->m_userName = aLayer->m_name;
1636}
1637
1638
1640{
1641 T token;
1642 wxString name;
1643 int dielectric_idx = 1; // the index of dielectric layers
1645
1646 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
1647 {
1648 if( CurTok() != T_LEFT )
1649 Expecting( T_LEFT );
1650
1651 token = NextTok();
1652
1653 if( token != T_layer )
1654 {
1655 switch( token )
1656 {
1657 case T_copper_finish:
1658 NeedSYMBOL();
1659 stackup.m_FinishType = FromUTF8();
1660 NeedRIGHT();
1661 break;
1662
1663 case T_edge_plating:
1664 token = NextTok();
1665 stackup.m_EdgePlating = token == T_yes;
1666 NeedRIGHT();
1667 break;
1668
1669 case T_dielectric_constraints:
1670 token = NextTok();
1671 stackup.m_HasDielectricConstrains = token == T_yes;
1672 NeedRIGHT();
1673 break;
1674
1675 case T_edge_connector:
1676 token = NextTok();
1678
1679 if( token == T_yes )
1681 else if( token == T_bevelled )
1683
1684 NeedRIGHT();
1685 break;
1686
1687 case T_castellated_pads:
1688 token = NextTok();
1689 stackup.m_CastellatedPads = token == T_yes;
1690 NeedRIGHT();
1691 break;
1692
1693 default:
1694 // Currently, skip this item if not defined, because the stackup def
1695 // is a moving target
1696 //Expecting( "copper_finish, edge_plating, dielectric_constrains, edge_connector, castellated_pads" );
1697 skipCurrent();
1698 break;
1699 }
1700
1701 continue;
1702 }
1703
1704 NeedSYMBOL();
1705 name = FromUTF8();
1706
1707 // init the layer id. For dielectric, layer id = UNDEFINED_LAYER
1708 PCB_LAYER_ID layerId = m_board->GetLayerID( name );
1709
1710 // Init the type
1712
1713 if( layerId == F_SilkS || layerId == B_SilkS )
1715 else if( layerId == F_Mask || layerId == B_Mask )
1717 else if( layerId == F_Paste || layerId == B_Paste )
1719 else if( layerId == UNDEFINED_LAYER )
1721 else if( !( layerId & 1 ) )
1722 type = BS_ITEM_TYPE_COPPER;
1723
1724 BOARD_STACKUP_ITEM* item = nullptr;
1725
1726 if( type != BS_ITEM_TYPE_UNDEFINED )
1727 {
1728 item = new BOARD_STACKUP_ITEM( type );
1729 item->SetBrdLayerId( layerId );
1730
1731 if( type == BS_ITEM_TYPE_DIELECTRIC )
1732 item->SetDielectricLayerId( dielectric_idx++ );
1733
1734 stackup.Add( item );
1735 }
1736 else
1737 {
1738 Expecting( "layer_name" );
1739 }
1740
1741 bool has_next_sublayer = true;
1742 int sublayer_idx = 0; // the index of dielectric sub layers
1743 // sublayer 0 is always existing (main sublayer)
1744
1745 while( has_next_sublayer )
1746 {
1747 has_next_sublayer = false;
1748
1749 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
1750 {
1751 if( token == T_addsublayer )
1752 {
1753 has_next_sublayer = true;
1754 break;
1755 }
1756
1757 if( token == T_LEFT )
1758 {
1759 token = NextTok();
1760
1761 switch( token )
1762 {
1763 case T_type:
1764 NeedSYMBOL();
1765 item->SetTypeName( FromUTF8() );
1766 NeedRIGHT();
1767 break;
1768
1769 case T_thickness:
1770 item->SetThickness( parseBoardUnits( T_thickness ), sublayer_idx );
1771 token = NextTok();
1772
1773 if( token == T_LEFT )
1774 break;
1775
1776 if( token == T_locked )
1777 {
1778 // Dielectric thickness can be locked (for impedance controlled layers)
1779 if( type == BS_ITEM_TYPE_DIELECTRIC )
1780 item->SetThicknessLocked( true, sublayer_idx );
1781
1782 NeedRIGHT();
1783 }
1784
1785 break;
1786
1787 case T_material:
1788 NeedSYMBOL();
1789 item->SetMaterial( FromUTF8(), sublayer_idx );
1790 NeedRIGHT();
1791 break;
1792
1793 case T_epsilon_r:
1794 NextTok();
1795 item->SetEpsilonR( parseDouble(), sublayer_idx );
1796 NeedRIGHT();
1797 break;
1798
1799 case T_loss_tangent:
1800 NextTok();
1801 item->SetLossTangent( parseDouble(), sublayer_idx );
1802 NeedRIGHT();
1803 break;
1804
1805 case T_color:
1806 NeedSYMBOL();
1807 name = FromUTF8();
1808
1809 // Older versions didn't store opacity with custom colors
1810 if( name.StartsWith( wxT( "#" ) ) && m_requiredVersion < 20210824 )
1811 {
1813
1814 if( item->GetType() == BS_ITEM_TYPE_SOLDERMASK )
1815 color = color.WithAlpha( DEFAULT_SOLDERMASK_OPACITY );
1816 else
1817 color = color.WithAlpha( 1.0 );
1818
1819 wxColour wx_color = color.ToColour();
1820
1821 // Open-code wxColour::GetAsString() because 3.0 doesn't handle rgba
1822 name.Printf( wxT("#%02X%02X%02X%02X" ),
1823 wx_color.Red(),
1824 wx_color.Green(),
1825 wx_color.Blue(),
1826 wx_color.Alpha() );
1827 }
1828
1829 item->SetColor( name, sublayer_idx );
1830 NeedRIGHT();
1831 break;
1832
1833 default:
1834 // Currently, skip this item if not defined, because the stackup def
1835 // is a moving target
1836 //Expecting( "type, thickness, material, epsilon_r, loss_tangent, color" );
1837 skipCurrent();
1838 }
1839 }
1840 }
1841
1842 if( has_next_sublayer ) // Prepare reading the next sublayer description
1843 {
1844 sublayer_idx++;
1845 item->AddDielectricPrms( sublayer_idx );
1846 }
1847 }
1848 }
1849
1850 if( token != T_RIGHT )
1851 {
1852 Expecting( ")" );
1853 }
1854
1855 // Success:
1857}
1858
1859
1860void PCB_IO_KICAD_SEXPR_PARSER::createOldLayerMapping( std::unordered_map< std::string, std::string >& aMap )
1861{
1862 // N.B. This mapping only includes Italian, Polish and French as they were the only languages
1863 // that mapped the layer names as of cc2022b1ac739aa673d2a0b7a2047638aa7a47b3 (kicad-i18n)
1864 // when the bug was fixed in KiCad source.
1865
1866 // Italian
1867 aMap["Adesivo.Retro"] = "B.Adhes";
1868 aMap["Adesivo.Fronte"] = "F.Adhes";
1869 aMap["Pasta.Retro"] = "B.Paste";
1870 aMap["Pasta.Fronte"] = "F.Paste";
1871 aMap["Serigrafia.Retro"] = "B.SilkS";
1872 aMap["Serigrafia.Fronte"] = "F.SilkS";
1873 aMap["Maschera.Retro"] = "B.Mask";
1874 aMap["Maschera.Fronte"] = "F.Mask";
1875 aMap["Grafica"] = "Dwgs.User";
1876 aMap["Commenti"] = "Cmts.User";
1877 aMap["Eco1"] = "Eco1.User";
1878 aMap["Eco2"] = "Eco2.User";
1879 aMap["Contorno.scheda"] = "Edge.Cuts";
1880
1881 // Polish
1882 aMap["Kleju_Dolna"] = "B.Adhes";
1883 aMap["Kleju_Gorna"] = "F.Adhes";
1884 aMap["Pasty_Dolna"] = "B.Paste";
1885 aMap["Pasty_Gorna"] = "F.Paste";
1886 aMap["Opisowa_Dolna"] = "B.SilkS";
1887 aMap["Opisowa_Gorna"] = "F.SilkS";
1888 aMap["Maski_Dolna"] = "B.Mask";
1889 aMap["Maski_Gorna"] = "F.Mask";
1890 aMap["Rysunkowa"] = "Dwgs.User";
1891 aMap["Komentarzy"] = "Cmts.User";
1892 aMap["ECO1"] = "Eco1.User";
1893 aMap["ECO2"] = "Eco2.User";
1894 aMap["Krawedziowa"] = "Edge.Cuts";
1895
1896 // French
1897 aMap["Dessous.Adhes"] = "B.Adhes";
1898 aMap["Dessus.Adhes"] = "F.Adhes";
1899 aMap["Dessous.Pate"] = "B.Paste";
1900 aMap["Dessus.Pate"] = "F.Paste";
1901 aMap["Dessous.SilkS"] = "B.SilkS";
1902 aMap["Dessus.SilkS"] = "F.SilkS";
1903 aMap["Dessous.Masque"] = "B.Mask";
1904 aMap["Dessus.Masque"] = "F.Mask";
1905 aMap["Dessin.User"] = "Dwgs.User";
1906 aMap["Contours.Ci"] = "Edge.Cuts";
1907}
1908
1909
1911{
1912 wxCHECK_RET( CurTok() == T_layers,
1913 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as layers." ) );
1914
1915 T token;
1916 LSET visibleLayers;
1917 LSET enabledLayers;
1918 int copperLayerCount = 0;
1919 LAYER layer;
1920 bool anyHidden = false;
1921
1922 std::unordered_map< std::string, std::string > v3_layer_names;
1923 std::vector<LAYER> cu;
1924
1925 createOldLayerMapping( v3_layer_names );
1926
1927 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
1928 {
1929 parseLayer( &layer );
1930
1931 if( layer.m_type == LT_UNDEFINED ) // it's a non-copper layer
1932 break;
1933
1934 cu.push_back( layer ); // it's copper
1935 }
1936
1937 // All Cu layers are parsed, but not the non-cu layers here.
1938
1939 // The original *.kicad_pcb file format and the inverted
1940 // Cu stack format both have all the Cu layers first, so use this
1941 // trick to handle either. The layer number in the (layers ..)
1942 // s-expression element are ignored.
1943 if( cu.size() )
1944 {
1945 // Rework the layer numbers, which changed when the Cu stack
1946 // was flipped. So we instead use position in the list.
1947 for( size_t i = 1; i < cu.size() - 1; i++ )
1948 {
1949 int tmpLayer = -1;
1950
1951 // Older versions didn't have a dedicated user name field
1952 if( m_requiredVersion >= 20200922 )
1953 tmpLayer = LSET::NameToLayer( cu[i].m_name );
1954
1955 if( tmpLayer < 0 )
1956 tmpLayer = ( i + 1 ) * 2;
1957
1958 cu[i].m_number = tmpLayer;
1959 }
1960
1961 cu[0].m_number = F_Cu;
1962 cu[cu.size()-1].m_number = B_Cu;
1963
1964 for( auto& cu_layer : cu )
1965 {
1966 enabledLayers.set( cu_layer.m_number );
1967
1968 if( cu_layer.m_visible )
1969 visibleLayers.set( cu_layer.m_number );
1970 else
1971 anyHidden = true;
1972
1973 m_board->SetLayerDescr( PCB_LAYER_ID( cu_layer.m_number ), cu_layer );
1974
1975 UTF8 name = cu_layer.m_name;
1976
1977 m_layerIndices[ name ] = PCB_LAYER_ID( cu_layer.m_number );
1978 m_layerMasks[ name ] = LSET( { PCB_LAYER_ID( cu_layer.m_number ) } );
1979 }
1980
1981 copperLayerCount = cu.size();
1982 }
1983
1984 // process non-copper layers
1985 while( token != T_RIGHT )
1986 {
1987 LAYER_ID_MAP::const_iterator it = m_layerIndices.find( UTF8( layer.m_name ) );
1988
1989 if( it == m_layerIndices.end() )
1990 {
1991 auto new_layer_it = v3_layer_names.find( layer.m_name.ToStdString() );
1992
1993 if( new_layer_it != v3_layer_names.end() )
1994 it = m_layerIndices.find( new_layer_it->second );
1995
1996 if( it == m_layerIndices.end() )
1997 {
1998 wxString error;
1999 error.Printf( _( "Layer '%s' in file '%s' at line %d is not in fixed layer hash." ),
2000 layer.m_name,
2001 CurSource(),
2002 CurLineNumber(),
2003 CurOffset() );
2004
2005 THROW_IO_ERROR( error );
2006 }
2007
2008 // If we are here, then we have found a translated layer name. Put it in the maps
2009 // so that items on this layer get the appropriate layer ID number.
2010 m_layerIndices[ UTF8( layer.m_name ) ] = it->second;
2011 m_layerMasks[ UTF8( layer.m_name ) ] = LSET( { it->second } );
2012 layer.m_name = it->first;
2013 }
2014
2015 layer.m_number = it->second;
2016 enabledLayers.set( layer.m_number );
2017
2018 if( layer.m_visible )
2019 visibleLayers.set( layer.m_number );
2020 else
2021 anyHidden = true;
2022
2023 m_board->SetLayerDescr( it->second, layer );
2024
2025 token = NextTok();
2026
2027 if( token != T_LEFT )
2028 break;
2029
2030 parseLayer( &layer );
2031 }
2032
2033 // We need at least 2 copper layers and there must be an even number of them.
2034 if( copperLayerCount < 2 || (copperLayerCount % 2) != 0 )
2035 {
2036 wxString err = wxString::Format( _( "%d is not a valid layer count" ), copperLayerCount );
2037
2038 THROW_PARSE_ERROR( err, CurSource(), CurLine(), CurLineNumber(), CurOffset() );
2039 }
2040
2041 m_board->SetCopperLayerCount( copperLayerCount );
2042 m_board->SetEnabledLayers( enabledLayers );
2043
2044 // Only set this if any layers were explicitly marked as hidden. Otherwise, we want to leave
2045 // this alone; default visibility will show everything
2046 if( anyHidden )
2047 m_board->m_LegacyVisibleLayers = visibleLayers;
2048}
2049
2050
2052{
2053 LSET_MAP::const_iterator it = aMap.find( curText );
2054
2055 if( it == aMap.end() )
2056 return LSET( { Rescue } );
2057
2058 return it->second;
2059}
2060
2061
2063{
2064 // avoid constructing another std::string, use lexer's directly
2065 LAYER_ID_MAP::const_iterator it = aMap.find( curText );
2066
2067 if( it == aMap.end() )
2068 {
2069 m_undefinedLayers.insert( curText );
2070 return Rescue;
2071 }
2072
2073 // Some files may have saved items to the Rescue Layer due to an issue in v5
2074 if( it->second == Rescue )
2075 m_undefinedLayers.insert( curText );
2076
2077 return it->second;
2078}
2079
2080
2082{
2083 wxCHECK_MSG( CurTok() == T_layer, UNDEFINED_LAYER,
2084 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as layer." ) );
2085
2086 NextTok();
2087
2088 PCB_LAYER_ID layerIndex = lookUpLayer( m_layerIndices );
2089
2090 // Handle closing ) in object parser.
2091
2092 return layerIndex;
2093}
2094
2095
2097{
2098 wxCHECK_MSG( CurTok() == T_layers, LSET(),
2099 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as item layers." ) );
2100
2101 LSET layerMask;
2102
2103 for( T token = NextTok(); token != T_RIGHT; token = NextTok() )
2104 {
2105 layerMask |= lookUpLayerSet( m_layerMasks );
2106 }
2107
2108 return layerMask;
2109}
2110
2111
2113{
2114 LSET layerMask = parseBoardItemLayersAsMask();
2115
2116 if( ( layerMask & LSET::AllCuMask() ).count() != 1 )
2117 Expecting( "single copper layer" );
2118
2119 if( ( layerMask & LSET( { F_Mask, B_Mask } ) ).count() > 1 )
2120 Expecting( "max one soldermask layer" );
2121
2122 if( ( ( layerMask & LSET::InternalCuMask() ).any()
2123 && ( layerMask & LSET( { F_Mask, B_Mask } ) ).any() ) )
2124 Expecting( "no mask layer when track is on internal layer" );
2125
2126 if( ( layerMask & LSET( { F_Cu, B_Mask } ) ).count() > 1 )
2127 Expecting( "copper and mask on the same side" );
2128
2129 if( ( layerMask & LSET( { B_Cu, F_Mask } ) ).count() > 1 )
2130 Expecting( "copper and mask on the same side" );
2131
2132 return layerMask;
2133}
2134
2135
2137{
2138 wxCHECK_RET( CurTok() == T_setup,
2139 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as setup." ) );
2140
2142 const std::shared_ptr<NETCLASS>& defaultNetClass = bds.m_NetSettings->GetDefaultNetclass();
2143 ZONE_SETTINGS& zoneSettings = bds.GetDefaultZoneSettings();
2144
2145 // Missing soldermask min width value means that the user has set the value to 0 and
2146 // not the default value (0.25mm)
2147 bds.m_SolderMaskMinWidth = 0;
2148
2149 for( T token = NextTok(); token != T_RIGHT; token = NextTok() )
2150 {
2151 if( token != T_LEFT )
2152 Expecting( T_LEFT );
2153
2154 token = NextTok();
2155
2156 switch( token )
2157 {
2158 case T_stackup:
2160 break;
2161
2162 case T_last_trace_width: // not used now
2163 /* lastTraceWidth =*/ parseBoardUnits( T_last_trace_width );
2164 NeedRIGHT();
2165 break;
2166
2167 case T_user_trace_width:
2168 {
2169 // Make room for the netclass value
2170 if( bds.m_TrackWidthList.empty() )
2171 bds.m_TrackWidthList.emplace_back( 0 );
2172
2173 int trackWidth = parseBoardUnits( T_user_trace_width );
2174
2175 if( !m_appendToExisting || !alg::contains( bds.m_TrackWidthList, trackWidth ) )
2176 bds.m_TrackWidthList.push_back( trackWidth );
2177
2179 NeedRIGHT();
2180 break;
2181 }
2182
2183 case T_trace_clearance:
2184 defaultNetClass->SetClearance( parseBoardUnits( T_trace_clearance ) );
2186 NeedRIGHT();
2187 break;
2188
2189 case T_zone_clearance:
2190 zoneSettings.m_ZoneClearance = parseBoardUnits( T_zone_clearance );
2192 NeedRIGHT();
2193 break;
2194
2195 case T_zone_45_only: // legacy setting
2196 /* zoneSettings.m_Zone_45_Only = */ parseBool();
2198 NeedRIGHT();
2199 break;
2200
2201 case T_clearance_min:
2202 bds.m_MinClearance = parseBoardUnits( T_clearance_min );
2204 NeedRIGHT();
2205 break;
2206
2207 case T_trace_min:
2208 bds.m_TrackMinWidth = parseBoardUnits( T_trace_min );
2210 NeedRIGHT();
2211 break;
2212
2213 case T_via_size:
2214 defaultNetClass->SetViaDiameter( parseBoardUnits( T_via_size ) );
2216 NeedRIGHT();
2217 break;
2218
2219 case T_via_drill:
2220 defaultNetClass->SetViaDrill( parseBoardUnits( T_via_drill ) );
2222 NeedRIGHT();
2223 break;
2224
2225 case T_via_min_annulus:
2226 bds.m_ViasMinAnnularWidth = parseBoardUnits( T_via_min_annulus );
2228 NeedRIGHT();
2229 break;
2230
2231 case T_via_min_size:
2232 bds.m_ViasMinSize = parseBoardUnits( T_via_min_size );
2234 NeedRIGHT();
2235 break;
2236
2237 case T_through_hole_min:
2238 bds.m_MinThroughDrill = parseBoardUnits( T_through_hole_min );
2240 NeedRIGHT();
2241 break;
2242
2243 // Legacy token for T_through_hole_min
2244 case T_via_min_drill:
2245 bds.m_MinThroughDrill = parseBoardUnits( T_via_min_drill );
2247 NeedRIGHT();
2248 break;
2249
2250 case T_hole_to_hole_min:
2251 bds.m_HoleToHoleMin = parseBoardUnits( T_hole_to_hole_min );
2253 NeedRIGHT();
2254 break;
2255
2256 case T_user_via:
2257 {
2258 int viaSize = parseBoardUnits( "user via size" );
2259 int viaDrill = parseBoardUnits( "user via drill" );
2260 VIA_DIMENSION via( viaSize, viaDrill );
2261
2262 // Make room for the netclass value
2263 if( bds.m_ViasDimensionsList.empty() )
2264 bds.m_ViasDimensionsList.emplace_back( VIA_DIMENSION( 0, 0 ) );
2265
2267 bds.m_ViasDimensionsList.emplace_back( via );
2268
2270 NeedRIGHT();
2271 break;
2272 }
2273
2274 case T_uvia_size:
2275 defaultNetClass->SetuViaDiameter( parseBoardUnits( T_uvia_size ) );
2277 NeedRIGHT();
2278 break;
2279
2280 case T_uvia_drill:
2281 defaultNetClass->SetuViaDrill( parseBoardUnits( T_uvia_drill ) );
2283 NeedRIGHT();
2284 break;
2285
2286 case T_uvias_allowed:
2287 parseBool();
2289 NeedRIGHT();
2290 break;
2291
2292 case T_blind_buried_vias_allowed:
2293 parseBool();
2295 NeedRIGHT();
2296 break;
2297
2298 case T_uvia_min_size:
2299 bds.m_MicroViasMinSize = parseBoardUnits( T_uvia_min_size );
2301 NeedRIGHT();
2302 break;
2303
2304 case T_uvia_min_drill:
2305 bds.m_MicroViasMinDrill = parseBoardUnits( T_uvia_min_drill );
2307 NeedRIGHT();
2308 break;
2309
2310 case T_user_diff_pair:
2311 {
2312 int width = parseBoardUnits( "user diff-pair width" );
2313 int gap = parseBoardUnits( "user diff-pair gap" );
2314 int viaGap = parseBoardUnits( "user diff-pair via gap" );
2315 DIFF_PAIR_DIMENSION diffPair( width, gap, viaGap );
2316
2318 bds.m_DiffPairDimensionsList.emplace_back( diffPair );
2319
2321 NeedRIGHT();
2322 break;
2323 }
2324
2325 case T_segment_width: // note: legacy (pre-6.0) token
2326 bds.m_LineThickness[ LAYER_CLASS_COPPER ] = parseBoardUnits( T_segment_width );
2328 NeedRIGHT();
2329 break;
2330
2331 case T_edge_width: // note: legacy (pre-6.0) token
2332 bds.m_LineThickness[ LAYER_CLASS_EDGES ] = parseBoardUnits( T_edge_width );
2334 NeedRIGHT();
2335 break;
2336
2337 case T_mod_edge_width: // note: legacy (pre-6.0) token
2338 bds.m_LineThickness[ LAYER_CLASS_SILK ] = parseBoardUnits( T_mod_edge_width );
2340 NeedRIGHT();
2341 break;
2342
2343 case T_pcb_text_width: // note: legacy (pre-6.0) token
2344 bds.m_TextThickness[ LAYER_CLASS_COPPER ] = parseBoardUnits( T_pcb_text_width );
2346 NeedRIGHT();
2347 break;
2348
2349 case T_mod_text_width: // note: legacy (pre-6.0) token
2350 bds.m_TextThickness[ LAYER_CLASS_SILK ] = parseBoardUnits( T_mod_text_width );
2352 NeedRIGHT();
2353 break;
2354
2355 case T_pcb_text_size: // note: legacy (pre-6.0) token
2356 bds.m_TextSize[ LAYER_CLASS_COPPER ].x = parseBoardUnits( "pcb text width" );
2357 bds.m_TextSize[ LAYER_CLASS_COPPER ].y = parseBoardUnits( "pcb text height" );
2359 NeedRIGHT();
2360 break;
2361
2362 case T_mod_text_size: // note: legacy (pre-6.0) token
2363 bds.m_TextSize[ LAYER_CLASS_SILK ].x = parseBoardUnits( "footprint text width" );
2364 bds.m_TextSize[ LAYER_CLASS_SILK ].y = parseBoardUnits( "footprint text height" );
2366 NeedRIGHT();
2367 break;
2368
2369 case T_defaults:
2370 parseDefaults( bds );
2372 break;
2373
2374 case T_pad_size:
2375 {
2376 VECTOR2I sz;
2377 sz.x = parseBoardUnits( "master pad width" );
2378 sz.y = parseBoardUnits( "master pad height" );
2379 bds.m_Pad_Master->SetSize( PADSTACK::ALL_LAYERS, sz );
2381 NeedRIGHT();
2382 break;
2383 }
2384
2385 case T_pad_drill:
2386 {
2387 int drillSize = parseBoardUnits( T_pad_drill );
2388 bds.m_Pad_Master->SetDrillSize( VECTOR2I( drillSize, drillSize ) );
2390 NeedRIGHT();
2391 break;
2392 }
2393
2394 case T_pad_to_mask_clearance:
2395 bds.m_SolderMaskExpansion = parseBoardUnits( T_pad_to_mask_clearance );
2396 NeedRIGHT();
2397 break;
2398
2399 case T_solder_mask_min_width:
2400 bds.m_SolderMaskMinWidth = parseBoardUnits( T_solder_mask_min_width );
2401 NeedRIGHT();
2402 break;
2403
2404 case T_pad_to_paste_clearance:
2405 bds.m_SolderPasteMargin = parseBoardUnits( T_pad_to_paste_clearance );
2406 NeedRIGHT();
2407 break;
2408
2409 case T_pad_to_paste_clearance_ratio:
2410 bds.m_SolderPasteMarginRatio = parseDouble( T_pad_to_paste_clearance_ratio );
2411 NeedRIGHT();
2412 break;
2413
2414 case T_allow_soldermask_bridges_in_footprints:
2416 NeedRIGHT();
2417 break;
2418
2419 case T_tenting:
2420 {
2421 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
2422 {
2423 if( token == T_front )
2424 bds.m_TentViasFront = true;
2425 else if( token == T_back )
2426 bds.m_TentViasBack = true;
2427 else if( token == T_none )
2428 bds.m_TentViasFront = bds.m_TentViasBack = false;
2429 else
2430 Expecting( "front, back, or none" );
2431 }
2432 break;
2433 }
2434
2435 case T_aux_axis_origin:
2436 {
2437 int x = parseBoardUnits( "auxiliary origin X" );
2438 int y = parseBoardUnits( "auxiliary origin Y" );
2439 bds.SetAuxOrigin( VECTOR2I( x, y ) );
2440
2441 // Aux origin still stored in board for the moment
2442 //m_board->m_LegacyDesignSettingsLoaded = true;
2443 NeedRIGHT();
2444 break;
2445 }
2446
2447 case T_grid_origin:
2448 {
2449 int x = parseBoardUnits( "grid origin X" );
2450 int y = parseBoardUnits( "grid origin Y" );
2451 bds.SetGridOrigin( VECTOR2I( x, y ) );
2452 // Grid origin still stored in board for the moment
2453 //m_board->m_LegacyDesignSettingsLoaded = true;
2454 NeedRIGHT();
2455 break;
2456 }
2457
2458 // Stored in board prior to 6.0
2459 case T_visible_elements:
2460 {
2461 // Make sure to start with DefaultVisible so all new layers are set
2463
2464 int visible = parseHex() | MIN_VISIBILITY_MASK;
2465
2466 for( size_t i = 0; i < sizeof( int ) * CHAR_BIT; i++ )
2467 m_board->m_LegacyVisibleItems.set( i, visible & ( 1u << i ) );
2468
2469 NeedRIGHT();
2470 break;
2471 }
2472
2473 case T_max_error:
2474 bds.m_MaxError = parseBoardUnits( T_max_error );
2476 NeedRIGHT();
2477 break;
2478
2479 case T_filled_areas_thickness:
2480 // Ignore this value, it is not used anymore
2481 parseBool();
2482 NeedRIGHT();
2483 break;
2484
2485 case T_pcbplotparams:
2486 {
2487 PCB_PLOT_PARAMS plotParams;
2488 PCB_PLOT_PARAMS_PARSER parser( reader );
2489 // parser must share the same current line as our current PCB parser
2490 // synchronize it.
2491 parser.SyncLineReaderWith( *this );
2492
2493 plotParams.Parse( &parser );
2494 SyncLineReaderWith( parser );
2495
2496 m_board->SetPlotOptions( plotParams );
2497
2498 if( plotParams.GetLegacyPlotViaOnMaskLayer().has_value() )
2499 {
2500 bool tent = !( *plotParams.GetLegacyPlotViaOnMaskLayer() );
2503 }
2504
2505 break;
2506 }
2507
2508 default:
2509 Unexpected( CurText() );
2510 }
2511 }
2512
2513 // Set up a default stackup in case the file doesn't define one, and now we know
2514 // the enabled layers
2516 {
2517 BOARD_STACKUP& stackup = bds.GetStackupDescriptor();
2518 stackup.RemoveAll();
2520 }
2521}
2522
2523
2525{
2526 T token;
2527
2528 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
2529 {
2530 if( token != T_LEFT )
2531 Expecting( T_LEFT );
2532
2533 token = NextTok();
2534
2535 switch( token )
2536 {
2537 case T_edge_clearance:
2538 designSettings.m_CopperEdgeClearance = parseBoardUnits( T_edge_clearance );
2540 NeedRIGHT();
2541 break;
2542
2543 case T_copper_line_width:
2544 designSettings.m_LineThickness[ LAYER_CLASS_COPPER ] = parseBoardUnits( token );
2545 NeedRIGHT();
2546 break;
2547
2548 case T_copper_text_dims:
2549 parseDefaultTextDims( designSettings, LAYER_CLASS_COPPER );
2550 break;
2551
2552 case T_courtyard_line_width:
2553 designSettings.m_LineThickness[ LAYER_CLASS_COURTYARD ] = parseBoardUnits( token );
2554 NeedRIGHT();
2555 break;
2556
2557 case T_edge_cuts_line_width:
2558 designSettings.m_LineThickness[ LAYER_CLASS_EDGES ] = parseBoardUnits( token );
2559 NeedRIGHT();
2560 break;
2561
2562 case T_silk_line_width:
2563 designSettings.m_LineThickness[ LAYER_CLASS_SILK ] = parseBoardUnits( token );
2564 NeedRIGHT();
2565 break;
2566
2567 case T_silk_text_dims:
2568 parseDefaultTextDims( designSettings, LAYER_CLASS_SILK );
2569 break;
2570
2571 case T_fab_layers_line_width:
2572 designSettings.m_LineThickness[ LAYER_CLASS_FAB ] = parseBoardUnits( token );
2573 NeedRIGHT();
2574 break;
2575
2576 case T_fab_layers_text_dims:
2577 parseDefaultTextDims( designSettings, LAYER_CLASS_FAB );
2578 break;
2579
2580 case T_other_layers_line_width:
2581 designSettings.m_LineThickness[ LAYER_CLASS_OTHERS ] = parseBoardUnits( token );
2582 NeedRIGHT();
2583 break;
2584
2585 case T_other_layers_text_dims:
2586 parseDefaultTextDims( designSettings, LAYER_CLASS_OTHERS );
2587 break;
2588
2589 case T_dimension_units:
2590 designSettings.m_DimensionUnitsMode =
2591 static_cast<DIM_UNITS_MODE>( parseInt( "dimension units" ) );
2592 NeedRIGHT();
2593 break;
2594
2595 case T_dimension_precision:
2596 designSettings.m_DimensionPrecision =
2597 static_cast<DIM_PRECISION>( parseInt( "dimension precision" ) );
2598 NeedRIGHT();
2599 break;
2600
2601 default:
2602 Unexpected( CurText() );
2603 }
2604 }
2605}
2606
2607
2609{
2610 T token;
2611
2612 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
2613 {
2614 if( token == T_LEFT )
2615 token = NextTok();
2616
2617 switch( token )
2618 {
2619 case T_size:
2620 aSettings.m_TextSize[ aLayer ].x = parseBoardUnits( "default text size X" );
2621 aSettings.m_TextSize[ aLayer ].y = parseBoardUnits( "default text size Y" );
2622 NeedRIGHT();
2623 break;
2624
2625 case T_thickness:
2626 aSettings.m_TextThickness[ aLayer ] = parseBoardUnits( "default text width" );
2627 NeedRIGHT();
2628 break;
2629
2630 case T_italic:
2631 aSettings.m_TextItalic[ aLayer ] = true;
2632 break;
2633
2634 case T_keep_upright:
2635 aSettings.m_TextUpright[ aLayer ] = true;
2636 break;
2637
2638 default:
2639 Expecting( "size, thickness, italic or keep_upright" );
2640 }
2641 }
2642}
2643
2644
2646{
2647 wxCHECK_RET( CurTok() == T_net,
2648 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as net." ) );
2649
2650 int netCode = parseInt( "net number" );
2651
2652 NeedSYMBOLorNUMBER();
2653 wxString name = FromUTF8();
2654
2655 // Convert overbar syntax from `~...~` to `~{...}`. These were left out of the first merge
2656 // so the version is a bit later.
2657 if( m_requiredVersion < 20210606 )
2659
2660 NeedRIGHT();
2661
2662 // net 0 should be already in list, so store this net
2663 // if it is not the net 0, or if the net 0 does not exists.
2664 // (TODO: a better test.)
2666 {
2667 NETINFO_ITEM* net = new NETINFO_ITEM( m_board, name, netCode );
2668 m_board->Add( net, ADD_MODE::INSERT, true );
2669
2670 // Store the new code mapping
2671 pushValueIntoMap( netCode, net->GetNetCode() );
2672 }
2673}
2674
2675
2677{
2678 wxCHECK_RET( CurTok() == T_net_class,
2679 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as net class." ) );
2680
2681 T token;
2682
2683 std::shared_ptr<NETCLASS> nc = std::make_shared<NETCLASS>( wxEmptyString );
2684
2685 // Read netclass name (can be a name or just a number like track width)
2686 NeedSYMBOLorNUMBER();
2687 nc->SetName( FromUTF8() );
2688 NeedSYMBOL();
2689 nc->SetDescription( FromUTF8() );
2690
2691 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
2692 {
2693 if( token != T_LEFT )
2694 Expecting( T_LEFT );
2695
2696 token = NextTok();
2697
2698 switch( token )
2699 {
2700 case T_clearance:
2701 nc->SetClearance( parseBoardUnits( T_clearance ) );
2702 break;
2703
2704 case T_trace_width:
2705 nc->SetTrackWidth( parseBoardUnits( T_trace_width ) );
2706 break;
2707
2708 case T_via_dia:
2709 nc->SetViaDiameter( parseBoardUnits( T_via_dia ) );
2710 break;
2711
2712 case T_via_drill:
2713 nc->SetViaDrill( parseBoardUnits( T_via_drill ) );
2714 break;
2715
2716 case T_uvia_dia:
2717 nc->SetuViaDiameter( parseBoardUnits( T_uvia_dia ) );
2718 break;
2719
2720 case T_uvia_drill:
2721 nc->SetuViaDrill( parseBoardUnits( T_uvia_drill ) );
2722 break;
2723
2724 case T_diff_pair_width:
2725 nc->SetDiffPairWidth( parseBoardUnits( T_diff_pair_width ) );
2726 break;
2727
2728 case T_diff_pair_gap:
2729 nc->SetDiffPairGap( parseBoardUnits( T_diff_pair_gap ) );
2730 break;
2731
2732 case T_add_net:
2733 {
2734 NeedSYMBOLorNUMBER();
2735
2736 wxString netName = FromUTF8();
2737
2738 // Convert overbar syntax from `~...~` to `~{...}`. These were left out of the
2739 // first merge so the version is a bit later.
2740 if( m_requiredVersion < 20210606 )
2741 netName = ConvertToNewOverbarNotation( FromUTF8() );
2742
2743 m_board->GetDesignSettings().m_NetSettings->SetNetclassPatternAssignment(
2744 netName, nc->GetName() );
2745
2746 break;
2747 }
2748
2749 default:
2750 Expecting( "clearance, trace_width, via_dia, via_drill, uvia_dia, uvia_drill, "
2751 "diff_pair_width, diff_pair_gap or add_net" );
2752 }
2753
2754 NeedRIGHT();
2755 }
2756
2757 std::shared_ptr<NET_SETTINGS>& netSettings = m_board->GetDesignSettings().m_NetSettings;
2758
2759 if( netSettings->HasNetclass( nc->GetName() ) )
2760 {
2761 // Must have been a name conflict, this is a bad board file.
2762 // User may have done a hand edit to the file.
2763 wxString error;
2764 error.Printf( _( "Duplicate NETCLASS name '%s' in file '%s' at line %d, offset %d." ),
2765 nc->GetName().GetData(), CurSource().GetData(), CurLineNumber(),
2766 CurOffset() );
2767 THROW_IO_ERROR( error );
2768 }
2769 else if( nc->GetName() == netSettings->GetDefaultNetclass()->GetName() )
2770 {
2771 netSettings->SetDefaultNetclass( nc );
2772 }
2773 else
2774 {
2775 netSettings->SetNetclass( nc->GetName(), nc );
2776 }
2777}
2778
2779
2781{
2782 wxCHECK_MSG( CurTok() == T_fp_arc || CurTok() == T_fp_circle || CurTok() == T_fp_curve ||
2783 CurTok() == T_fp_rect || CurTok() == T_fp_line || CurTok() == T_fp_poly ||
2784 CurTok() == T_gr_arc || CurTok() == T_gr_circle || CurTok() == T_gr_curve ||
2785 CurTok() == T_gr_rect || CurTok() == T_gr_bbox || CurTok() == T_gr_line ||
2786 CurTok() == T_gr_poly || CurTok() == T_gr_vector, nullptr,
2787 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as PCB_SHAPE." ) );
2788
2789 T token;
2790 VECTOR2I pt;
2791 STROKE_PARAMS stroke( 0, LINE_STYLE::SOLID );
2792 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( aParent );
2793
2794 switch( CurTok() )
2795 {
2796 case T_gr_arc:
2797 case T_fp_arc:
2798 shape->SetShape( SHAPE_T::ARC );
2799 token = NextTok();
2800
2801 if( token == T_locked )
2802 {
2803 shape->SetLocked( true );
2804 token = NextTok();
2805 }
2806
2807 if( token != T_LEFT )
2808 Expecting( T_LEFT );
2809
2810 token = NextTok();
2811
2813 {
2814 // In legacy files the start keyword actually gives the arc center...
2815 if( token != T_start )
2816 Expecting( T_start );
2817
2818 pt.x = parseBoardUnits( "X coordinate" );
2819 pt.y = parseBoardUnits( "Y coordinate" );
2820 shape->SetCenter( pt );
2821 NeedRIGHT();
2822 NeedLEFT();
2823 token = NextTok();
2824
2825 // ... and the end keyword gives the start point of the arc
2826 if( token != T_end )
2827 Expecting( T_end );
2828
2829 pt.x = parseBoardUnits( "X coordinate" );
2830 pt.y = parseBoardUnits( "Y coordinate" );
2831 shape->SetStart( pt );
2832 NeedRIGHT();
2833 NeedLEFT();
2834 token = NextTok();
2835
2836 if( token != T_angle )
2837 Expecting( T_angle );
2838
2839 shape->SetArcAngleAndEnd( EDA_ANGLE( parseDouble( "arc angle" ), DEGREES_T ), true );
2840 NeedRIGHT();
2841 }
2842 else
2843 {
2844 VECTOR2I arc_start, arc_mid, arc_end;
2845
2846 if( token != T_start )
2847 Expecting( T_start );
2848
2849 arc_start.x = parseBoardUnits( "X coordinate" );
2850 arc_start.y = parseBoardUnits( "Y coordinate" );
2851 NeedRIGHT();
2852 NeedLEFT();
2853 token = NextTok();
2854
2855 if( token != T_mid )
2856 Expecting( T_mid );
2857
2858 arc_mid.x = parseBoardUnits( "X coordinate" );
2859 arc_mid.y = parseBoardUnits( "Y coordinate" );
2860 NeedRIGHT();
2861 NeedLEFT();
2862 token = NextTok();
2863
2864 if( token != T_end )
2865 Expecting( T_end );
2866
2867 arc_end.x = parseBoardUnits( "X coordinate" );
2868 arc_end.y = parseBoardUnits( "Y coordinate" );
2869 NeedRIGHT();
2870
2871 shape->SetArcGeometry( arc_start, arc_mid, arc_end );
2872 }
2873
2874 break;
2875
2876 case T_gr_circle:
2877 case T_fp_circle:
2878 shape->SetShape( SHAPE_T::CIRCLE );
2879 token = NextTok();
2880
2881 if( token == T_locked )
2882 {
2883 shape->SetLocked( true );
2884 token = NextTok();
2885 }
2886
2887 if( token != T_LEFT )
2888 Expecting( T_LEFT );
2889
2890 token = NextTok();
2891
2892 if( token != T_center )
2893 Expecting( T_center );
2894
2895 pt.x = parseBoardUnits( "X coordinate" );
2896 pt.y = parseBoardUnits( "Y coordinate" );
2897 shape->SetStart( pt );
2898 NeedRIGHT();
2899 NeedLEFT();
2900
2901 token = NextTok();
2902
2903 if( token != T_end )
2904 Expecting( T_end );
2905
2906 pt.x = parseBoardUnits( "X coordinate" );
2907 pt.y = parseBoardUnits( "Y coordinate" );
2908 shape->SetEnd( pt );
2909 NeedRIGHT();
2910 break;
2911
2912 case T_gr_curve:
2913 case T_fp_curve:
2914 shape->SetShape( SHAPE_T::BEZIER );
2915 token = NextTok();
2916
2917 if( token == T_locked )
2918 {
2919 shape->SetLocked( true );
2920 token = NextTok();
2921 }
2922
2923 if( token != T_LEFT )
2924 Expecting( T_LEFT );
2925
2926 token = NextTok();
2927
2928 if( token != T_pts )
2929 Expecting( T_pts );
2930
2931 shape->SetStart( parseXY() );
2932 shape->SetBezierC1( parseXY());
2933 shape->SetBezierC2( parseXY());
2934 shape->SetEnd( parseXY() );
2935 shape->RebuildBezierToSegmentsPointsList( ARC_HIGH_DEF );
2936 NeedRIGHT();
2937 break;
2938
2939 case T_gr_bbox:
2940 case T_gr_rect:
2941 case T_fp_rect:
2942 shape->SetShape( SHAPE_T::RECTANGLE );
2943 token = NextTok();
2944
2945 if( token == T_locked )
2946 {
2947 shape->SetLocked( true );
2948 token = NextTok();
2949 }
2950
2951 if( token != T_LEFT )
2952 Expecting( T_LEFT );
2953
2954 token = NextTok();
2955
2956 if( token != T_start )
2957 Expecting( T_start );
2958
2959 pt.x = parseBoardUnits( "X coordinate" );
2960 pt.y = parseBoardUnits( "Y coordinate" );
2961 shape->SetStart( pt );
2962 NeedRIGHT();
2963 NeedLEFT();
2964 token = NextTok();
2965
2966 if( token != T_end )
2967 Expecting( T_end );
2968
2969 pt.x = parseBoardUnits( "X coordinate" );
2970 pt.y = parseBoardUnits( "Y coordinate" );
2971 shape->SetEnd( pt );
2972
2973 if( aParent && aParent->Type() == PCB_FOOTPRINT_T )
2974 {
2975 // Footprint shapes are stored in board-relative coordinates, but we want the
2976 // normalization to remain in footprint-relative coordinates.
2977 }
2978 else
2979 {
2980 shape->Normalize();
2981 }
2982
2983 NeedRIGHT();
2984 break;
2985
2986 case T_gr_vector:
2987 case T_gr_line:
2988 case T_fp_line:
2989 // Default PCB_SHAPE type is S_SEGMENT.
2990 token = NextTok();
2991
2992 if( token == T_locked )
2993 {
2994 shape->SetLocked( true );
2995 token = NextTok();
2996 }
2997
2998 if( token != T_LEFT )
2999 Expecting( T_LEFT );
3000
3001 token = NextTok();
3002
3003 if( token != T_start )
3004 Expecting( T_start );
3005
3006 pt.x = parseBoardUnits( "X coordinate" );
3007 pt.y = parseBoardUnits( "Y coordinate" );
3008 shape->SetStart( pt );
3009 NeedRIGHT();
3010 NeedLEFT();
3011 token = NextTok();
3012
3013 if( token != T_end )
3014 Expecting( T_end );
3015
3016 pt.x = parseBoardUnits( "X coordinate" );
3017 pt.y = parseBoardUnits( "Y coordinate" );
3018 shape->SetEnd( pt );
3019 NeedRIGHT();
3020 break;
3021
3022 case T_gr_poly:
3023 case T_fp_poly:
3024 {
3025 shape->SetShape( SHAPE_T::POLY );
3026 shape->SetPolyPoints( {} );
3027
3028 SHAPE_LINE_CHAIN& outline = shape->GetPolyShape().Outline( 0 );
3029
3030 token = NextTok();
3031
3032 if( token == T_locked )
3033 {
3034 shape->SetLocked( true );
3035 token = NextTok();
3036 }
3037
3038 if( token != T_LEFT )
3039 Expecting( T_LEFT );
3040
3041 token = NextTok();
3042
3043 if( token != T_pts )
3044 Expecting( T_pts );
3045
3046 while( (token = NextTok() ) != T_RIGHT )
3047 parseOutlinePoints( outline );
3048
3049 break;
3050 }
3051
3052 default:
3053 Expecting( "gr_arc, gr_circle, gr_curve, gr_line, gr_poly, gr_rect or gr_bbox" );
3054 }
3055
3056 bool foundFill = false;
3057
3058 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
3059 {
3060 if( token != T_LEFT )
3061 Expecting( T_LEFT );
3062
3063 token = NextTok();
3064
3065 switch( token )
3066 {
3067 case T_angle: // legacy token; ignore value
3068 parseDouble( "arc angle" );
3069 NeedRIGHT();
3070 break;
3071
3072 case T_layer:
3073 shape->SetLayer( parseBoardItemLayer() );
3074 NeedRIGHT();
3075 break;
3076
3077 case T_layers:
3078 shape->SetLayerSet( parseBoardItemLayersAsMask() );
3079 break;
3080
3081 case T_solder_mask_margin:
3082 shape->SetLocalSolderMaskMargin( parseBoardUnits( "local solder mask margin value" ) );
3083 NeedRIGHT();
3084 break;
3085
3086 case T_width: // legacy token
3087 stroke.SetWidth( parseBoardUnits( T_width ) );
3088 NeedRIGHT();
3089 break;
3090
3091 case T_stroke:
3092 {
3093 STROKE_PARAMS_PARSER strokeParser( reader, pcbIUScale.IU_PER_MM );
3094 strokeParser.SyncLineReaderWith( *this );
3095
3096 strokeParser.ParseStroke( stroke );
3097 SyncLineReaderWith( strokeParser );
3098 break;
3099 }
3100
3101 case T_tstamp:
3102 case T_uuid:
3103 NextTok();
3104 const_cast<KIID&>( shape->m_Uuid ) = CurStrToKIID();
3105 NeedRIGHT();
3106 break;
3107
3108 case T_fill:
3109 foundFill = true;
3110
3111 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
3112 {
3113 if( token == T_LEFT )
3114 token = NextTok();
3115
3116 switch( token )
3117 {
3118 // T_yes was used to indicate filling when first introduced,
3119 // so treat it like a solid fill since that was the only fill available
3120 case T_yes:
3121 case T_solid:
3122 shape->SetFilled( true );
3123 break;
3124
3125 case T_none:
3126 case T_no:
3127 shape->SetFilled( false );
3128 break;
3129
3130 default:
3131 Expecting( "yes, no, solid, none" );
3132 }
3133 }
3134
3135 break;
3136
3137 // We continue to parse the status field but it is no longer written
3138 case T_status:
3139 parseHex();
3140 NeedRIGHT();
3141 break;
3142
3143 // Handle (locked) from 5.99 development, and (locked yes) from modern times
3144 case T_locked:
3145 {
3146 bool locked = parseMaybeAbsentBool( true );
3147 shape->SetLocked( locked );
3148 break;
3149 }
3150
3151 case T_net:
3152 if( !shape->SetNetCode( getNetCode( parseInt( "net number" ) ), /* aNoAssert */ true ) )
3153 {
3154 wxLogError( _( "Invalid net ID in\nfile: '%s'\nline: %d\noffset: %d." ),
3155 CurSource(), CurLineNumber(), CurOffset() );
3156 }
3157 NeedRIGHT();
3158 break;
3159
3160 default:
3161 Expecting( "layer, width, fill, tstamp, uuid, locked, net, status, "
3162 "or solder_mask_margin" );
3163 }
3164 }
3165
3166 if( !foundFill )
3167 {
3168 // Legacy versions didn't have a filled flag but allowed some shapes to indicate they
3169 // should be filled by specifying a 0 stroke-width.
3170 if( stroke.GetWidth() == 0
3171 && ( shape->GetShape() == SHAPE_T::RECTANGLE || shape->GetShape() == SHAPE_T::CIRCLE ) )
3172 {
3173 shape->SetFilled( true );
3174 }
3175 else if( shape->GetShape() == SHAPE_T::POLY && shape->GetLayer() != Edge_Cuts )
3176 {
3177 // Polygons on non-Edge_Cuts layers were always filled.
3178 shape->SetFilled( true );
3179 }
3180 }
3181
3182 // Only filled shapes may have a zero line-width. This is not permitted in KiCad but some
3183 // external tools can generate invalid files.
3184 if( stroke.GetWidth() <= 0 && !shape->IsFilled() )
3185 {
3187 }
3188
3189 shape->SetStroke( stroke );
3190
3191 if( FOOTPRINT* parentFP = shape->GetParentFootprint() )
3192 {
3193 shape->Rotate( { 0, 0 }, parentFP->GetOrientation() );
3194 shape->Move( parentFP->GetPosition() );
3195 }
3196
3197 return shape.release();
3198}
3199
3200
3202{
3203 wxCHECK_MSG( CurTok() == T_image, nullptr,
3204 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a reference image." ) );
3205
3206 T token;
3207 std::unique_ptr<PCB_REFERENCE_IMAGE> bitmap = std::make_unique<PCB_REFERENCE_IMAGE>( aParent );
3208
3209 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
3210 {
3211 if( token != T_LEFT )
3212 Expecting( T_LEFT );
3213
3214 token = NextTok();
3215
3216 switch( token )
3217 {
3218 case T_at:
3219 {
3220 VECTOR2I pos;
3221 pos.x = parseBoardUnits( "X coordinate" );
3222 pos.y = parseBoardUnits( "Y coordinate" );
3223 bitmap->SetPosition( pos );
3224 NeedRIGHT();
3225 break;
3226 }
3227
3228 case T_layer:
3229 bitmap->SetLayer( parseBoardItemLayer() );
3230 NeedRIGHT();
3231 break;
3232
3233 case T_scale:
3234 {
3235 REFERENCE_IMAGE& refImage = bitmap->GetReferenceImage();
3236 refImage.SetImageScale( parseDouble( "image scale factor" ) );
3237
3238 if( !std::isnormal( refImage.GetImageScale() ) )
3239 refImage.SetImageScale( 1.0 );
3240
3241 NeedRIGHT();
3242 break;
3243 }
3244 case T_data:
3245 {
3246 token = NextTok();
3247
3248 wxString data;
3249
3250 // Reserve 512K because most image files are going to be larger than the default
3251 // 1K that wxString reserves.
3252 data.reserve( 1 << 19 );
3253
3254 while( token != T_RIGHT )
3255 {
3256 if( !IsSymbol( token ) )
3257 Expecting( "base64 image data" );
3258
3259 data += FromUTF8();
3260 token = NextTok();
3261 }
3262
3263 wxMemoryBuffer buffer = wxBase64Decode( data );
3264
3265 REFERENCE_IMAGE& refImage = bitmap->GetReferenceImage();
3266 if( !refImage.ReadImageFile( buffer ) )
3267 THROW_IO_ERROR( _( "Failed to read image data." ) );
3268
3269 break;
3270 }
3271
3272 case T_locked:
3273 {
3274 // This has only ever been (locked yes) format
3275 const bool locked = parseBool();
3276 bitmap->SetLocked( locked );
3277
3278 NeedRIGHT();
3279 break;
3280 }
3281
3282 case T_uuid:
3283 {
3284 NextTok();
3285 const_cast<KIID&>( bitmap->m_Uuid ) = CurStrToKIID();
3286 NeedRIGHT();
3287 break;
3288 }
3289
3290 default:
3291 Expecting( "at, layer, scale, data, locked or uuid" );
3292 }
3293 }
3294
3295 return bitmap.release();
3296}
3297
3298
3300{
3301 wxCHECK_MSG( CurTok() == T_gr_text || CurTok() == T_fp_text, nullptr,
3302 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as PCB_TEXT." ) );
3303
3304 FOOTPRINT* parentFP = dynamic_cast<FOOTPRINT*>( aParent );
3305 std::unique_ptr<PCB_TEXT> text;
3306
3307 T token = NextTok();
3308
3309 // If a base text is provided, we have a derived text already parsed and just need to update it
3310 if( aBaseText )
3311 {
3312 text = std::unique_ptr<PCB_TEXT>( aBaseText );
3313 }
3314 else if( parentFP )
3315 {
3316 switch( token )
3317 {
3318 case T_reference:
3319 text = std::make_unique<PCB_FIELD>( parentFP, REFERENCE_FIELD );
3320 break;
3321
3322 case T_value:
3323 text = std::make_unique<PCB_FIELD>( parentFP, VALUE_FIELD );
3324 break;
3325
3326 case T_user:
3327 text = std::make_unique<PCB_TEXT>( parentFP );
3328 break;
3329
3330 default:
3331 THROW_IO_ERROR( wxString::Format( _( "Cannot handle footprint text type %s" ),
3332 FromUTF8() ) );
3333 }
3334
3335 token = NextTok();
3336 }
3337 else
3338 {
3339 text = std::make_unique<PCB_TEXT>( aParent );
3340 }
3341
3342 // Legacy bare locked token
3343 if( token == T_locked )
3344 {
3345 text->SetLocked( true );
3346 token = NextTok();
3347 }
3348
3349 if( !IsSymbol( token ) && (int) token != DSN_NUMBER )
3350 Expecting( "text value" );
3351
3352 wxString value = FromUTF8();
3353 value.Replace( wxT( "%V" ), wxT( "${VALUE}" ) );
3354 value.Replace( wxT( "%R" ), wxT( "${REFERENCE}" ) );
3355 text->SetText( value );
3356
3357 NeedLEFT();
3358
3359 parsePCB_TEXT_effects( text.get(), aBaseText );
3360
3361 return text.release();
3362}
3363
3364
3366{
3367 FOOTPRINT* parentFP = dynamic_cast<FOOTPRINT*>( aText->GetParent() );
3368 bool hasAngle = false; // Old files do not have a angle specified.
3369 // in this case it is 0 expected to be 0
3370 bool hasPos = false;
3371
3372 // By default, texts in footprints have a locked rotation (i.e. rot = -90 ... 90 deg)
3373 if( parentFP )
3374 aText->SetKeepUpright( true );
3375
3376 for( T token = NextTok(); token != T_RIGHT; token = NextTok() )
3377 {
3378 if( token == T_LEFT )
3379 token = NextTok();
3380
3381 switch( token )
3382 {
3383 case T_at:
3384 {
3385 VECTOR2I pt;
3386
3387 hasPos = true;
3388 pt.x = parseBoardUnits( "X coordinate" );
3389 pt.y = parseBoardUnits( "Y coordinate" );
3390 aText->SetTextPos( pt );
3391 token = NextTok();
3392
3393 if( CurTok() == T_NUMBER )
3394 {
3396 hasAngle = true;
3397 token = NextTok();
3398 }
3399
3400 // Legacy location of this token; presence implies true
3401 if( parentFP && CurTok() == T_unlocked )
3402 {
3403 aText->SetKeepUpright( false );
3404 token = NextTok();
3405 }
3406
3407 if( (int) token != DSN_RIGHT )
3408 Expecting( DSN_RIGHT );
3409
3410 break;
3411 }
3412
3413 case T_layer:
3414 aText->SetLayer( parseBoardItemLayer() );
3415
3416 token = NextTok();
3417
3418 if( token == T_knockout )
3419 {
3420 aText->SetIsKnockout( true );
3421 token = NextTok();
3422 }
3423
3424 if( (int) token != DSN_RIGHT )
3425 Expecting( DSN_RIGHT );
3426
3427 break;
3428
3429 case T_tstamp:
3430 case T_uuid:
3431 NextTok();
3432 const_cast<KIID&>( aText->m_Uuid ) = CurStrToKIID();
3433 NeedRIGHT();
3434 break;
3435
3436 case T_hide:
3437 {
3438 // In older files, the hide token appears bare, and indicates hide==true.
3439 // In newer files, it will be an explicit bool in a list like (hide yes)
3440 bool hide = parseMaybeAbsentBool( true );
3441
3442 if( parentFP )
3443 aText->SetVisible( !hide );
3444 else
3445 Expecting( "layer, effects, locked, render_cache, uuid or tstamp" );
3446
3447 break;
3448 }
3449
3450 case T_locked:
3451 // Newer list-enclosed locked
3452 aText->SetLocked( parseBool() );
3453 NeedRIGHT();
3454 break;
3455
3456 // Confusingly, "unlocked" is not the opposite of "locked", but refers to "keep upright"
3457 case T_unlocked:
3458 if( parentFP )
3459 aText->SetKeepUpright( !parseBool() );
3460 else
3461 Expecting( "layer, effects, locked, render_cache or tstamp" );
3462
3463 NeedRIGHT();
3464 break;
3465
3466 case T_effects:
3467 parseEDA_TEXT( static_cast<EDA_TEXT*>( aText ) );
3468 break;
3469
3470 case T_render_cache:
3471 parseRenderCache( static_cast<EDA_TEXT*>( aText ) );
3472 break;
3473
3474 default:
3475 if( parentFP )
3476 Expecting( "layer, hide, effects, locked, render_cache or tstamp" );
3477 else
3478 Expecting( "layer, effects, locked, render_cache or tstamp" );
3479 }
3480 }
3481
3482 // If there is no orientation defined, then it is the default value of 0 degrees.
3483 if( !hasAngle )
3484 aText->SetTextAngle( ANGLE_0 );
3485
3486 if( parentFP && !dynamic_cast<PCB_DIMENSION_BASE*>( aBaseText ) )
3487 {
3488 // make PCB_TEXT rotation relative to the parent footprint.
3489 // It was read as absolute rotation from file
3490 // Note: this is not rue for PCB_DIMENSION items that use the board
3491 // coordinates
3492 aText->SetTextAngle( aText->GetTextAngle() - parentFP->GetOrientation() );
3493
3494 // Move and rotate the text to its board coordinates
3495 aText->Rotate( { 0, 0 }, parentFP->GetOrientation() );
3496
3497 // Only move offset from parent position if we read a position from the file.
3498 // These positions are relative to the parent footprint. If we don't have a position
3499 // then the text defaults to the parent position and moving again will double it.
3500 if (hasPos)
3501 aText->Move( parentFP->GetPosition() );
3502 }
3503}
3504
3505
3507{
3508 wxCHECK_MSG( CurTok() == T_gr_text_box || CurTok() == T_fp_text_box, nullptr,
3509 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as PCB_TEXTBOX." ) );
3510
3511 std::unique_ptr<PCB_TEXTBOX> textbox = std::make_unique<PCB_TEXTBOX>( aParent );
3512
3513 parseTextBoxContent( textbox.get() );
3514
3515 return textbox.release();
3516}
3517
3518
3520{
3521 wxCHECK_MSG( CurTok() == T_table_cell, nullptr,
3522 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a table cell." ) );
3523
3524 std::unique_ptr<PCB_TABLECELL> cell = std::make_unique<PCB_TABLECELL>( aParent );
3525
3526 parseTextBoxContent( cell.get() );
3527
3528 return cell.release();
3529}
3530
3531
3533{
3534 int left;
3535 int top;
3536 int right;
3537 int bottom;
3538 STROKE_PARAMS stroke( -1, LINE_STYLE::SOLID );
3539 bool foundMargins = false;
3540
3541 T token = NextTok();
3542
3543 // Legacy locked
3544 if( token == T_locked )
3545 {
3546 aTextBox->SetLocked( true );
3547 token = NextTok();
3548 }
3549
3550 if( !IsSymbol( token ) && (int) token != DSN_NUMBER )
3551 Expecting( "text value" );
3552
3553 aTextBox->SetText( FromUTF8() );
3554
3555 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
3556 {
3557 if( token != T_LEFT )
3558 Expecting( T_LEFT );
3559
3560 token = NextTok();
3561
3562 switch( token )
3563 {
3564 case T_locked:
3565 aTextBox->SetLocked( parseMaybeAbsentBool( true ) );
3566 break;
3567
3568 case T_start:
3569 {
3570 int x = parseBoardUnits( "X coordinate" );
3571 int y = parseBoardUnits( "Y coordinate" );
3572 aTextBox->SetStart( VECTOR2I( x, y ) );
3573 NeedRIGHT();
3574
3575 NeedLEFT();
3576 token = NextTok();
3577
3578 if( token != T_end )
3579 Expecting( T_end );
3580
3581 x = parseBoardUnits( "X coordinate" );
3582 y = parseBoardUnits( "Y coordinate" );
3583 aTextBox->SetEnd( VECTOR2I( x, y ) );
3584 NeedRIGHT();
3585 break;
3586 }
3587
3588 case T_pts:
3589 {
3590 aTextBox->SetShape( SHAPE_T::POLY );
3591 aTextBox->GetPolyShape().RemoveAllContours();
3592 aTextBox->GetPolyShape().NewOutline();
3593
3594 while( (token = NextTok() ) != T_RIGHT )
3595 parseOutlinePoints( aTextBox->GetPolyShape().Outline( 0 ) );
3596
3597 break;
3598 }
3599
3600 case T_angle:
3601 // Set the angle of the text only, the coordinates of the box (a polygon) are
3602 // already at the right position, and must not be rotated
3603 aTextBox->EDA_TEXT::SetTextAngle( EDA_ANGLE( parseDouble( "text box angle" ), DEGREES_T ) );
3604 NeedRIGHT();
3605 break;
3606
3607 case T_stroke:
3608 {
3609 STROKE_PARAMS_PARSER strokeParser( reader, pcbIUScale.IU_PER_MM );
3610 strokeParser.SyncLineReaderWith( *this );
3611
3612 strokeParser.ParseStroke( stroke );
3613 SyncLineReaderWith( strokeParser );
3614 break;
3615 }
3616
3617 case T_border:
3618 aTextBox->SetBorderEnabled( parseBool() );
3619 NeedRIGHT();
3620 break;
3621
3622 case T_margins:
3623 parseMargins( left, top, right, bottom );
3624 aTextBox->SetMarginLeft( left );
3625 aTextBox->SetMarginTop( top );
3626 aTextBox->SetMarginRight( right );
3627 aTextBox->SetMarginBottom( bottom );
3628 foundMargins = true;
3629 NeedRIGHT();
3630 break;
3631
3632 case T_layer:
3633 aTextBox->SetLayer( parseBoardItemLayer() );
3634 NeedRIGHT();
3635 break;
3636
3637 case T_span:
3638 if( PCB_TABLECELL* cell = dynamic_cast<PCB_TABLECELL*>( aTextBox ) )
3639 {
3640 cell->SetColSpan( parseInt( "column span" ) );
3641 cell->SetRowSpan( parseInt( "row span" ) );
3642 }
3643 else
3644 {
3645 Expecting( "angle, width, layer, effects, render_cache, uuid or tstamp" );
3646 }
3647
3648 NeedRIGHT();
3649 break;
3650
3651 case T_tstamp:
3652 case T_uuid:
3653 NextTok();
3654 const_cast<KIID&>( aTextBox->m_Uuid ) = CurStrToKIID();
3655 NeedRIGHT();
3656 break;
3657
3658 case T_effects:
3659 parseEDA_TEXT( static_cast<EDA_TEXT*>( aTextBox ) );
3660 break;
3661
3662 case T_render_cache:
3663 parseRenderCache( static_cast<EDA_TEXT*>( aTextBox ) );
3664 break;
3665
3666 default:
3667 if( dynamic_cast<PCB_TABLECELL*>( aTextBox ) != nullptr )
3668 Expecting( "locked, start, pts, angle, width, layer, effects, span, render_cache, uuid or tstamp" );
3669 else
3670 Expecting( "locked, start, pts, angle, width, layer, effects, render_cache, uuid or tstamp" );
3671 }
3672 }
3673
3674 aTextBox->SetStroke( stroke );
3675
3676 if( m_requiredVersion < 20230825 ) // compat, we move to an explicit flag
3677 aTextBox->SetBorderEnabled( stroke.GetWidth() >= 0 );
3678
3679 if( !foundMargins )
3680 {
3681 int margin = aTextBox->GetLegacyTextMargin();
3682 aTextBox->SetMarginLeft( margin );
3683 aTextBox->SetMarginTop( margin );
3684 aTextBox->SetMarginRight( margin );
3685 aTextBox->SetMarginBottom( margin );
3686 }
3687
3688 if( FOOTPRINT* parentFP = aTextBox->GetParentFootprint() )
3689 {
3690 aTextBox->Rotate( { 0, 0 }, parentFP->GetOrientation() );
3691 aTextBox->Move( parentFP->GetPosition() );
3692 }
3693}
3694
3695
3697{
3698 wxCHECK_MSG( CurTok() == T_table, nullptr,
3699 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a table." ) );
3700
3701 T token;
3702 STROKE_PARAMS borderStroke( -1, LINE_STYLE::SOLID );
3703 STROKE_PARAMS separatorsStroke( -1, LINE_STYLE::SOLID );
3704 std::unique_ptr<PCB_TABLE> table = std::make_unique<PCB_TABLE>( aParent, -1 );
3705
3706 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
3707 {
3708 if( token != T_LEFT )
3709 Expecting( T_LEFT );
3710
3711 token = NextTok();
3712
3713 switch( token )
3714 {
3715 case T_column_count:
3716 table->SetColCount( parseInt( "column count" ) );
3717 NeedRIGHT();
3718 break;
3719
3720 case T_locked:
3721 table->SetLocked( parseBool() );
3722 NeedRIGHT();
3723 break;
3724
3725 case T_angle:
3726 table->SetOrientation( EDA_ANGLE( parseDouble( "table angle" ), DEGREES_T ) );
3727 NeedRIGHT();
3728 break;
3729
3730 case T_layer:
3731 table->SetLayer( parseBoardItemLayer() );
3732 NeedRIGHT();
3733 break;
3734
3735 case T_column_widths:
3736 {
3737 int col = 0;
3738
3739 while( ( token = NextTok() ) != T_RIGHT )
3740 table->SetColWidth( col++, parseBoardUnits() );
3741
3742 break;
3743 }
3744
3745 case T_row_heights:
3746 {
3747 int row = 0;
3748
3749 while( ( token = NextTok() ) != T_RIGHT )
3750 table->SetRowHeight( row++, parseBoardUnits() );
3751
3752 break;
3753 }
3754
3755 case T_cells:
3756 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
3757 {
3758 if( token != T_LEFT )
3759 Expecting( T_LEFT );
3760
3761 token = NextTok();
3762
3763 if( token != T_table_cell )
3764 Expecting( "table_cell" );
3765
3766 table->AddCell( parsePCB_TABLECELL( table.get() ) );
3767 }
3768
3769 break;
3770
3771 case T_border:
3772 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
3773 {
3774 if( token != T_LEFT )
3775 Expecting( T_LEFT );
3776
3777 token = NextTok();
3778
3779 switch( token )
3780 {
3781 case T_external:
3782 table->SetStrokeExternal( parseBool() );
3783 NeedRIGHT();
3784 break;
3785
3786 case T_header:
3787 table->SetStrokeHeader( parseBool() );
3788 NeedRIGHT();
3789 break;
3790
3791 case T_stroke:
3792 {
3793 STROKE_PARAMS_PARSER strokeParser( reader, pcbIUScale.IU_PER_MM );
3794 strokeParser.SyncLineReaderWith( *this );
3795
3796 strokeParser.ParseStroke( borderStroke );
3797 SyncLineReaderWith( strokeParser );
3798
3799 table->SetBorderStroke( borderStroke );
3800 break;
3801 }
3802
3803 default:
3804 Expecting( "external, header or stroke" );
3805 break;
3806 }
3807 }
3808
3809 break;
3810
3811 case T_separators:
3812 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
3813 {
3814 if( token != T_LEFT )
3815 Expecting( T_LEFT );
3816
3817 token = NextTok();
3818
3819 switch( token )
3820 {
3821 case T_rows:
3822 table->SetStrokeRows( parseBool() );
3823 NeedRIGHT();
3824 break;
3825
3826 case T_cols:
3827 table->SetStrokeColumns( parseBool() );
3828 NeedRIGHT();
3829 break;
3830
3831 case T_stroke:
3832 {
3833 STROKE_PARAMS_PARSER strokeParser( reader, pcbIUScale.IU_PER_MM );
3834 strokeParser.SyncLineReaderWith( *this );
3835
3836 strokeParser.ParseStroke( separatorsStroke );
3837 SyncLineReaderWith( strokeParser );
3838
3839 table->SetSeparatorsStroke( separatorsStroke );
3840 break;
3841 }
3842
3843 default:
3844 Expecting( "rows, cols, or stroke" );
3845 break;
3846 }
3847 }
3848
3849 break;
3850
3851 default:
3852 Expecting( "columns, layer, col_widths, row_heights, border, separators, header or "
3853 "cells" );
3854 }
3855 }
3856
3857 if( FOOTPRINT* parentFP = table->GetParentFootprint() )
3858 table->SetOrientation( table->GetOrientation() + parentFP->GetOrientation() );
3859
3860 return table.release();
3861}
3862
3863
3865{
3866 wxCHECK_MSG( CurTok() == T_dimension, nullptr,
3867 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as DIMENSION." ) );
3868
3869 T token;
3870 bool locked = false;
3871 std::unique_ptr<PCB_DIMENSION_BASE> dim;
3872
3873 token = NextTok();
3874
3875 // Free 'locked' token from 6.0/7.0 formats
3876 if( token == T_locked )
3877 {
3878 locked = true;
3879 token = NextTok();
3880 }
3881
3882 // skip value that used to be saved
3883 if( token != T_LEFT )
3884 NeedLEFT();
3885
3886 token = NextTok();
3887
3888 bool isLegacyDimension = false;
3889 bool isStyleKnown = false;
3890
3891 // Old format
3892 if( token == T_width )
3893 {
3894 isLegacyDimension = true;
3895 dim = std::make_unique<PCB_DIM_ALIGNED>( aParent );
3896 dim->SetLineThickness( parseBoardUnits( "dimension width value" ) );
3897 NeedRIGHT();
3898 }
3899 else
3900 {
3901 if( token != T_type )
3902 Expecting( T_type );
3903
3904 switch( NextTok() )
3905 {
3906 case T_aligned: dim = std::make_unique<PCB_DIM_ALIGNED>( aParent ); break;
3907 case T_orthogonal: dim = std::make_unique<PCB_DIM_ORTHOGONAL>( aParent ); break;
3908 case T_leader: dim = std::make_unique<PCB_DIM_LEADER>( aParent ); break;
3909 case T_center: dim = std::make_unique<PCB_DIM_CENTER>( aParent ); break;
3910 case T_radial: dim = std::make_unique<PCB_DIM_RADIAL>( aParent ); break;
3911 default: wxFAIL_MSG( wxT( "Cannot parse unknown dimension type " )
3912 + GetTokenString( CurTok() ) );
3913 }
3914
3915 NeedRIGHT();
3916
3917 // Before parsing further, set default properites for old KiCad file
3918 // versions that didnt have these properties:
3919 dim->SetArrowDirection( DIM_ARROW_DIRECTION::OUTWARD );
3920 }
3921
3922 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
3923 {
3924 if( token != T_LEFT )
3925 Expecting( T_LEFT );
3926
3927 token = NextTok();
3928
3929 switch( token )
3930 {
3931 case T_layer:
3932 dim->SetLayer( parseBoardItemLayer() );
3933 NeedRIGHT();
3934 break;
3935
3936 case T_tstamp:
3937 case T_uuid:
3938 NextTok();
3939 const_cast<KIID&>( dim->m_Uuid ) = CurStrToKIID();
3940 NeedRIGHT();
3941 break;
3942
3943 case T_gr_text:
3944 {
3945 // In old pcb files, when parsing the text we do not yet know
3946 // if the text is kept aligned or not, and its DIM_TEXT_POSITION option.
3947 // Leave the text not aligned for now to read the text angle, and no
3948 // constraint for DIM_TEXT_POSITION in this case.
3949 // It will be set aligned (or not) later
3950 bool is_aligned = dim->GetKeepTextAligned();
3951 DIM_TEXT_POSITION t_dim_pos = dim->GetTextPositionMode();
3952
3953 if( !isStyleKnown )
3954 {
3955 dim->SetTextPositionMode( DIM_TEXT_POSITION::MANUAL );
3956 dim->SetKeepTextAligned( false );
3957 }
3958
3959 parsePCB_TEXT( m_board, dim.get() );
3960
3961 if( isLegacyDimension )
3962 {
3963 EDA_UNITS units = EDA_UNITS::MILLIMETRES;
3964
3965 if( !EDA_UNIT_UTILS::FetchUnitsFromString( dim->GetText(), units ) )
3966 dim->SetAutoUnits( true ); //Not determined => use automatic units
3967
3968 dim->SetUnits( units );
3969 }
3970
3971 if( !isStyleKnown )
3972 {
3973 dim->SetKeepTextAligned( is_aligned );
3974 dim->SetTextPositionMode( t_dim_pos );
3975 }
3976 break;
3977 }
3978
3979 // New format: feature points
3980 case T_pts:
3981 {
3982 VECTOR2I point;
3983
3984 parseXY( &point.x, &point.y );
3985 dim->SetStart( point );
3986 parseXY( &point.x, &point.y );
3987 dim->SetEnd( point );
3988
3989 NeedRIGHT();
3990 break;
3991 }
3992
3993 case T_height:
3994 {
3995 int height = parseBoardUnits( "dimension height value" );
3996 NeedRIGHT();
3997
3998 if( dim->Type() == PCB_DIM_ORTHOGONAL_T || dim->Type() == PCB_DIM_ALIGNED_T )
3999 {
4000 PCB_DIM_ALIGNED* aligned = static_cast<PCB_DIM_ALIGNED*>( dim.get() );
4001 aligned->SetHeight( height );
4002 }
4003
4004 break;
4005 }
4006
4007 case T_leader_length:
4008 {
4009 int length = parseBoardUnits( "leader length value" );
4010 NeedRIGHT();
4011
4012 if( dim->Type() == PCB_DIM_RADIAL_T )
4013 {
4014 PCB_DIM_RADIAL* radial = static_cast<PCB_DIM_RADIAL*>( dim.get() );
4015 radial->SetLeaderLength( length );
4016 }
4017
4018 break;
4019 }
4020
4021 case T_orientation:
4022 {
4023 int orientation = parseInt( "orthogonal dimension orientation" );
4024 NeedRIGHT();
4025
4026 if( dim->Type() == PCB_DIM_ORTHOGONAL_T )
4027 {
4028 PCB_DIM_ORTHOGONAL* ortho = static_cast<PCB_DIM_ORTHOGONAL*>( dim.get() );
4029 orientation = std::clamp( orientation, 0, 1 );
4030 ortho->SetOrientation( static_cast<PCB_DIM_ORTHOGONAL::DIR>( orientation ) );
4031 }
4032
4033 break;
4034 }
4035
4036 case T_format:
4037 {
4038 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
4039 {
4040 switch( token )
4041 {
4042 case T_LEFT:
4043 continue;
4044
4045 case T_prefix:
4046 NeedSYMBOLorNUMBER();
4047 dim->SetPrefix( FromUTF8() );
4048 NeedRIGHT();
4049 break;
4050
4051 case T_suffix:
4052 NeedSYMBOLorNUMBER();
4053 dim->SetSuffix( FromUTF8() );
4054 NeedRIGHT();
4055 break;
4056
4057 case T_units:
4058 {
4059 int mode = parseInt( "dimension units mode" );
4060 mode = std::max( 0, std::min( 4, mode ) );
4061 dim->SetUnitsMode( static_cast<DIM_UNITS_MODE>( mode ) );
4062 NeedRIGHT();
4063 break;
4064 }
4065
4066 case T_units_format:
4067 {
4068 int format = parseInt( "dimension units format" );
4069 format = std::clamp( format, 0, 3 );
4070 dim->SetUnitsFormat( static_cast<DIM_UNITS_FORMAT>( format ) );
4071 NeedRIGHT();
4072 break;
4073 }
4074
4075 case T_precision:
4076 dim->SetPrecision( static_cast<DIM_PRECISION>( parseInt( "dimension precision" ) ) );
4077 NeedRIGHT();
4078 break;
4079
4080 case T_override_value:
4081 NeedSYMBOLorNUMBER();
4082 dim->SetOverrideTextEnabled( true );
4083 dim->SetOverrideText( FromUTF8() );
4084 NeedRIGHT();
4085 break;
4086
4087 case T_suppress_zeroes:
4088 dim->SetSuppressZeroes( parseMaybeAbsentBool( true ) );
4089 break;
4090
4091 default:
4092 std::cerr << "Unknown format token: " << GetTokenString( token ) << std::endl;
4093 Expecting( "prefix, suffix, units, units_format, precision, override_value, "
4094 "suppress_zeroes" );
4095 }
4096 }
4097 break;
4098 }
4099
4100 case T_style:
4101 {
4102 isStyleKnown = true;
4103
4104 // new format: default to keep text aligned off unless token is present
4105 dim->SetKeepTextAligned( false );
4106
4107 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
4108 {
4109 switch( token )
4110 {
4111 case T_LEFT:
4112 continue;
4113
4114 case T_thickness:
4115 dim->SetLineThickness( parseBoardUnits( "extension line thickness value" ) );
4116 NeedRIGHT();
4117 break;
4118
4119 case T_arrow_direction:
4120 {
4121 token = NextTok();
4122
4123 if( token == T_inward )
4124 dim->ChangeArrowDirection( DIM_ARROW_DIRECTION::INWARD );
4125 else if( token == T_outward )
4126 dim->ChangeArrowDirection( DIM_ARROW_DIRECTION::OUTWARD );
4127 else
4128 Expecting( "inward or outward" );
4129
4130 NeedRIGHT();
4131 break;
4132 }
4133 case T_arrow_length:
4134
4135 dim->SetArrowLength( parseBoardUnits( "arrow length value" ) );
4136 NeedRIGHT();
4137 break;
4138
4139 case T_text_position_mode:
4140 {
4141 int mode = parseInt( "text position mode" );
4142 mode = std::max( 0, std::min( 3, mode ) );
4143 dim->SetTextPositionMode( static_cast<DIM_TEXT_POSITION>( mode ) );
4144 NeedRIGHT();
4145 break;
4146 }
4147
4148 case T_extension_height:
4149 {
4150 PCB_DIM_ALIGNED* aligned = dynamic_cast<PCB_DIM_ALIGNED*>( dim.get() );
4151 wxCHECK_MSG( aligned, nullptr, wxT( "Invalid extension_height token" ) );
4152 aligned->SetExtensionHeight( parseBoardUnits( "extension height value" ) );
4153 NeedRIGHT();
4154 break;
4155 }
4156
4157 case T_extension_offset:
4158 dim->SetExtensionOffset( parseBoardUnits( "extension offset value" ) );
4159 NeedRIGHT();
4160 break;
4161
4162 case T_keep_text_aligned:
4163 dim->SetKeepTextAligned( true );
4164 break;
4165
4166 case T_text_frame:
4167 {
4168 wxCHECK_MSG( dim->Type() == PCB_DIM_LEADER_T, nullptr,
4169 wxT( "Invalid text_frame token" ) );
4170
4171 PCB_DIM_LEADER* leader = static_cast<PCB_DIM_LEADER*>( dim.get() );
4172
4173 int textFrame = parseInt( "text frame mode" );
4174 textFrame = std::clamp( textFrame, 0, 3 );
4175 leader->SetTextBorder( static_cast<DIM_TEXT_BORDER>( textFrame ));
4176 NeedRIGHT();
4177 break;
4178 }
4179
4180 default:
4181 Expecting( "thickness, arrow_length, arrow_direction, text_position_mode, "
4182 "extension_height, extension_offset" );
4183 }
4184 }
4185
4186 break;
4187 }
4188
4189 // Old format: feature1 stores a feature line. We only care about the origin.
4190 case T_feature1:
4191 {
4192 NeedLEFT();
4193 token = NextTok();
4194
4195 if( token != T_pts )
4196 Expecting( T_pts );
4197
4198 VECTOR2I point;
4199
4200 parseXY( &point.x, &point.y );
4201 dim->SetStart( point );
4202
4203 parseXY( nullptr, nullptr ); // Ignore second point
4204 NeedRIGHT();
4205 NeedRIGHT();
4206 break;
4207 }
4208
4209 // Old format: feature2 stores a feature line. We only care about the end point.
4210 case T_feature2:
4211 {
4212 NeedLEFT();
4213 token = NextTok();
4214
4215 if( token != T_pts )
4216 Expecting( T_pts );
4217
4218 VECTOR2I point;
4219
4220 parseXY( &point.x, &point.y );
4221 dim->SetEnd( point );
4222
4223 parseXY( nullptr, nullptr ); // Ignore second point
4224
4225 NeedRIGHT();
4226 NeedRIGHT();
4227 break;
4228 }
4229
4230 case T_crossbar:
4231 {
4232 NeedLEFT();
4233 token = NextTok();
4234
4235 if( token == T_pts )
4236 {
4237 // If we have a crossbar, we know we're an old aligned dim
4238 PCB_DIM_ALIGNED* aligned = static_cast<PCB_DIM_ALIGNED*>( dim.get() );
4239
4240 // Old style: calculate height from crossbar
4241 VECTOR2I point1, point2;
4242 parseXY( &point1.x, &point1.y );
4243 parseXY( &point2.x, &point2.y );
4244 aligned->UpdateHeight( point2, point1 ); // Yes, backwards intentionally
4245 NeedRIGHT();
4246 }
4247
4248 NeedRIGHT();
4249 break;
4250 }
4251
4252 // Arrow: no longer saved; no-op
4253 case T_arrow1a:
4254 NeedLEFT();
4255 token = NextTok();
4256
4257 if( token != T_pts )
4258 Expecting( T_pts );
4259
4260 parseXY( nullptr, nullptr );
4261 parseXY( nullptr, nullptr );
4262 NeedRIGHT();
4263 NeedRIGHT();
4264 break;
4265
4266 // Arrow: no longer saved; no-op
4267 case T_arrow1b:
4268 NeedLEFT();
4269 token = NextTok();
4270
4271 if( token != T_pts )
4272 Expecting( T_pts );
4273
4274 parseXY( nullptr, nullptr );
4275 parseXY( nullptr, nullptr );
4276 NeedRIGHT();
4277 NeedRIGHT();
4278 break;
4279
4280 // Arrow: no longer saved; no-op
4281 case T_arrow2a:
4282 NeedLEFT();
4283 token = NextTok();
4284
4285 if( token != T_pts )
4286 Expecting( T_pts );
4287
4288 parseXY( nullptr, nullptr );
4289 parseXY( nullptr, nullptr );
4290 NeedRIGHT();
4291 NeedRIGHT();
4292 break;
4293
4294 // Arrow: no longer saved; no-op
4295 case T_arrow2b:
4296 NeedLEFT();
4297 token = NextTok();
4298
4299 if( token != T_pts )
4300 Expecting( T_pts );
4301
4302 parseXY( nullptr, nullptr );
4303 parseXY( nullptr, nullptr );
4304 NeedRIGHT();
4305 NeedRIGHT();
4306 break;
4307
4308 // Handle (locked yes) from modern times
4309 case T_locked:
4310 {
4311 // Unsure if we ever wrote out (locked) for dimensions, so use maybeAbsent just in case
4312 bool isLocked = parseMaybeAbsentBool( true );
4313 dim->SetLocked( isLocked );
4314 break;
4315 }
4316
4317 default:
4318 Expecting( "layer, tstamp, uuid, gr_text, feature1, feature2, crossbar, arrow1a, "
4319 "arrow1b, arrow2a, or arrow2b" );
4320 }
4321 }
4322
4323 if( locked )
4324 dim->SetLocked( true );
4325
4326 dim->Update();
4327
4328 return dim.release();
4329}
4330
4331
4332FOOTPRINT* PCB_IO_KICAD_SEXPR_PARSER::parseFOOTPRINT( wxArrayString* aInitialComments )
4333{
4334 try
4335 {
4336 return parseFOOTPRINT_unchecked( aInitialComments );
4337 }
4338 catch( const PARSE_ERROR& parse_error )
4339 {
4340 if( m_tooRecent )
4341 throw FUTURE_FORMAT_ERROR( parse_error, GetRequiredVersion() );
4342 else
4343 throw;
4344 }
4345}
4346
4347
4349{
4350 wxCHECK_MSG( CurTok() == T_module || CurTok() == T_footprint, nullptr,
4351 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as FOOTPRINT." ) );
4352
4353 wxString name;
4354 VECTOR2I pt;
4355 T token;
4356 LIB_ID fpid;
4357 int attributes = 0;
4358
4359 std::unique_ptr<FOOTPRINT> footprint = std::make_unique<FOOTPRINT>( m_board );
4360
4361 footprint->SetInitialComments( aInitialComments );
4362
4363 if( m_board )
4364 {
4365 footprint->SetComponentClass( m_board->GetComponentClassManager().GetNoneComponentClass() );
4366 }
4367
4368 token = NextTok();
4369
4370 if( !IsSymbol( token ) && token != T_NUMBER )
4371 Expecting( "symbol|number" );
4372
4373 name = FromUTF8();
4374
4375 if( !name.IsEmpty() && fpid.Parse( name, true ) >= 0 )
4376 {
4377 THROW_IO_ERROR( wxString::Format( _( "Invalid footprint ID in\nfile: %s\nline: %d\n"
4378 "offset: %d." ),
4379 CurSource(), CurLineNumber(), CurOffset() ) );
4380 }
4381
4382 auto checkVersion =
4383 [&]()
4384 {
4386 {
4387 throw FUTURE_FORMAT_ERROR( fmt::format( "{}", m_requiredVersion ),
4389 }
4390 };
4391
4392 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
4393 {
4394 if( token == T_LEFT )
4395 token = NextTok();
4396
4397 switch( token )
4398 {
4399 case T_version:
4400 {
4401 // Theoretically a footprint nested in a PCB could declare its own version, though
4402 // as of writing this comment we don't do that. Just in case, take the greater
4403 // version.
4404 int this_version = parseInt( FromUTF8().mb_str( wxConvUTF8 ) );
4405 NeedRIGHT();
4406 m_requiredVersion = std::max( m_requiredVersion, this_version );
4408 footprint->SetFileFormatVersionAtLoad( this_version );
4409 break;
4410 }
4411
4412 case T_generator:
4413 // We currently ignore the generator when parsing. It is included in the file for manual
4414 // indication of where the footprint came from.
4415 NeedSYMBOL();
4416 NeedRIGHT();
4417 break;
4418
4419 case T_generator_version:
4420 {
4421 NeedSYMBOL();
4422 m_generatorVersion = FromUTF8();
4423 NeedRIGHT();
4424
4425 // If the format includes a generator version, by this point we have enough info to
4426 // do the version check here
4427 checkVersion();
4428
4429 break;
4430 }
4431
4432 case T_locked:
4433 footprint->SetLocked( parseMaybeAbsentBool( true ) );
4434 break;
4435
4436 case T_placed:
4437 footprint->SetIsPlaced( parseMaybeAbsentBool( true ) );
4438 break;
4439
4440 case T_layer:
4441 {
4442 // Footprints can be only on the front side or the back side.
4443 // but because we can find some stupid layer in file, ensure a
4444 // acceptable layer is set for the footprint
4446 footprint->SetLayer( layer == B_Cu ? B_Cu : F_Cu );
4447 NeedRIGHT();
4448 break;
4449 }
4450
4451 case T_tedit:
4452 parseHex();
4453 NeedRIGHT();
4454 break;
4455
4456 case T_tstamp:
4457 case T_uuid:
4458 NextTok();
4459 const_cast<KIID&>( footprint->m_Uuid ) = CurStrToKIID();
4460 NeedRIGHT();
4461 break;
4462
4463 case T_at:
4464 pt.x = parseBoardUnits( "X coordinate" );
4465 pt.y = parseBoardUnits( "Y coordinate" );
4466 footprint->SetPosition( pt );
4467 token = NextTok();
4468
4469 if( token == T_NUMBER )
4470 {
4471 footprint->SetOrientation( EDA_ANGLE( parseDouble(), DEGREES_T ) );
4472 NeedRIGHT();
4473 }
4474 else if( token != T_RIGHT )
4475 {
4476 Expecting( T_RIGHT );
4477 }
4478
4479 break;
4480
4481 case T_descr:
4482 NeedSYMBOLorNUMBER(); // some symbols can be 0508, so a number is also a symbol here
4483 footprint->SetLibDescription( FromUTF8() );
4484 NeedRIGHT();
4485 break;
4486
4487 case T_tags:
4488 NeedSYMBOLorNUMBER(); // some symbols can be 0508, so a number is also a symbol here
4489 footprint->SetKeywords( FromUTF8() );
4490 NeedRIGHT();
4491 break;
4492
4493 case T_property:
4494 {
4495 wxString value;
4496
4497 NeedSYMBOL();
4498 wxString pName = FromUTF8();
4499 NeedSYMBOL();
4500 wxString pValue = FromUTF8();
4501
4502 // Prior to PCB fields, we used to use properties for special values instead of
4503 // using (keyword_example "value")
4504 if( m_requiredVersion < 20230620 )
4505 {
4506 // Skip legacy non-field properties sent from symbols that should not be kept
4507 // in footprints.
4508 if( pName == "ki_keywords" || pName == "ki_locked" )
4509 {
4510 NeedRIGHT();
4511 break;
4512 }
4513
4514 // Description from symbol (not the fooprint library description stored in (descr) )
4515 // used to be stored as a reserved key value
4516 if( pName == "ki_description" )
4517 {
4518 footprint->GetFieldById( DESCRIPTION_FIELD )->SetText( pValue );
4519 NeedRIGHT();
4520 break;
4521 }
4522
4523 // Sheet file and name used to be stored as properties invisible to the user
4524 if( pName == "Sheetfile" || pName == "Sheet file" )
4525 {
4526 footprint->SetSheetfile( pValue );
4527 NeedRIGHT();
4528 break;
4529 }
4530
4531 if( pName == "Sheetname" || pName == "Sheet name" )
4532 {
4533 footprint->SetSheetname( pValue );
4534 NeedRIGHT();
4535 break;
4536 }
4537 }
4538
4539 // 8.0.0rc3 had a bug where these properties were mistakenly added to the footprint as fields,
4540 // this will remove them as fields but still correctly set the footprint filters
4541 if( pName == "ki_fp_filters" )
4542 {
4543 footprint->SetFilters( pValue );
4544
4545 // Use the text effect parsing function because it will handle ki_fp_filters as a property
4546 // with no text effects, but will also handle parsing the text effects. We just drop the effects
4547 // if they're present.
4548 PCB_FIELD ignored = PCB_FIELD( footprint.get(), footprint->GetFieldCount(), pName );
4549
4550 parsePCB_TEXT_effects( &ignored );
4551
4552 // This doesn't currently happen, but if it does, we don't want to leave a dangling pointer in the map
4553 if( auto it = m_fontTextMap.find( &ignored ); it != m_fontTextMap.end() )
4554 m_fontTextMap.erase( it );
4555
4556 break;
4557 }
4558
4559 PCB_FIELD* field = nullptr;
4560
4561 if( footprint->HasFieldByName( pName ) )
4562 {
4563 field = footprint->GetFieldByName( pName );
4564 field->SetText( pValue );
4565 }
4566 else
4567 {
4568 field = footprint->AddField( PCB_FIELD( footprint.get(), footprint->GetFieldCount(),
4569 pName ) );
4570
4571 field->SetText( pValue );
4572 field->SetLayer( footprint->GetLayer() == F_Cu ? F_Fab : B_Fab );
4573
4574 if( m_board ) // can be null when reading a lib
4576 }
4577
4578 // Hide the field by default if it is a legacy field that did not have
4579 // text effects applied, since hide is a negative effect
4580 if( m_requiredVersion < 20230620 )
4581 field->SetVisible( false );
4582 else
4583 field->SetVisible( true );
4584
4585 parsePCB_TEXT_effects( field );
4586 }
4587 break;
4588
4589 case T_path:
4590 NeedSYMBOLorNUMBER(); // Paths can be numerical so a number is also a symbol here
4591 footprint->SetPath( KIID_PATH( FromUTF8() ) );
4592 NeedRIGHT();
4593 break;
4594
4595 case T_sheetname:
4596 NeedSYMBOL();
4597 footprint->SetSheetname( FromUTF8() );
4598 NeedRIGHT();
4599 break;
4600
4601 case T_sheetfile:
4602 NeedSYMBOL();
4603 footprint->SetSheetfile( FromUTF8() );
4604 NeedRIGHT();
4605 break;
4606
4607 case T_autoplace_cost90:
4608 case T_autoplace_cost180:
4609 parseInt( "legacy auto-place cost" );
4610 NeedRIGHT();
4611 break;
4612
4613 case T_private_layers:
4614 {
4615 LSET privateLayers;
4616
4617 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
4618 {
4619 auto it = m_layerIndices.find( CurStr() );
4620
4621 if( it != m_layerIndices.end() )
4622 privateLayers.set( it->second );
4623 else
4624 Expecting( "layer name" );
4625 }
4626
4627 if( m_requiredVersion < 20220427 )
4628 {
4629 privateLayers.set( Edge_Cuts, false );
4630 privateLayers.set( Margin, false );
4631 }
4632
4633 footprint->SetPrivateLayers( privateLayers );
4634 break;
4635 }
4636
4637 case T_net_tie_pad_groups:
4638 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
4639 footprint->AddNetTiePadGroup( CurStr() );
4640
4641 break;
4642
4643 case T_solder_mask_margin:
4644 footprint->SetLocalSolderMaskMargin( parseBoardUnits( "local solder mask margin value" ) );
4645 NeedRIGHT();
4646
4647 // In pre-9.0 files "0" meant inherit.
4648 if( m_requiredVersion <= 20240201 && footprint->GetLocalSolderMaskMargin() == 0 )
4649 footprint->SetLocalSolderMaskMargin( {} );
4650
4651 break;
4652
4653 case T_solder_paste_margin:
4654 footprint->SetLocalSolderPasteMargin( parseBoardUnits( "local solder paste margin value" ) );
4655 NeedRIGHT();
4656
4657 // In pre-9.0 files "0" meant inherit.
4658 if( m_requiredVersion <= 20240201 && footprint->GetLocalSolderPasteMargin() == 0 )
4659 footprint->SetLocalSolderPasteMargin( {} );
4660
4661 break;
4662
4663 case T_solder_paste_ratio: // legacy token
4664 case T_solder_paste_margin_ratio:
4665 footprint->SetLocalSolderPasteMarginRatio( parseDouble( "local solder paste margin ratio value" ) );
4666 NeedRIGHT();
4667
4668 // In pre-9.0 files "0" meant inherit.
4669 if( m_requiredVersion <= 20240201 && footprint->GetLocalSolderPasteMarginRatio() == 0 )
4670 footprint->SetLocalSolderPasteMarginRatio( {} );
4671
4672 break;
4673
4674 case T_clearance:
4675 footprint->SetLocalClearance( parseBoardUnits( "local clearance value" ) );
4676 NeedRIGHT();
4677
4678 // In pre-9.0 files "0" meant inherit.
4679 if( m_requiredVersion <= 20240201 && footprint->GetLocalClearance() == 0 )
4680 footprint->SetLocalClearance( {} );
4681
4682 break;
4683
4684 case T_zone_connect:
4685 footprint->SetLocalZoneConnection((ZONE_CONNECTION) parseInt( "zone connection value" ) );
4686 NeedRIGHT();
4687 break;
4688
4689 case T_thermal_width:
4690 case T_thermal_gap:
4691 // Interestingly, these have never been exposed in the GUI
4692 parseBoardUnits( token );
4693 NeedRIGHT();
4694 break;
4695
4696 case T_attr:
4697 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
4698 {
4699 switch( token )
4700 {
4701 case T_virtual: // legacy token prior to version 20200826
4703 break;
4704
4705 case T_through_hole:
4706 attributes |= FP_THROUGH_HOLE;
4707 break;
4708
4709 case T_smd:
4710 attributes |= FP_SMD;
4711 break;
4712
4713 case T_board_only:
4714 attributes |= FP_BOARD_ONLY;
4715 break;
4716
4717 case T_exclude_from_pos_files:
4718 attributes |= FP_EXCLUDE_FROM_POS_FILES;
4719 break;
4720
4721 case T_exclude_from_bom:
4722 attributes |= FP_EXCLUDE_FROM_BOM;
4723 break;
4724
4725 case T_allow_missing_courtyard:
4726 attributes |= FP_ALLOW_MISSING_COURTYARD;
4727 break;
4728
4729 case T_dnp:
4730 attributes |= FP_DNP;
4731 break;
4732
4733 case T_allow_soldermask_bridges:
4734 attributes |= FP_ALLOW_SOLDERMASK_BRIDGES;
4735 break;
4736
4737 default:
4738 Expecting( "through_hole, smd, virtual, board_only, exclude_from_pos_files, "
4739 "exclude_from_bom or allow_solder_mask_bridges" );
4740 }
4741 }
4742
4743 break;
4744
4745 case T_fp_text:
4746 {
4747 PCB_TEXT* text = parsePCB_TEXT( footprint.get() );
4748
4749 if( PCB_FIELD* field = dynamic_cast<PCB_FIELD*>( text ) )
4750 {
4751 // Fields other than reference and value weren't historically
4752 // stored in fp_texts so we don't need to handle them here
4753 switch( field->GetId() )
4754 {
4755 case REFERENCE_FIELD:
4756 footprint->Reference() = PCB_FIELD( *text, REFERENCE_FIELD );
4757 const_cast<KIID&>( footprint->Reference().m_Uuid ) = text->m_Uuid;
4758 // We don't want to leave a dangling pointer in the map
4759 removeReplaceEntryInFontTextMap( text, &footprint->Reference() );
4760 delete text;
4761 break;
4762
4763 case VALUE_FIELD:
4764 footprint->Value() = PCB_FIELD( *text, VALUE_FIELD );
4765 const_cast<KIID&>( footprint->Value().m_Uuid ) = text->m_Uuid;
4766 removeReplaceEntryInFontTextMap( text, &footprint->Value() );
4767 delete text;
4768 break;
4769 }
4770 }
4771 else
4772 footprint->Add( text, ADD_MODE::APPEND, true );
4773
4774 break;
4775 }
4776
4777 case T_fp_text_box:
4778 {
4779 PCB_TEXTBOX* textbox = parsePCB_TEXTBOX( footprint.get() );
4780 footprint->Add( textbox, ADD_MODE::APPEND, true );
4781 break;
4782 }
4783
4784 case T_table:
4785 {
4786 PCB_TABLE* table = parsePCB_TABLE( footprint.get() );
4787 footprint->Add( table, ADD_MODE::APPEND, true );
4788 break;
4789 }
4790
4791 case T_fp_arc:
4792 case T_fp_circle:
4793 case T_fp_curve:
4794 case T_fp_rect:
4795 case T_fp_line:
4796 case T_fp_poly:
4797 {
4798 PCB_SHAPE* shape = parsePCB_SHAPE( footprint.get() );
4799 footprint->Add( shape, ADD_MODE::APPEND, true );
4800 break;
4801 }
4802
4803 case T_image:
4804 {
4806 footprint->Add( image, ADD_MODE::APPEND, true );
4807 break;
4808 }
4809
4810 case T_dimension:
4811 {
4812 PCB_DIMENSION_BASE* dimension = parseDIMENSION( footprint.get() );
4813 footprint->Add( dimension, ADD_MODE::APPEND, true );
4814 break;
4815 }
4816
4817 case T_pad:
4818 {
4819 PAD* pad = parsePAD( footprint.get() );
4820 footprint->Add( pad, ADD_MODE::APPEND, true );
4821 break;
4822 }
4823
4824 case T_model:
4825 {
4826 FP_3DMODEL* model = parse3DModel();
4827 footprint->Add3DModel( model );
4828 delete model;
4829 break;
4830 }
4831
4832 case T_zone:
4833 {
4834 ZONE* zone = parseZONE( footprint.get() );
4835 footprint->Add( zone, ADD_MODE::APPEND, true );
4836 break;
4837 }
4838
4839 case T_group:
4840 parseGROUP( footprint.get() );
4841 break;
4842
4843 case T_embedded_fonts:
4844 {
4845 footprint->GetEmbeddedFiles()->SetAreFontsEmbedded( parseBool() );
4846 NeedRIGHT();
4847 break;
4848 }
4849
4850 case T_embedded_files:
4851 {
4852 EMBEDDED_FILES_PARSER embeddedFilesParser( reader );
4853 embeddedFilesParser.SyncLineReaderWith( *this );
4854
4855 try
4856 {
4857 embeddedFilesParser.ParseEmbedded( footprint->GetEmbeddedFiles() );
4858 }
4859 catch( const PARSE_ERROR& e )
4860 {
4861 wxLogError( e.What() );
4862 }
4863
4864 SyncLineReaderWith( embeddedFilesParser );
4865 break;
4866 }
4867
4868 case T_component_classes:
4869 {
4870 std::unordered_set<wxString> componentClassNames;
4871
4872 while( ( token = NextTok() ) != T_RIGHT )
4873 {
4874 if( token != T_LEFT )
4875 Expecting( T_LEFT );
4876
4877 if( ( token = NextTok() ) != T_class )
4878 Expecting( T_class );
4879
4880 NeedSYMBOLorNUMBER();
4881 componentClassNames.insert( From_UTF8( CurText() ) );
4882 NeedRIGHT();
4883 }
4884
4885 if( m_board )
4886 {
4887 const COMPONENT_CLASS* componentClass =
4889 componentClassNames );
4890 footprint->SetComponentClass( componentClass );
4891 }
4892
4893 break;
4894 }
4895
4896 default:
4897 Expecting( "at, descr, locked, placed, tedit, tstamp, uuid, "
4898 "autoplace_cost90, autoplace_cost180, attr, clearance, "
4899 "embedded_files, fp_arc, fp_circle, fp_curve, fp_line, fp_poly, "
4900 "fp_rect, fp_text, pad, group, generator, model, path, solder_mask_margin, "
4901 "solder_paste_margin, solder_paste_margin_ratio, tags, thermal_gap, "
4902 "version, zone, zone_connect, or component_classes" );
4903 }
4904 }
4905
4906 // In legacy files the lack of attributes indicated a through-hole component which was by
4907 // default excluded from pos files. However there was a hack to look for SMD pads and
4908 // consider those "mislabeled through-hole components" and therefore include them in place
4909 // files. We probably don't want to get into that game so we'll just include them by
4910 // default and let the user change it if required.
4911 if( m_requiredVersion < 20200826 && attributes == 0 )
4912 attributes |= FP_THROUGH_HOLE;
4913
4915 {
4916 if( footprint->GetKeywords().StartsWith( wxT( "net tie" ) ) )
4917 {
4918 wxString padGroup;
4919
4920 for( PAD* pad : footprint->Pads() )
4921 {
4922 if( !padGroup.IsEmpty() )
4923 padGroup += wxS( ", " );
4924
4925 padGroup += pad->GetNumber();
4926 }
4927
4928 if( !padGroup.IsEmpty() )
4929 footprint->AddNetTiePadGroup( padGroup );
4930 }
4931 }
4932
4933 footprint->SetAttributes( attributes );
4934
4935 footprint->SetFPID( fpid );
4936
4937 return footprint.release();
4938}
4939
4940
4942{
4943 wxCHECK_MSG( CurTok() == T_pad, nullptr,
4944 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as PAD." ) );
4945
4946 VECTOR2I sz;
4947 VECTOR2I pt;
4948 bool foundNet = false;
4949
4950 std::unique_ptr<PAD> pad = std::make_unique<PAD>( aParent );
4951
4952 NeedSYMBOLorNUMBER();
4953 pad->SetNumber( FromUTF8() );
4954
4955 T token = NextTok();
4956
4957 switch( token )
4958 {
4959 case T_thru_hole:
4960 pad->SetAttribute( PAD_ATTRIB::PTH );
4961
4962 // The drill token is usually missing if 0 drill size is specified.
4963 // Emulate it using 1 nm drill size to avoid errors.
4964 // Drill size cannot be set to 0 in newer versions.
4965 pad->SetDrillSize( VECTOR2I( 1, 1 ) );
4966 break;
4967
4968 case T_smd:
4969 pad->SetAttribute( PAD_ATTRIB::SMD );
4970
4971 // Default PAD object is thru hole with drill.
4972 // SMD pads have no hole
4973 pad->SetDrillSize( VECTOR2I( 0, 0 ) );
4974 break;
4975
4976 case T_connect:
4977 pad->SetAttribute( PAD_ATTRIB::CONN );
4978
4979 // Default PAD object is thru hole with drill.
4980 // CONN pads have no hole
4981 pad->SetDrillSize( VECTOR2I( 0, 0 ) );
4982 break;
4983
4984 case T_np_thru_hole:
4985 pad->SetAttribute( PAD_ATTRIB::NPTH );
4986 break;
4987
4988 default:
4989 Expecting( "thru_hole, smd, connect, or np_thru_hole" );
4990 }
4991
4992 token = NextTok();
4993
4994 switch( token )
4995 {
4996 case T_circle:
4997 pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CIRCLE );
4998 break;
4999
5000 case T_rect:
5001 pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::RECTANGLE );
5002 break;
5003
5004 case T_oval:
5005 pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::OVAL );
5006 break;
5007
5008 case T_trapezoid:
5009 pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::TRAPEZOID );
5010 break;
5011
5012 case T_roundrect:
5013 // Note: the shape can be PAD_SHAPE::ROUNDRECT or PAD_SHAPE::CHAMFERED_RECT
5014 // (if chamfer parameters are found later in pad descr.)
5015 pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::ROUNDRECT );
5016 break;
5017
5018 case T_custom:
5019 pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CUSTOM );
5020 break;
5021
5022 default:
5023 Expecting( "circle, rectangle, roundrect, oval, trapezoid or custom" );
5024 }
5025
5026 std::optional<EDA_ANGLE> thermalBrAngleOverride;
5027
5028 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
5029 {
5030 if( token == T_locked )
5031 {
5032 // Pad locking is now a session preference
5033 token = NextTok();
5034 }
5035
5036 if( token != T_LEFT )
5037 Expecting( T_LEFT );
5038
5039 token = NextTok();
5040
5041 switch( token )
5042 {
5043 case T_size:
5044 sz.x = parseBoardUnits( "width value" );
5045 sz.y = parseBoardUnits( "height value" );
5046 pad->SetSize( PADSTACK::ALL_LAYERS, sz );
5047 NeedRIGHT();
5048 break;
5049
5050 case T_at:
5051 pt.x = parseBoardUnits( "X coordinate" );
5052 pt.y = parseBoardUnits( "Y coordinate" );
5053 pad->SetFPRelativePosition( pt );
5054 token = NextTok();
5055
5056 if( token == T_NUMBER )
5057 {
5058 pad->SetOrientation( EDA_ANGLE( parseDouble(), DEGREES_T ) );
5059 NeedRIGHT();
5060 }
5061 else if( token != T_RIGHT )
5062 {
5063 Expecting( ") or angle value" );
5064 }
5065
5066 break;
5067
5068 case T_rect_delta:
5069 {
5071 delta.x = parseBoardUnits( "rectangle delta width" );
5072 delta.y = parseBoardUnits( "rectangle delta height" );
5073 pad->SetDelta( PADSTACK::ALL_LAYERS, delta );
5074 NeedRIGHT();
5075 break;
5076 }
5077
5078 case T_drill:
5079 {
5080 bool haveWidth = false;
5081 VECTOR2I drillSize = pad->GetDrillSize();
5082
5083 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
5084 {
5085 if( token == T_LEFT )
5086 token = NextTok();
5087
5088 switch( token )
5089 {
5090 case T_oval: pad->SetDrillShape( PAD_DRILL_SHAPE::OBLONG ); break;
5091
5092 case T_NUMBER:
5093 {
5094 if( !haveWidth )
5095 {
5096 drillSize.x = parseBoardUnits();
5097
5098 // If height is not defined the width and height are the same.
5099 drillSize.y = drillSize.x;
5100 haveWidth = true;
5101 }
5102 else
5103 {
5104 drillSize.y = parseBoardUnits();
5105 }
5106 }
5107
5108 break;
5109
5110 case T_offset:
5111 pt.x = parseBoardUnits( "drill offset x" );
5112 pt.y = parseBoardUnits( "drill offset y" );
5113 pad->SetOffset( PADSTACK::ALL_LAYERS, pt );
5114 NeedRIGHT();
5115 break;
5116
5117 default:
5118 Expecting( "oval, size, or offset" );
5119 }
5120 }
5121
5122 // This fixes a bug caused by setting the default PAD drill size to a value other
5123 // than 0 used to fix a bunch of debug assertions even though it is defined as a
5124 // through hole pad. Wouldn't a though hole pad with no drill be a surface mount
5125 // pad (or a conn pad which is a smd pad with no solder paste)?
5126 if( pad->GetAttribute() != PAD_ATTRIB::SMD && pad->GetAttribute() != PAD_ATTRIB::CONN )
5127 pad->SetDrillSize( drillSize );
5128 else
5129 pad->SetDrillSize( VECTOR2I( 0, 0 ) );
5130
5131 break;
5132 }
5133
5134 case T_layers:
5135 {
5136 LSET layerMask = parseBoardItemLayersAsMask();
5137 pad->SetLayerSet( layerMask );
5138 break;
5139 }
5140
5141 case T_net:
5142 foundNet = true;
5143
5144 if( ! pad->SetNetCode( getNetCode( parseInt( "net number" ) ), /* aNoAssert */ true ) )
5145 {
5146 wxLogError( _( "Invalid net ID in\nfile: %s\nline: %d offset: %d" ),
5147 CurSource(), CurLineNumber(), CurOffset() );
5148 }
5149
5150 NeedSYMBOLorNUMBER();
5151
5152 // Test validity of the netname in file for netcodes expected having a net name
5153 if( m_board && pad->GetNetCode() > 0 )
5154 {
5155 wxString netName( FromUTF8() );
5156
5157 // Convert overbar syntax from `~...~` to `~{...}`. These were left out of the
5158 // first merge so the version is a bit later.
5159 if( m_requiredVersion < 20210606 )
5160 netName = ConvertToNewOverbarNotation( netName );
5161
5162 if( netName != m_board->FindNet( pad->GetNetCode() )->GetNetname() )
5163 {
5164 pad->SetNetCode( NETINFO_LIST::ORPHANED, /* aNoAssert */ true );
5165 wxLogError( _( "Net name doesn't match ID in\nfile: %s\nline: %d offset: %d" ),
5166 CurSource(), CurLineNumber(), CurOffset() );
5167 }
5168 }
5169
5170 NeedRIGHT();
5171 break;
5172
5173 case T_pinfunction:
5174 NeedSYMBOLorNUMBER();
5175 pad->SetPinFunction( FromUTF8() );
5176 NeedRIGHT();
5177 break;
5178
5179 case T_pintype:
5180 NeedSYMBOLorNUMBER();
5181 pad->SetPinType( FromUTF8() );
5182 NeedRIGHT();
5183 break;
5184
5185 case T_die_length:
5186 pad->SetPadToDieLength( parseBoardUnits( T_die_length ) );
5187 NeedRIGHT();
5188 break;
5189
5190 case T_solder_mask_margin:
5191 pad->SetLocalSolderMaskMargin( parseBoardUnits( "local solder mask margin value" ) );
5192 NeedRIGHT();
5193
5194 // In pre-9.0 files "0" meant inherit.
5195 if( m_requiredVersion <= 20240201 && pad->GetLocalSolderMaskMargin() == 0 )
5196 pad->SetLocalSolderMaskMargin( {} );
5197
5198 break;
5199
5200 case T_solder_paste_margin:
5201 pad->SetLocalSolderPasteMargin( parseBoardUnits( "local solder paste margin value" ) );
5202 NeedRIGHT();
5203
5204 // In pre-9.0 files "0" meant inherit.
5205 if( m_requiredVersion <= 20240201 && pad->GetLocalSolderPasteMargin() == 0 )
5206 pad->SetLocalSolderPasteMargin( {} );
5207
5208 break;
5209
5210 case T_solder_paste_margin_ratio:
5211 pad->SetLocalSolderPasteMarginRatio( parseDouble( "local solder paste margin ratio value" ) );
5212 NeedRIGHT();
5213
5214 // In pre-9.0 files "0" meant inherit.
5215 if( m_requiredVersion <= 20240201 && pad->GetLocalSolderPasteMarginRatio() == 0 )
5216 pad->SetLocalSolderPasteMarginRatio( {} );
5217
5218 break;
5219
5220 case T_clearance:
5221 pad->SetLocalClearance( parseBoardUnits( "local clearance value" ) );
5222 NeedRIGHT();
5223
5224 // In pre-9.0 files "0" meant inherit.
5225 if( m_requiredVersion <= 20240201 && pad->GetLocalClearance() == 0 )
5226 pad->SetLocalClearance( {} );
5227
5228 break;
5229
5230 case T_teardrops:
5231 parseTEARDROP_PARAMETERS( &pad->GetTeardropParams() );
5232 break;
5233
5234 case T_zone_connect:
5235 pad->SetLocalZoneConnection( (ZONE_CONNECTION) parseInt( "zone connection value" ) );
5236 NeedRIGHT();
5237 break;
5238
5239 case T_thermal_width: // legacy token
5240 case T_thermal_bridge_width:
5241 pad->SetThermalSpokeWidth( parseBoardUnits( token ) );
5242 NeedRIGHT();
5243 break;
5244
5245 case T_thermal_bridge_angle:
5246 thermalBrAngleOverride = EDA_ANGLE( parseDouble( "thermal spoke angle" ), DEGREES_T );
5247 NeedRIGHT();
5248 break;
5249
5250
5251 case T_thermal_gap:
5252 pad->SetThermalGap( parseBoardUnits( "thermal relief gap value" ) );
5253 NeedRIGHT();
5254 break;
5255
5256 case T_roundrect_rratio:
5257 pad->SetRoundRectRadiusRatio( PADSTACK::ALL_LAYERS,
5258 parseDouble( "roundrect radius ratio" ) );
5259 NeedRIGHT();
5260 break;
5261
5262 case T_chamfer_ratio:
5263 pad->SetChamferRectRatio( PADSTACK::ALL_LAYERS, parseDouble( "chamfer ratio" ) );
5264
5265 if( pad->GetChamferRectRatio( PADSTACK::ALL_LAYERS ) > 0 )
5266 pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CHAMFERED_RECT );
5267
5268 NeedRIGHT();
5269 break;
5270
5271 case T_chamfer:
5272 {
5273 int chamfers = 0;
5274 bool end_list = false;
5275
5276 while( !end_list )
5277 {
5278 token = NextTok();
5279
5280 switch( token )
5281 {
5282 case T_top_left:
5283 chamfers |= RECT_CHAMFER_TOP_LEFT;
5284 break;
5285
5286 case T_top_right:
5287 chamfers |= RECT_CHAMFER_TOP_RIGHT;
5288 break;
5289
5290 case T_bottom_left:
5291 chamfers |= RECT_CHAMFER_BOTTOM_LEFT;
5292 break;
5293
5294 case T_bottom_right:
5295 chamfers |= RECT_CHAMFER_BOTTOM_RIGHT;
5296 break;
5297
5298 case T_RIGHT:
5299 pad->SetChamferPositions( PADSTACK::ALL_LAYERS, chamfers );
5300 end_list = true;
5301 break;
5302
5303 default:
5304 Expecting( "chamfer_top_left chamfer_top_right chamfer_bottom_left or "
5305 "chamfer_bottom_right" );
5306 }
5307 }
5308
5309 if( pad->GetChamferPositions( PADSTACK::ALL_LAYERS ) != RECT_NO_CHAMFER )
5310 pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CHAMFERED_RECT );
5311
5312 break;
5313 }
5314
5315 case T_property:
5316 while( token != T_RIGHT )
5317 {
5318 token = NextTok();
5319
5320 switch( token )
5321 {
5322 case T_pad_prop_bga: pad->SetProperty( PAD_PROP::BGA ); break;
5323 case T_pad_prop_fiducial_glob: pad->SetProperty( PAD_PROP::FIDUCIAL_GLBL ); break;
5324 case T_pad_prop_fiducial_loc: pad->SetProperty( PAD_PROP::FIDUCIAL_LOCAL ); break;
5325 case T_pad_prop_testpoint: pad->SetProperty( PAD_PROP::TESTPOINT ); break;
5326 case T_pad_prop_castellated: pad->SetProperty( PAD_PROP::CASTELLATED ); break;
5327 case T_pad_prop_heatsink: pad->SetProperty( PAD_PROP::HEATSINK ); break;
5328 case T_pad_prop_mechanical: pad->SetProperty( PAD_PROP::MECHANICAL ); break;
5329 case T_none: pad->SetProperty( PAD_PROP::NONE ); break;
5330 case T_RIGHT: break;
5331
5332 default:
5333#if 0 // Currently: skip unknown property
5334 Expecting( "pad_prop_bga pad_prop_fiducial_glob pad_prop_fiducial_loc"
5335 " pad_prop_heatsink or pad_prop_castellated" );
5336#endif
5337 break;
5338 }
5339 }
5340
5341 break;
5342
5343 case T_options:
5344 parsePAD_option( pad.get() );
5345 break;
5346
5347 case T_padstack:
5348 parsePadstack( pad.get() );
5349 break;
5350
5351 case T_primitives:
5352 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
5353 {
5354 if( token == T_LEFT )
5355 token = NextTok();
5356
5357 switch( token )
5358 {
5359 case T_gr_arc:
5360 case T_gr_line:
5361 case T_gr_circle:
5362 case T_gr_rect:
5363 case T_gr_poly:
5364 case T_gr_curve:
5365 pad->AddPrimitive( PADSTACK::ALL_LAYERS, parsePCB_SHAPE( nullptr ) );
5366 break;
5367
5368 case T_gr_bbox:
5369 {
5370 PCB_SHAPE* numberBox = parsePCB_SHAPE( nullptr );
5371 numberBox->SetIsProxyItem();
5372 pad->AddPrimitive( PADSTACK::ALL_LAYERS, numberBox );
5373 break;
5374 }
5375
5376 case T_gr_vector:
5377 {
5378 PCB_SHAPE* spokeTemplate = parsePCB_SHAPE( nullptr );
5379 spokeTemplate->SetIsProxyItem();
5380 pad->AddPrimitive( PADSTACK::ALL_LAYERS, spokeTemplate );
5381 break;
5382 }
5383
5384 default:
5385 Expecting( "gr_line, gr_arc, gr_circle, gr_curve, gr_rect, gr_bbox or gr_poly" );
5386 break;
5387 }
5388 }
5389
5390 break;
5391
5392 case T_remove_unused_layers:
5393 {
5394 bool remove = parseMaybeAbsentBool( true );
5395 pad->SetRemoveUnconnected( remove );
5396 break;
5397 }
5398
5399 case T_keep_end_layers:
5400 {
5401 bool keep = parseMaybeAbsentBool( true );
5402 pad->SetKeepTopBottom( keep );
5403 break;
5404 }
5405
5406 case T_zone_layer_connections:
5407 {
5408 LSET cuLayers = pad->GetLayerSet() & LSET::AllCuMask();
5409
5410 for( PCB_LAYER_ID layer : cuLayers.Seq() )
5411 pad->SetZoneLayerOverride( layer, ZLO_FORCE_NO_ZONE_CONNECTION );
5412
5413 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
5414 {
5416
5417 if( !IsCopperLayer( layer ) )
5418 Expecting( "copper layer name" );
5419
5420 pad->SetZoneLayerOverride( layer, ZLO_FORCE_FLASHED );
5421 }
5422
5423 break;
5424 }
5425
5426 // Continue to process "(locked)" format which was output during 5.99 development
5427 case T_locked:
5428 // Pad locking is now a session preference
5429 parseMaybeAbsentBool( true );
5430 break;
5431
5432 case T_tstamp:
5433 case T_uuid:
5434 NextTok();
5435 const_cast<KIID&>( pad->m_Uuid ) = CurStrToKIID();
5436 NeedRIGHT();
5437 break;
5438
5439 default:
5440 Expecting( "at, locked, drill, layers, net, die_length, roundrect_rratio, "
5441 "solder_mask_margin, solder_paste_margin, solder_paste_margin_ratio, uuid, "
5442 "clearance, tstamp, primitives, remove_unused_layers, keep_end_layers, "
5443 "pinfunction, pintype, zone_connect, thermal_width, thermal_gap, padstack or "
5444 "teardrops" );
5445 }
5446 }
5447
5448 if( !foundNet )
5449 {
5450 // Make sure default netclass is correctly assigned to pads that don't define a net.
5451 pad->SetNetCode( 0, /* aNoAssert */ true );
5452 }
5453
5454 if( thermalBrAngleOverride )
5455 {
5456 pad->SetThermalSpokeAngle( *thermalBrAngleOverride );
5457 }
5458 else
5459 {
5460 // This is here because custom pad anchor shape isn't known before reading (options
5461 if( pad->GetShape( PADSTACK::ALL_LAYERS ) == PAD_SHAPE::CIRCLE )
5462 {
5463 pad->SetThermalSpokeAngle( ANGLE_45 );
5464 }
5465 else if( pad->GetShape( PADSTACK::ALL_LAYERS ) == PAD_SHAPE::CUSTOM
5466 && pad->GetAnchorPadShape( PADSTACK::ALL_LAYERS ) == PAD_SHAPE::CIRCLE )
5467 {
5468 if( m_requiredVersion <= 20211014 ) // 6.0
5469 pad->SetThermalSpokeAngle( ANGLE_90 );
5470 else
5471 pad->SetThermalSpokeAngle( ANGLE_45 );
5472 }
5473 else
5474 {
5475 pad->SetThermalSpokeAngle( ANGLE_90 );
5476 }
5477 }
5478
5479 if( !pad->CanHaveNumber() )
5480 {
5481 // At some point it was possible to assign a number to aperture pads so we need to clean
5482 // those out here.
5483 pad->SetNumber( wxEmptyString );
5484 }
5485
5486 // Zero-sized pads are likely algorithmically unsafe.
5487 if( pad->GetSizeX() <= 0 || pad->GetSizeY() <= 0 )
5488 {
5489 pad->SetSize( PADSTACK::ALL_LAYERS,
5490 VECTOR2I( pcbIUScale.mmToIU( 0.001 ), pcbIUScale.mmToIU( 0.001 ) ) );
5491
5492 wxLogWarning( _( "Invalid zero-sized pad pinned to %s in\nfile: %s\nline: %d\noffset: %d" ),
5493 wxT( "1µm" ), CurSource(), CurLineNumber(), CurOffset() );
5494 }
5495
5496 return pad.release();
5497}
5498
5499
5501{
5502 // Parse only the (option ...) inside a pad description
5503 for( T token = NextTok(); token != T_RIGHT; token = NextTok() )
5504 {
5505 if( token != T_LEFT )
5506 Expecting( T_LEFT );
5507
5508 token = NextTok();
5509
5510 switch( token )
5511 {
5512 case T_anchor:
5513 token = NextTok();
5514 // Custom shaped pads have a "anchor pad", which is the reference
5515 // for connection calculations.
5516 // Because this is an anchor, only the 2 very basic shapes are managed:
5517 // circle and rect. The default is circle
5518 switch( token )
5519 {
5520 case T_circle: // default
5521 break;
5522
5523 case T_rect:
5524 aPad->SetAnchorPadShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::RECTANGLE );
5525 break;
5526
5527 default:
5528 // Currently, because pad options is a moving target
5529 // just skip unknown keywords
5530 break;
5531 }
5532 NeedRIGHT();
5533 break;
5534
5535 case T_clearance:
5536 token = NextTok();
5537 // Custom shaped pads have a clearance area that is the pad shape
5538 // (like usual pads) or the convex hull of the pad shape.
5539 switch( token )
5540 {
5541 case T_outline:
5543 break;
5544
5545 case T_convexhull:
5547 break;
5548
5549 default:
5550 // Currently, because pad options is a moving target
5551 // just skip unknown keywords
5552 break;
5553 }
5554
5555 NeedRIGHT();
5556 break;
5557
5558 default:
5559 // Currently, because pad options is a moving target
5560 // just skip unknown keywords
5561 while( (token = NextTok() ) != T_RIGHT )
5562 {}
5563
5564 break;
5565 }
5566 }
5567
5568 return true;
5569}
5570
5571
5573{
5574 PADSTACK& padstack = aPad->Padstack();
5575
5576 for( T token = NextTok(); token != T_RIGHT; token = NextTok() )
5577 {
5578 if( token != T_LEFT )
5579 Expecting( T_LEFT );
5580
5581 token = NextTok();
5582
5583 switch( token )
5584 {
5585 case T_mode:
5586 token = NextTok();
5587
5588 switch( token )
5589 {
5590 case T_front_inner_back:
5592 break;
5593
5594 case T_custom:
5595 padstack.SetMode( PADSTACK::MODE::CUSTOM );
5596 break;
5597
5598 default:
5599 Expecting( "front_inner_back or custom" );
5600 }
5601
5602 NeedRIGHT();
5603 break;
5604
5605 case T_layer:
5606 {
5607 NextTok();
5608 PCB_LAYER_ID curLayer = UNDEFINED_LAYER;
5609
5610 if( curText == "Inner" )
5611 {
5612 if( padstack.Mode() != PADSTACK::MODE::FRONT_INNER_BACK )
5613 {
5614 THROW_IO_ERROR( wxString::Format( _( "Invalid padstack layer in\nfile: %s\n"
5615 "line: %d\noffset: %d." ),
5616 CurSource(), CurLineNumber(), CurOffset() ) );
5617 }
5618
5619 curLayer = PADSTACK::INNER_LAYERS;
5620 }
5621 else
5622 {
5623 curLayer = lookUpLayer( m_layerIndices );
5624 }
5625
5626 if( !IsCopperLayer( curLayer ) )
5627 {
5628 wxString error;
5629 error.Printf( _( "Invalid padstack layer '%s' in file '%s' at line %d, offset %d." ),
5630 curText, CurSource().GetData(), CurLineNumber(), CurOffset() );
5631 THROW_IO_ERROR( error );
5632 }
5633
5634 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
5635 {
5636 if( token != T_LEFT )
5637 Expecting( T_LEFT );
5638
5639 token = NextTok();
5640
5641 switch( token )
5642 {
5643 case T_shape:
5644 token = NextTok();
5645
5646 switch( token )
5647 {
5648 case T_circle:
5649 aPad->SetShape( curLayer, PAD_SHAPE::CIRCLE );
5650 break;
5651
5652 case T_rect:
5653 aPad->SetShape( curLayer, PAD_SHAPE::RECTANGLE );
5654 break;
5655
5656 case T_oval:
5657 aPad->SetShape( curLayer, PAD_SHAPE::OVAL );
5658 break;
5659
5660 case T_trapezoid:
5661 aPad->SetShape( curLayer, PAD_SHAPE::TRAPEZOID );
5662 break;
5663
5664 case T_roundrect:
5665 // Note: the shape can be PAD_SHAPE::ROUNDRECT or PAD_SHAPE::CHAMFERED_RECT
5666 // (if chamfer parameters are found later in pad descr.)
5667 aPad->SetShape( curLayer, PAD_SHAPE::ROUNDRECT );
5668 break;
5669
5670 case T_custom:
5671 aPad->SetShape( curLayer, PAD_SHAPE::CUSTOM );
5672 break;
5673
5674 default:
5675 Expecting( "circle, rectangle, roundrect, oval, trapezoid or custom" );
5676 }
5677
5678 NeedRIGHT();
5679 break;
5680
5681 case T_size:
5682 {
5683 VECTOR2I sz;
5684 sz.x = parseBoardUnits( "width value" );
5685 sz.y = parseBoardUnits( "height value" );
5686 aPad->SetSize( curLayer, sz );
5687 NeedRIGHT();
5688 break;
5689 }
5690
5691 case T_offset:
5692 {
5693 VECTOR2I pt;
5694 pt.x = parseBoardUnits( "drill offset x" );
5695 pt.y = parseBoardUnits( "drill offset y" );
5696 aPad->SetOffset( curLayer, pt );
5697 NeedRIGHT();
5698 break;
5699 }
5700
5701 case T_rect_delta:
5702 {
5704 delta.x = parseBoardUnits( "rectangle delta width" );
5705 delta.y = parseBoardUnits( "rectangle delta height" );
5706 aPad->SetDelta( curLayer, delta );
5707 NeedRIGHT();
5708 break;
5709 }
5710
5711 case T_roundrect_rratio:
5712 aPad->SetRoundRectRadiusRatio( curLayer,
5713 parseDouble( "roundrect radius ratio" ) );
5714 NeedRIGHT();
5715 break;
5716
5717 case T_chamfer_ratio:
5718 {
5719 double ratio = parseDouble( "chamfer ratio" );
5720 aPad->SetChamferRectRatio( curLayer, ratio );
5721
5722 if( ratio > 0 )
5723 aPad->SetShape( curLayer, PAD_SHAPE::CHAMFERED_RECT );
5724
5725 NeedRIGHT();
5726 break;
5727 }
5728
5729 case T_chamfer:
5730 {
5731 int chamfers = 0;
5732 bool end_list = false;
5733
5734 while( !end_list )
5735 {
5736 token = NextTok();
5737
5738 switch( token )
5739 {
5740 case T_top_left:
5741 chamfers |= RECT_CHAMFER_TOP_LEFT;
5742 break;
5743
5744 case T_top_right:
5745 chamfers |= RECT_CHAMFER_TOP_RIGHT;
5746 break;
5747
5748 case T_bottom_left:
5749 chamfers |= RECT_CHAMFER_BOTTOM_LEFT;
5750 break;
5751
5752 case T_bottom_right:
5753 chamfers |= RECT_CHAMFER_BOTTOM_RIGHT;
5754 break;
5755
5756 case T_RIGHT:
5757 aPad->SetChamferPositions( curLayer, chamfers );
5758 end_list = true;
5759 break;
5760
5761 default:
5762 Expecting( "chamfer_top_left chamfer_top_right chamfer_bottom_left or "
5763 "chamfer_bottom_right" );
5764 }
5765 }
5766
5767 if( end_list && chamfers != RECT_NO_CHAMFER )
5768 aPad->SetShape( curLayer, PAD_SHAPE::CHAMFERED_RECT );
5769
5770 break;
5771 }
5772
5773 case T_thermal_bridge_width:
5774 padstack.ThermalSpokeWidth( curLayer ) =
5775 parseBoardUnits( "thermal relief spoke width" );
5776 NeedRIGHT();
5777 break;
5778
5779 case T_thermal_gap:
5780 padstack.ThermalGap( curLayer ) = parseBoardUnits( "thermal relief gap value" );
5781 NeedRIGHT();
5782 break;
5783
5784 case T_thermal_bridge_angle:
5785 padstack.SetThermalSpokeAngle(
5786 EDA_ANGLE( parseDouble( "thermal spoke angle" ), DEGREES_T ) );
5787 NeedRIGHT();
5788 break;
5789
5790 case T_zone_connect:
5791 padstack.ZoneConnection( curLayer ) = magic_enum::enum_cast<ZONE_CONNECTION>(
5792 parseInt( "zone connection value" ) );
5793 NeedRIGHT();
5794 break;
5795
5796 case T_clearance:
5797 padstack.Clearance( curLayer ) = parseBoardUnits( "local clearance value" );
5798 NeedRIGHT();
5799 break;
5800
5801 case T_tenting:
5802 parseTenting( padstack );
5803 break;
5804
5805 // TODO: refactor parsePAD_options to work on padstacks too
5806 case T_options:
5807 {
5808 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
5809 {
5810 if( token != T_LEFT )
5811 Expecting( T_LEFT );
5812
5813 token = NextTok();
5814
5815 switch( token )
5816 {
5817 case T_anchor:
5818 token = NextTok();
5819 // Custom shaped pads have a "anchor pad", which is the reference
5820 // for connection calculations.
5821 // Because this is an anchor, only the 2 very basic shapes are managed:
5822 // circle and rect. The default is circle
5823 switch( token )
5824 {
5825 case T_circle: // default
5826 break;
5827
5828 case T_rect:
5829 padstack.SetAnchorShape( PAD_SHAPE::RECTANGLE, curLayer );
5830 break;
5831
5832 default:
5833 // Currently, because pad options is a moving target
5834 // just skip unknown keywords
5835 break;
5836 }
5837 NeedRIGHT();
5838 break;
5839
5840 case T_clearance:
5841 token = NextTok();
5842 // TODO: m_customShapeInZoneMode is not per-layer at the moment
5843 NeedRIGHT();
5844 break;
5845
5846 default:
5847 // Currently, because pad options is a moving target
5848 // just skip unknown keywords
5849 while( ( token = NextTok() ) != T_RIGHT )
5850 {
5851 }
5852
5853 break;
5854 }
5855 }
5856
5857 break;
5858 }
5859
5860 // TODO: deduplicate with non-padstack parser
5861 case T_primitives:
5862 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
5863 {
5864 if( token == T_LEFT )
5865 token = NextTok();
5866
5867 switch( token )
5868 {
5869 case T_gr_arc:
5870 case T_gr_line:
5871 case T_gr_circle:
5872 case T_gr_rect:
5873 case T_gr_poly:
5874 case T_gr_curve:
5875 padstack.AddPrimitive( parsePCB_SHAPE( nullptr ), curLayer );
5876 break;
5877
5878 case T_gr_bbox:
5879 {
5880 PCB_SHAPE* numberBox = parsePCB_SHAPE( nullptr );
5881 numberBox->SetIsProxyItem();
5882 padstack.AddPrimitive( numberBox, curLayer );
5883 break;
5884 }
5885
5886 case T_gr_vector:
5887 {
5888 PCB_SHAPE* spokeTemplate = parsePCB_SHAPE( nullptr );
5889 spokeTemplate->SetIsProxyItem();
5890 padstack.AddPrimitive( spokeTemplate, curLayer );
5891 break;
5892 }
5893
5894 default:
5895 Expecting( "gr_line, gr_arc, gr_circle, gr_curve, gr_rect, gr_bbox or gr_poly" );
5896 break;
5897 }
5898 }
5899
5900 break;
5901
5902 default:
5903 // Not strict-parsing padstack layers yet
5904 continue;
5905 }
5906 }
5907
5908 break;
5909 }
5910
5911 default:
5912 Expecting( "mode or layer" );
5913 break;
5914 }
5915 }
5916}
5917
5918
5920{
5921 T token;
5922
5923 while( ( token = NextTok() ) != T_RIGHT )
5924 {
5925 // This token is the Uuid of the item in the group.
5926 // Since groups are serialized at the end of the file/footprint, the Uuid should already
5927 // have been seen and exist in the board.
5928 KIID uuid( CurStr() );
5929 aGroupInfo.memberUuids.push_back( uuid );
5930 }
5931}
5932
5933
5935{
5936 wxCHECK_RET( CurTok() == T_group,
5937 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as PCB_GROUP." ) );
5938
5939 T token;
5940
5941 m_groupInfos.push_back( GROUP_INFO() );
5942 GROUP_INFO& groupInfo = m_groupInfos.back();
5943 groupInfo.parent = aParent;
5944
5945 while( ( token = NextTok() ) != T_LEFT )
5946 {
5947 if( token == T_STRING )
5948 groupInfo.name = FromUTF8();
5949 else if( token == T_locked )
5950 groupInfo.locked = true;
5951 else
5952 Expecting( "group name or locked" );
5953 }
5954
5955 for( ; token != T_RIGHT; token = NextTok() )
5956 {
5957 if( token != T_LEFT )
5958 Expecting( T_LEFT );
5959
5960 token = NextTok();
5961
5962 switch( token )
5963 {
5964 // From formats [20200811, 20231215), 'id' was used instead of 'uuid'
5965 case T_id:
5966 case T_uuid:
5967 NextTok();
5968 groupInfo.uuid = CurStrToKIID();
5969 NeedRIGHT();
5970 break;
5971
5972 case T_locked:
5973 groupInfo.locked = parseBool();
5974 NeedRIGHT();
5975 break;
5976
5977 case T_members:
5978 {
5979 parseGROUP_members( groupInfo );
5980 break;
5981 }
5982
5983 default:
5984 Expecting( "uuid, locked, or members" );
5985 }
5986 }
5987}
5988
5989
5991{
5992 wxCHECK_RET( CurTok() == T_generated, wxT( "Cannot parse " ) + GetTokenString( CurTok() )
5993 + wxT( " as PCB_GENERATOR." ) );
5994
5995 T token;
5996
5997 m_generatorInfos.push_back( GENERATOR_INFO() );
5998 GENERATOR_INFO& genInfo = m_generatorInfos.back();
5999
6000 genInfo.layer = F_Cu;
6001 genInfo.parent = aParent;
6003
6004 NeedLEFT();
6005 token = NextTok();
6006
6007 // For formats [20231007, 20231215), 'id' was used instead of 'uuid'
6008 if( token != T_uuid && token != T_id )
6009 Expecting( T_uuid );
6010
6011 NextTok();
6012 genInfo.uuid = CurStrToKIID();
6013 NeedRIGHT();
6014
6015 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
6016 {
6017 if( token != T_LEFT )
6018 Expecting( T_LEFT );
6019
6020 token = NextTok();
6021
6022 switch( token )
6023 {
6024 case T_type:
6025 NeedSYMBOL();
6026 genInfo.genType = FromUTF8();
6027 NeedRIGHT();
6028 break;
6029
6030 case T_name:
6031 NeedSYMBOL();
6032 genInfo.name = FromUTF8();
6033 NeedRIGHT();
6034 break;
6035
6036 case T_locked:
6037 token = NextTok();
6038 genInfo.locked = token == T_yes;
6039 NeedRIGHT();
6040 break;
6041
6042 case T_layer:
6043 genInfo.layer = parseBoardItemLayer();
6044 NeedRIGHT();
6045 break;
6046
6047 case T_members: parseGROUP_members( genInfo ); break;
6048
6049 default:
6050 {
6051 wxString pName = FromUTF8();
6052 T tok1 = NextTok();
6053
6054 switch( tok1 )
6055 {
6056 case T_yes:
6057 {
6058 genInfo.properties.emplace( pName, wxAny( true ) );
6059 NeedRIGHT();
6060 break;
6061 }
6062 case T_no:
6063 {
6064 genInfo.properties.emplace( pName, wxAny( false ) );