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 The 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
29
30#include "layer_ids.h"
31#include <cerrno>
32#include <charconv>
33#include <confirm.h>
34#include <macros.h>
35#include <fmt/format.h>
36#include <title_block.h>
37#include <trigo.h>
38
39#include <board.h>
42#include <font/fontconfig.h>
43#include <magic_enum.hpp>
44#include <pcb_dimension.h>
45#include <pcb_shape.h>
46#include <pcb_reference_image.h>
47#include <pcb_barcode.h>
48#include <pcb_group.h>
49#include <pcb_generator.h>
50#include <pcb_point.h>
51#include <pcb_target.h>
52#include <pcb_track.h>
53#include <pcb_textbox.h>
54#include <pcb_table.h>
55#include <pad.h>
56#include <generators_mgr.h>
57#include <zone.h>
58#include <footprint.h>
60#include <font/font.h>
61#include <core/ignore.h>
62#include <netclass.h>
65#include <pcb_plot_params.h>
66#include <zones.h>
68#include <convert_basic_shapes_to_polygon.h> // for RECT_CHAMFER_POSITIONS definition
69#include <math/util.h> // KiROUND, Clamp
70#include <string_utils.h>
72#include <wx/log.h>
73#include <progress_reporter.h>
75#include <pgm_base.h>
76
77// For some reason wxWidgets is built with wxUSE_BASE64 unset so expose the wxWidgets
78// base64 code. Needed for PCB_REFERENCE_IMAGE
79#define wxUSE_BASE64 1
80#include <wx/base64.h>
81#include <wx/log.h>
82#include <wx/mstream.h>
83
84// We currently represent board units as integers. Any values that are
85// larger or smaller than those board units represent undefined behavior for
86// the system. We limit values to the largest usable
87// i.e. std::numeric_limits<int>::max().
88// However to avoid issues in comparisons, use a slightly smaller value
89// Note also the usable limits are much smaller to avoid overflows in intermediate
90// calculations.
91constexpr double INT_LIMIT = std::numeric_limits<int>::max() - 10;
92
93using namespace PCB_KEYS_T;
94
95
97{
100 m_tooRecent = false;
102 m_layerIndices.clear();
103 m_layerMasks.clear();
104 m_resetKIIDMap.clear();
105
106 // Add untranslated default (i.e. English) layernames.
107 // Some may be overridden later if parsing a board rather than a footprint.
108 // The English name will survive if parsing only a footprint.
109 for( int layer = 0; layer < PCB_LAYER_ID_COUNT; ++layer )
110 {
111 std::string untranslated = TO_UTF8( LSET::Name( PCB_LAYER_ID( layer ) ) );
112
113 m_layerIndices[untranslated] = PCB_LAYER_ID( layer );
114 m_layerMasks[untranslated] = LSET( { PCB_LAYER_ID( layer ) } );
115 }
116
117 m_layerMasks[ "*.Cu" ] = LSET::AllCuMask();
118 m_layerMasks[ "*In.Cu" ] = LSET::InternalCuMask();
119 m_layerMasks[ "F&B.Cu" ] = LSET( { F_Cu, B_Cu } );
120 m_layerMasks[ "*.Adhes" ] = LSET( { B_Adhes, F_Adhes } );
121 m_layerMasks[ "*.Paste" ] = LSET( { B_Paste, F_Paste } );
122 m_layerMasks[ "*.Mask" ] = LSET( { B_Mask, F_Mask } );
123 m_layerMasks[ "*.SilkS" ] = LSET( { B_SilkS, F_SilkS } );
124 m_layerMasks[ "*.Fab" ] = LSET( { B_Fab, F_Fab } );
125 m_layerMasks[ "*.CrtYd" ] = LSET( { B_CrtYd, F_CrtYd } );
126
127 // This is for the first pretty & *.kicad_pcb formats, which had
128 // Inner1_Cu - Inner14_Cu with the numbering sequence
129 // reversed from the subsequent format's In1_Cu - In30_Cu numbering scheme.
130 // The newer format brought in an additional 16 Cu layers and flipped the cu stack but
131 // kept the gap between one of the outside layers and the last cu internal.
132
133 for( int i=1; i<=14; ++i )
134 {
135 std::string key = StrPrintf( "Inner%d.Cu", i );
136
137 m_layerMasks[key] = LSET( { PCB_LAYER_ID( In15_Cu - 2 * i ) } );
138 }
139}
140
141
143{
145 {
146 TIME_PT curTime = CLOCK::now();
147 unsigned curLine = reader->LineNumber();
148 auto delta = std::chrono::duration_cast<TIMEOUT>( curTime - m_lastProgressTime );
149
150 if( delta > std::chrono::milliseconds( 250 ) )
151 {
152 m_progressReporter->SetCurrentProgress( ( (double) curLine )
153 / std::max( 1U, m_lineCount ) );
154
155 if( !m_progressReporter->KeepRefreshing() )
156 THROW_IO_ERROR( _( "Open canceled by user." ) );
157
158 m_lastProgressTime = curTime;
159 }
160 }
161}
162
163
165{
166 int curr_level = 0;
167 T token;
168
169 while( ( token = NextTok() ) != T_EOF )
170 {
171 if( token == T_LEFT )
172 curr_level--;
173
174 if( token == T_RIGHT )
175 {
176 curr_level++;
177
178 if( curr_level > 0 )
179 return;
180 }
181 }
182}
183
184
186{
187 // Add aValue in netcode mapping (m_netCodes) at index aNetCode
188 // ensure there is room in m_netCodes for that, and add room if needed.
189
190 if( (int)m_netCodes.size() <= aIndex )
191 m_netCodes.resize( static_cast<std::size_t>( aIndex ) + 1 );
192
193 m_netCodes[aIndex] = aValue;
194}
195
196
198{
199 // There should be no major rounding issues here, since the values in
200 // the file are in mm and get converted to nano-meters.
201 // See test program tools/test-nm-biu-to-ascii-mm-round-tripping.cpp
202 // to confirm or experiment. Use a similar strategy in both places, here
203 // and in the test program. Make that program with:
204 // $ make test-nm-biu-to-ascii-mm-round-tripping
205 auto retval = parseDouble() * pcbIUScale.IU_PER_MM;
206
207 // N.B. we currently represent board units as integers. Any values that are
208 // larger or smaller than those board units represent undefined behavior for
209 // the system. We limit values to the largest that is visible on the screen
210 return KiROUND( std::clamp( retval, -INT_LIMIT, INT_LIMIT ) );
211}
212
213
215{
216 auto retval = parseDouble( aExpected ) * pcbIUScale.IU_PER_MM;
217
218 // N.B. we currently represent board units as integers. Any values that are
219 // larger or smaller than those board units represent undefined behavior for
220 // the system. We limit values to the largest that is visible on the screen
221 return KiROUND( std::clamp( retval, -INT_LIMIT, INT_LIMIT ) );
222}
223
224
226{
227 T token = NextTok();
228
229 if( token == T_yes )
230 return true;
231 else if( token == T_no )
232 return false;
233 else
234 Expecting( "yes or no" );
235
236 return false;
237}
238
239
241{
242 T token = NextTok();
243
244 if( token == T_yes )
245 return true;
246 else if( token == T_no )
247 return false;
248 else if( token == T_none )
249 return std::nullopt;
250 else
251 Expecting( "yes, no or none" );
252
253 return false;
254}
255
256
257/*
258 * e.g. "hide", "hide)", "(hide yes)"
259 */
261{
262 bool ret = aDefaultValue;
263
264 if( PrevTok() == T_LEFT )
265 {
266 T token = NextTok();
267
268 // "hide)"
269 if( static_cast<int>( token ) == DSN_RIGHT )
270 return aDefaultValue;
271
272 if( token == T_yes || token == T_true )
273 ret = true;
274 else if( token == T_no || token == T_false )
275 ret = false;
276 else
277 Expecting( "yes or no" );
278
279 NeedRIGHT();
280 }
281 else
282 {
283 // "hide"
284 return aDefaultValue;
285 }
286
287 return ret;
288}
289
290
292{
293 int year, month, day;
294
295 year = m_requiredVersion / 10000;
296 month = ( m_requiredVersion / 100 ) - ( year * 100 );
297 day = m_requiredVersion - ( year * 10000 ) - ( month * 100 );
298
299 // wx throws an assertion, not a catchable exception, when the date is invalid.
300 // User input shouldn't give wx asserts, so check manually and throw a proper
301 // error instead
302 if( day <= 0 || month <= 0 || month > 12 ||
303 day > wxDateTime::GetNumberOfDays( (wxDateTime::Month)( month - 1 ), year ) )
304 {
305 wxString err;
306 err.Printf( _( "Cannot interpret date code %d" ), m_requiredVersion );
307 THROW_PARSE_ERROR( err, CurSource(), CurLine(), CurLineNumber(), CurOffset() );
308 }
309
310 wxDateTime date( day, (wxDateTime::Month)( month - 1 ), year, 0, 0, 0, 0 );
311 return date.FormatDate();
312}
313
314
316{
317 if( CurTok() != T_LEFT )
318 NeedLEFT();
319
320 VECTOR2I pt;
321 T token = NextTok();
322
323 if( token != T_xy )
324 Expecting( T_xy );
325
326 pt.x = parseBoardUnits( "X coordinate" );
327 pt.y = parseBoardUnits( "Y coordinate" );
328
329 NeedRIGHT();
330
331 return pt;
332}
333
334
336{
337 if( CurTok() != T_LEFT )
338 NeedLEFT();
339
340 T token = NextTok();
341
342 switch( token )
343 {
344 case T_xy:
345 {
346 int x = parseBoardUnits( "X coordinate" );
347 int y = parseBoardUnits( "Y coordinate" );
348
349 NeedRIGHT();
350
351 aPoly.Append( x, y );
352 break;
353 }
354 case T_arc:
355 {
356 bool has_start = false;
357 bool has_mid = false;
358 bool has_end = false;
359
360 VECTOR2I arc_start, arc_mid, arc_end;
361
362 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
363 {
364 if( token != T_LEFT )
365 Expecting( T_LEFT );
366
367 token = NextTok();
368
369 switch( token )
370 {
371 case T_start:
372 arc_start.x = parseBoardUnits( "start x" );
373 arc_start.y = parseBoardUnits( "start y" );
374 has_start = true;
375 break;
376
377 case T_mid:
378 arc_mid.x = parseBoardUnits( "mid x" );
379 arc_mid.y = parseBoardUnits( "mid y" );
380 has_mid = true;
381 break;
382
383 case T_end:
384 arc_end.x = parseBoardUnits( "end x" );
385 arc_end.y = parseBoardUnits( "end y" );
386 has_end = true;
387 break;
388
389 default:
390 Expecting( "start, mid or end" );
391 }
392
393 NeedRIGHT();
394 }
395
396 if( !has_start )
397 Expecting( "start" );
398
399 if( !has_mid )
400 Expecting( "mid" );
401
402 if( !has_end )
403 Expecting( "end" );
404
405 SHAPE_ARC arc( arc_start, arc_mid, arc_end, 0 );
406
407 aPoly.Append( arc );
408
409 if( token != T_RIGHT )
410 Expecting( T_RIGHT );
411
412 break;
413 }
414 default:
415 Expecting( "xy or arc" );
416 }
417}
418
419
421{
422 VECTOR2I pt = parseXY();
423
424 if( aX )
425 *aX = pt.x;
426
427 if( aY )
428 *aY = pt.y;
429}
430
431
432void PCB_IO_KICAD_SEXPR_PARSER::parseMargins( int& aLeft, int& aTop, int& aRight, int& aBottom )
433{
434 aLeft = parseBoardUnits( "left margin" );
435 aTop = parseBoardUnits( "top margin" );
436 aRight = parseBoardUnits( "right margin" );
437 aBottom = parseBoardUnits( "bottom margin" );
438}
439
440
442{
443 wxString pName;
444 wxString pValue;
445
446 NeedSYMBOL();
447 pName = FromUTF8();
448 NeedSYMBOL();
449 pValue = FromUTF8();
450 NeedRIGHT();
451
452 return { pName, pValue };
453}
454
455
457{
458 tdParams->m_Enabled = false;
459 tdParams->m_AllowUseTwoTracks = false;
460 tdParams->m_TdOnPadsInZones = true;
461
462 for( T token = NextTok(); token != T_RIGHT; token = NextTok() )
463 {
464 if( token == T_LEFT )
465 token = NextTok();
466
467 switch( token )
468 {
469 case T_enabled:
470 tdParams->m_Enabled = parseMaybeAbsentBool( true );
471 break;
472
473 case T_allow_two_segments:
474 tdParams->m_AllowUseTwoTracks = parseMaybeAbsentBool( true );
475 break;
476
477 case T_prefer_zone_connections:
478 tdParams->m_TdOnPadsInZones = !parseMaybeAbsentBool( false );
479 break;
480
481 case T_best_length_ratio:
482 tdParams->m_BestLengthRatio = parseDouble( "teardrop best length ratio" );
483 NeedRIGHT();
484 break;
485
486 case T_max_length:
487 tdParams->m_TdMaxLen = parseBoardUnits( "teardrop max length" );
488 NeedRIGHT();
489 break;
490
491 case T_best_width_ratio:
492 tdParams->m_BestWidthRatio = parseDouble( "teardrop best width ratio" );
493 NeedRIGHT();
494 break;
495
496 case T_max_width:
497 tdParams->m_TdMaxWidth = parseBoardUnits( "teardrop max width" );
498 NeedRIGHT();
499 break;
500
501 // Legacy token
502 case T_curve_points:
503 tdParams->m_CurvedEdges = parseInt( "teardrop curve points count" ) > 0;
504 NeedRIGHT();
505 break;
506
507 case T_curved_edges:
508 tdParams->m_CurvedEdges = parseMaybeAbsentBool( true );
509 break;
510
511 case T_filter_ratio:
512 tdParams->m_WidthtoSizeFilterRatio = parseDouble( "teardrop filter ratio" );
513 NeedRIGHT();
514 break;
515
516 default:
517 Expecting( "enabled, allow_two_segments, prefer_zone_connections, best_length_ratio, "
518 "max_length, best_width_ratio, max_width, curve_points or filter_ratio" );
519 }
520 }
521}
522
523
525{
526 wxCHECK_RET( CurTok() == T_effects,
527 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as EDA_TEXT." ) );
528
529 // These are not written out if center/center and/or no mirror,
530 // so we have to make sure we start that way.
531 // (these parameters will be set in T_justify section, when existing)
534 aText->SetMirrored( false );
535
536 // In version 20210606 the notation for overbars was changed from `~...~` to `~{...}`.
537 // We need to convert the old syntax to the new one.
538 if( m_requiredVersion < 20210606 )
539 aText->SetText( ConvertToNewOverbarNotation( aText->GetText() ) );
540
541 T token;
542
543 // Prior to v5.0 text size was omitted from file format if equal to 60mils
544 // Now, it is always explicitly written to file
545 bool foundTextSize = false;
546
547 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
548 {
549 if( token == T_LEFT )
550 token = NextTok();
551
552 switch( token )
553 {
554 case T_font:
555 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
556 {
557 if( token == T_LEFT )
558 continue;
559
560 switch( token )
561 {
562 case T_face:
563 NeedSYMBOL();
564 aText->SetUnresolvedFontName( FromUTF8() );
565 NeedRIGHT();
566 break;
567
568 case T_size:
569 {
570 VECTOR2I sz;
571 sz.y = parseBoardUnits( "text height" );
572 sz.x = parseBoardUnits( "text width" );
573 aText->SetTextSize( sz );
574 NeedRIGHT();
575
576 foundTextSize = true;
577 break;
578 }
579
580 case T_line_spacing:
581 aText->SetLineSpacing( parseDouble( "line spacing" ) );
582 NeedRIGHT();
583 break;
584
585 case T_thickness:
586 aText->SetTextThickness( parseBoardUnits( "text thickness" ) );
587 NeedRIGHT();
588 break;
589
590 case T_bold:
591 aText->SetBoldFlag( parseMaybeAbsentBool( true ) );
592 break;
593
594 case T_italic:
595 aText->SetItalicFlag( parseMaybeAbsentBool( true ) );
596 break;
597
598 default:
599 Expecting( "face, size, line_spacing, thickness, bold, or italic" );
600 }
601 }
602
603 break;
604
605 case T_justify:
606 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
607 {
608 if( token == T_LEFT )
609 continue;
610
611 switch( token )
612 {
613 case T_left: aText->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT ); break;
614 case T_right: aText->SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT ); break;
615 case T_top: aText->SetVertJustify( GR_TEXT_V_ALIGN_TOP ); break;
616 case T_bottom: aText->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM ); break;
617 case T_mirror: aText->SetMirrored( true ); break;
618 default: Expecting( "left, right, top, bottom, or mirror" );
619 }
620
621 }
622
623 break;
624
625 case T_hide:
626 {
627 // In older files, the hide token appears bare, and indicates hide==true.
628 // In newer files, it will be an explicit bool in a list like (hide yes)
629 bool hide = parseMaybeAbsentBool( true );
630 aText->SetVisible( !hide );
631 break;
632 }
633
634 default:
635 Expecting( "font, justify, or hide" );
636 }
637 }
638
639 // Text size was not specified in file, force legacy default units
640 // 60mils is 1.524mm
641 if( !foundTextSize )
642 {
643 const double defaultTextSize = 1.524 * pcbIUScale.IU_PER_MM;
644
645 aText->SetTextSize( VECTOR2I( defaultTextSize, defaultTextSize ) );
646 }
647}
648
649
651{
652 T token;
653
654 NeedSYMBOLorNUMBER();
655 wxString cacheText = From_UTF8( CurText() );
656 EDA_ANGLE cacheAngle( parseDouble( "render cache angle" ), DEGREES_T );
657
658 text->SetupRenderCache( cacheText, text->GetFont(), cacheAngle, { 0, 0 } );
659
660 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
661 {
662 if( token != T_LEFT )
663 Expecting( T_LEFT );
664
665 token = NextTok();
666
667 if( token != T_polygon )
668 Expecting( T_polygon );
669
670 SHAPE_POLY_SET poly;
671
672 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
673 {
674 if( token != T_LEFT )
675 Expecting( T_LEFT );
676
677 token = NextTok();
678
679 if( token != T_pts )
680 Expecting( T_pts );
681
682 SHAPE_LINE_CHAIN lineChain;
683
684 while( (token = NextTok() ) != T_RIGHT )
685 parseOutlinePoints( lineChain );
686
687 lineChain.SetClosed( true );
688
689 if( poly.OutlineCount() == 0 )
690 poly.AddOutline( lineChain );
691 else
692 poly.AddHole( lineChain );
693 }
694
695 text->AddRenderCacheGlyph( poly );
696 }
697}
698
699
701{
702 wxCHECK_MSG( CurTok() == T_model, nullptr,
703 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as FP_3DMODEL." ) );
704
705 T token;
706
707 FP_3DMODEL* n3D = new FP_3DMODEL;
708 NeedSYMBOLorNUMBER();
709 n3D->m_Filename = FromUTF8();
710
711 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
712 {
713 if( token == T_LEFT )
714 token = NextTok();
715
716 switch( token )
717 {
718 case T_at:
719 NeedLEFT();
720 token = NextTok();
721
722 if( token != T_xyz )
723 Expecting( T_xyz );
724
725 /* Note:
726 * Prior to KiCad v5, model offset was designated by "at",
727 * and the units were in inches.
728 * Now we use mm, but support reading of legacy files
729 */
730
731 n3D->m_Offset.x = parseDouble( "x value" ) * 25.4f;
732 n3D->m_Offset.y = parseDouble( "y value" ) * 25.4f;
733 n3D->m_Offset.z = parseDouble( "z value" ) * 25.4f;
734
735 NeedRIGHT(); // xyz
736 NeedRIGHT(); // at
737 break;
738
739 case T_hide:
740 {
741 // In older files, the hide token appears bare, and indicates hide==true.
742 // In newer files, it will be an explicit bool in a list like (hide yes)
743 bool hide = parseMaybeAbsentBool( true );
744 n3D->m_Show = !hide;
745 break;
746 }
747
748 case T_opacity:
749 n3D->m_Opacity = parseDouble( "opacity value" );
750 NeedRIGHT();
751 break;
752
753 case T_offset:
754 NeedLEFT();
755 token = NextTok();
756
757 if( token != T_xyz )
758 Expecting( T_xyz );
759
760 /*
761 * 3D model offset is in mm
762 */
763 n3D->m_Offset.x = parseDouble( "x value" );
764 n3D->m_Offset.y = parseDouble( "y value" );
765 n3D->m_Offset.z = parseDouble( "z value" );
766
767 NeedRIGHT(); // xyz
768 NeedRIGHT(); // offset
769 break;
770
771 case T_scale:
772 NeedLEFT();
773 token = NextTok();
774
775 if( token != T_xyz )
776 Expecting( T_xyz );
777
778 n3D->m_Scale.x = parseDouble( "x value" );
779 n3D->m_Scale.y = parseDouble( "y value" );
780 n3D->m_Scale.z = parseDouble( "z value" );
781
782 NeedRIGHT(); // xyz
783 NeedRIGHT(); // scale
784 break;
785
786 case T_rotate:
787 NeedLEFT();
788 token = NextTok();
789
790 if( token != T_xyz )
791 Expecting( T_xyz );
792
793 n3D->m_Rotation.x = parseDouble( "x value" );
794 n3D->m_Rotation.y = parseDouble( "y value" );
795 n3D->m_Rotation.z = parseDouble( "z value" );
796
797 NeedRIGHT(); // xyz
798 NeedRIGHT(); // rotate
799 break;
800
801 default:
802 Expecting( "at, hide, opacity, offset, scale, or rotate" );
803 }
804
805 }
806
807 return n3D;
808}
809
810
812{
813 m_groupInfos.clear();
814
815 // See Parse() - FOOTPRINTS can be prefixed with an initial block of single line comments,
816 // eventually BOARD might be the same
817 ReadCommentLines();
818
819 if( CurTok() != T_LEFT )
820 return false;
821
822 if( NextTok() != T_kicad_pcb)
823 return false;
824
825 return true;
826}
827
828
830{
831 T token;
832 BOARD_ITEM* item;
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 const std::vector<wxString>* embeddedFonts = item->GetEmbeddedFiles()->UpdateFontFiles();
873
874 item->RunOnChildren(
875 [&]( BOARD_ITEM* aChild )
876 {
877 if( EDA_TEXT* textItem = dynamic_cast<EDA_TEXT*>( aChild ) )
878 textItem->ResolveFont( embeddedFonts );
879 },
881
882 resolveGroups( item );
883
884 return item;
885}
886
887
889{
890 try
891 {
892 return parseBOARD_unchecked();
893 }
894 catch( const PARSE_ERROR& parse_error )
895 {
896 if( m_tooRecent )
897 throw FUTURE_FORMAT_ERROR( parse_error, GetRequiredVersion() );
898 else
899 throw;
900 }
901}
902
903
905{
906 T token;
907 std::map<wxString, wxString> properties;
908
909 parseHeader();
910
911 auto checkVersion =
912 [&]()
913 {
915 {
916 throw FUTURE_FORMAT_ERROR( fmt::format( "{}", m_requiredVersion ),
918 }
919 };
920
921 std::vector<BOARD_ITEM*> bulkAddedItems;
922 BOARD_ITEM* item = nullptr;
923
924 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
925 {
926 checkpoint();
927
928 if( token != T_LEFT )
929 Expecting( T_LEFT );
930
931 token = NextTok();
932
933 if( token == T_page && m_requiredVersion <= 20200119 )
934 token = T_paper;
935
936 switch( token )
937 {
938 case T_host: // legacy token
939 NeedSYMBOL();
940 m_board->SetGenerator( FromUTF8() );
941
942 // Older formats included build data
944 NeedSYMBOL();
945
946 NeedRIGHT();
947 break;
948
949 case T_generator:
950 NeedSYMBOL();
951 m_board->SetGenerator( FromUTF8() );
952 NeedRIGHT();
953 break;
954
955 case T_generator_version:
956 {
957 NeedSYMBOL();
958 m_generatorVersion = FromUTF8();
959 NeedRIGHT();
960
961 // If the format includes a generator version, by this point we have enough info to
962 // do the version check here
963 checkVersion();
964
965 break;
966 }
967
968 case T_general:
969 // Do another version check here, for older files that do not include generator_version
970 checkVersion();
971
973 break;
974
975 case T_paper:
977 break;
978
979 case T_title_block:
981 break;
982
983 case T_layers:
984 parseLayers();
985 break;
986
987 case T_setup:
988 parseSetup();
989 break;
990
991 case T_property:
992 properties.insert( parseBoardProperty() );
993 break;
994
995 case T_net:
997 break;
998
999 case T_net_class:
1000 parseNETCLASS();
1001 m_board->m_LegacyNetclassesLoaded = true;
1002 break;
1003
1004 case T_gr_arc:
1005 case T_gr_curve:
1006 case T_gr_line:
1007 case T_gr_poly:
1008 case T_gr_circle:
1009 case T_gr_rect:
1010 item = parsePCB_SHAPE( m_board );
1011 m_board->Add( item, ADD_MODE::BULK_APPEND, true );
1012 bulkAddedItems.push_back( item );
1013 break;
1014
1015 case T_image:
1017 m_board->Add( item, ADD_MODE::BULK_APPEND, true );
1018 bulkAddedItems.push_back( item );
1019 break;
1020
1021 case T_barcode:
1022 item = parsePCB_BARCODE( m_board );
1023 m_board->Add( item, ADD_MODE::BULK_APPEND, true );
1024 bulkAddedItems.push_back( item );
1025 break;
1026
1027 case T_gr_text:
1028 item = parsePCB_TEXT( m_board );
1029 m_board->Add( item, ADD_MODE::BULK_APPEND, true );
1030 bulkAddedItems.push_back( item );
1031 break;
1032
1033 case T_gr_text_box:
1034 item = parsePCB_TEXTBOX( m_board );
1035 m_board->Add( item, ADD_MODE::BULK_APPEND, true );
1036 bulkAddedItems.push_back( item );
1037 break;
1038
1039 case T_table:
1040 item = parsePCB_TABLE( m_board );
1041 m_board->Add( item, ADD_MODE::BULK_APPEND, true );
1042 bulkAddedItems.push_back( item );
1043 break;
1044
1045 case T_dimension:
1046 item = parseDIMENSION( m_board );
1047 m_board->Add( item, ADD_MODE::BULK_APPEND, true );
1048 bulkAddedItems.push_back( item );
1049 break;
1050
1051 case T_module: // legacy token
1052 case T_footprint:
1053 item = parseFOOTPRINT();
1054 m_board->Add( item, ADD_MODE::BULK_APPEND, true );
1055 bulkAddedItems.push_back( item );
1056 break;
1057
1058 case T_segment:
1059 if( PCB_TRACK* track = parsePCB_TRACK() )
1060 {
1061 m_board->Add( track, ADD_MODE::BULK_APPEND, true );
1062 bulkAddedItems.push_back( track );
1063 }
1064
1065 break;
1066
1067 case T_arc:
1068 if( PCB_ARC* arc = parseARC() )
1069 {
1070 m_board->Add( arc, ADD_MODE::BULK_APPEND, true );
1071 bulkAddedItems.push_back( arc );
1072 }
1073
1074 break;
1075
1076 case T_group:
1078 break;
1079
1080 case T_generated:
1082 break;
1083
1084 case T_via:
1085 item = parsePCB_VIA();
1086 m_board->Add( item, ADD_MODE::BULK_APPEND, true );
1087 bulkAddedItems.push_back( item );
1088 break;
1089
1090 case T_zone:
1091 item = parseZONE( m_board );
1092 m_board->Add( item, ADD_MODE::BULK_APPEND, true );
1093 bulkAddedItems.push_back( item );
1094 break;
1095
1096 case T_target:
1097 item = parsePCB_TARGET();
1098 m_board->Add( item, ADD_MODE::BULK_APPEND, true );
1099 bulkAddedItems.push_back( item );
1100 break;
1101
1102 case T_point:
1103 item = parsePCB_POINT();
1104 m_board->Add( item, ADD_MODE::BULK_APPEND, true );
1105 bulkAddedItems.push_back( item );
1106 break;
1107
1108 case T_embedded_fonts:
1109 {
1110 m_board->GetEmbeddedFiles()->SetAreFontsEmbedded( parseBool() );
1111 NeedRIGHT();
1112 break;
1113 }
1114
1115 case T_embedded_files:
1116 {
1117 EMBEDDED_FILES_PARSER embeddedFilesParser( reader );
1118 embeddedFilesParser.SyncLineReaderWith( *this );
1119
1120 try
1121 {
1122 embeddedFilesParser.ParseEmbedded( m_board->GetEmbeddedFiles() );
1123 }
1124 catch( const PARSE_ERROR& e )
1125 {
1126 wxLogError( e.What() );
1127 }
1128
1129 SyncLineReaderWith( embeddedFilesParser );
1130 break;
1131 }
1132
1133 default:
1134 wxString err;
1135 err.Printf( _( "Unknown token '%s'" ), FromUTF8() );
1136 THROW_PARSE_ERROR( err, CurSource(), CurLine(), CurLineNumber(), CurOffset() );
1137 }
1138 }
1139
1140 if( bulkAddedItems.size() > 0 )
1141 m_board->FinalizeBulkAdd( bulkAddedItems );
1142
1143 m_board->SetProperties( properties );
1144
1145 if( m_undefinedLayers.size() > 0 )
1146 {
1147 PCB_LAYER_ID destLayer = Cmts_User;
1148 wxString msg, undefinedLayerNames, destLayerName;
1149
1150 for( const wxString& layerName : m_undefinedLayers )
1151 {
1152 if( !undefinedLayerNames.IsEmpty() )
1153 undefinedLayerNames += wxT( ", " );
1154
1155 undefinedLayerNames += layerName;
1156 }
1157
1158 destLayerName = m_board->GetLayerName( destLayer );
1159
1160 if( Pgm().IsGUI() && m_queryUserCallback )
1161 {
1162 msg.Printf( _( "Items found on undefined layers (%s).\n"
1163 "Do you wish to rescue them to the %s layer?\n"
1164 "\n"
1165 "Zones will need to be refilled." ),
1166 undefinedLayerNames, destLayerName );
1167
1168 if( !m_queryUserCallback( _( "Undefined Layers Warning" ), wxICON_WARNING, msg,
1169 _( "Rescue" ) ) )
1170 {
1171 THROW_IO_ERROR( wxT( "CANCEL" ) );
1172 }
1173
1174 // Make sure the destination layer is enabled, even if not in the file
1175 m_board->SetEnabledLayers( LSET( m_board->GetEnabledLayers() ).set( destLayer ) );
1176
1177 const auto visitItem = [&]( BOARD_ITEM& curr_item )
1178 {
1179 LSET layers = curr_item.GetLayerSet();
1180
1181 if( layers.test( Rescue ) )
1182 {
1183 layers.set( destLayer );
1184 layers.reset( Rescue );
1185 }
1186
1187 curr_item.SetLayerSet( layers );
1188 };
1189
1190 for( PCB_TRACK* track : m_board->Tracks() )
1191 {
1192 if( track->Type() == PCB_VIA_T )
1193 {
1194 PCB_VIA* via = static_cast<PCB_VIA*>( track );
1195 PCB_LAYER_ID top_layer, bottom_layer;
1196
1197 if( via->GetViaType() == VIATYPE::THROUGH )
1198 continue;
1199
1200 via->LayerPair( &top_layer, &bottom_layer );
1201
1202 if( top_layer == Rescue || bottom_layer == Rescue )
1203 {
1204 if( top_layer == Rescue )
1205 top_layer = F_Cu;
1206
1207 if( bottom_layer == Rescue )
1208 bottom_layer = B_Cu;
1209
1210 via->SetLayerPair( top_layer, bottom_layer );
1211 }
1212 }
1213 else
1214 {
1215 visitItem( *track );
1216 }
1217 }
1218
1219 for( BOARD_ITEM* zone : m_board->Zones() )
1220 visitItem( *zone );
1221
1222 for( BOARD_ITEM* drawing : m_board->Drawings() )
1223 visitItem( *drawing );
1224
1225 for( FOOTPRINT* fp : m_board->Footprints() )
1226 {
1227 for( BOARD_ITEM* drawing : fp->GraphicalItems() )
1228 visitItem( *drawing );
1229
1230 for( BOARD_ITEM* zone : fp->Zones() )
1231 visitItem( *zone );
1232
1233 for( PCB_FIELD* field : fp->GetFields() )
1234 visitItem( *field );
1235 }
1236
1237 m_undefinedLayers.clear();
1238 }
1239 else
1240 {
1241 THROW_IO_ERROR( wxT( "One or more undefined undefinedLayerNames was found; "
1242 "open the board in the PCB Editor to resolve." ) );
1243 }
1244 }
1245
1246 // Clear unused zone data
1247 {
1248 LSET layers = m_board->GetEnabledLayers();
1249
1250 for( BOARD_ITEM* zone : m_board->Zones() )
1251 {
1252 ZONE* z = static_cast<ZONE*>( zone );
1253
1254 z->SetLayerSetAndRemoveUnusedFills( z->GetLayerSet() & layers );
1255 }
1256 }
1257
1258 // Ensure all footprints have their embedded data from the board
1259 m_board->FixupEmbeddedData();
1260
1261 return m_board;
1262}
1263
1264
1266{
1267 auto getItem =
1268 [&]( const KIID& aId )
1269 {
1270 BOARD_ITEM* aItem = nullptr;
1271
1272 if( BOARD* board = dynamic_cast<BOARD*>( aParent ) )
1273 {
1274 aItem = board->ResolveItem( aId, true );
1275 }
1276 else if( FOOTPRINT* footprint = dynamic_cast<FOOTPRINT*>( aParent ) )
1277 {
1278 footprint->RunOnChildren(
1279 [&]( BOARD_ITEM* child )
1280 {
1281 if( child->m_Uuid == aId )
1282 aItem = child;
1283 },
1285 }
1286
1287 return aItem;
1288 };
1289
1290 // Now that we've parsed the other Uuids in the file we can resolve the uuids referred
1291 // to in the group declarations we saw.
1292 //
1293 // First add all group objects so subsequent GetItem() calls for nested groups work.
1294
1295 std::vector<const GROUP_INFO*> groupTypeObjects;
1296
1297 for( const GROUP_INFO& groupInfo : m_groupInfos )
1298 groupTypeObjects.emplace_back( &groupInfo );
1299
1300 for( const GENERATOR_INFO& genInfo : m_generatorInfos )
1301 groupTypeObjects.emplace_back( &genInfo );
1302
1303 for( const GROUP_INFO* groupInfo : groupTypeObjects )
1304 {
1305 PCB_GROUP* group = nullptr;
1306
1307 if( const GENERATOR_INFO* genInfo = dynamic_cast<const GENERATOR_INFO*>( groupInfo ) )
1308 {
1310
1311 PCB_GENERATOR* gen;
1312 group = gen = mgr.CreateFromType( genInfo->genType );
1313
1314 if( !gen )
1315 {
1316 THROW_IO_ERROR( wxString::Format( _( "Cannot create generated object of type '%s'" ),
1317 genInfo->genType ) );
1318 }
1319
1320 gen->SetLayer( genInfo->layer );
1321 gen->SetProperties( genInfo->properties );
1322 }
1323 else
1324 {
1325 group = new PCB_GROUP( groupInfo->parent );
1326 group->SetName( groupInfo->name );
1327 }
1328
1329 const_cast<KIID&>( group->m_Uuid ) = groupInfo->uuid;
1330
1331 if( groupInfo->libId.IsValid() )
1332 group->SetDesignBlockLibId( groupInfo->libId );
1333
1334 if( groupInfo->locked )
1335 group->SetLocked( true );
1336
1337 if( groupInfo->parent->Type() == PCB_FOOTPRINT_T )
1338 static_cast<FOOTPRINT*>( groupInfo->parent )->Add( group, ADD_MODE::INSERT, true );
1339 else
1340 static_cast<BOARD*>( groupInfo->parent )->Add( group, ADD_MODE::INSERT, true );
1341 }
1342
1343 for( const GROUP_INFO* groupInfo : groupTypeObjects )
1344 {
1345 if( PCB_GROUP* group = dynamic_cast<PCB_GROUP*>( getItem( groupInfo->uuid ) ) )
1346 {
1347 for( const KIID& aUuid : groupInfo->memberUuids )
1348 {
1349 BOARD_ITEM* item = nullptr;
1350
1351 if( m_appendToExisting )
1352 item = getItem( m_resetKIIDMap[ aUuid.AsString() ] );
1353 else
1354 item = getItem( aUuid );
1355
1356 // We used to allow fp items in non-footprint groups. It was a mistake. Check
1357 // to make sure they the item and group are owned by the same parent (will both
1358 // be nullptr in the board case).
1359 if( item && item->GetParentFootprint() == group->GetParentFootprint() )
1360 group->AddItem( item );
1361 }
1362 }
1363 }
1364
1365 // Don't allow group cycles
1366 if( m_board )
1367 m_board->GroupsSanityCheck( true );
1368}
1369
1370
1372{
1373 wxCHECK_RET( CurTok() == T_kicad_pcb,
1374 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a header." ) );
1375
1376 NeedLEFT();
1377
1378 T tok = NextTok();
1379
1380 if( tok == T_version )
1381 {
1382 m_requiredVersion = parseInt( FromUTF8().mb_str( wxConvUTF8 ) );
1383 NeedRIGHT();
1384 }
1385 else
1386 {
1387 m_requiredVersion = 20201115; // Last version before we started writing version #s
1388 // in footprint files as well as board files.
1389 }
1390
1392
1393 // Prior to this, bar was a valid string char for unquoted strings.
1394 SetKnowsBar( m_requiredVersion >= 20240706 );
1395
1396 m_board->SetFileFormatVersionAtLoad( m_requiredVersion );
1397}
1398
1399
1401{
1402 wxCHECK_RET( CurTok() == T_general,
1403 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a general section." ) );
1404
1405 T token;
1406
1407 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
1408 {
1409 if( token != T_LEFT )
1410 Expecting( T_LEFT );
1411
1412 token = NextTok();
1413
1414 switch( token )
1415 {
1416 case T_thickness:
1417 m_board->GetDesignSettings().SetBoardThickness( parseBoardUnits( T_thickness ) );
1418 NeedRIGHT();
1419 break;
1420
1421 case T_legacy_teardrops:
1422 m_board->SetLegacyTeardrops( parseMaybeAbsentBool( true ) );
1423 break;
1424
1425 default: // Skip everything else.
1426 while( ( token = NextTok() ) != T_RIGHT )
1427 {
1428 if( !IsSymbol( token ) && token != T_NUMBER )
1429 Expecting( "symbol or number" );
1430 }
1431 }
1432 }
1433}
1434
1435
1437{
1438 wxCHECK_RET( ( CurTok() == T_page && m_requiredVersion <= 20200119 ) || CurTok() == T_paper,
1439 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a PAGE_INFO." ) );
1440
1441 T token;
1442 PAGE_INFO pageInfo;
1443
1444 NeedSYMBOL();
1445
1446 wxString pageType = FromUTF8();
1447
1448 if( !pageInfo.SetType( pageType ) )
1449 {
1450 wxString err;
1451 err.Printf( _( "Page type '%s' is not valid." ), FromUTF8() );
1452 THROW_PARSE_ERROR( err, CurSource(), CurLine(), CurLineNumber(), CurOffset() );
1453 }
1454
1455 if( pageInfo.GetType() == PAGE_SIZE_TYPE::User )
1456 {
1457 double width = parseDouble( "width" ); // width in mm
1458
1459 // Perform some controls to avoid crashes if the size is edited by hands
1460 if( width < MIN_PAGE_SIZE_MM )
1461 width = MIN_PAGE_SIZE_MM;
1462 else if( width > MAX_PAGE_SIZE_PCBNEW_MM )
1464
1465 double height = parseDouble( "height" ); // height in mm
1466
1467 if( height < MIN_PAGE_SIZE_MM )
1468 height = MIN_PAGE_SIZE_MM;
1469 else if( height > MAX_PAGE_SIZE_PCBNEW_MM )
1470 height = MAX_PAGE_SIZE_PCBNEW_MM;
1471
1472 pageInfo.SetWidthMM( width );
1473 pageInfo.SetHeightMM( height );
1474 }
1475
1476 token = NextTok();
1477
1478 if( token == T_portrait )
1479 {
1480 pageInfo.SetPortrait( true );
1481 NeedRIGHT();
1482 }
1483 else if( token != T_RIGHT )
1484 {
1485 Expecting( "portrait|)" );
1486 }
1487
1488 m_board->SetPageSettings( pageInfo );
1489}
1490
1491
1493{
1494 wxCHECK_RET( CurTok() == T_title_block,
1495 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as TITLE_BLOCK." ) );
1496
1497 T token;
1498 TITLE_BLOCK titleBlock;
1499
1500 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
1501 {
1502 if( token != T_LEFT )
1503 Expecting( T_LEFT );
1504
1505 token = NextTok();
1506
1507 switch( token )
1508 {
1509 case T_title:
1510 NextTok();
1511 titleBlock.SetTitle( FromUTF8() );
1512 break;
1513
1514 case T_date:
1515 NextTok();
1516 titleBlock.SetDate( FromUTF8() );
1517 break;
1518
1519 case T_rev:
1520 NextTok();
1521 titleBlock.SetRevision( FromUTF8() );
1522 break;
1523
1524 case T_company:
1525 NextTok();
1526 titleBlock.SetCompany( FromUTF8() );
1527 break;
1528
1529 case T_comment:
1530 {
1531 int commentNumber = parseInt( "comment" );
1532
1533 switch( commentNumber )
1534 {
1535 case 1:
1536 NextTok();
1537 titleBlock.SetComment( 0, FromUTF8() );
1538 break;
1539
1540 case 2:
1541 NextTok();
1542 titleBlock.SetComment( 1, FromUTF8() );
1543 break;
1544
1545 case 3:
1546 NextTok();
1547 titleBlock.SetComment( 2, FromUTF8() );
1548 break;
1549
1550 case 4:
1551 NextTok();
1552 titleBlock.SetComment( 3, FromUTF8() );
1553 break;
1554
1555 case 5:
1556 NextTok();
1557 titleBlock.SetComment( 4, FromUTF8() );
1558 break;
1559
1560 case 6:
1561 NextTok();
1562 titleBlock.SetComment( 5, FromUTF8() );
1563 break;
1564
1565 case 7:
1566 NextTok();
1567 titleBlock.SetComment( 6, FromUTF8() );
1568 break;
1569
1570 case 8:
1571 NextTok();
1572 titleBlock.SetComment( 7, FromUTF8() );
1573 break;
1574
1575 case 9:
1576 NextTok();
1577 titleBlock.SetComment( 8, FromUTF8() );
1578 break;
1579
1580 default:
1581 wxString err;
1582 err.Printf( wxT( "%d is not a valid title block comment number" ), commentNumber );
1583 THROW_PARSE_ERROR( err, CurSource(), CurLine(), CurLineNumber(), CurOffset() );
1584 }
1585
1586 break;
1587 }
1588
1589 default:
1590 Expecting( "title, date, rev, company, or comment" );
1591 }
1592
1593 NeedRIGHT();
1594 }
1595
1596 m_board->SetTitleBlock( titleBlock );
1597}
1598
1599
1601{
1602 T token;
1603
1604 std::string name;
1605 std::string userName;
1606 std::string type;
1607 bool isVisible = true;
1608
1609 aLayer->clear();
1610
1611 if( CurTok() != T_LEFT )
1612 Expecting( T_LEFT );
1613
1614 // this layer_num is not used, we DO depend on LAYER_T however.
1615 int layer_num = parseInt( "layer index" );
1616
1617 NeedSYMBOLorNUMBER();
1618 name = CurText();
1619
1620 NeedSYMBOL();
1621 type = CurText();
1622
1623 token = NextTok();
1624
1625 // @todo Figure out why we are looking for a hide token in the layer definition.
1626 if( token == T_hide )
1627 {
1628 isVisible = false;
1629 NeedRIGHT();
1630 }
1631 else if( token == T_STRING )
1632 {
1633 userName = CurText();
1634 NeedRIGHT();
1635 }
1636 else if( token != T_RIGHT )
1637 {
1638 Expecting( "hide, user defined name, or )" );
1639 }
1640
1641 aLayer->m_type = LAYER::ParseType( type.c_str() );
1642 aLayer->m_number = layer_num;
1643 aLayer->m_visible = isVisible;
1644
1645 if( m_requiredVersion >= 20200922 )
1646 {
1647 aLayer->m_userName = From_UTF8( userName.c_str() );
1648 aLayer->m_name = From_UTF8( name.c_str() );
1649 }
1650 else // Older versions didn't have a dedicated user name field
1651 {
1652 aLayer->m_name = aLayer->m_userName = From_UTF8( name.c_str() );
1653 }
1654}
1655
1656
1658{
1659 T token;
1660 wxString name;
1661 int dielectric_idx = 1; // the index of dielectric layers
1662 BOARD_STACKUP& stackup = m_board->GetDesignSettings().GetStackupDescriptor();
1663
1664 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
1665 {
1666 if( CurTok() != T_LEFT )
1667 Expecting( T_LEFT );
1668
1669 token = NextTok();
1670
1671 if( token != T_layer )
1672 {
1673 switch( token )
1674 {
1675 case T_copper_finish:
1676 NeedSYMBOL();
1677 stackup.m_FinishType = FromUTF8();
1678 NeedRIGHT();
1679 break;
1680
1681 case T_edge_plating:
1682 token = NextTok();
1683 stackup.m_EdgePlating = token == T_yes;
1684 NeedRIGHT();
1685 break;
1686
1687 case T_dielectric_constraints:
1688 token = NextTok();
1689 stackup.m_HasDielectricConstrains = token == T_yes;
1690 NeedRIGHT();
1691 break;
1692
1693 case T_edge_connector:
1694 token = NextTok();
1696
1697 if( token == T_yes )
1699 else if( token == T_bevelled )
1701
1702 NeedRIGHT();
1703 break;
1704
1705 case T_castellated_pads: // Legacy compatibility. just skip it
1706 token = NextTok();
1707 NeedRIGHT();
1708 break;
1709
1710 default:
1711 // Currently, skip this item if not defined, because the stackup def
1712 // is a moving target
1713 //Expecting( "copper_finish, edge_plating, dielectric_constrains,
1714 // edge_connector, castellated_pads" );
1715 skipCurrent();
1716 break;
1717 }
1718
1719 continue;
1720 }
1721
1722 NeedSYMBOL();
1723 name = FromUTF8();
1724
1725 // init the layer id. For dielectric, layer id = UNDEFINED_LAYER
1726 PCB_LAYER_ID layerId = m_board->GetLayerID( name );
1727
1728 // Init the type
1730
1731 if( layerId == F_SilkS || layerId == B_SilkS )
1733 else if( layerId == F_Mask || layerId == B_Mask )
1735 else if( layerId == F_Paste || layerId == B_Paste )
1737 else if( layerId == UNDEFINED_LAYER )
1739 else if( !( layerId & 1 ) )
1740 type = BS_ITEM_TYPE_COPPER;
1741
1742 BOARD_STACKUP_ITEM* item = nullptr;
1743
1744 if( type != BS_ITEM_TYPE_UNDEFINED )
1745 {
1746 item = new BOARD_STACKUP_ITEM( type );
1747 item->SetBrdLayerId( layerId );
1748
1749 if( type == BS_ITEM_TYPE_DIELECTRIC )
1750 item->SetDielectricLayerId( dielectric_idx++ );
1751
1752 stackup.Add( item );
1753 }
1754 else
1755 {
1756 Expecting( "layer_name" );
1757 }
1758
1759 bool has_next_sublayer = true;
1760 int sublayer_idx = 0; // the index of dielectric sub layers
1761 // sublayer 0 is always existing (main sublayer)
1762
1763 while( has_next_sublayer )
1764 {
1765 has_next_sublayer = false;
1766
1767 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
1768 {
1769 if( token == T_addsublayer )
1770 {
1771 has_next_sublayer = true;
1772 break;
1773 }
1774
1775 if( token == T_LEFT )
1776 {
1777 token = NextTok();
1778
1779 switch( token )
1780 {
1781 case T_type:
1782 NeedSYMBOL();
1783 item->SetTypeName( FromUTF8() );
1784 NeedRIGHT();
1785 break;
1786
1787 case T_thickness:
1788 item->SetThickness( parseBoardUnits( T_thickness ), sublayer_idx );
1789 token = NextTok();
1790
1791 if( token == T_LEFT )
1792 break;
1793
1794 if( token == T_locked )
1795 {
1796 // Dielectric thickness can be locked (for impedance controlled layers)
1797 if( type == BS_ITEM_TYPE_DIELECTRIC )
1798 item->SetThicknessLocked( true, sublayer_idx );
1799
1800 NeedRIGHT();
1801 }
1802
1803 break;
1804
1805 case T_material:
1806 NeedSYMBOL();
1807 item->SetMaterial( FromUTF8(), sublayer_idx );
1808 NeedRIGHT();
1809 break;
1810
1811 case T_epsilon_r:
1812 NextTok();
1813 item->SetEpsilonR( parseDouble(), sublayer_idx );
1814 NeedRIGHT();
1815 break;
1816
1817 case T_loss_tangent:
1818 NextTok();
1819 item->SetLossTangent( parseDouble(), sublayer_idx );
1820 NeedRIGHT();
1821 break;
1822
1823 case T_color:
1824 NeedSYMBOL();
1825 name = FromUTF8();
1826
1827 // Older versions didn't store opacity with custom colors
1828 if( name.StartsWith( wxT( "#" ) ) && m_requiredVersion < 20210824 )
1829 {
1831
1832 if( item->GetType() == BS_ITEM_TYPE_SOLDERMASK )
1833 color = color.WithAlpha( DEFAULT_SOLDERMASK_OPACITY );
1834 else
1835 color = color.WithAlpha( 1.0 );
1836
1837 wxColour wx_color = color.ToColour();
1838
1839 // Open-code wxColour::GetAsString() because 3.0 doesn't handle rgba
1840 name.Printf( wxT("#%02X%02X%02X%02X" ),
1841 wx_color.Red(),
1842 wx_color.Green(),
1843 wx_color.Blue(),
1844 wx_color.Alpha() );
1845 }
1846
1847 item->SetColor( name, sublayer_idx );
1848 NeedRIGHT();
1849 break;
1850
1851 default:
1852 // Currently, skip this item if not defined, because the stackup def
1853 // is a moving target
1854 //Expecting( "type, thickness, material, epsilon_r, loss_tangent, color" );
1855 skipCurrent();
1856 }
1857 }
1858 }
1859
1860 if( has_next_sublayer ) // Prepare reading the next sublayer description
1861 {
1862 sublayer_idx++;
1863 item->AddDielectricPrms( sublayer_idx );
1864 }
1865 }
1866 }
1867
1868 if( token != T_RIGHT )
1869 {
1870 Expecting( ")" );
1871 }
1872
1873 // Success:
1874 m_board->GetDesignSettings().m_HasStackup = true;
1875}
1876
1877
1878void PCB_IO_KICAD_SEXPR_PARSER::createOldLayerMapping( std::unordered_map< std::string, std::string >& aMap )
1879{
1880 // N.B. This mapping only includes Italian, Polish and French as they were the only languages
1881 // that mapped the layer names as of cc2022b1ac739aa673d2a0b7a2047638aa7a47b3 (kicad-i18n)
1882 // when the bug was fixed in KiCad source.
1883
1884 // Italian
1885 aMap["Adesivo.Retro"] = "B.Adhes";
1886 aMap["Adesivo.Fronte"] = "F.Adhes";
1887 aMap["Pasta.Retro"] = "B.Paste";
1888 aMap["Pasta.Fronte"] = "F.Paste";
1889 aMap["Serigrafia.Retro"] = "B.SilkS";
1890 aMap["Serigrafia.Fronte"] = "F.SilkS";
1891 aMap["Maschera.Retro"] = "B.Mask";
1892 aMap["Maschera.Fronte"] = "F.Mask";
1893 aMap["Grafica"] = "Dwgs.User";
1894 aMap["Commenti"] = "Cmts.User";
1895 aMap["Eco1"] = "Eco1.User";
1896 aMap["Eco2"] = "Eco2.User";
1897 aMap["Contorno.scheda"] = "Edge.Cuts";
1898
1899 // Polish
1900 aMap["Kleju_Dolna"] = "B.Adhes";
1901 aMap["Kleju_Gorna"] = "F.Adhes";
1902 aMap["Pasty_Dolna"] = "B.Paste";
1903 aMap["Pasty_Gorna"] = "F.Paste";
1904 aMap["Opisowa_Dolna"] = "B.SilkS";
1905 aMap["Opisowa_Gorna"] = "F.SilkS";
1906 aMap["Maski_Dolna"] = "B.Mask";
1907 aMap["Maski_Gorna"] = "F.Mask";
1908 aMap["Rysunkowa"] = "Dwgs.User";
1909 aMap["Komentarzy"] = "Cmts.User";
1910 aMap["ECO1"] = "Eco1.User";
1911 aMap["ECO2"] = "Eco2.User";
1912 aMap["Krawedziowa"] = "Edge.Cuts";
1913
1914 // French
1915 aMap["Dessous.Adhes"] = "B.Adhes";
1916 aMap["Dessus.Adhes"] = "F.Adhes";
1917 aMap["Dessous.Pate"] = "B.Paste";
1918 aMap["Dessus.Pate"] = "F.Paste";
1919 aMap["Dessous.SilkS"] = "B.SilkS";
1920 aMap["Dessus.SilkS"] = "F.SilkS";
1921 aMap["Dessous.Masque"] = "B.Mask";
1922 aMap["Dessus.Masque"] = "F.Mask";
1923 aMap["Dessin.User"] = "Dwgs.User";
1924 aMap["Contours.Ci"] = "Edge.Cuts";
1925}
1926
1927
1929{
1930 wxCHECK_RET( CurTok() == T_layers,
1931 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as layers." ) );
1932
1933 T token;
1934 LSET visibleLayers;
1935 LSET enabledLayers;
1936 int copperLayerCount = 0;
1937 LAYER layer;
1938 bool anyHidden = false;
1939
1940 std::unordered_map< std::string, std::string > v3_layer_names;
1941 std::vector<LAYER> cu;
1942
1943 createOldLayerMapping( v3_layer_names );
1944
1945 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
1946 {
1947 parseLayer( &layer );
1948
1949 if( layer.m_type == LT_UNDEFINED ) // it's a non-copper layer
1950 break;
1951
1952 cu.push_back( layer ); // it's copper
1953 }
1954
1955 // All Cu layers are parsed, but not the non-cu layers here.
1956
1957 // The original *.kicad_pcb file format and the inverted
1958 // Cu stack format both have all the Cu layers first, so use this
1959 // trick to handle either. The layer number in the (layers ..)
1960 // s-expression element are ignored.
1961 if( cu.size() )
1962 {
1963 // Rework the layer numbers, which changed when the Cu stack
1964 // was flipped. So we instead use position in the list.
1965 for( size_t i = 1; i < cu.size() - 1; i++ )
1966 {
1967 int tmpLayer = LSET::NameToLayer( cu[i].m_name );
1968
1969 if( tmpLayer < 0 )
1970 tmpLayer = ( i + 1 ) * 2;
1971
1972 cu[i].m_number = tmpLayer;
1973 }
1974
1975 cu[0].m_number = F_Cu;
1976 cu[cu.size()-1].m_number = B_Cu;
1977
1978 for( auto& cu_layer : cu )
1979 {
1980 enabledLayers.set( cu_layer.m_number );
1981
1982 if( cu_layer.m_visible )
1983 visibleLayers.set( cu_layer.m_number );
1984 else
1985 anyHidden = true;
1986
1987 m_board->SetLayerDescr( PCB_LAYER_ID( cu_layer.m_number ), cu_layer );
1988
1989 UTF8 name = cu_layer.m_name;
1990
1991 m_layerIndices[ name ] = PCB_LAYER_ID( cu_layer.m_number );
1992 m_layerMasks[ name ] = LSET( { PCB_LAYER_ID( cu_layer.m_number ) } );
1993 }
1994
1995 copperLayerCount = cu.size();
1996 }
1997
1998 // process non-copper layers
1999 while( token != T_RIGHT )
2000 {
2001 LAYER_ID_MAP::const_iterator it = m_layerIndices.find( UTF8( layer.m_name ) );
2002
2003 if( it == m_layerIndices.end() )
2004 {
2005 auto new_layer_it = v3_layer_names.find( layer.m_name.ToStdString() );
2006
2007 if( new_layer_it != v3_layer_names.end() )
2008 it = m_layerIndices.find( new_layer_it->second );
2009
2010 if( it == m_layerIndices.end() )
2011 {
2012 wxString error;
2013 error.Printf( _( "Layer '%s' in file '%s' at line %d is not in fixed layer hash." ),
2014 layer.m_name,
2015 CurSource(),
2016 CurLineNumber(),
2017 CurOffset() );
2018
2019 THROW_IO_ERROR( error );
2020 }
2021
2022 // If we are here, then we have found a translated layer name. Put it in the maps
2023 // so that items on this layer get the appropriate layer ID number.
2024 m_layerIndices[ UTF8( layer.m_name ) ] = it->second;
2025 m_layerMasks[ UTF8( layer.m_name ) ] = LSET( { it->second } );
2026 layer.m_name = it->first;
2027 }
2028
2029 layer.m_number = it->second;
2030 enabledLayers.set( layer.m_number );
2031
2032 if( layer.m_visible )
2033 visibleLayers.set( layer.m_number );
2034 else
2035 anyHidden = true;
2036
2037 m_board->SetLayerDescr( it->second, layer );
2038
2039 token = NextTok();
2040
2041 if( token != T_LEFT )
2042 break;
2043
2044 parseLayer( &layer );
2045 }
2046
2047 // We need at least 2 copper layers and there must be an even number of them.
2048 if( copperLayerCount < 2 || (copperLayerCount % 2) != 0 )
2049 {
2050 wxString err = wxString::Format( _( "%d is not a valid layer count" ), copperLayerCount );
2051
2052 THROW_PARSE_ERROR( err, CurSource(), CurLine(), CurLineNumber(), CurOffset() );
2053 }
2054
2055 m_board->SetCopperLayerCount( copperLayerCount );
2056 m_board->SetEnabledLayers( enabledLayers );
2057
2058 // Only set this if any layers were explicitly marked as hidden. Otherwise, we want to leave
2059 // this alone; default visibility will show everything
2060 if( anyHidden )
2061 m_board->m_LegacyVisibleLayers = visibleLayers;
2062}
2063
2064
2066{
2067 LSET_MAP::const_iterator it = aMap.find( curText );
2068
2069 if( it == aMap.end() )
2070 return LSET( { Rescue } );
2071
2072 return it->second;
2073}
2074
2075
2077{
2078 // avoid constructing another std::string, use lexer's directly
2079 LAYER_ID_MAP::const_iterator it = aMap.find( curText );
2080
2081 if( it == aMap.end() )
2082 {
2083 m_undefinedLayers.insert( curText );
2084 return Rescue;
2085 }
2086
2087 // Some files may have saved items to the Rescue Layer due to an issue in v5
2088 if( it->second == Rescue )
2089 m_undefinedLayers.insert( curText );
2090
2091 return it->second;
2092}
2093
2094
2096{
2097 wxCHECK_MSG( CurTok() == T_layer, UNDEFINED_LAYER,
2098 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as layer." ) );
2099
2100 NextTok();
2101
2102 PCB_LAYER_ID layerIndex = lookUpLayer( m_layerIndices );
2103
2104 // Handle closing ) in object parser.
2105
2106 return layerIndex;
2107}
2108
2109
2111{
2112 wxCHECK_MSG( CurTok() == T_layers, LSET(),
2113 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as item layers." ) );
2114
2115 LSET layerMask;
2116
2117 for( T token = NextTok(); token != T_RIGHT; token = NextTok() )
2118 {
2119 layerMask |= lookUpLayerSet( m_layerMasks );
2120 }
2121
2122 return layerMask;
2123}
2124
2125
2127{
2128 LSET layerMask = parseBoardItemLayersAsMask();
2129
2130 if( ( layerMask & LSET::AllCuMask() ).count() != 1 )
2131 Expecting( "single copper layer" );
2132
2133 if( ( layerMask & LSET( { F_Mask, B_Mask } ) ).count() > 1 )
2134 Expecting( "max one soldermask layer" );
2135
2136 if( ( ( layerMask & LSET::InternalCuMask() ).any()
2137 && ( layerMask & LSET( { F_Mask, B_Mask } ) ).any() ) )
2138 {
2139 Expecting( "no mask layer when track is on internal layer" );
2140 }
2141
2142 if( ( layerMask & LSET( { F_Cu, B_Mask } ) ).count() > 1 )
2143 Expecting( "copper and mask on the same side" );
2144
2145 if( ( layerMask & LSET( { B_Cu, F_Mask } ) ).count() > 1 )
2146 Expecting( "copper and mask on the same side" );
2147
2148 return layerMask;
2149}
2150
2151
2153{
2154 wxCHECK_RET( CurTok() == T_setup,
2155 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as setup." ) );
2156
2157 BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
2158 const std::shared_ptr<NETCLASS>& defaultNetClass = bds.m_NetSettings->GetDefaultNetclass();
2159 ZONE_SETTINGS& zoneSettings = bds.GetDefaultZoneSettings();
2160
2161 // Missing soldermask min width value means that the user has set the value to 0 and
2162 // not the default value (0.25mm)
2163 bds.m_SolderMaskMinWidth = 0;
2164
2165 for( T token = NextTok(); token != T_RIGHT; token = NextTok() )
2166 {
2167 if( token != T_LEFT )
2168 Expecting( T_LEFT );
2169
2170 token = NextTok();
2171
2172 switch( token )
2173 {
2174 case T_stackup:
2176 break;
2177
2178 case T_last_trace_width: // not used now
2179 /* lastTraceWidth =*/ parseBoardUnits( T_last_trace_width );
2180 NeedRIGHT();
2181 break;
2182
2183 case T_user_trace_width:
2184 {
2185 // Make room for the netclass value
2186 if( bds.m_TrackWidthList.empty() )
2187 bds.m_TrackWidthList.emplace_back( 0 );
2188
2189 int trackWidth = parseBoardUnits( T_user_trace_width );
2190
2191 if( !m_appendToExisting || !alg::contains( bds.m_TrackWidthList, trackWidth ) )
2192 bds.m_TrackWidthList.push_back( trackWidth );
2193
2194 m_board->m_LegacyDesignSettingsLoaded = true;
2195 NeedRIGHT();
2196 break;
2197 }
2198
2199 case T_trace_clearance:
2200 defaultNetClass->SetClearance( parseBoardUnits( T_trace_clearance ) );
2201 m_board->m_LegacyDesignSettingsLoaded = true;
2202 NeedRIGHT();
2203 break;
2204
2205 case T_zone_clearance:
2206 zoneSettings.m_ZoneClearance = parseBoardUnits( T_zone_clearance );
2207 m_board->m_LegacyDesignSettingsLoaded = true;
2208 NeedRIGHT();
2209 break;
2210
2211 case T_zone_45_only: // legacy setting
2212 /* zoneSettings.m_Zone_45_Only = */ parseBool();
2213 m_board->m_LegacyDesignSettingsLoaded = true;
2214 NeedRIGHT();
2215 break;
2216
2217 case T_clearance_min:
2218 bds.m_MinClearance = parseBoardUnits( T_clearance_min );
2219 m_board->m_LegacyDesignSettingsLoaded = true;
2220 NeedRIGHT();
2221 break;
2222
2223 case T_trace_min:
2224 bds.m_TrackMinWidth = parseBoardUnits( T_trace_min );
2225 m_board->m_LegacyDesignSettingsLoaded = true;
2226 NeedRIGHT();
2227 break;
2228
2229 case T_via_size:
2230 defaultNetClass->SetViaDiameter( parseBoardUnits( T_via_size ) );
2231 m_board->m_LegacyDesignSettingsLoaded = true;
2232 NeedRIGHT();
2233 break;
2234
2235 case T_via_drill:
2236 defaultNetClass->SetViaDrill( parseBoardUnits( T_via_drill ) );
2237 m_board->m_LegacyDesignSettingsLoaded = true;
2238 NeedRIGHT();
2239 break;
2240
2241 case T_via_min_annulus:
2242 bds.m_ViasMinAnnularWidth = parseBoardUnits( T_via_min_annulus );
2243 m_board->m_LegacyDesignSettingsLoaded = true;
2244 NeedRIGHT();
2245 break;
2246
2247 case T_via_min_size:
2248 bds.m_ViasMinSize = parseBoardUnits( T_via_min_size );
2249 m_board->m_LegacyDesignSettingsLoaded = true;
2250 NeedRIGHT();
2251 break;
2252
2253 case T_through_hole_min:
2254 bds.m_MinThroughDrill = parseBoardUnits( T_through_hole_min );
2255 m_board->m_LegacyDesignSettingsLoaded = true;
2256 NeedRIGHT();
2257 break;
2258
2259 // Legacy token for T_through_hole_min
2260 case T_via_min_drill:
2261 bds.m_MinThroughDrill = parseBoardUnits( T_via_min_drill );
2262 m_board->m_LegacyDesignSettingsLoaded = true;
2263 NeedRIGHT();
2264 break;
2265
2266 case T_hole_to_hole_min:
2267 bds.m_HoleToHoleMin = parseBoardUnits( T_hole_to_hole_min );
2268 m_board->m_LegacyDesignSettingsLoaded = true;
2269 NeedRIGHT();
2270 break;
2271
2272 case T_user_via:
2273 {
2274 int viaSize = parseBoardUnits( "user via size" );
2275 int viaDrill = parseBoardUnits( "user via drill" );
2276 VIA_DIMENSION via( viaSize, viaDrill );
2277
2278 // Make room for the netclass value
2279 if( bds.m_ViasDimensionsList.empty() )
2280 bds.m_ViasDimensionsList.emplace_back( VIA_DIMENSION( 0, 0 ) );
2281
2283 bds.m_ViasDimensionsList.emplace_back( via );
2284
2285 m_board->m_LegacyDesignSettingsLoaded = true;
2286 NeedRIGHT();
2287 break;
2288 }
2289
2290 case T_uvia_size:
2291 defaultNetClass->SetuViaDiameter( parseBoardUnits( T_uvia_size ) );
2292 m_board->m_LegacyDesignSettingsLoaded = true;
2293 NeedRIGHT();
2294 break;
2295
2296 case T_uvia_drill:
2297 defaultNetClass->SetuViaDrill( parseBoardUnits( T_uvia_drill ) );
2298 m_board->m_LegacyDesignSettingsLoaded = true;
2299 NeedRIGHT();
2300 break;
2301
2302 case T_uvias_allowed:
2303 parseBool();
2304 m_board->m_LegacyDesignSettingsLoaded = true;
2305 NeedRIGHT();
2306 break;
2307
2308 case T_blind_buried_vias_allowed:
2309 parseBool();
2310 m_board->m_LegacyDesignSettingsLoaded = true;
2311 NeedRIGHT();
2312 break;
2313
2314 case T_uvia_min_size:
2315 bds.m_MicroViasMinSize = parseBoardUnits( T_uvia_min_size );
2316 m_board->m_LegacyDesignSettingsLoaded = true;
2317 NeedRIGHT();
2318 break;
2319
2320 case T_uvia_min_drill:
2321 bds.m_MicroViasMinDrill = parseBoardUnits( T_uvia_min_drill );
2322 m_board->m_LegacyDesignSettingsLoaded = true;
2323 NeedRIGHT();
2324 break;
2325
2326 case T_user_diff_pair:
2327 {
2328 int width = parseBoardUnits( "user diff-pair width" );
2329 int gap = parseBoardUnits( "user diff-pair gap" );
2330 int viaGap = parseBoardUnits( "user diff-pair via gap" );
2331 DIFF_PAIR_DIMENSION diffPair( width, gap, viaGap );
2332
2334 bds.m_DiffPairDimensionsList.emplace_back( diffPair );
2335
2336 m_board->m_LegacyDesignSettingsLoaded = true;
2337 NeedRIGHT();
2338 break;
2339 }
2340
2341 case T_segment_width: // note: legacy (pre-6.0) token
2342 bds.m_LineThickness[ LAYER_CLASS_COPPER ] = parseBoardUnits( T_segment_width );
2343 m_board->m_LegacyDesignSettingsLoaded = true;
2344 NeedRIGHT();
2345 break;
2346
2347 case T_edge_width: // note: legacy (pre-6.0) token
2348 bds.m_LineThickness[ LAYER_CLASS_EDGES ] = parseBoardUnits( T_edge_width );
2349 m_board->m_LegacyDesignSettingsLoaded = true;
2350 NeedRIGHT();
2351 break;
2352
2353 case T_mod_edge_width: // note: legacy (pre-6.0) token
2354 bds.m_LineThickness[ LAYER_CLASS_SILK ] = parseBoardUnits( T_mod_edge_width );
2355 m_board->m_LegacyDesignSettingsLoaded = true;
2356 NeedRIGHT();
2357 break;
2358
2359 case T_pcb_text_width: // note: legacy (pre-6.0) token
2360 bds.m_TextThickness[ LAYER_CLASS_COPPER ] = parseBoardUnits( T_pcb_text_width );
2361 m_board->m_LegacyDesignSettingsLoaded = true;
2362 NeedRIGHT();
2363 break;
2364
2365 case T_mod_text_width: // note: legacy (pre-6.0) token
2366 bds.m_TextThickness[ LAYER_CLASS_SILK ] = parseBoardUnits( T_mod_text_width );
2367 m_board->m_LegacyDesignSettingsLoaded = true;
2368 NeedRIGHT();
2369 break;
2370
2371 case T_pcb_text_size: // note: legacy (pre-6.0) token
2372 bds.m_TextSize[ LAYER_CLASS_COPPER ].x = parseBoardUnits( "pcb text width" );
2373 bds.m_TextSize[ LAYER_CLASS_COPPER ].y = parseBoardUnits( "pcb text height" );
2374 m_board->m_LegacyDesignSettingsLoaded = true;
2375 NeedRIGHT();
2376 break;
2377
2378 case T_mod_text_size: // note: legacy (pre-6.0) token
2379 bds.m_TextSize[ LAYER_CLASS_SILK ].x = parseBoardUnits( "footprint text width" );
2380 bds.m_TextSize[ LAYER_CLASS_SILK ].y = parseBoardUnits( "footprint text height" );
2381 m_board->m_LegacyDesignSettingsLoaded = true;
2382 NeedRIGHT();
2383 break;
2384
2385 case T_defaults:
2386 parseDefaults( bds );
2387 m_board->m_LegacyDesignSettingsLoaded = true;
2388 break;
2389
2390 case T_pad_size:
2391 {
2392 VECTOR2I sz;
2393 sz.x = parseBoardUnits( "master pad width" );
2394 sz.y = parseBoardUnits( "master pad height" );
2396 m_board->m_LegacyDesignSettingsLoaded = true;
2397 NeedRIGHT();
2398 break;
2399 }
2400
2401 case T_pad_drill:
2402 {
2403 int drillSize = parseBoardUnits( T_pad_drill );
2404 bds.m_Pad_Master->SetDrillSize( VECTOR2I( drillSize, drillSize ) );
2405 m_board->m_LegacyDesignSettingsLoaded = true;
2406 NeedRIGHT();
2407 break;
2408 }
2409
2410 case T_pad_to_mask_clearance:
2411 bds.m_SolderMaskExpansion = parseBoardUnits( T_pad_to_mask_clearance );
2412 NeedRIGHT();
2413 break;
2414
2415 case T_solder_mask_min_width:
2416 bds.m_SolderMaskMinWidth = parseBoardUnits( T_solder_mask_min_width );
2417 NeedRIGHT();
2418 break;
2419
2420 case T_pad_to_paste_clearance:
2421 bds.m_SolderPasteMargin = parseBoardUnits( T_pad_to_paste_clearance );
2422 NeedRIGHT();
2423 break;
2424
2425 case T_pad_to_paste_clearance_ratio:
2426 bds.m_SolderPasteMarginRatio = parseDouble( T_pad_to_paste_clearance_ratio );
2427 NeedRIGHT();
2428 break;
2429
2430 case T_allow_soldermask_bridges_in_footprints:
2432 NeedRIGHT();
2433 break;
2434
2435 case T_tenting:
2436 {
2437 auto [front, back] = parseFrontBackOptBool( true );
2438 bds.m_TentViasFront = front.value_or( false );
2439 bds.m_TentViasBack = back.value_or( false );
2440 break;
2441 }
2442
2443 case T_covering:
2444 {
2445 auto [front, back] = parseFrontBackOptBool();
2446 bds.m_CoverViasFront = front.value_or( false );
2447 bds.m_CoverViasBack = back.value_or( false );
2448 break;
2449 }
2450
2451 case T_plugging:
2452 {
2453 auto [front, back] = parseFrontBackOptBool();
2454 bds.m_PlugViasFront = front.value_or( false );
2455 bds.m_PlugViasBack = back.value_or( false );
2456 break;
2457 }
2458
2459 case T_capping:
2460 {
2461 bds.m_CapVias = parseBool();
2462 NeedRIGHT();
2463 break;
2464 }
2465
2466 case T_filling:
2467 {
2468 bds.m_FillVias = parseBool();
2469 NeedRIGHT();
2470 break;
2471 }
2472
2473 case T_aux_axis_origin:
2474 {
2475 int x = parseBoardUnits( "auxiliary origin X" );
2476 int y = parseBoardUnits( "auxiliary origin Y" );
2477 bds.SetAuxOrigin( VECTOR2I( x, y ) );
2478
2479 // Aux origin still stored in board for the moment
2480 //m_board->m_LegacyDesignSettingsLoaded = true;
2481 NeedRIGHT();
2482 break;
2483 }
2484
2485 case T_grid_origin:
2486 {
2487 int x = parseBoardUnits( "grid origin X" );
2488 int y = parseBoardUnits( "grid origin Y" );
2489 bds.SetGridOrigin( VECTOR2I( x, y ) );
2490 // Grid origin still stored in board for the moment
2491 //m_board->m_LegacyDesignSettingsLoaded = true;
2492 NeedRIGHT();
2493 break;
2494 }
2495
2496 // Stored in board prior to 6.0
2497 case T_visible_elements:
2498 {
2499 // Make sure to start with DefaultVisible so all new layers are set
2500 m_board->m_LegacyVisibleItems = GAL_SET::DefaultVisible();
2501
2502 int visible = parseHex() | MIN_VISIBILITY_MASK;
2503
2504 for( size_t i = 0; i < sizeof( int ) * CHAR_BIT; i++ )
2505 m_board->m_LegacyVisibleItems.set( i, visible & ( 1u << i ) );
2506
2507 NeedRIGHT();
2508 break;
2509 }
2510
2511 case T_max_error:
2512 bds.m_MaxError = parseBoardUnits( T_max_error );
2513 m_board->m_LegacyDesignSettingsLoaded = true;
2514 NeedRIGHT();
2515 break;
2516
2517 case T_filled_areas_thickness:
2518 // Ignore this value, it is not used anymore
2519 parseBool();
2520 NeedRIGHT();
2521 break;
2522
2523 case T_pcbplotparams:
2524 {
2525 PCB_PLOT_PARAMS plotParams;
2526 PCB_PLOT_PARAMS_PARSER parser( reader, m_requiredVersion );
2527 // parser must share the same current line as our current PCB parser
2528 // synchronize it.
2529 parser.SyncLineReaderWith( *this );
2530
2531 plotParams.Parse( &parser );
2532 SyncLineReaderWith( parser );
2533
2534 m_board->SetPlotOptions( plotParams );
2535
2536 if( plotParams.GetLegacyPlotViaOnMaskLayer().has_value() )
2537 {
2538 bool tent = !( *plotParams.GetLegacyPlotViaOnMaskLayer() );
2539 m_board->GetDesignSettings().m_TentViasFront = tent;
2540 m_board->GetDesignSettings().m_TentViasBack = tent;
2541 }
2542
2543 break;
2544 }
2545 case T_zone_defaults:
2547 break;
2548
2549 default:
2550 Unexpected( CurText() );
2551 }
2552 }
2553
2554 // Set up a default stackup in case the file doesn't define one, and now we know
2555 // the enabled layers
2556 if( ! m_board->GetDesignSettings().m_HasStackup )
2557 {
2558 BOARD_STACKUP& stackup = bds.GetStackupDescriptor();
2559 stackup.RemoveAll();
2560 stackup.BuildDefaultStackupList( &bds, m_board->GetCopperLayerCount() );
2561 }
2562}
2563
2564
2566{
2567 T token;
2568
2569 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
2570 {
2571 if( token != T_LEFT )
2572 {
2573 Expecting( T_LEFT );
2574 }
2575
2576 token = NextTok();
2577
2578 switch( token )
2579 {
2580 case T_property:
2582 break;
2583 default:
2584 Unexpected( CurText() );
2585 }
2586 }
2587}
2588
2589
2591 std::map<PCB_LAYER_ID, ZONE_LAYER_PROPERTIES>& aProperties )
2592{
2593 T token;
2594
2596 ZONE_LAYER_PROPERTIES properties;
2597
2598 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
2599 {
2600 if( token != T_LEFT )
2601 {
2602 Expecting( T_LEFT );
2603 }
2604
2605 token = NextTok();
2606
2607 switch( token )
2608 {
2609 case T_layer:
2610 layer = parseBoardItemLayer();
2611 NeedRIGHT();
2612 break;
2613 case T_hatch_position:
2614 {
2615 properties.hatching_offset = parseXY();
2616 NeedRIGHT();
2617 break;
2618 }
2619 default:
2620 Unexpected( CurText() );
2621 break;
2622 }
2623 }
2624
2625 aProperties.emplace( layer, properties );
2626}
2627
2628
2630{
2631 T token;
2632
2633 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
2634 {
2635 if( token != T_LEFT )
2636 Expecting( T_LEFT );
2637
2638 token = NextTok();
2639
2640 switch( token )
2641 {
2642 case T_edge_clearance:
2643 designSettings.m_CopperEdgeClearance = parseBoardUnits( T_edge_clearance );
2644 m_board->m_LegacyCopperEdgeClearanceLoaded = true;
2645 NeedRIGHT();
2646 break;
2647
2648 case T_copper_line_width:
2649 designSettings.m_LineThickness[ LAYER_CLASS_COPPER ] = parseBoardUnits( token );
2650 NeedRIGHT();
2651 break;
2652
2653 case T_copper_text_dims:
2654 parseDefaultTextDims( designSettings, LAYER_CLASS_COPPER );
2655 break;
2656
2657 case T_courtyard_line_width:
2658 designSettings.m_LineThickness[ LAYER_CLASS_COURTYARD ] = parseBoardUnits( token );
2659 NeedRIGHT();
2660 break;
2661
2662 case T_edge_cuts_line_width:
2663 designSettings.m_LineThickness[ LAYER_CLASS_EDGES ] = parseBoardUnits( token );
2664 NeedRIGHT();
2665 break;
2666
2667 case T_silk_line_width:
2668 designSettings.m_LineThickness[ LAYER_CLASS_SILK ] = parseBoardUnits( token );
2669 NeedRIGHT();
2670 break;
2671
2672 case T_silk_text_dims:
2673 parseDefaultTextDims( designSettings, LAYER_CLASS_SILK );
2674 break;
2675
2676 case T_fab_layers_line_width:
2677 designSettings.m_LineThickness[ LAYER_CLASS_FAB ] = parseBoardUnits( token );
2678 NeedRIGHT();
2679 break;
2680
2681 case T_fab_layers_text_dims:
2682 parseDefaultTextDims( designSettings, LAYER_CLASS_FAB );
2683 break;
2684
2685 case T_other_layers_line_width:
2686 designSettings.m_LineThickness[ LAYER_CLASS_OTHERS ] = parseBoardUnits( token );
2687 NeedRIGHT();
2688 break;
2689
2690 case T_other_layers_text_dims:
2691 parseDefaultTextDims( designSettings, LAYER_CLASS_OTHERS );
2692 break;
2693
2694 case T_dimension_units:
2695 designSettings.m_DimensionUnitsMode =
2696 static_cast<DIM_UNITS_MODE>( parseInt( "dimension units" ) );
2697 NeedRIGHT();
2698 break;
2699
2700 case T_dimension_precision:
2701 designSettings.m_DimensionPrecision =
2702 static_cast<DIM_PRECISION>( parseInt( "dimension precision" ) );
2703 NeedRIGHT();
2704 break;
2705
2706 default:
2707 Unexpected( CurText() );
2708 }
2709 }
2710}
2711
2712
2714{
2715 T token;
2716
2717 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
2718 {
2719 if( token == T_LEFT )
2720 token = NextTok();
2721
2722 switch( token )
2723 {
2724 case T_size:
2725 aSettings.m_TextSize[ aLayer ].x = parseBoardUnits( "default text size X" );
2726 aSettings.m_TextSize[ aLayer ].y = parseBoardUnits( "default text size Y" );
2727 NeedRIGHT();
2728 break;
2729
2730 case T_thickness:
2731 aSettings.m_TextThickness[ aLayer ] = parseBoardUnits( "default text width" );
2732 NeedRIGHT();
2733 break;
2734
2735 case T_italic:
2736 aSettings.m_TextItalic[ aLayer ] = true;
2737 break;
2738
2739 case T_keep_upright:
2740 aSettings.m_TextUpright[ aLayer ] = true;
2741 break;
2742
2743 default:
2744 Expecting( "size, thickness, italic or keep_upright" );
2745 }
2746 }
2747}
2748
2749
2751{
2752 wxCHECK_RET( CurTok() == T_net,
2753 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as net." ) );
2754
2755 int netCode = parseInt( "net number" );
2756
2757 NeedSYMBOLorNUMBER();
2758 wxString name = FromUTF8();
2759
2760 // Convert overbar syntax from `~...~` to `~{...}`. These were left out of the first merge
2761 // so the version is a bit later.
2762 if( m_requiredVersion < 20210606 )
2764
2765 NeedRIGHT();
2766
2767 // net 0 should be already in list, so store this net
2768 // if it is not the net 0, or if the net 0 does not exists.
2769 // (TODO: a better test.)
2770 if( netCode > NETINFO_LIST::UNCONNECTED || !m_board->FindNet( NETINFO_LIST::UNCONNECTED ) )
2771 {
2772 NETINFO_ITEM* net = new NETINFO_ITEM( m_board, name, netCode );
2773 m_board->Add( net, ADD_MODE::INSERT, true );
2774
2775 // Store the new code mapping
2776 pushValueIntoMap( netCode, net->GetNetCode() );
2777 }
2778}
2779
2780
2782{
2783 wxCHECK_RET( CurTok() == T_net_class,
2784 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as net class." ) );
2785
2786 T token;
2787
2788 std::shared_ptr<NETCLASS> nc = std::make_shared<NETCLASS>( wxEmptyString );
2789
2790 // Read netclass name (can be a name or just a number like track width)
2791 NeedSYMBOLorNUMBER();
2792 nc->SetName( FromUTF8() );
2793 NeedSYMBOL();
2794 nc->SetDescription( FromUTF8() );
2795
2796 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
2797 {
2798 if( token != T_LEFT )
2799 Expecting( T_LEFT );
2800
2801 token = NextTok();
2802
2803 switch( token )
2804 {
2805 case T_clearance:
2806 nc->SetClearance( parseBoardUnits( T_clearance ) );
2807 break;
2808
2809 case T_trace_width:
2810 nc->SetTrackWidth( parseBoardUnits( T_trace_width ) );
2811 break;
2812
2813 case T_via_dia:
2814 nc->SetViaDiameter( parseBoardUnits( T_via_dia ) );
2815 break;
2816
2817 case T_via_drill:
2818 nc->SetViaDrill( parseBoardUnits( T_via_drill ) );
2819 break;
2820
2821 case T_uvia_dia:
2822 nc->SetuViaDiameter( parseBoardUnits( T_uvia_dia ) );
2823 break;
2824
2825 case T_uvia_drill:
2826 nc->SetuViaDrill( parseBoardUnits( T_uvia_drill ) );
2827 break;
2828
2829 case T_diff_pair_width:
2830 nc->SetDiffPairWidth( parseBoardUnits( T_diff_pair_width ) );
2831 break;
2832
2833 case T_diff_pair_gap:
2834 nc->SetDiffPairGap( parseBoardUnits( T_diff_pair_gap ) );
2835 break;
2836
2837 case T_add_net:
2838 {
2839 NeedSYMBOLorNUMBER();
2840
2841 wxString netName = FromUTF8();
2842
2843 // Convert overbar syntax from `~...~` to `~{...}`. These were left out of the
2844 // first merge so the version is a bit later.
2845 if( m_requiredVersion < 20210606 )
2846 netName = ConvertToNewOverbarNotation( FromUTF8() );
2847
2848 m_board->GetDesignSettings().m_NetSettings->SetNetclassPatternAssignment(
2849 netName, nc->GetName() );
2850
2851 break;
2852 }
2853
2854 default:
2855 Expecting( "clearance, trace_width, via_dia, via_drill, uvia_dia, uvia_drill, "
2856 "diff_pair_width, diff_pair_gap or add_net" );
2857 }
2858
2859 NeedRIGHT();
2860 }
2861
2862 std::shared_ptr<NET_SETTINGS>& netSettings = m_board->GetDesignSettings().m_NetSettings;
2863
2864 if( netSettings->HasNetclass( nc->GetName() ) )
2865 {
2866 // Must have been a name conflict, this is a bad board file.
2867 // User may have done a hand edit to the file.
2868 wxString error;
2869 error.Printf( _( "Duplicate NETCLASS name '%s' in file '%s' at line %d, offset %d." ),
2870 nc->GetName().GetData(), CurSource().GetData(), CurLineNumber(),
2871 CurOffset() );
2872 THROW_IO_ERROR( error );
2873 }
2874 else if( nc->GetName() == netSettings->GetDefaultNetclass()->GetName() )
2875 {
2876 netSettings->SetDefaultNetclass( nc );
2877 }
2878 else
2879 {
2880 netSettings->SetNetclass( nc->GetName(), nc );
2881 }
2882}
2883
2884
2886{
2887 wxCHECK_MSG( CurTok() == T_fp_arc || CurTok() == T_fp_circle || CurTok() == T_fp_curve ||
2888 CurTok() == T_fp_rect || CurTok() == T_fp_line || CurTok() == T_fp_poly ||
2889 CurTok() == T_gr_arc || CurTok() == T_gr_circle || CurTok() == T_gr_curve ||
2890 CurTok() == T_gr_rect || CurTok() == T_gr_bbox || CurTok() == T_gr_line ||
2891 CurTok() == T_gr_poly || CurTok() == T_gr_vector, nullptr,
2892 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as PCB_SHAPE." ) );
2893
2894 T token;
2895 VECTOR2I pt;
2896 STROKE_PARAMS stroke( 0, LINE_STYLE::SOLID );
2897 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( aParent );
2898
2899 switch( CurTok() )
2900 {
2901 case T_gr_arc:
2902 case T_fp_arc:
2903 shape->SetShape( SHAPE_T::ARC );
2904 token = NextTok();
2905
2906 if( token == T_locked )
2907 {
2908 shape->SetLocked( true );
2909 token = NextTok();
2910 }
2911
2912 if( token != T_LEFT )
2913 Expecting( T_LEFT );
2914
2915 token = NextTok();
2916
2918 {
2919 // In legacy files the start keyword actually gives the arc center...
2920 if( token != T_start )
2921 Expecting( T_start );
2922
2923 pt.x = parseBoardUnits( "X coordinate" );
2924 pt.y = parseBoardUnits( "Y coordinate" );
2925 shape->SetCenter( pt );
2926 NeedRIGHT();
2927 NeedLEFT();
2928 token = NextTok();
2929
2930 // ... and the end keyword gives the start point of the arc
2931 if( token != T_end )
2932 Expecting( T_end );
2933
2934 pt.x = parseBoardUnits( "X coordinate" );
2935 pt.y = parseBoardUnits( "Y coordinate" );
2936 shape->SetStart( pt );
2937 NeedRIGHT();
2938 NeedLEFT();
2939 token = NextTok();
2940
2941 if( token != T_angle )
2942 Expecting( T_angle );
2943
2944 shape->SetArcAngleAndEnd( EDA_ANGLE( parseDouble( "arc angle" ), DEGREES_T ), true );
2945 NeedRIGHT();
2946 }
2947 else
2948 {
2949 VECTOR2I arc_start, arc_mid, arc_end;
2950
2951 if( token != T_start )
2952 Expecting( T_start );
2953
2954 arc_start.x = parseBoardUnits( "X coordinate" );
2955 arc_start.y = parseBoardUnits( "Y coordinate" );
2956 NeedRIGHT();
2957 NeedLEFT();
2958 token = NextTok();
2959
2960 if( token != T_mid )
2961 Expecting( T_mid );
2962
2963 arc_mid.x = parseBoardUnits( "X coordinate" );
2964 arc_mid.y = parseBoardUnits( "Y coordinate" );
2965 NeedRIGHT();
2966 NeedLEFT();
2967 token = NextTok();
2968
2969 if( token != T_end )
2970 Expecting( T_end );
2971
2972 arc_end.x = parseBoardUnits( "X coordinate" );
2973 arc_end.y = parseBoardUnits( "Y coordinate" );
2974 NeedRIGHT();
2975
2976 shape->SetArcGeometry( arc_start, arc_mid, arc_end );
2977 }
2978
2979 break;
2980
2981 case T_gr_circle:
2982 case T_fp_circle:
2983 shape->SetShape( SHAPE_T::CIRCLE );
2984 token = NextTok();
2985
2986 if( token == T_locked )
2987 {
2988 shape->SetLocked( true );
2989 token = NextTok();
2990 }
2991
2992 if( token != T_LEFT )
2993 Expecting( T_LEFT );
2994
2995 token = NextTok();
2996
2997 if( token != T_center )
2998 Expecting( T_center );
2999
3000 pt.x = parseBoardUnits( "X coordinate" );
3001 pt.y = parseBoardUnits( "Y coordinate" );
3002 shape->SetStart( pt );
3003 NeedRIGHT();
3004 NeedLEFT();
3005
3006 token = NextTok();
3007
3008 if( token != T_end )
3009 Expecting( T_end );
3010
3011 pt.x = parseBoardUnits( "X coordinate" );
3012 pt.y = parseBoardUnits( "Y coordinate" );
3013 shape->SetEnd( pt );
3014 NeedRIGHT();
3015 break;
3016
3017 case T_gr_curve:
3018 case T_fp_curve:
3019 shape->SetShape( SHAPE_T::BEZIER );
3020 token = NextTok();
3021
3022 if( token == T_locked )
3023 {
3024 shape->SetLocked( true );
3025 token = NextTok();
3026 }
3027
3028 if( token != T_LEFT )
3029 Expecting( T_LEFT );
3030
3031 token = NextTok();
3032
3033 if( token != T_pts )
3034 Expecting( T_pts );
3035
3036 shape->SetStart( parseXY() );
3037 shape->SetBezierC1( parseXY());
3038 shape->SetBezierC2( parseXY());
3039 shape->SetEnd( parseXY() );
3040
3041 if( m_board )
3042 shape->RebuildBezierToSegmentsPointsList( m_board->GetDesignSettings().m_MaxError );
3043 else
3044 shape->RebuildBezierToSegmentsPointsList( ARC_HIGH_DEF );
3045
3046 NeedRIGHT();
3047 break;
3048
3049 case T_gr_bbox:
3050 case T_gr_rect:
3051 case T_fp_rect:
3052 shape->SetShape( SHAPE_T::RECTANGLE );
3053 token = NextTok();
3054
3055 if( token == T_locked )
3056 {
3057 shape->SetLocked( true );
3058 token = NextTok();
3059 }
3060
3061 if( token != T_LEFT )
3062 Expecting( T_LEFT );
3063
3064 token = NextTok();
3065
3066 if( token != T_start )
3067 Expecting( T_start );
3068
3069 pt.x = parseBoardUnits( "X coordinate" );
3070 pt.y = parseBoardUnits( "Y coordinate" );
3071 shape->SetStart( pt );
3072 NeedRIGHT();
3073 NeedLEFT();
3074 token = NextTok();
3075
3076 if( token != T_end )
3077 Expecting( T_end );
3078
3079 pt.x = parseBoardUnits( "X coordinate" );
3080 pt.y = parseBoardUnits( "Y coordinate" );
3081 shape->SetEnd( pt );
3082
3083 if( aParent && aParent->Type() == PCB_FOOTPRINT_T )
3084 {
3085 // Footprint shapes are stored in board-relative coordinates, but we want the
3086 // normalization to remain in footprint-relative coordinates.
3087 }
3088 else
3089 {
3090 shape->Normalize();
3091 }
3092
3093 NeedRIGHT();
3094 break;
3095
3096 case T_gr_vector:
3097 case T_gr_line:
3098 case T_fp_line:
3099 shape->SetShape( SHAPE_T::SEGMENT );
3100 token = NextTok();
3101
3102 if( token == T_locked )
3103 {
3104 shape->SetLocked( true );
3105 token = NextTok();
3106 }
3107
3108 if( token != T_LEFT )
3109 Expecting( T_LEFT );
3110
3111 token = NextTok();
3112
3113 if( token != T_start )
3114 Expecting( T_start );
3115
3116 pt.x = parseBoardUnits( "X coordinate" );
3117 pt.y = parseBoardUnits( "Y coordinate" );
3118 shape->SetStart( pt );
3119 NeedRIGHT();
3120 NeedLEFT();
3121 token = NextTok();
3122
3123 if( token != T_end )
3124 Expecting( T_end );
3125
3126 pt.x = parseBoardUnits( "X coordinate" );
3127 pt.y = parseBoardUnits( "Y coordinate" );
3128 shape->SetEnd( pt );
3129 NeedRIGHT();
3130 break;
3131
3132 case T_gr_poly:
3133 case T_fp_poly:
3134 {
3135 shape->SetShape( SHAPE_T::POLY );
3136 shape->SetPolyPoints( {} );
3137
3138 SHAPE_LINE_CHAIN& outline = shape->GetPolyShape().Outline( 0 );
3139
3140 token = NextTok();
3141
3142 if( token == T_locked )
3143 {
3144 shape->SetLocked( true );
3145 token = NextTok();
3146 }
3147
3148 if( token != T_LEFT )
3149 Expecting( T_LEFT );
3150
3151 token = NextTok();
3152
3153 if( token != T_pts )
3154 Expecting( T_pts );
3155
3156 while( (token = NextTok() ) != T_RIGHT )
3157 parseOutlinePoints( outline );
3158
3159 break;
3160 }
3161
3162 default:
3163 if( aParent && aParent->Type() == PCB_FOOTPRINT_T )
3164 {
3165 Expecting( "fp_arc, fp_circle, fp_curve, fp_line, fp_poly or fp_rect" );
3166 }
3167 else
3168 {
3169 Expecting( "gr_arc, gr_circle, gr_curve, gr_vector, gr_line, gr_poly, gr_rect or "
3170 "gr_bbox" );
3171 }
3172 }
3173
3174 bool foundFill = false;
3175
3176 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
3177 {
3178 if( token != T_LEFT )
3179 Expecting( T_LEFT );
3180
3181 token = NextTok();
3182
3183 switch( token )
3184 {
3185 case T_angle: // legacy token; ignore value
3186 parseDouble( "arc angle" );
3187 NeedRIGHT();
3188 break;
3189
3190 case T_layer:
3191 shape->SetLayer( parseBoardItemLayer() );
3192 NeedRIGHT();
3193 break;
3194
3195 case T_layers:
3196 shape->SetLayerSet( parseBoardItemLayersAsMask() );
3197 break;
3198
3199 case T_solder_mask_margin:
3200 shape->SetLocalSolderMaskMargin( parseBoardUnits( "local solder mask margin value" ) );
3201 NeedRIGHT();
3202 break;
3203
3204 case T_width: // legacy token
3205 stroke.SetWidth( parseBoardUnits( T_width ) );
3206 NeedRIGHT();
3207 break;
3208
3209 case T_radius:
3210 shape->SetCornerRadius( parseBoardUnits( "corner radius" ) );
3211 NeedRIGHT();
3212 break;
3213
3214 case T_stroke:
3215 {
3216 STROKE_PARAMS_PARSER strokeParser( reader, pcbIUScale.IU_PER_MM );
3217 strokeParser.SyncLineReaderWith( *this );
3218
3219 strokeParser.ParseStroke( stroke );
3220 SyncLineReaderWith( strokeParser );
3221 break;
3222 }
3223
3224 case T_tstamp:
3225 case T_uuid:
3226 NextTok();
3227 const_cast<KIID&>( shape->m_Uuid ) = CurStrToKIID();
3228 NeedRIGHT();
3229 break;
3230
3231 case T_fill:
3232 foundFill = true;
3233
3234 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
3235 {
3236 if( token == T_LEFT )
3237 token = NextTok();
3238
3239 switch( token )
3240 {
3241 // T_yes was used to indicate filling when first introduced, so treat it like a
3242 // solid fill since that was the only fill available at the time.
3243 case T_yes:
3244 case T_solid: shape->SetFillMode( FILL_T::FILLED_SHAPE ); break;
3245
3246 case T_none:
3247 case T_no: shape->SetFillMode( FILL_T::NO_FILL ); break;
3248
3249 case T_hatch: shape->SetFillMode( FILL_T::HATCH ); break;
3250 case T_reverse_hatch: shape->SetFillMode( FILL_T::REVERSE_HATCH ); break;
3251 case T_cross_hatch: shape->SetFillMode( FILL_T::CROSS_HATCH ); break;
3252
3253 default: Expecting( "yes, no, solid, none, hatch, reverse_hatch or cross_hatch" );
3254 }
3255 }
3256
3257 break;
3258
3259 case T_status: // legacy token; ignore value
3260 parseHex();
3261 NeedRIGHT();
3262 break;
3263
3264 // Handle "(locked)" from 5.99 development, and "(locked yes)" from modern times
3265 case T_locked:
3266 shape->SetLocked( parseMaybeAbsentBool( true ) );
3267 break;
3268
3269 case T_net:
3270 if( !shape->SetNetCode( getNetCode( parseInt( "net number" ) ), /* aNoAssert */ true ) )
3271 {
3272 wxLogError( _( "Invalid net ID in\nfile: '%s'\nline: %d\noffset: %d." ),
3273 CurSource(), CurLineNumber(), CurOffset() );
3274 }
3275
3276 NeedRIGHT();
3277 break;
3278
3279 default:
3280 Expecting( "layer, width, fill, tstamp, uuid, locked, net, status, "
3281 "or solder_mask_margin" );
3282 }
3283 }
3284
3285 if( !foundFill )
3286 {
3287 // Legacy versions didn't have a filled flag but allowed some shapes to indicate they
3288 // should be filled by specifying a 0 stroke-width.
3289 if( stroke.GetWidth() == 0
3290 && ( shape->GetShape() == SHAPE_T::RECTANGLE || shape->GetShape() == SHAPE_T::CIRCLE ) )
3291 {
3292 shape->SetFilled( true );
3293 }
3294 else if( shape->GetShape() == SHAPE_T::POLY && shape->GetLayer() != Edge_Cuts )
3295 {
3296 // Polygons on non-Edge_Cuts layers were always filled.
3297 shape->SetFilled( true );
3298 }
3299 }
3300
3301 // Only filled shapes may have a zero line-width. This is not permitted in KiCad but some
3302 // external tools can generate invalid files.
3303 if( stroke.GetWidth() <= 0 && !shape->IsAnyFill() )
3304 {
3305 stroke.SetWidth( pcbIUScale.mmToIU( DEFAULT_LINE_WIDTH ) );
3306 }
3307
3308 shape->SetStroke( stroke );
3309
3310 if( FOOTPRINT* parentFP = shape->GetParentFootprint() )
3311 {
3312 shape->Rotate( { 0, 0 }, parentFP->GetOrientation() );
3313 shape->Move( parentFP->GetPosition() );
3314 }
3315
3316 return shape.release();
3317}
3318
3319
3321{
3322 wxCHECK_MSG( CurTok() == T_image, nullptr,
3323 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a reference image." ) );
3324
3325 T token;
3326 std::unique_ptr<PCB_REFERENCE_IMAGE> bitmap = std::make_unique<PCB_REFERENCE_IMAGE>( aParent );
3327
3328 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
3329 {
3330 if( token != T_LEFT )
3331 Expecting( T_LEFT );
3332
3333 token = NextTok();
3334
3335 switch( token )
3336 {
3337 case T_at:
3338 {
3339 VECTOR2I pos;
3340 pos.x = parseBoardUnits( "X coordinate" );
3341 pos.y = parseBoardUnits( "Y coordinate" );
3342 bitmap->SetPosition( pos );
3343 NeedRIGHT();
3344 break;
3345 }
3346
3347 case T_layer:
3348 bitmap->SetLayer( parseBoardItemLayer() );
3349 NeedRIGHT();
3350 break;
3351
3352 case T_scale:
3353 {
3354 REFERENCE_IMAGE& refImage = bitmap->GetReferenceImage();
3355 refImage.SetImageScale( parseDouble( "image scale factor" ) );
3356
3357 if( !std::isnormal( refImage.GetImageScale() ) )
3358 refImage.SetImageScale( 1.0 );
3359
3360 NeedRIGHT();
3361 break;
3362 }
3363 case T_data:
3364 {
3365 token = NextTok();
3366
3367 wxString data;
3368
3369 // Reserve 512K because most image files are going to be larger than the default
3370 // 1K that wxString reserves.
3371 data.reserve( 1 << 19 );
3372
3373 while( token != T_RIGHT )
3374 {
3375 if( !IsSymbol( token ) )
3376 Expecting( "base64 image data" );
3377
3378 data += FromUTF8();
3379 token = NextTok();
3380 }
3381
3382 wxMemoryBuffer buffer = wxBase64Decode( data );
3383
3384 REFERENCE_IMAGE& refImage = bitmap->GetReferenceImage();
3385 if( !refImage.ReadImageFile( buffer ) )
3386 THROW_IO_ERROR( _( "Failed to read image data." ) );
3387
3388 break;
3389 }
3390
3391 case T_locked:
3392 {
3393 // This has only ever been (locked yes) format
3394 const bool locked = parseBool();
3395 bitmap->SetLocked( locked );
3396
3397 NeedRIGHT();
3398 break;
3399 }
3400
3401 case T_uuid:
3402 {
3403 NextTok();
3404 const_cast<KIID&>( bitmap->m_Uuid ) = CurStrToKIID();
3405 NeedRIGHT();
3406 break;
3407 }
3408
3409 default:
3410 Expecting( "at, layer, scale, data, locked or uuid" );
3411 }
3412 }
3413
3414 return bitmap.release();
3415}
3416
3417
3419{
3420 wxCHECK_MSG( CurTok() == T_gr_text || CurTok() == T_fp_text, nullptr,
3421 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as PCB_TEXT." ) );
3422
3423 FOOTPRINT* parentFP = dynamic_cast<FOOTPRINT*>( aParent );
3424 std::unique_ptr<PCB_TEXT> text;
3425
3426 T token = NextTok();
3427
3428 // If a base text is provided, we have a derived text already parsed and just need to update it
3429 if( aBaseText )
3430 {
3431 text = std::unique_ptr<PCB_TEXT>( aBaseText );
3432 }
3433 else if( parentFP )
3434 {
3435 switch( token )
3436 {
3437 case T_reference:
3438 text = std::make_unique<PCB_FIELD>( parentFP, FIELD_T::REFERENCE );
3439 break;
3440
3441 case T_value:
3442 text = std::make_unique<PCB_FIELD>( parentFP, FIELD_T::VALUE );
3443 break;
3444
3445 case T_user:
3446 text = std::make_unique<PCB_TEXT>( parentFP );
3447 break;
3448
3449 default:
3450 THROW_IO_ERROR( wxString::Format( _( "Cannot handle footprint text type %s" ),
3451 FromUTF8() ) );
3452 }
3453
3454 token = NextTok();
3455 }
3456 else
3457 {
3458 text = std::make_unique<PCB_TEXT>( aParent );
3459 }
3460
3461 // Legacy bare locked token
3462 if( token == T_locked )
3463 {
3464 text->SetLocked( true );
3465 token = NextTok();
3466 }
3467
3468 if( !IsSymbol( token ) && (int) token != DSN_NUMBER )
3469 Expecting( "text value" );
3470
3471 wxString value = FromUTF8();
3472 value.Replace( wxT( "%V" ), wxT( "${VALUE}" ) );
3473 value.Replace( wxT( "%R" ), wxT( "${REFERENCE}" ) );
3474 text->SetText( value );
3475
3476 NeedLEFT();
3477
3478 parsePCB_TEXT_effects( text.get(), aBaseText );
3479
3480 if( parentFP )
3481 {
3482 // Convert hidden footprint text (which is no longer supported) into a hidden field
3483 if( !text->IsVisible() && text->Type() == PCB_TEXT_T )
3484 {
3485 wxString fieldName = GetUserFieldName( parentFP->GetFields().size(), !DO_TRANSLATE );
3486 return new PCB_FIELD( *text.get(), FIELD_T::USER, fieldName );
3487 }
3488 }
3489 else
3490 {
3491 // Hidden PCB text is no longer supported
3492 text->SetVisible( true );
3493 }
3494
3495 return text.release();
3496}
3497
3498
3500{
3501 FOOTPRINT* parentFP = dynamic_cast<FOOTPRINT*>( aText->GetParent() );
3502 bool hasAngle = false; // Old files do not have a angle specified.
3503 // in this case it is 0 expected to be 0
3504 bool hasPos = false;
3505
3506 // By default, texts in footprints have a locked rotation (i.e. rot = -90 ... 90 deg)
3507 if( parentFP )
3508 aText->SetKeepUpright( true );
3509
3510 for( T token = NextTok(); token != T_RIGHT; token = NextTok() )
3511 {
3512 if( token == T_LEFT )
3513 token = NextTok();
3514
3515 switch( token )
3516 {
3517 case T_at:
3518 {
3519 VECTOR2I pt;
3520
3521 hasPos = true;
3522 pt.x = parseBoardUnits( "X coordinate" );
3523 pt.y = parseBoardUnits( "Y coordinate" );
3524 aText->SetTextPos( pt );
3525 token = NextTok();
3526
3527 if( CurTok() == T_NUMBER )
3528 {
3530 hasAngle = true;
3531 token = NextTok();
3532 }
3533
3534 // Legacy location of this token; presence implies true
3535 if( parentFP && CurTok() == T_unlocked )
3536 {
3537 aText->SetKeepUpright( false );
3538 token = NextTok();
3539 }
3540
3541 if( (int) token != DSN_RIGHT )
3542 Expecting( DSN_RIGHT );
3543
3544 break;
3545 }
3546
3547 case T_layer:
3548 aText->SetLayer( parseBoardItemLayer() );
3549
3550 token = NextTok();
3551
3552 if( token == T_knockout )
3553 {
3554 aText->SetIsKnockout( true );
3555 token = NextTok();
3556 }
3557
3558 if( (int) token != DSN_RIGHT )
3559 Expecting( DSN_RIGHT );
3560
3561 break;
3562
3563 case T_tstamp:
3564 case T_uuid:
3565 NextTok();
3566 const_cast<KIID&>( aText->m_Uuid ) = CurStrToKIID();
3567 NeedRIGHT();
3568 break;
3569
3570 case T_hide:
3571 {
3572 // In older files, the hide token appears bare, and indicates hide==true.
3573 // In newer files, it will be an explicit bool in a list like (hide yes)
3574 bool hide = parseMaybeAbsentBool( true );
3575
3576 if( parentFP )
3577 aText->SetVisible( !hide );
3578 else
3579 Expecting( "layer, effects, locked, render_cache, uuid or tstamp" );
3580
3581 break;
3582 }
3583
3584 case T_locked:
3585 // Newer list-enclosed locked
3586 aText->SetLocked( parseBool() );
3587 NeedRIGHT();
3588 break;
3589
3590 // Confusingly, "unlocked" is not the opposite of "locked", but refers to "keep upright"
3591 case T_unlocked:
3592 if( parentFP )
3593 aText->SetKeepUpright( !parseBool() );
3594 else
3595 Expecting( "layer, effects, locked, render_cache or tstamp" );
3596
3597 NeedRIGHT();
3598 break;
3599
3600 case T_effects:
3601 parseEDA_TEXT( static_cast<EDA_TEXT*>( aText ) );
3602 break;
3603
3604 case T_render_cache:
3605 parseRenderCache( static_cast<EDA_TEXT*>( aText ) );
3606 break;
3607
3608 default:
3609 if( parentFP )
3610 Expecting( "layer, hide, effects, locked, render_cache or tstamp" );
3611 else
3612 Expecting( "layer, effects, locked, render_cache or tstamp" );
3613 }
3614 }
3615
3616 // If there is no orientation defined, then it is the default value of 0 degrees.
3617 if( !hasAngle )
3618 aText->SetTextAngle( ANGLE_0 );
3619
3620 if( parentFP && !dynamic_cast<PCB_DIMENSION_BASE*>( aBaseText ) )
3621 {
3622 // make PCB_TEXT rotation relative to the parent footprint.
3623 // It was read as absolute rotation from file
3624 // Note: this is not rue for PCB_DIMENSION items that use the board
3625 // coordinates
3626 aText->SetTextAngle( aText->GetTextAngle() - parentFP->GetOrientation() );
3627
3628 // Move and rotate the text to its board coordinates
3629 aText->Rotate( { 0, 0 }, parentFP->GetOrientation() );
3630
3631 // Only move offset from parent position if we read a position from the file.
3632 // These positions are relative to the parent footprint. If we don't have a position
3633 // then the text defaults to the parent position and moving again will double it.
3634 if (hasPos)
3635 aText->Move( parentFP->GetPosition() );
3636 }
3637}
3638
3639
3641{
3642 wxCHECK_MSG( CurTok() == T_barcode, nullptr,
3643 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as PCB_BARCODE." ) );
3644
3645 std::unique_ptr<PCB_BARCODE> barcode = std::make_unique<PCB_BARCODE>( aParent );
3646
3647 for( T token = NextTok(); token != T_RIGHT; token = NextTok() )
3648 {
3649 if( token != T_LEFT )
3650 Expecting( T_LEFT );
3651
3652 token = NextTok();
3653
3654 switch( token )
3655 {
3656 case T_at:
3657 {
3658 VECTOR2I pos;
3659 pos.x = parseBoardUnits( "X coordinate" );
3660 pos.y = parseBoardUnits( "Y coordinate" );
3661 barcode->SetPosition( pos );
3662 token = NextTok();
3663
3664 if( CurTok() == T_NUMBER )
3665 barcode->SetOrientation( parseDouble() );
3666
3667 NeedRIGHT();
3668 break;
3669 }
3670
3671 case T_layer:
3672 barcode->SetLayer( parseBoardItemLayer() );
3673 NeedRIGHT();
3674 break;
3675
3676 case T_size:
3677 {
3678 int w = parseBoardUnits( "barcode width" );
3679 int h = parseBoardUnits( "barcode height" );
3680 barcode->SetWidth( w );
3681 barcode->SetHeight( h );
3682 NeedRIGHT();
3683 break;
3684 }
3685
3686 case T_text:
3687
3688 if( NextTok() != T_STRING )
3689 Expecting( T_STRING );
3690
3691 barcode->SetText( FromUTF8() );
3692 NeedRIGHT();
3693 break;
3694
3695 case T_text_height:
3696 {
3697 int h = parseBoardUnits( "barcode text height" );
3698 barcode->SetTextSize( h );
3699 NeedRIGHT();
3700 break;
3701 }
3702
3703 case T_type:
3704 NeedSYMBOL();
3705 {
3706 std::string kind = CurText();
3707 if( kind == "code39" )
3708 barcode->SetKind( BARCODE_T::CODE_39 );
3709 else if( kind == "code128" )
3710 barcode->SetKind( BARCODE_T::CODE_128 );
3711 else if( kind == "datamatrix" || kind == "data_matrix" )
3712 barcode->SetKind( BARCODE_T::DATA_MATRIX );
3713 else if( kind == "qr" || kind == "qrcode" )
3714 barcode->SetKind( BARCODE_T::QR_CODE );
3715 else if( kind == "microqr" || kind == "micro_qr" )
3716 barcode->SetKind( BARCODE_T::MICRO_QR_CODE );
3717 else
3718 Expecting( "barcode type" );
3719 }
3720 NeedRIGHT();
3721 break;
3722
3723 case T_ecc_level:
3724 NeedSYMBOL();
3725 {
3726 std::string ecc = CurText();
3727 if( ecc == "L" || ecc == "l" )
3728 barcode->SetErrorCorrection( BARCODE_ECC_T::L );
3729 else if( ecc == "M" || ecc == "m" )
3730 barcode->SetErrorCorrection( BARCODE_ECC_T::M );
3731 else if( ecc == "Q" || ecc == "q" )
3732 barcode->SetErrorCorrection( BARCODE_ECC_T::Q );
3733 else if( ecc == "H" || ecc == "h" )
3734 barcode->SetErrorCorrection( BARCODE_ECC_T::H );
3735 else
3736 Expecting( "ecc level" );
3737 }
3738 NeedRIGHT();
3739 break;
3740
3741
3742 case T_locked:
3743 barcode->SetLocked( parseMaybeAbsentBool( true ) );
3744 break;
3745
3746 case T_tstamp:
3747 case T_uuid:
3748 NextTok();
3749 const_cast<KIID&>( barcode->m_Uuid ) = CurStrToKIID();
3750 NeedRIGHT();
3751 break;
3752
3753 default:
3754 Expecting( "at, layer, size, text, text_height, type, ecc_level, locked or uuid" );
3755 }
3756 }
3757
3758 barcode->AssembleBarcode();
3759
3760 return barcode.release();
3761}
3762
3763
3765{
3766 wxCHECK_MSG( CurTok() == T_gr_text_box || CurTok() == T_fp_text_box, nullptr,
3767 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as PCB_TEXTBOX." ) );
3768
3769 std::unique_ptr<PCB_TEXTBOX> textbox = std::make_unique<PCB_TEXTBOX>( aParent );
3770
3771 parseTextBoxContent( textbox.get() );
3772
3773 return textbox.release();
3774}
3775
3776
3778{
3779 wxCHECK_MSG( CurTok() == T_table_cell, nullptr,
3780 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a table cell." ) );
3781
3782 std::unique_ptr<PCB_TABLECELL> cell = std::make_unique<PCB_TABLECELL>( aParent );
3783
3784 parseTextBoxContent( cell.get() );
3785
3786 return cell.release();
3787}
3788
3789
3791{
3792 int left;
3793 int top;
3794 int right;
3795 int bottom;
3796 STROKE_PARAMS stroke( -1, LINE_STYLE::SOLID );
3797 bool foundMargins = false;
3798
3799 T token = NextTok();
3800
3801 // Legacy locked
3802 if( token == T_locked )
3803 {
3804 aTextBox->SetLocked( true );
3805 token = NextTok();
3806 }
3807
3808 if( !IsSymbol( token ) && (int) token != DSN_NUMBER )
3809 Expecting( "text value" );
3810
3811 aTextBox->SetText( FromUTF8() );
3812
3813 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
3814 {
3815 if( token != T_LEFT )
3816 Expecting( T_LEFT );
3817
3818 token = NextTok();
3819
3820 switch( token )
3821 {
3822 case T_locked:
3823 aTextBox->SetLocked( parseMaybeAbsentBool( true ) );
3824 break;
3825
3826 case T_start:
3827 {
3828 int x = parseBoardUnits( "X coordinate" );
3829 int y = parseBoardUnits( "Y coordinate" );
3830 aTextBox->SetStart( VECTOR2I( x, y ) );
3831 NeedRIGHT();
3832
3833 NeedLEFT();
3834 token = NextTok();
3835
3836 if( token != T_end )
3837 Expecting( T_end );
3838
3839 x = parseBoardUnits( "X coordinate" );
3840 y = parseBoardUnits( "Y coordinate" );
3841 aTextBox->SetEnd( VECTOR2I( x, y ) );
3842 NeedRIGHT();
3843 break;
3844 }
3845
3846 case T_pts:
3847 {
3848 aTextBox->SetShape( SHAPE_T::POLY );
3849 aTextBox->GetPolyShape().RemoveAllContours();
3850 aTextBox->GetPolyShape().NewOutline();
3851
3852 while( (token = NextTok() ) != T_RIGHT )
3853 parseOutlinePoints( aTextBox->GetPolyShape().Outline( 0 ) );
3854
3855 break;
3856 }
3857
3858 case T_angle:
3859 // Set the angle of the text only, the coordinates of the box (a polygon) are
3860 // already at the right position, and must not be rotated
3861 aTextBox->EDA_TEXT::SetTextAngle( EDA_ANGLE( parseDouble( "text box angle" ), DEGREES_T ) );
3862 NeedRIGHT();
3863 break;
3864
3865 case T_stroke:
3866 {
3867 STROKE_PARAMS_PARSER strokeParser( reader, pcbIUScale.IU_PER_MM );
3868 strokeParser.SyncLineReaderWith( *this );
3869
3870 strokeParser.ParseStroke( stroke );
3871 SyncLineReaderWith( strokeParser );
3872 break;
3873 }
3874
3875 case T_border:
3876 aTextBox->SetBorderEnabled( parseBool() );
3877 NeedRIGHT();
3878 break;
3879
3880 case T_margins:
3881 parseMargins( left, top, right, bottom );
3882 aTextBox->SetMarginLeft( left );
3883 aTextBox->SetMarginTop( top );
3884 aTextBox->SetMarginRight( right );
3885 aTextBox->SetMarginBottom( bottom );
3886 foundMargins = true;
3887 NeedRIGHT();
3888 break;
3889
3890 case T_layer:
3891 aTextBox->SetLayer( parseBoardItemLayer() );
3892 NeedRIGHT();
3893 break;
3894
3895 case T_knockout:
3896 if( [[maybe_unused]] PCB_TABLECELL* cell = dynamic_cast<PCB_TABLECELL*>( aTextBox ) )
3897 {
3898 Expecting( "locked, start, pts, angle, width, margins, layer, effects, span, "
3899 "render_cache, uuid or tstamp" );
3900 }
3901 else
3902 {
3903 aTextBox->SetIsKnockout( parseBool() );
3904 }
3905
3906 NeedRIGHT();
3907 break;
3908
3909 case T_span:
3910 if( PCB_TABLECELL* cell = dynamic_cast<PCB_TABLECELL*>( aTextBox ) )
3911 {
3912 cell->SetColSpan( parseInt( "column span" ) );
3913 cell->SetRowSpan( parseInt( "row span" ) );
3914 }
3915 else
3916 {
3917 Expecting( "locked, start, pts, angle, width, stroke, border, margins, knockout, "
3918 "layer, effects, render_cache, uuid or tstamp" );
3919 }
3920
3921 NeedRIGHT();
3922 break;
3923
3924 case T_tstamp:
3925 case T_uuid:
3926 NextTok();
3927 const_cast<KIID&>( aTextBox->m_Uuid ) = CurStrToKIID();
3928 NeedRIGHT();
3929 break;
3930
3931 case T_effects:
3932 parseEDA_TEXT( static_cast<EDA_TEXT*>( aTextBox ) );
3933 break;
3934
3935 case T_render_cache:
3936 parseRenderCache( static_cast<EDA_TEXT*>( aTextBox ) );
3937 break;
3938
3939 default:
3940 if( dynamic_cast<PCB_TABLECELL*>( aTextBox ) != nullptr )
3941 {
3942 Expecting( "locked, start, pts, angle, width, margins, layer, effects, span, "
3943 "render_cache, uuid or tstamp" );
3944 }
3945 else
3946 {
3947 Expecting( "locked, start, pts, angle, width, stroke, border, margins, knockout,"
3948 "layer, effects, render_cache, uuid or tstamp" );
3949 }
3950 }
3951 }
3952
3953 aTextBox->SetStroke( stroke );
3954
3955 if( m_requiredVersion < 20230825 ) // compat, we move to an explicit flag
3956 aTextBox->SetBorderEnabled( stroke.GetWidth() >= 0 );
3957
3958 if( !foundMargins )
3959 {
3960 int margin = aTextBox->GetLegacyTextMargin();
3961 aTextBox->SetMarginLeft( margin );
3962 aTextBox->SetMarginTop( margin );
3963 aTextBox->SetMarginRight( margin );
3964 aTextBox->SetMarginBottom( margin );
3965 }
3966
3967 if( FOOTPRINT* parentFP = aTextBox->GetParentFootprint() )
3968 {
3969 aTextBox->Rotate( { 0, 0 }, parentFP->GetOrientation() );
3970 aTextBox->Move( parentFP->GetPosition() );
3971 }
3972}
3973
3974
3976{
3977 wxCHECK_MSG( CurTok() == T_table, nullptr,
3978 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a table." ) );
3979
3980 T token;
3981 STROKE_PARAMS borderStroke( -1, LINE_STYLE::SOLID );
3982 STROKE_PARAMS separatorsStroke( -1, LINE_STYLE::SOLID );
3983 std::unique_ptr<PCB_TABLE> table = std::make_unique<PCB_TABLE>( aParent, -1 );
3984
3985 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
3986 {
3987 if( token != T_LEFT )
3988 Expecting( T_LEFT );
3989
3990 token = NextTok();
3991
3992 switch( token )
3993 {
3994 case T_column_count:
3995 table->SetColCount( parseInt( "column count" ) );
3996 NeedRIGHT();
3997 break;
3998
3999 case T_uuid:
4000 NextTok();
4001 const_cast<KIID&>( table->m_Uuid ) = CurStrToKIID();
4002 NeedRIGHT();
4003 break;
4004
4005 case T_locked:
4006 table->SetLocked( parseBool() );
4007 NeedRIGHT();
4008 break;
4009
4010 case T_angle: // legacy token no longer used
4011 NeedRIGHT();
4012 break;
4013
4014 case T_layer:
4015 table->SetLayer( parseBoardItemLayer() );
4016 NeedRIGHT();
4017 break;
4018
4019 case T_column_widths:
4020 {
4021 int col = 0;
4022
4023 while( ( token = NextTok() ) != T_RIGHT )
4024 table->SetColWidth( col++, parseBoardUnits() );
4025
4026 break;
4027 }
4028
4029 case T_row_heights:
4030 {
4031 int row = 0;
4032
4033 while( ( token = NextTok() ) != T_RIGHT )
4034 table->SetRowHeight( row++, parseBoardUnits() );
4035
4036 break;
4037 }
4038
4039 case T_cells:
4040 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
4041 {
4042 if( token != T_LEFT )
4043 Expecting( T_LEFT );
4044
4045 token = NextTok();
4046
4047 if( token != T_table_cell )
4048 Expecting( "table_cell" );
4049
4050 table->AddCell( parsePCB_TABLECELL( table.get() ) );
4051 }
4052
4053 break;
4054
4055 case T_border:
4056 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
4057 {
4058 if( token != T_LEFT )
4059 Expecting( T_LEFT );
4060
4061 token = NextTok();
4062
4063 switch( token )
4064 {
4065 case T_external:
4066 table->SetStrokeExternal( parseBool() );
4067 NeedRIGHT();
4068 break;
4069
4070 case T_header:
4071 table->SetStrokeHeaderSeparator( parseBool() );
4072 NeedRIGHT();
4073 break;
4074
4075 case T_stroke:
4076 {
4077 STROKE_PARAMS_PARSER strokeParser( reader, pcbIUScale.IU_PER_MM );
4078 strokeParser.SyncLineReaderWith( *this );
4079
4080 strokeParser.ParseStroke( borderStroke );
4081 SyncLineReaderWith( strokeParser );
4082
4083 table->SetBorderStroke( borderStroke );
4084 break;
4085 }
4086
4087 default:
4088 Expecting( "external, header or stroke" );
4089 break;
4090 }
4091 }
4092
4093 break;
4094
4095 case T_separators:
4096 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
4097 {
4098 if( token != T_LEFT )
4099 Expecting( T_LEFT );
4100
4101 token = NextTok();
4102
4103 switch( token )
4104 {
4105 case T_rows:
4106 table->SetStrokeRows( parseBool() );
4107 NeedRIGHT();
4108 break;
4109
4110 case T_cols:
4111 table->SetStrokeColumns( parseBool() );
4112 NeedRIGHT();
4113 break;
4114
4115 case T_stroke:
4116 {
4117 STROKE_PARAMS_PARSER strokeParser( reader, pcbIUScale.IU_PER_MM );
4118 strokeParser.SyncLineReaderWith( *this );
4119
4120 strokeParser.ParseStroke( separatorsStroke );
4121 SyncLineReaderWith( strokeParser );
4122
4123 table->SetSeparatorsStroke( separatorsStroke );
4124 break;
4125 }
4126
4127 default:
4128 Expecting( "rows, cols, or stroke" );
4129 break;
4130 }
4131 }
4132
4133 break;
4134
4135 default:
4136 Expecting( "columns, layer, col_widths, row_heights, border, separators, header or "
4137 "cells" );
4138 }
4139 }
4140
4141 return table.release();
4142}
4143
4144
4146{
4147 wxCHECK_MSG( CurTok() == T_dimension, nullptr,
4148 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as DIMENSION." ) );
4149
4150 T token;
4151 bool locked = false;
4152 std::unique_ptr<PCB_DIMENSION_BASE> dim;
4153
4154 token = NextTok();
4155
4156 // Free 'locked' token from 6.0/7.0 formats
4157 if( token == T_locked )
4158 {
4159 locked = true;
4160 token = NextTok();
4161 }
4162
4163 // skip value that used to be saved
4164 if( token != T_LEFT )
4165 NeedLEFT();
4166
4167 token = NextTok();
4168
4169 bool isLegacyDimension = false;
4170 bool isStyleKnown = false;
4171
4172 // Old format
4173 if( token == T_width )
4174 {
4175 isLegacyDimension = true;
4176 dim = std::make_unique<PCB_DIM_ALIGNED>( aParent );
4177 dim->SetLineThickness( parseBoardUnits( "dimension width value" ) );
4178 NeedRIGHT();
4179 }
4180 else
4181 {
4182 if( token != T_type )
4183 Expecting( T_type );
4184
4185 switch( NextTok() )
4186 {
4187 case T_aligned: dim = std::make_unique<PCB_DIM_ALIGNED>( aParent ); break;
4188 case T_orthogonal: dim = std::make_unique<PCB_DIM_ORTHOGONAL>( aParent ); break;
4189 case T_leader: dim = std::make_unique<PCB_DIM_LEADER>( aParent ); break;
4190 case T_center: dim = std::make_unique<PCB_DIM_CENTER>( aParent ); break;
4191 case T_radial: dim = std::make_unique<PCB_DIM_RADIAL>( aParent ); break;
4192 default: wxFAIL_MSG( wxT( "Cannot parse unknown dimension type " )
4193 + GetTokenString( CurTok() ) );
4194 }
4195
4196 NeedRIGHT();
4197
4198 // Before parsing further, set default properites for old KiCad file
4199 // versions that didnt have these properties:
4200 dim->SetArrowDirection( DIM_ARROW_DIRECTION::OUTWARD );
4201 }
4202
4203 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
4204 {
4205 if( token != T_LEFT )
4206 Expecting( T_LEFT );
4207
4208 token = NextTok();
4209
4210 switch( token )
4211 {
4212 case T_layer:
4213 dim->SetLayer( parseBoardItemLayer() );
4214 NeedRIGHT();
4215 break;
4216
4217 case T_tstamp:
4218 case T_uuid:
4219 NextTok();
4220 const_cast<KIID&>( dim->m_Uuid ) = CurStrToKIID();
4221 NeedRIGHT();
4222 break;
4223
4224 case T_gr_text:
4225 {
4226 // In old pcb files, when parsing the text we do not yet know
4227 // if the text is kept aligned or not, and its DIM_TEXT_POSITION option.
4228 // Leave the text not aligned for now to read the text angle, and no
4229 // constraint for DIM_TEXT_POSITION in this case.
4230 // It will be set aligned (or not) later
4231 bool is_aligned = dim->GetKeepTextAligned();
4232 DIM_TEXT_POSITION t_dim_pos = dim->GetTextPositionMode();
4233
4234 if( !isStyleKnown )
4235 {
4236 dim->SetTextPositionMode( DIM_TEXT_POSITION::MANUAL );
4237 dim->SetKeepTextAligned( false );
4238 }
4239
4240 parsePCB_TEXT( m_board, dim.get() );
4241
4242 if( isLegacyDimension )
4243 {
4244 EDA_UNITS units = EDA_UNITS::MM;
4245
4246 if( !EDA_UNIT_UTILS::FetchUnitsFromString( dim->GetText(), units ) )
4247 dim->SetAutoUnits( true ); //Not determined => use automatic units
4248
4249 dim->SetUnits( units );
4250 }
4251
4252 if( !isStyleKnown )
4253 {
4254 dim->SetKeepTextAligned( is_aligned );
4255 dim->SetTextPositionMode( t_dim_pos );
4256 }
4257 break;
4258 }
4259
4260 // New format: feature points
4261 case T_pts:
4262 {
4263 VECTOR2I point;
4264
4265 parseXY( &point.x, &point.y );
4266 dim->SetStart( point );
4267 parseXY( &point.x, &point.y );
4268 dim->SetEnd( point );
4269
4270 NeedRIGHT();
4271 break;
4272 }
4273
4274 case T_height:
4275 {
4276 int height = parseBoardUnits( "dimension height value" );
4277 NeedRIGHT();
4278
4279 if( dim->Type() == PCB_DIM_ORTHOGONAL_T || dim->Type() == PCB_DIM_ALIGNED_T )
4280 {
4281 PCB_DIM_ALIGNED* aligned = static_cast<PCB_DIM_ALIGNED*>( dim.get() );
4282 aligned->SetHeight( height );
4283 }
4284
4285 break;
4286 }
4287
4288 case T_leader_length:
4289 {
4290 int length = parseBoardUnits( "leader length value" );
4291 NeedRIGHT();
4292
4293 if( dim->Type() == PCB_DIM_RADIAL_T )
4294 {
4295 PCB_DIM_RADIAL* radial = static_cast<PCB_DIM_RADIAL*>( dim.get() );
4296 radial->SetLeaderLength( length );
4297 }
4298
4299 break;
4300 }
4301
4302 case T_orientation:
4303 {
4304 int orientation = parseInt( "orthogonal dimension orientation" );
4305 NeedRIGHT();
4306
4307 if( dim->Type() == PCB_DIM_ORTHOGONAL_T )
4308 {
4309 PCB_DIM_ORTHOGONAL* ortho = static_cast<PCB_DIM_ORTHOGONAL*>( dim.get() );
4310 orientation = std::clamp( orientation, 0, 1 );
4311 ortho->SetOrientation( static_cast<PCB_DIM_ORTHOGONAL::DIR>( orientation ) );
4312 }
4313
4314 break;
4315 }
4316
4317 case T_format:
4318 {
4319 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
4320 {
4321 switch( token )
4322 {
4323 case T_LEFT:
4324 continue;
4325
4326 case T_prefix:
4327 NeedSYMBOLorNUMBER();
4328 dim->SetPrefix( FromUTF8() );
4329 NeedRIGHT();
4330 break;
4331
4332 case T_suffix:
4333 NeedSYMBOLorNUMBER();
4334 dim->SetSuffix( FromUTF8() );
4335 NeedRIGHT();
4336 break;
4337
4338 case T_units:
4339 {
4340 int mode = parseInt( "dimension units mode" );
4341 mode = std::max( 0, std::min( 4, mode ) );
4342 dim->SetUnitsMode( static_cast<DIM_UNITS_MODE>( mode ) );
4343 NeedRIGHT();
4344 break;
4345 }
4346
4347 case T_units_format:
4348 {
4349 int format = parseInt( "dimension units format" );
4350 format = std::clamp( format, 0, 3 );
4351 dim->SetUnitsFormat( static_cast<DIM_UNITS_FORMAT>( format ) );
4352 NeedRIGHT();
4353 break;
4354 }
4355
4356 case T_precision:
4357 dim->SetPrecision( static_cast<DIM_PRECISION>( parseInt( "dimension precision" ) ) );
4358 NeedRIGHT();
4359 break;
4360
4361 case T_override_value:
4362 NeedSYMBOLorNUMBER();
4363 dim->SetOverrideTextEnabled( true );
4364 dim->SetOverrideText( FromUTF8() );
4365 NeedRIGHT();
4366 break;
4367
4368 case T_suppress_zeroes:
4369 dim->SetSuppressZeroes( parseMaybeAbsentBool( true ) );
4370 break;
4371
4372 default:
4373 std::cerr << "Unknown format token: " << GetTokenString( token ) << std::endl;
4374 Expecting( "prefix, suffix, units, units_format, precision, override_value, "
4375 "suppress_zeroes" );
4376 }
4377 }
4378 break;
4379 }
4380
4381 case T_style:
4382 {
4383 isStyleKnown = true;
4384
4385 // new format: default to keep text aligned off unless token is present
4386 dim->SetKeepTextAligned( false );
4387
4388 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
4389 {
4390 switch( token )
4391 {
4392 case T_LEFT:
4393 continue;
4394
4395 case T_thickness:
4396 dim->SetLineThickness( parseBoardUnits( "extension line thickness value" ) );
4397 NeedRIGHT();
4398 break;
4399
4400 case T_arrow_direction:
4401 {
4402 token = NextTok();
4403
4404 if( token == T_inward )
4405 dim->ChangeArrowDirection( DIM_ARROW_DIRECTION::INWARD );
4406 else if( token == T_outward )
4407 dim->ChangeArrowDirection( DIM_ARROW_DIRECTION::OUTWARD );
4408 else
4409 Expecting( "inward or outward" );
4410
4411 NeedRIGHT();
4412 break;
4413 }
4414 case T_arrow_length:
4415
4416 dim->SetArrowLength( parseBoardUnits( "arrow length value" ) );
4417 NeedRIGHT();
4418 break;
4419
4420 case T_text_position_mode:
4421 {
4422 int mode = parseInt( "text position mode" );
4423 mode = std::max( 0, std::min( 3, mode ) );
4424 dim->SetTextPositionMode( static_cast<DIM_TEXT_POSITION>( mode ) );
4425 NeedRIGHT();
4426 break;
4427 }
4428
4429 case T_extension_height:
4430 {
4431 PCB_DIM_ALIGNED* aligned = dynamic_cast<PCB_DIM_ALIGNED*>( dim.get() );
4432 wxCHECK_MSG( aligned, nullptr, wxT( "Invalid extension_height token" ) );
4433 aligned->SetExtensionHeight( parseBoardUnits( "extension height value" ) );
4434 NeedRIGHT();
4435 break;
4436 }
4437
4438 case T_extension_offset:
4439 dim->SetExtensionOffset( parseBoardUnits( "extension offset value" ) );
4440 NeedRIGHT();
4441 break;
4442
4443 case T_keep_text_aligned:
4444 dim->SetKeepTextAligned( parseMaybeAbsentBool( true ) );
4445 break;
4446
4447 case T_text_frame:
4448 {
4449 wxCHECK_MSG( dim->Type() == PCB_DIM_LEADER_T, nullptr,
4450 wxT( "Invalid text_frame token" ) );
4451
4452 PCB_DIM_LEADER* leader = static_cast<PCB_DIM_LEADER*>( dim.get() );
4453
4454 int textFrame = parseInt( "text frame mode" );
4455 textFrame = std::clamp( textFrame, 0, 3 );
4456 leader->SetTextBorder( static_cast<DIM_TEXT_BORDER>( textFrame ));
4457 NeedRIGHT();
4458 break;
4459 }
4460
4461 default:
4462 Expecting( "thickness, arrow_length, arrow_direction, text_position_mode, "
4463 "extension_height, extension_offset" );
4464 }
4465 }
4466
4467 break;
4468 }
4469
4470 // Old format: feature1 stores a feature line. We only care about the origin.
4471 case T_feature1:
4472 {
4473 NeedLEFT();
4474 token = NextTok();
4475
4476 if( token != T_pts )
4477 Expecting( T_pts );
4478
4479 VECTOR2I point;
4480
4481 parseXY( &point.x, &point.y );
4482 dim->SetStart( point );
4483
4484 parseXY( nullptr, nullptr ); // Ignore second point
4485 NeedRIGHT();
4486 NeedRIGHT();
4487 break;
4488 }
4489
4490 // Old format: feature2 stores a feature line. We only care about the end point.
4491 case T_feature2:
4492 {
4493 NeedLEFT();
4494 token = NextTok();
4495
4496 if( token != T_pts )
4497 Expecting( T_pts );
4498
4499 VECTOR2I point;
4500
4501 parseXY( &point.x, &point.y );
4502 dim->SetEnd( point );
4503
4504 parseXY( nullptr, nullptr ); // Ignore second point
4505
4506 NeedRIGHT();
4507 NeedRIGHT();
4508 break;
4509 }
4510
4511 case T_crossbar:
4512 {
4513 NeedLEFT();
4514 token = NextTok();
4515
4516 if( token == T_pts )
4517 {
4518 // If we have a crossbar, we know we're an old aligned dim
4519 PCB_DIM_ALIGNED* aligned = static_cast<PCB_DIM_ALIGNED*>( dim.get() );
4520
4521 // Old style: calculate height from crossbar
4522 VECTOR2I point1, point2;
4523 parseXY( &point1.x, &point1.y );
4524 parseXY( &point2.x, &point2.y );
4525 aligned->UpdateHeight( point2, point1 ); // Yes, backwards intentionally
4526 NeedRIGHT();
4527 }
4528
4529 NeedRIGHT();
4530 break;
4531 }
4532
4533 // Arrow: no longer saved; no-op
4534 case T_arrow1a:
4535 NeedLEFT();
4536 token = NextTok();
4537
4538 if( token != T_pts )
4539 Expecting( T_pts );
4540
4541 parseXY( nullptr, nullptr );
4542 parseXY( nullptr, nullptr );
4543 NeedRIGHT();
4544 NeedRIGHT();
4545 break;
4546
4547 // Arrow: no longer saved; no-op
4548 case T_arrow1b:
4549 NeedLEFT();
4550 token = NextTok();
4551
4552 if( token != T_pts )
4553 Expecting( T_pts );
4554
4555 parseXY( nullptr, nullptr );
4556 parseXY( nullptr, nullptr );
4557 NeedRIGHT();
4558 NeedRIGHT();
4559 break;
4560
4561 // Arrow: no longer saved; no-op
4562 case T_arrow2a:
4563 NeedLEFT();
4564 token = NextTok();
4565
4566 if( token != T_pts )
4567 Expecting( T_pts );
4568
4569 parseXY( nullptr, nullptr );
4570 parseXY( nullptr, nullptr );
4571 NeedRIGHT();
4572 NeedRIGHT();
4573 break;
4574
4575 // Arrow: no longer saved; no-op
4576 case T_arrow2b:
4577 NeedLEFT();
4578 token = NextTok();
4579
4580 if( token != T_pts )
4581 Expecting( T_pts );
4582
4583 parseXY( nullptr, nullptr );
4584 parseXY( nullptr, nullptr );
4585 NeedRIGHT();
4586 NeedRIGHT();
4587 break;
4588
4589 // Handle (locked yes) from modern times
4590 case T_locked:
4591 {
4592 // Unsure if we ever wrote out (locked) for dimensions, so use maybeAbsent just in case
4593 bool isLocked = parseMaybeAbsentBool( true );
4594 dim->SetLocked( isLocked );
4595 break;
4596 }
4597
4598 default:
4599 Expecting( "layer, tstamp, uuid, gr_text, feature1, feature2, crossbar, arrow1a, "
4600 "arrow1b, arrow2a, or arrow2b" );
4601 }
4602 }
4603
4604 if( locked )
4605 dim->SetLocked( true );
4606
4607 dim->Update();
4608
4609 return dim.release();
4610}
4611
4612
4613FOOTPRINT* PCB_IO_KICAD_SEXPR_PARSER::parseFOOTPRINT( wxArrayString* aInitialComments )
4614{
4615 try
4616 {
4617 return parseFOOTPRINT_unchecked( aInitialComments );
4618 }
4619 catch( const PARSE_ERROR& parse_error )
4620 {
4621 if( m_tooRecent )
4622 throw FUTURE_FORMAT_ERROR( parse_error, GetRequiredVersion() );
4623 else
4624 throw;
4625 }
4626}
4627
4628
4630{
4631 wxCHECK_MSG( CurTok() == T_module || CurTok() == T_footprint, nullptr,
4632 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as FOOTPRINT." ) );
4633
4634 wxString name;
4635 VECTOR2I pt;
4636 T token;
4637 LIB_ID fpid;
4638 int attributes = 0;
4639
4640 std::unique_ptr<FOOTPRINT> footprint = std::make_unique<FOOTPRINT>( m_board );
4641
4642 footprint->SetInitialComments( aInitialComments );
4643
4644 if( m_board )
4645 {
4646 footprint->SetStaticComponentClass(
4647 m_board->GetComponentClassManager().GetNoneComponentClass() );
4648 }
4649
4650 token = NextTok();
4651
4652 if( !IsSymbol( token ) && token != T_NUMBER )
4653 Expecting( "symbol|number" );
4654
4655 name = FromUTF8();
4656
4657 if( !name.IsEmpty() && fpid.Parse( name, true ) >= 0 )
4658 {
4659 THROW_IO_ERROR( wxString::Format( _( "Invalid footprint ID in\nfile: %s\nline: %d\n"
4660 "offset: %d." ),
4661 CurSource(), CurLineNumber(), CurOffset() ) );
4662 }
4663
4664 auto checkVersion =
4665 [&]()
4666 {
4668 {
4669 throw FUTURE_FORMAT_ERROR( fmt::format( "{}", m_requiredVersion ),
4671 }
4672 };
4673
4674 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
4675 {
4676 if( token == T_LEFT )
4677 token = NextTok();
4678
4679 switch( token )
4680 {
4681 case T_version:
4682 {
4683 // Theoretically a footprint nested in a PCB could declare its own version, though
4684 // as of writing this comment we don't do that. Just in case, take the greater
4685 // version.
4686 int this_version = parseInt( FromUTF8().mb_str( wxConvUTF8 ) );
4687 NeedRIGHT();
4688 m_requiredVersion = std::max( m_requiredVersion, this_version );
4690 SetKnowsBar( m_requiredVersion >= 20240706 ); // Bar token is known from this version
4691 footprint->SetFileFormatVersionAtLoad( this_version );
4692 break;
4693 }
4694
4695 case T_generator:
4696 // We currently ignore the generator when parsing. It is included in the file for manual
4697 // indication of where the footprint came from.
4698 NeedSYMBOL();
4699 NeedRIGHT();
4700 break;
4701
4702 case T_generator_version:
4703 {
4704 NeedSYMBOL();
4705 m_generatorVersion = FromUTF8();
4706 NeedRIGHT();
4707
4708 // If the format includes a generator version, by this point we have enough info to
4709 // do the version check here
4710 checkVersion();
4711
4712 break;
4713 }
4714
4715 case T_locked:
4716 footprint->SetLocked( parseMaybeAbsentBool( true ) );
4717 break;
4718
4719 case T_placed:
4720 footprint->SetIsPlaced( parseMaybeAbsentBool( true ) );
4721 break;
4722
4723 case T_layer:
4724 {
4725 // Footprints can be only on the front side or the back side.
4726 // but because we can find some stupid layer in file, ensure a
4727 // acceptable layer is set for the footprint
4729 footprint->SetLayer( layer == B_Cu ? B_Cu : F_Cu );
4730 NeedRIGHT();
4731 break;
4732 }
4733
4734 case T_stackup:
4735 {
4736 parseFootprintStackup( *footprint );
4737 break;
4738 }
4739
4740 case T_tedit:
4741 parseHex();
4742 NeedRIGHT();
4743 break;
4744
4745 case T_tstamp:
4746 case T_uuid:
4747 NextTok();
4748 const_cast<KIID&>( footprint->m_Uuid ) = CurStrToKIID();
4749 NeedRIGHT();
4750 break;
4751
4752 case T_at:
4753 pt.x = parseBoardUnits( "X coordinate" );
4754 pt.y = parseBoardUnits( "Y coordinate" );
4755 footprint->SetPosition( pt );
4756 token = NextTok();
4757
4758 if( token == T_NUMBER )
4759 {
4760 footprint->SetOrientation( EDA_ANGLE( parseDouble(), DEGREES_T ) );
4761 NeedRIGHT();
4762 }
4763 else if( token != T_RIGHT )
4764 {
4765 Expecting( T_RIGHT );
4766 }
4767
4768 break;
4769
4770 case T_descr:
4771 NeedSYMBOLorNUMBER(); // some symbols can be 0508, so a number is also a symbol here
4772 footprint->SetLibDescription( FromUTF8() );
4773 NeedRIGHT();
4774 break;
4775
4776 case T_tags:
4777 NeedSYMBOLorNUMBER(); // some symbols can be 0508, so a number is also a symbol here
4778 footprint->SetKeywords( FromUTF8() );
4779 NeedRIGHT();
4780 break;
4781
4782 case T_property:
4783 {
4784 NeedSYMBOL();
4785 wxString pName = FromUTF8();
4786 NeedSYMBOL();
4787 wxString pValue = FromUTF8();
4788
4789 // Prior to PCB fields, we used to use properties for special values instead of
4790 // using (keyword_example "value")
4791 if( m_requiredVersion < 20230620 )
4792 {
4793 // Skip legacy non-field properties sent from symbols that should not be kept
4794 // in footprints.
4795 if( pName == "ki_keywords" || pName == "ki_locked" )
4796 {
4797 NeedRIGHT();
4798 break;
4799 }
4800
4801 // Description from symbol (not the fooprint library description stored in (descr) )
4802 // used to be stored as a reserved key value
4803 if( pName == "ki_description" )
4804 {
4805 footprint->GetField( FIELD_T::DESCRIPTION )->SetText( pValue );
4806 NeedRIGHT();
4807 break;
4808 }
4809
4810 // Sheet file and name used to be stored as properties invisible to the user
4811 if( pName == "Sheetfile" || pName == "Sheet file" )
4812 {
4813 footprint->SetSheetfile( pValue );
4814 NeedRIGHT();
4815 break;
4816 }
4817
4818 if( pName == "Sheetname" || pName == "Sheet name" )
4819 {
4820 footprint->SetSheetname( pValue );
4821 NeedRIGHT();
4822 break;
4823 }
4824 }
4825
4826 PCB_FIELD* field = nullptr;
4827 std::unique_ptr<PCB_FIELD> unusedField;
4828
4829 // 8.0.0rc3 had a bug where these properties were mistakenly added to the footprint as
4830 // fields, this will remove them as fields but still correctly set the footprint filters
4831 if( pName == "ki_fp_filters" )
4832 {
4833 footprint->SetFilters( pValue );
4834
4835 // Use the text effect parsing function because it will handle ki_fp_filters as a
4836 // property with no text effects, but will also handle parsing the text effects.
4837 // We just drop the effects if they're present.
4838 unusedField = std::make_unique<PCB_FIELD>( footprint.get(), FIELD_T::USER );
4839 field = unusedField.get();
4840 }
4841 else if( pName == "Footprint" )
4842 {
4843 // Until V9, footprints had a Footprint field that usually (but not always)
4844 // duplicated the footprint's LIB_ID. In V9 this was removed. Parse it
4845 // like any other, but don't add it to anything.
4846 unusedField = std::make_unique<PCB_FIELD>( footprint.get(), FIELD_T::FOOTPRINT );
4847 field = unusedField.get();
4848 }
4849 else if( footprint->HasField( pName ) )
4850 {
4851 field = footprint->GetField( pName );
4852 field->SetText( pValue );
4853 }
4854 else
4855 {
4856 field = new PCB_FIELD( footprint.get(), FIELD_T::USER, pName );
4857 footprint->Add( field );
4858
4859 field->SetText( pValue );
4860 field->SetLayer( footprint->GetLayer() == F_Cu ? F_Fab : B_Fab );
4861
4862 if( m_board ) // can be null when reading a lib
4863 field->StyleFromSettings( m_board->GetDesignSettings(), true );
4864 }
4865
4866 // Hide the field by default if it is a legacy field that did not have
4867 // text effects applied, since hide is a negative effect
4868 if( m_requiredVersion < 20230620 )
4869 field->SetVisible( false );
4870 else
4871 field->SetVisible( true );
4872
4873 parsePCB_TEXT_effects( field );
4874 }
4875 break;
4876
4877 case T_path:
4878 NeedSYMBOLorNUMBER(); // Paths can be numerical so a number is also a symbol here
4879 footprint->SetPath( KIID_PATH( FromUTF8() ) );
4880 NeedRIGHT();
4881 break;
4882
4883 case T_sheetname:
4884 NeedSYMBOL();
4885 footprint->SetSheetname( FromUTF8() );
4886 NeedRIGHT();
4887 break;
4888
4889 case T_sheetfile:
4890 NeedSYMBOL();
4891 footprint->SetSheetfile( FromUTF8() );
4892 NeedRIGHT();
4893 break;
4894
4895 case T_units:
4896 {
4897 std::vector<FOOTPRINT::FP_UNIT_INFO> unitInfos;
4898
4899 // (units (unit (name "A") (pins "1" "2" ...)) ...)
4900 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
4901 {
4902 if( token == T_LEFT )
4903 token = NextTok();
4904
4905 if( token == T_unit )
4906 {
4908
4909 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
4910 {
4911 if( token == T_LEFT )
4912 token = NextTok();
4913
4914 if( token == T_name )
4915 {
4916 NeedSYMBOLorNUMBER();
4917 info.m_unitName = FromUTF8();
4918 NeedRIGHT();
4919 }
4920 else if( token == T_pins )
4921 {
4922 // Parse a flat list of quoted numbers or symbols until ')'
4923 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
4924 {
4925 if( token == T_STRING || token == T_NUMBER )
4926 {
4927 info.m_pins.emplace_back( FromUTF8() );
4928 }
4929 else
4930 {
4931 Expecting( "pin number" );
4932 }
4933 }
4934 }
4935 else
4936 {
4937 // Unknown sub-token inside unit; skip its list if any
4938 skipCurrent();
4939 }
4940 }
4941
4942 unitInfos.push_back( info );
4943 }
4944 else
4945 {
4946 // Unknown entry under units; skip
4947 skipCurrent();
4948 }
4949 }
4950
4951 if( !unitInfos.empty() )
4952 footprint->SetUnitInfo( unitInfos );
4953
4954 break;
4955 }
4956
4957 case T_autoplace_cost90:
4958 case T_autoplace_cost180:
4959 parseInt( "legacy auto-place cost" );
4960 NeedRIGHT();
4961 break;
4962
4963 case T_private_layers:
4964 {
4965 LSET privateLayers;
4966
4967 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
4968 {
4969 auto it = m_layerIndices.find( CurStr() );
4970
4971 if( it != m_layerIndices.end() )
4972 privateLayers.set( it->second );
4973 else
4974 Expecting( "layer name" );
4975 }
4976
4977 if( m_requiredVersion < 20220427 )
4978 {
4979 privateLayers.set( Edge_Cuts, false );
4980 privateLayers.set( Margin, false );
4981 }
4982
4983 footprint->SetPrivateLayers( privateLayers );
4984 break;
4985 }
4986
4987 case T_net_tie_pad_groups:
4988 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
4989 footprint->AddNetTiePadGroup( CurStr() );
4990
4991 break;
4992
4993 case T_duplicate_pad_numbers_are_jumpers:
4994 footprint->SetDuplicatePadNumbersAreJumpers( parseBool() );
4995 NeedRIGHT();
4996 break;
4997
4998 case T_jumper_pad_groups:
4999 {
5000 // This should only be formatted if there is at least one group
5001 std::vector<std::set<wxString>>& groups = footprint->JumperPadGroups();
5002 std::set<wxString>* currentGroup = nullptr;
5003
5004 for( token = NextTok(); currentGroup || token != T_RIGHT; token = NextTok() )
5005 {
5006 switch( static_cast<int>( token ) )
5007 {
5008 case T_LEFT:
5009 currentGroup = &groups.emplace_back();
5010 break;
5011
5012 case DSN_STRING:
5013 if( currentGroup )
5014 currentGroup->insert( FromUTF8() );
5015
5016 break;
5017
5018 case T_RIGHT:
5019 currentGroup = nullptr;
5020 break;
5021
5022 default:
5023 Expecting( "list of pad names" );
5024 }
5025 }
5026
5027 break;
5028 }
5029
5030 case T_solder_mask_margin:
5031 footprint->SetLocalSolderMaskMargin( parseBoardUnits( "local solder mask margin value" ) );
5032 NeedRIGHT();
5033
5034 // In pre-9.0 files "0" meant inherit.
5035 if( m_requiredVersion <= 20240201 && footprint->GetLocalSolderMaskMargin() == 0 )
5036 footprint->SetLocalSolderMaskMargin( {} );
5037
5038 break;
5039
5040 case T_solder_paste_margin:
5041 footprint->SetLocalSolderPasteMargin( parseBoardUnits( "local solder paste margin value" ) );
5042 NeedRIGHT();
5043
5044 // In pre-9.0 files "0" meant inherit.
5045 if( m_requiredVersion <= 20240201 && footprint->GetLocalSolderPasteMargin() == 0 )
5046 footprint->SetLocalSolderPasteMargin( {} );
5047
5048 break;
5049
5050 case T_solder_paste_ratio: // legacy token
5051 case T_solder_paste_margin_ratio:
5052 footprint->SetLocalSolderPasteMarginRatio( parseDouble( "local solder paste margin ratio value" ) );
5053 NeedRIGHT();
5054
5055 // In pre-9.0 files "0" meant inherit.
5056 if( m_requiredVersion <= 20240201 && footprint->GetLocalSolderPasteMarginRatio() == 0 )
5057 footprint->SetLocalSolderPasteMarginRatio( {} );
5058
5059 break;
5060
5061 case T_clearance:
5062 footprint->SetLocalClearance( parseBoardUnits( "local clearance value" ) );
5063 NeedRIGHT();
5064
5065 // In pre-9.0 files "0" meant inherit.
5066 if( m_requiredVersion <= 20240201 && footprint->GetLocalClearance() == 0 )
5067 footprint->SetLocalClearance( {} );
5068
5069 break;
5070
5071 case T_zone_connect:
5072 footprint->SetLocalZoneConnection((ZONE_CONNECTION) parseInt( "zone connection value" ) );
5073 NeedRIGHT();
5074 break;
5075
5076 case T_thermal_width:
5077 case T_thermal_gap:
5078 // Interestingly, these have never been exposed in the GUI
5079 parseBoardUnits( token );
5080 NeedRIGHT();
5081 break;
5082
5083 case T_attr:
5084 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
5085 {
5086 switch( token )
5087 {
5088 case T_virtual: // legacy token prior to version 20200826
5090 break;
5091
5092 case T_through_hole:
5093 attributes |= FP_THROUGH_HOLE;
5094 break;
5095
5096 case T_smd:
5097 attributes |= FP_SMD;
5098 break;
5099
5100 case T_board_only:
5101 attributes |= FP_BOARD_ONLY;
5102 break;
5103
5104 case T_exclude_from_pos_files:
5105 attributes |= FP_EXCLUDE_FROM_POS_FILES;
5106 break;
5107
5108 case T_exclude_from_bom:
5109 attributes |= FP_EXCLUDE_FROM_BOM;
5110 break;
5111
5112 case T_allow_missing_courtyard:
5113 footprint->SetAllowMissingCourtyard( true );
5114 break;
5115
5116 case T_dnp:
5117 attributes |= FP_DNP;
5118 break;
5119
5120 case T_allow_soldermask_bridges:
5121 footprint->SetAllowSolderMaskBridges( true );
5122 break;
5123
5124 default:
5125 Expecting( "through_hole, smd, virtual, board_only, exclude_from_pos_files, "
5126 "exclude_from_bom or allow_solder_mask_bridges" );
5127 }
5128 }
5129
5130 break;
5131
5132 case T_fp_text:
5133 {
5134 PCB_TEXT* text = parsePCB_TEXT( footprint.get() );
5135
5136 if( PCB_FIELD* field = dynamic_cast<PCB_FIELD*>( text ) )
5137 {
5138 switch( field->GetId() )
5139 {
5140 case FIELD_T::REFERENCE:
5141 footprint->Reference() = PCB_FIELD( *text, FIELD_T::REFERENCE );
5142 const_cast<KIID&>( footprint->Reference().m_Uuid ) = text->m_Uuid;
5143 delete text;
5144 break;
5145
5146 case FIELD_T::VALUE:
5147 footprint->Value() = PCB_FIELD( *text, FIELD_T::VALUE );
5148 const_cast<KIID&>( footprint->Value().m_Uuid ) = text->m_Uuid;
5149 delete text;
5150 break;
5151
5152 default:
5153 // Fields other than reference and value aren't treated specially,
5154 // and can be created if the fp_text was hidden on the board,
5155 // so just add those to the footprint as normal.
5156 footprint->Add(text, ADD_MODE::APPEND, true );
5157 break;
5158 }
5159 }
5160 else
5161 {
5162 footprint->Add( text, ADD_MODE::APPEND, true );
5163 }
5164
5165 break;
5166 }
5167
5168 case T_fp_text_box:
5169 {
5170 PCB_TEXTBOX* textbox = parsePCB_TEXTBOX( footprint.get() );
5171 footprint->Add( textbox, ADD_MODE::APPEND, true );
5172 break;
5173 }
5174
5175 case T_table:
5176 {
5177 PCB_TABLE* table = parsePCB_TABLE( footprint.get() );
5178 footprint->Add( table, ADD_MODE::APPEND, true );
5179 break;
5180 }
5181
5182 case T_fp_arc:
5183 case T_fp_circle:
5184 case T_fp_curve:
5185 case T_fp_rect:
5186 case T_fp_line:
5187 case T_fp_poly:
5188 {
5189 PCB_SHAPE* shape = parsePCB_SHAPE( footprint.get() );
5190 footprint->Add( shape, ADD_MODE::APPEND, true );
5191 break;
5192 }
5193
5194 case T_image:
5195 {
5197 footprint->Add( image, ADD_MODE::APPEND, true );
5198 break;
5199 }
5200
5201 case T_barcode:
5202 {
5203 PCB_BARCODE* barcode = parsePCB_BARCODE( footprint.get() );
5204 footprint->Add( barcode, ADD_MODE::APPEND, true );
5205 break;
5206 }
5207
5208 case T_dimension:
5209 {
5210 PCB_DIMENSION_BASE* dimension = parseDIMENSION( footprint.get() );
5211 footprint->Add( dimension, ADD_MODE::APPEND, true );
5212 break;
5213 }
5214
5215 case T_pad:
5216 {
5217 PAD* pad = parsePAD( footprint.get() );
5218 footprint->Add( pad, ADD_MODE::APPEND, true );
5219 break;
5220 }
5221
5222 case T_model:
5223 {
5224 FP_3DMODEL* model = parse3DModel();
5225 footprint->Add3DModel( model );
5226 delete model;
5227 break;
5228 }
5229
5230 case T_zone:
5231 {
5232 ZONE* zone = parseZONE( footprint.get() );
5233 footprint->Add( zone, ADD_MODE::APPEND, true );
5234 break;
5235 }
5236
5237 case T_group:
5238 parseGROUP( footprint.get() );
5239 break;
5240
5241 case T_point:
5242 {
5243 PCB_POINT* point = parsePCB_POINT();
5244 footprint->Add( point, ADD_MODE::APPEND, true );
5245 break;
5246 }
5247 case T_embedded_fonts:
5248 {
5249 footprint->GetEmbeddedFiles()->SetAreFontsEmbedded( parseBool() );
5250 NeedRIGHT();
5251 break;
5252 }
5253
5254 case T_embedded_files:
5255 {
5256 EMBEDDED_FILES_PARSER embeddedFilesParser( reader );
5257 embeddedFilesParser.SyncLineReaderWith( *this );
5258
5259 try
5260 {
5261 embeddedFilesParser.ParseEmbedded( footprint->GetEmbeddedFiles() );
5262 }
5263 catch( const PARSE_ERROR& e )
5264 {
5265 wxLogError( e.What() );
5266 }
5267
5268 SyncLineReaderWith( embeddedFilesParser );
5269 break;
5270 }
5271
5272 case T_component_classes:
5273 {
5274 std::unordered_set<wxString> componentClassNames;
5275
5276 while( ( token = NextTok() ) != T_RIGHT )
5277 {
5278 if( token != T_LEFT )
5279 Expecting( T_LEFT );
5280
5281 if( ( token = NextTok() ) != T_class )
5282 Expecting( T_class );
5283
5284 NeedSYMBOLorNUMBER();
5285 componentClassNames.insert( From_UTF8( CurText() ) );
5286 NeedRIGHT();
5287 }
5288
5289 footprint->SetTransientComponentClassNames( componentClassNames );
5290
5291 if( m_board )
5292 footprint->ResolveComponentClassNames( m_board, componentClassNames );
5293
5294 break;
5295 }
5296
5297 default:
5298 Expecting( "at, descr, locked, placed, tedit, tstamp, uuid, "
5299 "autoplace_cost90, autoplace_cost180, attr, clearance, "
5300 "embedded_files, fp_arc, fp_circle, fp_curve, fp_line, fp_poly, "
5301 "fp_rect, fp_text, pad, group, generator, model, path, solder_mask_margin, "
5302 "solder_paste_margin, solder_paste_margin_ratio, tags, thermal_gap, "
5303 "version, zone, zone_connect, or component_classes" );
5304 }
5305 }
5306
5307 // In legacy files the lack of attributes indicated a through-hole component which was by
5308 // default excluded from pos files. However there was a hack to look for SMD pads and
5309 // consider those "mislabeled through-hole components" and therefore include them in place
5310 // files. We probably don't want to get into that game so we'll just include them by
5311 // default and let the user change it if required.
5312 if( m_requiredVersion < 20200826 && attributes == 0 )
5313 attributes |= FP_THROUGH_HOLE;
5314
5316 {
5317 if( footprint->GetKeywords().StartsWith( wxT( "net tie" ) ) )
5318 {
5319 wxString padGroup;
5320
5321 for( PAD* pad : footprint->Pads() )
5322 {
5323 if( !padGroup.IsEmpty() )
5324 padGroup += wxS( ", " );
5325
5326 padGroup += pad->GetNumber();
5327 }
5328
5329 if( !padGroup.IsEmpty() )
5330 footprint->AddNetTiePadGroup( padGroup );
5331 }
5332 }
5333
5334 footprint->SetAttributes( attributes );
5335
5336 footprint->SetFPID( fpid );
5337
5338 return footprint.release();
5339}
5340
5341
5343{
5344 wxCHECK_RET( CurTok() == T_stackup, "Expected stackup token" );
5345
5346 // If we have a stackup list at all, we must be in custom layer mode
5348 LSET layers = LSET{};
5349
5350 for( T token = NextTok(); token != T_RIGHT; token = NextTok() )
5351 {
5352 if( CurTok() != T_LEFT )
5353 Expecting( T_LEFT );
5354
5355 token = NextTok();
5356
5357 switch( token )
5358 {
5359 case T_layer:
5360 {
5361 NeedSYMBOLorNUMBER();
5362
5363 const auto it = m_layerIndices.find( CurStr() );
5364 if( it == m_layerIndices.end() )
5365 {
5366 Expecting( "layer name" );
5367 }
5368 else
5369 {
5370 layers.set( it->second );
5371 }
5372
5373 NeedRIGHT();
5374 break;
5375 }
5376 default:
5377 {
5378 Expecting( "layer" );
5379 break;
5380 }
5381 }
5382 }
5383
5384 // Check that the copper layers are sensible and contiguous
5385 const LSET gotCuLayers = layers & LSET::AllCuMask();
5386
5387 // Remove this check when we support odd copper layer stackups
5388 if( gotCuLayers.count() % 2 != 0 )
5389 {
5390 THROW_IO_ERROR( wxString::Format( _( "Invalid stackup in footprint: "
5391 "odd number of copper layers (%d)." ),
5392 gotCuLayers.count() ) );
5393 }
5394
5395 const LSET expectedCuLayers = LSET::AllCuMask( gotCuLayers.count() );
5396 if( gotCuLayers != expectedCuLayers )
5397 {
5398 THROW_IO_ERROR( wxString::Format( _( "Invalid stackup in footprint: "
5399 "copper layers are not contiguous." ) ) );
5400 }
5401
5402 if( ( layers & LSET::AllTechMask() ).count() > 0 )
5403 {
5404 THROW_IO_ERROR( wxString::Format( _( "Invalid stackup in footprint: "
5405 "technology layers are implicit in footprints and "
5406 "should not be specified in the stackup." ) ) );
5407 }
5408
5409 // Set the mode first, so that the layer count is unlocked if needed
5410 aFootprint.SetStackupMode( stackupMode );
5411 aFootprint.SetStackupLayers( std::move( layers ) );
5412}
5413
5414
5416{
5417 wxCHECK_MSG( CurTok() == T_pad, nullptr,
5418 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as PAD." ) );
5419
5420 VECTOR2I sz;
5421 VECTOR2I pt;
5422 bool foundNet = false;
5423
5424 std::unique_ptr<PAD> pad = std::make_unique<PAD>( aParent );
5425
5426 NeedSYMBOLorNUMBER();
5427 pad->SetNumber( FromUTF8() );
5428
5429 T token = NextTok();
5430
5431 switch( token )
5432 {
5433 case T_thru_hole:
5434 pad->SetAttribute( PAD_ATTRIB::PTH );
5435
5436 // The drill token is usually missing if 0 drill size is specified.
5437 // Emulate it using 1 nm drill size to avoid errors.
5438 // Drill size cannot be set to 0 in newer versions.
5439 pad->SetDrillSize( VECTOR2I( 1, 1 ) );
5440 break;
5441
5442 case T_smd:
5443 pad->SetAttribute( PAD_ATTRIB::SMD );
5444
5445 // Default PAD object is thru hole with drill.
5446 // SMD pads have no hole
5447 pad->SetDrillSize( VECTOR2I( 0, 0 ) );
5448 break;
5449
5450 case T_connect:
5451 pad->SetAttribute( PAD_ATTRIB::CONN );
5452
5453 // Default PAD object is thru hole with drill.
5454 // CONN pads have no hole
5455 pad->SetDrillSize( VECTOR2I( 0, 0 ) );
5456 break;
5457
5458 case T_np_thru_hole:
5459 pad->SetAttribute( PAD_ATTRIB::NPTH );
5460 break;
5461
5462 default:
5463 Expecting( "thru_hole, smd, connect, or np_thru_hole" );
5464 }
5465
5466 token = NextTok();
5467
5468 switch( token )
5469 {
5470 case T_circle:
5472 break;
5473
5474 case T_rect:
5476 break;
5477
5478 case T_oval:
5480 break;
5481
5482 case T_trapezoid:
5484 break;
5485
5486 case T_roundrect:
5487 // Note: the shape can be PAD_SHAPE::ROUNDRECT or PAD_SHAPE::CHAMFERED_RECT
5488 // (if chamfer parameters are found later in pad descr.)
5490 break;
5491
5492 case T_custom:
5494 break;
5495
5496 default:
5497 Expecting( "circle, rectangle, roundrect, oval, trapezoid or custom" );
5498 }
5499
5500 std::optional<EDA_ANGLE> thermalBrAngleOverride;
5501
5502 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
5503 {
5504 if( token == T_locked )
5505 {
5506 // Pad locking is now a session preference
5507 token = NextTok();
5508 }
5509
5510 if( token != T_LEFT )
5511 Expecting( T_LEFT );
5512
5513 token = NextTok();
5514
5515 switch( token )
5516 {
5517 case T_size:
5518 sz.x = parseBoardUnits( "width value" );
5519 sz.y = parseBoardUnits( "height value" );
5520 pad->SetSize( PADSTACK::ALL_LAYERS, sz );
5521 NeedRIGHT();
5522 break;
5523
5524 case T_at:
5525 pt.x = parseBoardUnits( "X coordinate" );
5526 pt.y = parseBoardUnits( "Y coordinate" );
5527 pad->SetFPRelativePosition( pt );
5528 token = NextTok();
5529
5530 if( token == T_NUMBER )
5531 {
5532 pad->SetOrientation( EDA_ANGLE( parseDouble(), DEGREES_T ) );
5533 NeedRIGHT();
5534 }
5535 else if( token != T_RIGHT )
5536 {
5537 Expecting( ") or angle value" );
5538 }
5539
5540 break;
5541
5542 case T_rect_delta:
5543 {
5545 delta.x = parseBoardUnits( "rectangle delta width" );
5546 delta.y = parseBoardUnits( "rectangle delta height" );
5547 pad->SetDelta( PADSTACK::ALL_LAYERS, delta );
5548 NeedRIGHT();
5549 break;
5550 }
5551
5552 case T_drill:
5553 {
5554 bool haveWidth = false;
5555 VECTOR2I drillSize = pad->GetDrillSize();
5556
5557 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
5558 {
5559 if( token == T_LEFT )
5560 token = NextTok();
5561
5562 switch( token )
5563 {
5564 case T_oval: pad->SetDrillShape( PAD_DRILL_SHAPE::OBLONG ); break;
5565
5566 case T_NUMBER:
5567 {
5568 if( !haveWidth )
5569 {
5570 drillSize.x = parseBoardUnits();
5571
5572 // If height is not defined the width and height are the same.
5573 drillSize.y = drillSize.x;
5574 haveWidth = true;
5575 }
5576 else
5577 {
5578 drillSize.y = parseBoardUnits();
5579 }
5580 }
5581
5582 break;
5583
5584 case T_offset:
5585 pt.x = parseBoardUnits( "drill offset x" );
5586 pt.y = parseBoardUnits( "drill offset y" );
5587 pad->SetOffset( PADSTACK::ALL_LAYERS, pt );
5588 NeedRIGHT();
5589 break;
5590
5591 default:
5592 Expecting( "oval, size, or offset" );
5593 }
5594 }
5595
5596 // This fixes a bug caused by setting the default PAD drill size to a value other
5597 // than 0 used to fix a bunch of debug assertions even though it is defined as a
5598 // through hole pad. Wouldn't a though hole pad with no drill be a surface mount
5599 // pad (or a conn pad which is a smd pad with no solder paste)?
5600 if( pad->GetAttribute() != PAD_ATTRIB::SMD && pad->GetAttribute() != PAD_ATTRIB::CONN )
5601 pad->SetDrillSize( drillSize );
5602 else
5603 pad->SetDrillSize( VECTOR2I( 0, 0 ) );
5604
5605 break;
5606 }
5607
5608 case T_layers:
5609 {
5610 LSET layerMask = parseBoardItemLayersAsMask();
5611
5612 // We force this mask to include all copper layers if the pad is a PTH pad.
5613 // This is because PTH pads are always drawn on all copper layers, even if the
5614 // padstack has inner layers that are smaller than the hole. There was a corner
5615 // case in the past where a PTH pad was defined with NPTH layer set (F&B.Cu) and
5616 // could not be reset without effort
5617 if( pad->GetAttribute() == PAD_ATTRIB::PTH && m_board )
5618 layerMask |= LSET::AllCuMask( m_board->GetCopperLayerCount() );
5619
5620 pad->SetLayerSet( layerMask );
5621 break;
5622 }
5623
5624 case T_net:
5625 foundNet = true;
5626
5627 if( ! pad->SetNetCode( getNetCode( parseInt( "net number" ) ), /* aNoAssert */ true ) )
5628 {
5629 wxLogError( _( "Invalid net ID in\nfile: %s\nline: %d offset: %d" ),
5630 CurSource(), CurLineNumber(), CurOffset() );
5631 }
5632
5633 NeedSYMBOLorNUMBER();
5634
5635 // Test validity of the netname in file for netcodes expected having a net name
5636 if( m_board && pad->GetNetCode() > 0 )
5637 {
5638 wxString netName( FromUTF8() );
5639
5640 // Convert overbar syntax from `~...~` to `~{...}`. These were left out of the
5641 // first merge so the version is a bit later.
5642 if( m_requiredVersion < 20210606 )
5643 netName = ConvertToNewOverbarNotation( netName );
5644
5645 if( netName != m_board->FindNet( pad->GetNetCode() )->GetNetname() )
5646 {
5647 pad->SetNetCode( NETINFO_LIST::ORPHANED, /* aNoAssert */ true );
5648 wxLogError( _( "Net name doesn't match ID in\nfile: %s\nline: %d offset: %d" ),
5649 CurSource(), CurLineNumber(), CurOffset() );
5650 }
5651 }
5652
5653 NeedRIGHT();
5654 break;
5655
5656 case T_pinfunction:
5657 NeedSYMBOLorNUMBER();
5658 pad->SetPinFunction( FromUTF8() );
5659 NeedRIGHT();
5660 break;
5661
5662 case T_pintype:
5663 NeedSYMBOLorNUMBER();
5664 pad->SetPinType( FromUTF8() );
5665 NeedRIGHT();
5666 break;
5667
5668 case T_die_length:
5669 pad->SetPadToDieLength( parseBoardUnits( T_die_length ) );
5670 NeedRIGHT();
5671 break;
5672
5673 case T_die_delay:
5674 pad->SetPadToDieDelay( parseBoardUnits( T_die_delay ) );
5675 NeedRIGHT();
5676 break;
5677
5678 case T_solder_mask_margin:
5679 pad->SetLocalSolderMaskMargin( parseBoardUnits( "local solder mask margin value" ) );
5680 NeedRIGHT();
5681
5682 // In pre-9.0 files "0" meant inherit.
5683 if( m_requiredVersion <= 20240201 && pad->GetLocalSolderMaskMargin() == 0 )
5684 pad->SetLocalSolderMaskMargin( {} );
5685
5686 break;
5687
5688 case T_solder_paste_margin:
5689 pad->SetLocalSolderPasteMargin( parseBoardUnits( "local solder paste margin value" ) );
5690 NeedRIGHT();
5691
5692 // In pre-9.0 files "0" meant inherit.
5693 if( m_requiredVersion <= 20240201 && pad->GetLocalSolderPasteMargin() == 0 )
5694 pad->SetLocalSolderPasteMargin( {} );
5695
5696 break;
5697
5698 case T_solder_paste_margin_ratio:
5699 pad->SetLocalSolderPasteMarginRatio( parseDouble( "local solder paste margin ratio value" ) );
5700 NeedRIGHT();
5701
5702 // In pre-9.0 files "0" meant inherit.
5703 if( m_requiredVersion <= 20240201 && pad->GetLocalSolderPasteMarginRatio() == 0 )
5704 pad->SetLocalSolderPasteMarginRatio( {} );
5705
5706 break;
5707
5708 case T_clearance:
5709 pad->SetLocalClearance( parseBoardUnits( "local clearance value" ) );
5710 NeedRIGHT();
5711
5712 // In pre-9.0 files "0" meant inherit.
5713 if( m_requiredVersion <= 20240201 && pad->GetLocalClearance() == 0 )
5714 pad->SetLocalClearance( {} );
5715
5716 break;
5717
5718 case T_teardrops:
5719 parseTEARDROP_PARAMETERS( &pad->GetTeardropParams() );
5720 break;
5721
5722 case T_zone_connect:
5723 pad->SetLocalZoneConnection( (ZONE_CONNECTION) parseInt( "zone connection value" ) );
5724 NeedRIGHT();
5725 break;
5726
5727 case T_thermal_width: // legacy token
5728 case T_thermal_bridge_width:
5729 pad->SetLocalThermalSpokeWidthOverride( parseBoardUnits( token ) );
5730 NeedRIGHT();
5731 break;
5732
5733 case T_thermal_bridge_angle:
5734 thermalBrAngleOverride = EDA_ANGLE( parseDouble( "thermal spoke angle" ), DEGREES_T );
5735 NeedRIGHT();
5736 break;
5737
5738
5739 case T_thermal_gap:
5740 pad->SetThermalGap( parseBoardUnits( "thermal relief gap value" ) );
5741 NeedRIGHT();
5742 break;
5743
5744 case T_roundrect_rratio:
5745 pad->SetRoundRectRadiusRatio( PADSTACK::ALL_LAYERS,
5746 parseDouble( "roundrect radius ratio" ) );
5747 NeedRIGHT();
5748 break;
5749
5750 case T_chamfer_ratio:
5751 pad->SetChamferRectRatio( PADSTACK::ALL_LAYERS, parseDouble( "chamfer ratio" ) );
5752
5753 if( pad->GetChamferRectRatio( PADSTACK::ALL_LAYERS ) > 0 )
5755
5756 NeedRIGHT();
5757 break;
5758
5759 case T_chamfer:
5760 {
5761 int chamfers = 0;
5762 bool end_list = false;
5763
5764 while( !end_list )
5765 {
5766 token = NextTok();
5767
5768 switch( token )
5769 {
5770 case T_top_left:
5771 chamfers |= RECT_CHAMFER_TOP_LEFT;
5772 break;
5773
5774 case T_top_right:
5775 chamfers |= RECT_CHAMFER_TOP_RIGHT;
5776 break;
5777
5778 case T_bottom_left:
5779 chamfers |= RECT_CHAMFER_BOTTOM_LEFT;
5780 break;
5781
5782 case T_bottom_right:
5783 chamfers |= RECT_CHAMFER_BOTTOM_RIGHT;
5784 break;
5785
5786 case T_RIGHT:
5787 pad->SetChamferPositions( PADSTACK::ALL_LAYERS, chamfers );
5788 end_list = true;
5789 break;
5790
5791 default:
5792 Expecting( "chamfer_top_left chamfer_top_right chamfer_bottom_left or "
5793 "chamfer_bottom_right" );
5794 }
5795 }
5796
5797 if( pad->GetChamferPositions( PADSTACK::ALL_LAYERS ) != RECT_NO_CHAMFER )
5799
5800 break;
5801 }
5802
5803 case T_property:
5804 while( token != T_RIGHT )
5805 {
5806 token = NextTok();
5807
5808 switch( token )
5809 {
5810 case T_pad_prop_bga: pad->SetProperty( PAD_PROP::BGA ); break;
5811 case T_pad_prop_fiducial_glob: pad->SetProperty( PAD_PROP::FIDUCIAL_GLBL ); break;
5812 case T_pad_prop_fiducial_loc: pad->SetProperty( PAD_PROP::FIDUCIAL_LOCAL ); break;
5813 case T_pad_prop_testpoint: pad->SetProperty( PAD_PROP::TESTPOINT ); break;
5814 case T_pad_prop_castellated: pad->SetProperty( PAD_PROP::CASTELLATED ); break;
5815 case T_pad_prop_heatsink: pad->SetProperty( PAD_PROP::HEATSINK ); break;
5816 case T_pad_prop_mechanical: pad->SetProperty( PAD_PROP::MECHANICAL ); break;
5817 case T_pad_prop_pressfit: pad->SetProperty( PAD_PROP::PRESSFIT ); break;
5818 case T_none: pad->SetProperty( PAD_PROP::NONE ); break;
5819 case T_RIGHT: break;
5820
5821 default:
5822#if 0 // Currently: skip unknown property
5823 Expecting( "pad_prop_bga pad_prop_fiducial_glob pad_prop_fiducial_loc"
5824 " pad_prop_heatsink or pad_prop_castellated" );
5825#endif
5826 break;
5827 }
5828 }
5829
5830 break;
5831
5832 case T_options:
5833 parsePAD_option( pad.get() );
5834 break;
5835
5836 case T_padstack:
5837 parsePadstack( pad.get() );
5838 break;
5839
5840 case T_primitives:
5841 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
5842 {
5843 if( token == T_LEFT )
5844 token = NextTok();
5845
5846 switch( token )
5847 {
5848 case T_gr_arc:
5849 case T_gr_line:
5850 case T_gr_circle:
5851 case T_gr_rect:
5852 case T_gr_poly:
5853 case T_gr_curve:
5854 pad->AddPrimitive( PADSTACK::ALL_LAYERS, parsePCB_SHAPE( nullptr ) );
5855 break;
5856
5857 case T_gr_bbox:
5858 {
5859 PCB_SHAPE* numberBox = parsePCB_SHAPE( nullptr );
5860 numberBox->SetIsProxyItem();
5861 pad->AddPrimitive( PADSTACK::ALL_LAYERS, numberBox );
5862 break;
5863 }
5864
5865 case T_gr_vector:
5866 {
5867 PCB_SHAPE* spokeTemplate = parsePCB_SHAPE( nullptr );
5868 spokeTemplate->SetIsProxyItem();
5869 pad->AddPrimitive( PADSTACK::ALL_LAYERS, spokeTemplate );
5870 break;
5871 }
5872
5873 default:
5874 Expecting( "gr_line, gr_arc, gr_circle, gr_curve, gr_rect, gr_bbox or gr_poly" );
5875 break;
5876 }
5877 }
5878
5879 break;
5880
5881 case T_remove_unused_layers:
5882 {
5883 bool remove = parseMaybeAbsentBool( true );
5884 pad->SetRemoveUnconnected( remove );
5885 break;
5886 }
5887
5888 case T_keep_end_layers:
5889 {
5890 bool keep = parseMaybeAbsentBool( true );
5891 pad->SetKeepTopBottom( keep );
5892 break;
5893 }
5894
5895 case T_tenting:
5896 {
5897 auto [front, back] = parseFrontBackOptBool( true );
5898 pad->Padstack().FrontOuterLayers().has_solder_mask = front;
5899 pad->Padstack().BackOuterLayers().has_solder_mask = back;
5900 break;
5901 }
5902
5903 case T_zone_layer_connections:
5904 {
5905 LSET cuLayers = pad->GetLayerSet() & LSET::AllCuMask();
5906
5907 for( PCB_LAYER_ID layer : cuLayers )
5908 pad->SetZoneLayerOverride( layer, ZLO_FORCE_NO_ZONE_CONNECTION );
5909
5910 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
5911 {
5913
5914 if( !IsCopperLayer( layer ) )
5915 Expecting( "copper layer name" );
5916
5917 pad->SetZoneLayerOverride( layer, ZLO_FORCE_FLASHED );
5918 }
5919
5920 break;
5921 }
5922
5923 // Continue to process "(locked)" format which was output during 5.99 development
5924 case T_locked:
5925 // Pad locking is now a session preference
5926 parseMaybeAbsentBool( true );
5927 break;
5928
5929 case T_tstamp:
5930 case T_uuid:
5931 NextTok();
5932 const_cast<KIID&>( pad->m_Uuid ) = CurStrToKIID();
5933 NeedRIGHT();
5934 break;
5935
5936 default:
5937 Expecting( "at, locked, drill, layers, net, die_length, roundrect_rratio, "
5938 "solder_mask_margin, solder_paste_margin, solder_paste_margin_ratio, uuid, "
5939 "clearance, tstamp, primitives, remove_unused_layers, keep_end_layers, "
5940 "pinfunction, pintype, zone_connect, thermal_width, thermal_gap, padstack or "
5941 "teardrops" );
5942 }
5943 }
5944
5945 if( !foundNet )
5946 {
5947 // Make sure default netclass is correctly assigned to pads that don't define a net.
5948 pad->SetNetCode( 0, /* aNoAssert */ true );
5949 }
5950
5951 if( thermalBrAngleOverride )
5952 {
5953 pad->SetThermalSpokeAngle( *thermalBrAngleOverride );
5954 }
5955 else
5956 {
5957 // This is here because custom pad anchor shape isn't known before reading (options
5958 if( pad->GetShape( PADSTACK::ALL_LAYERS ) == PAD_SHAPE::CIRCLE )
5959 {
5960 pad->SetThermalSpokeAngle( ANGLE_45 );
5961 }
5962 else if( pad->GetShape( PADSTACK::ALL_LAYERS ) == PAD_SHAPE::CUSTOM
5963 && pad->GetAnchorPadShape( PADSTACK::ALL_LAYERS ) == PAD_SHAPE::CIRCLE )
5964 {
5965 if( m_requiredVersion <= 20211014 ) // 6.0
5966 pad->SetThermalSpokeAngle( ANGLE_90 );
5967 else
5968 pad->SetThermalSpokeAngle( ANGLE_45 );
5969 }
5970 else
5971 {
5972 pad->SetThermalSpokeAngle( ANGLE_90 );
5973 }
5974 }
5975
5976 if( !pad->CanHaveNumber() )
5977 {
5978 // At some point it was possible to assign a number to aperture pads so we need to clean
5979 // those out here.
5980 pad->SetNumber( wxEmptyString );
5981 }
5982
5983 // Zero-sized pads are likely algorithmically unsafe.
5984 if( pad->GetSizeX() <= 0 || pad->GetSizeY() <= 0 )
5985 {
5986 pad->SetSize( PADSTACK::ALL_LAYERS,
5987 VECTOR2I( pcbIUScale.mmToIU( 0.001 ), pcbIUScale.mmToIU( 0.001 ) ) );
5988
5989 wxLogWarning( _( "Invalid zero-sized pad pinned to %s in\nfile: %s\nline: %d\noffset: %d" ),
5990 wxT( "1µm" ), CurSource(), CurLineNumber(), CurOffset() );
5991 }
5992
5993 return pad.release();
5994}
5995
5996
5998{
5999 // Parse only the (option ...) inside a pad description
6000 for( T token = NextTok(); token != T_RIGHT; token = NextTok() )
6001 {
6002 if( token != T_LEFT )
6003 Expecting( T_LEFT );
6004
6005 token = NextTok();
6006
6007 switch( token )
6008 {
6009 case T_anchor:
6010 token = NextTok();
6011 // Custom shaped pads have a "anchor pad", which is the reference
6012 // for connection calculations.
6013 // Because this is an anchor, only the 2 very basic shapes are managed:
6014 // circle and rect.
6015 switch( token )
6016 {
6017 case T_circle:
6019 break;
6020
6021 case T_rect:
6023 break;
6024
6025 default:
6026 // Currently, because pad options is a moving target
6027 // just skip unknown keywords
6028 break;
6029 }
6030 NeedRIGHT();
6031 break;
6032
6033 case T_clearance:
6034 token = NextTok();
6035 // Custom shaped pads have a clearance area that is the pad shape
6036 // (like usual pads) or the convex hull of the pad shape.
6037 switch( token )
6038 {
6039 case T_outline:
6041 break;
6042
6043 case T_convexhull:
6045 break;
6046
6047 default:
6048 // Currently, because pad options is a moving target
6049 // just skip unknown keywords
6050 break;
6051 }
6052
6053 NeedRIGHT();
6054 break;
6055
6056 default:
6057 // Currently, because pad options is a moving target
6058 // just skip unknown keywords
6059 while( (token = NextTok() ) != T_RIGHT )
6060 {}
6061
6062 break;
6063 }
6064 }
6065
6066 return true;
6067}
6068
6069
6071{
6072 PADSTACK& padstack = aPad->Padstack();
6073
6074 for( T token = NextTok(); token != T_RIGHT; token = NextTok() )
6075 {
6076 if( token != T_LEFT )
6077 Expecting( T_LEFT );
6078
6079 token = NextTok();
6080
6081 switch( token )
6082 {
6083 case T_mode:
6084 token = NextTok();
6085
6086 switch( token )
6087 {
6088 case T_front_inner_back:
6090 break;
6091
6092 case T_custom:
6093 padstack.SetMode( PADSTACK::MODE::CUSTOM );
6094 break;
6095
6096 default:
6097 Expecting( "front_inner_back or custom" );
6098 }
6099
6100 NeedRIGHT();
6101 break;
6102
6103 case T_layer:
6104 {
6105 NextTok();
6106 PCB_LAYER_ID curLayer = UNDEFINED_LAYER;
6107
6108 if( curText == "Inner" )
6109 {
6110 if( padstack.Mode() != PADSTACK::MODE::FRONT_INNER_BACK )
6111 {
6112 THROW_IO_ERROR( wxString::Format( _( "Invalid padstack layer in\nfile: %s\n"
6113 "line: %d\noffset: %d." ),
6114 CurSource(), CurLineNumber(), CurOffset() ) );
6115 }
6116
6117 curLayer = PADSTACK::INNER_LAYERS;
6118 }
6119 else
6120 {
6121 curLayer = lookUpLayer( m_layerIndices );
6122 }
6123
6124 if( !IsCopperLayer( curLayer ) )
6125 {
6126 wxString error;
6127 error.Printf( _( "Invalid padstack layer '%s' in file '%s' at line %d, offset %d." ),
6128 curText, CurSource().GetData(), CurLineNumber(), CurOffset() );
6129 THROW_IO_ERROR( error );
6130 }
6131
6132 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
6133 {
6134 if( token != T_LEFT )
6135 Expecting( T_LEFT );
6136
6137 token = NextTok();
6138
6139 switch( token )
6140 {
6141 case T_shape:
6142 token = NextTok();
6143
6144 switch( token )
6145 {
6146 case T_circle:
6147 aPad->SetShape( curLayer, PAD_SHAPE::CIRCLE );
6148 break;
6149
6150 case T_rect:
6151 aPad->SetShape( curLayer, PAD_SHAPE::RECTANGLE );
6152 break;
6153
6154 case T_oval:
6155 aPad->SetShape( curLayer, PAD_SHAPE::OVAL );
6156 break;
6157
6158 case T_trapezoid:
6159 aPad->SetShape( curLayer, PAD_SHAPE::TRAPEZOID );
6160 break;
6161
6162 case T_roundrect:
6163 // Note: the shape can be PAD_SHAPE::ROUNDRECT or PAD_SHAPE::CHAMFERED_RECT
6164 // (if chamfer parameters are found later in pad descr.)
6165 aPad->SetShape( curLayer, PAD_SHAPE::ROUNDRECT );
6166 break;
6167
6168 case T_custom:
6169 aPad->SetShape( curLayer, PAD_SHAPE::CUSTOM );
6170 break;
6171
6172 default:
6173 Expecting( "circle, rectangle, roundrect, oval, trapezoid or custom" );
6174 }
6175
6176 NeedRIGHT();
6177 break;
6178
6179 case T_size:
6180 {
6181 VECTOR2I sz;
6182 sz.x = parseBoardUnits( "width value" );
6183 sz.y = parseBoardUnits( "height value" );
6184 aPad->SetSize( curLayer, sz );
6185 NeedRIGHT();
6186 break;
6187 }
6188
6189 case T_offset:
6190 {
6191 VECTOR2I pt;
6192 pt.x = parseBoardUnits( "drill offset x" );
6193 pt.y = parseBoardUnits( "drill offset y" );
6194 aPad->SetOffset( curLayer, pt );
6195 NeedRIGHT();
6196 break;
6197 }
6198
6199 case T_rect_delta:
6200 {
6202 delta.x = parseBoardUnits( "rectangle delta width" );
6203 delta.y = parseBoardUnits( "rectangle delta height" );
6204 aPad->SetDelta( curLayer, delta );
6205 NeedRIGHT();
6206 break;
6207 }
6208
6209 case T_roundrect_rratio:
6210 aPad->SetRoundRectRadiusRatio( curLayer,
6211 parseDouble( "roundrect radius ratio" ) );
6212 NeedRIGHT();
6213 break;
6214
6215 case T_chamfer_ratio:
6216 {
6217 double ratio = parseDouble( "chamfer ratio" );
6218 aPad->SetChamferRectRatio( curLayer, ratio );
6219
6220 if( ratio > 0 )
6221 aPad->SetShape( curLayer, PAD_SHAPE::CHAMFERED_RECT );
6222
6223 NeedRIGHT();
6224 break;
6225 }
6226
6227 case T_chamfer:
6228 {
6229 int chamfers = 0;
6230 bool end_list = false;
6231
6232 while( !end_list )
6233 {
6234 token = NextTok();
6235
6236 switch( token )
6237 {
6238 case T_top_left:
6239 chamfers |= RECT_CHAMFER_TOP_LEFT;
6240 break;
6241
6242 case T_top_right:
6243 chamfers |= RECT_CHAMFER_TOP_RIGHT;
6244 break;
6245
6246 case T_bottom_left:
6247 chamfers |= RECT_CHAMFER_BOTTOM_LEFT;
6248 break;
6249
6250 case T_bottom_right:
6251 chamfers |= RECT_CHAMFER_BOTTOM_RIGHT;
6252 break;
6253
6254 case T_RIGHT:
6255 aPad->SetChamferPositions( curLayer, chamfers );
6256 end_list = true;
6257 break;
6258
6259 default:
6260 Expecting( "chamfer_top_left chamfer_top_right chamfer_bottom_left or "
6261 "chamfer_bottom_right" );
6262 }
6263 }
6264
6265 if( end_list && chamfers != RECT_NO_CHAMFER )
6266 aPad->SetShape( curLayer, PAD_SHAPE::CHAMFERED_RECT );
6267
6268 break;
6269 }
6270
6271 case T_thermal_bridge_width:
6272 padstack.ThermalSpokeWidth( curLayer ) =
6273 parseBoardUnits( "thermal relief spoke width" );
6274 NeedRIGHT();
6275 break;
6276
6277 case T_thermal_gap:
6278 padstack.ThermalGap( curLayer ) = parseBoardUnits( "thermal relief gap value" );
6279 NeedRIGHT();
6280 break;
6281
6282 case T_thermal_bridge_angle:
6283 padstack.SetThermalSpokeAngle(
6284 EDA_ANGLE( parseDouble( "thermal spoke angle" ), DEGREES_T ) );
6285 NeedRIGHT();
6286 break;
6287
6288 case T_zone_connect:
6289 padstack.ZoneConnection( curLayer ) = magic_enum::enum_cast<ZONE_CONNECTION>(
6290 parseInt( "zone connection value" ) );
6291 NeedRIGHT();
6292 break;
6293
6294 case T_clearance:
6295 padstack.Clearance( curLayer ) = parseBoardUnits( "local clearance value" );
6296 NeedRIGHT();
6297 break;
6298
6299 case T_tenting:
6300 {
6301 auto [front, back] = parseFrontBackOptBool( true );
6302 padstack.FrontOuterLayers().has_solder_mask = front;
6303 padstack.BackOuterLayers().has_solder_mask = back;
6304 break;
6305 }
6306
6307 // TODO: refactor parsePAD_options to work on padstacks too
6308 case T_options:
6309 {
6310 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
6311 {
6312 if( token != T_LEFT )
6313 Expecting( T_LEFT );
6314
6315 token = NextTok();
6316
6317 switch( token )
6318 {
6319 case T_anchor:
6320 token = NextTok();
6321 // Custom shaped pads have a "anchor pad", which is the reference
6322 // for connection calculations.
6323 // Because this is an anchor, only the 2 very basic shapes are managed:
6324 // circle and rect.
6325 switch( token )
6326 {
6327 case T_circle:
6328 padstack.SetAnchorShape( PAD_SHAPE::CIRCLE, curLayer );
6329 break;
6330
6331 case T_rect:
6332 padstack.SetAnchorShape( PAD_SHAPE::RECTANGLE, curLayer );
6333 break;
6334
6335 default:
6336 // Currently, because pad options is a moving target
6337 // just skip unknown keywords
6338 break;
6339 }
6340 NeedRIGHT();
6341 break;
6342
6343 case T_clearance:
6344 token = NextTok();
6345 // TODO: m_customShapeInZoneMode is not per-layer at the moment
6346 NeedRIGHT();
6347 break;
6348
6349 default:
6350 // Currently, because pad options is a moving target
6351 // just skip unknown keywords
6352 while( ( token = NextTok() ) != T_RIGHT )
6353 {
6354 }
6355
6356 break;
6357 }
6358 }
6359
6360 break;
6361 }
6362
6363 // TODO: deduplicate with non-padstack parser
6364 case T_primitives:
6365 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
6366 {
6367 if( token == T_LEFT )
6368 token = NextTok();
6369
6370 switch( token )
6371 {
6372 case T_gr_arc:
6373 case T_gr_line:
6374 case T_gr_circle:
6375 case T_gr_rect:
6376 case T_gr_poly:
6377 case T_gr_curve:
6378 padstack.AddPrimitive( parsePCB_SHAPE( nullptr ), curLayer );
6379 break;
6380
6381 case T_gr_bbox:
6382 {
6383 PCB_SHAPE* numberBox = parsePCB_SHAPE( nullptr );
6384 numberBox->SetIsProxyItem();
6385 padstack.AddPrimitive( numberBox, curLayer );
6386 break;
6387 }
6388
6389 case T_gr_vector:
6390 {
6391 PCB_SHAPE* spokeTemplate = parsePCB_SHAPE( nullptr );
6392 spokeTemplate->SetIsProxyItem();
6393 padstack.AddPrimitive( spokeTemplate, curLayer );
6394 break;
6395 }
6396
6397 default:
6398 Expecting( "gr_line, gr_arc, gr_circle, gr_curve, gr_rect, gr_bbox or gr_poly" );
6399 break;
6400 }
6401 }
6402
6403 break;
6404
6405 default:
6406 // Not strict-parsing padstack layers yet
6407 continue;
6408 }
6409 }
6410
6411 break;
6412 }
6413
6414 default:
6415 Expecting( "mode or layer" );
6416 break;
6417 }
6418 }
6419}
6420
6421
6423{
6424 T token;
6425
6426 while( ( token = NextTok() ) != T_RIGHT )
6427 {
6428 // This token is the Uuid of the item in the group.
6429 // Since groups are serialized at the end of the file/footprint, the Uuid should already
6430 // have been seen and exist in the board.
6431 KIID uuid( CurStr() );
6432 aGroupInfo.memberUuids.push_back( uuid );
6433 }
6434}
6435
6436
6438{
6439 wxCHECK_RET( CurTok() == T_group,
6440 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as PCB_GROUP." ) );
6441
6442 T token;
6443
6444 m_groupInfos.push_back( GROUP_INFO() );
6445 GROUP_INFO& groupInfo = m_groupInfos.back();
6446 groupInfo.parent = aParent;
6447
6448 while( ( token = NextTok() ) != T_LEFT )
6449 {
6450 if( token == T_STRING )
6451 groupInfo.name = FromUTF8();
6452 else if( token == T_locked )
6453 groupInfo.locked = true;
6454 else
6455 Expecting( "group name or locked" );
6456 }
6457
6458 for( ; token != T_RIGHT; token = NextTok() )
6459 {
6460 if( token != T_LEFT )
6461 Expecting( T_LEFT );
6462
6463 token = NextTok();
6464
6465 switch( token )
6466 {
6467 // From formats [20200811, 20231215), 'id' was used instead of 'uuid'
6468 case T_id:
6469 case T_uuid:
6470 NextTok();
6471 groupInfo.uuid = CurStrToKIID();
6472 NeedRIGHT();
6473 break;
6474
6475 case T_lib_id:
6476 {
6477 token = NextTok();
6478
6479 if( !IsSymbol( token ) && token != T_NUMBER )
6480 Expecting( "symbol|number" );
6481
6482 wxString name = FromUTF8();
6483 // Some symbol LIB_IDs have the '/' character escaped which can break
6484 // symbol links. The '/' character is no longer an illegal LIB_ID character so
6485 // it doesn't need to be escaped.
6486 name.Replace( "{slash}", "/" );
6487
6488 int bad_pos = groupInfo.libId.Parse( name );
6489
6490 if( bad_pos >= 0 )
6491 {
6492 if( static_cast<int>( name.size() ) > bad_pos )
6493 {
6494 wxString msg = wxString::Format( _( "Group library link %s contains invalid character '%c'" ),
6495 name,
6496 name[bad_pos] );
6497
6498 THROW_PARSE_ERROR( msg, CurSource(), CurLine(), CurLineNumber(), CurOffset() );
6499 }
6500
6501 THROW_PARSE_ERROR( _( "Invalid library ID" ), CurSource(), CurLine(), CurLineNumber(), CurOffset() );
6502 }
6503
6504 NeedRIGHT();
6505 break;
6506 }
6507
6508 case T_locked:
6509 groupInfo.locked = parseBool();
6510 NeedRIGHT();
6511 break;
6512
6513 case T_members:
6514 {
6515 parseGROUP_members( groupInfo );
6516 break;
6517 }
6518
6519 default:
6520 Expecting( "uuid, locked, lib_id, or members" );
6521 }
6522 }
6523}
6524
6525
6527{
6528 wxCHECK_RET( CurTok() == T_generated,
6529 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as PCB_GENERATOR." ) );
6530
6531 T token;
6532
6533 m_generatorInfos.push_back( GENERATOR_INFO() );
6534 GENERATOR_INFO& genInfo = m_generatorInfos.back();
6535
6536 genInfo.layer = F_Cu;
6537 genInfo.parent = aParent;
6538 genInfo.properties = STRING_ANY_MAP( pcbIUScale.IU_PER_MM );
6539
6540 NeedLEFT();
6541 token = NextTok();
6542
6543 // For formats [20231007, 20231215), 'id' was used instead of 'uuid'
6544 if( token != T_uuid && token != T_id )
6545 Expecting( T_uuid );
6546
6547 NextTok();
6548 genInfo.uuid = CurStrToKIID();
6549 NeedRIGHT();
6550
6551 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
6552 {
6553 if( token != T_LEFT )
6554 Expecting( T_LEFT );
6555
6556 token = NextTok();
6557
6558 switch( token )
6559 {
6560 case T_type:
6561 NeedSYMBOL();
6562 genInfo.genType = FromUTF8();
6563 NeedRIGHT();
6564 break;
6565
6566 case T_name:
6567 NeedSYMBOL();
6568 genInfo.name = FromUTF8();
6569 NeedRIGHT();
6570 break;
6571
6572 case T_locked:
6573 token = NextTok();
6574 genInfo.locked = token == T_yes;
6575 NeedRIGHT();
6576 break;
6577
6578 case T_layer:
6579 genInfo.layer = parseBoardItemLayer();
6580 NeedRIGHT();
6581 break;
6582
6583 case T_members:
6584 parseGROUP_members( genInfo );
6585 break;
6586
6587 default:
6588 {
6589 wxString pName = FromUTF8();
6590 T tok1 = NextTok();
6591
6592 switch( tok1 )
6593 {
6594 case T_yes:
6595 {
6596 genInfo.properties.emplace( pName, wxAny( true ) );
6597 NeedRIGHT();
6598 break;
6599 }
6600 case T_no:
6601 {
6602 genInfo.properties.emplace( pName, wxAny( false ) );
6603 NeedRIGHT();
6604 break;
6605 }
6606 case T_NUMBER:
6607 {
6608 double pValue = parseDouble();
6609 genInfo.properties.emplace( pName, wxAny( pValue ) );
6610 NeedRIGHT();
6611 break;
6612 }
6613 case T_STRING: // Quoted string
6614 {
6615 wxString pValue = FromUTF8();
6616 genInfo.properties.emplace( pName, pValue );
6617 NeedRIGHT();
6618 break;
6619 }
6620 case T_LEFT:
6621 {
6622 NeedSYMBOL();
6623 T tok2 = CurTok();
6624
6625 switch( tok2 )
6626 {
6627 case T_xy:
6628 {
6629 VECTOR2I pt;
6630
6631 pt.x = parseBoardUnits( "X coordinate" );
6632 pt.y = parseBoardUnits( "Y coordinate" );
6633
6634 genInfo.properties.emplace( pName, wxAny( pt ) );
6635 NeedRIGHT();
6636 NeedRIGHT();
6637
6638 break;
6639 }
6640 case T_pts:
6641 {
6643
6644 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
6646
6647 NeedRIGHT();
6648
6649 genInfo.properties.emplace( pName, wxAny( chain ) );
6650 break;
6651 }
6652 default: Expecting( "xy or pts" );
6653 }
6654
6655 break;
6656 }
6657 default: Expecting( "a number, symbol, string or (" );
6658 }
6659
6660 break;
6661 }
6662 }
6663 }
6664
6665 // Previous versions had bugs which could save ghost tuning patterns. Ignore them.
6666 if( genInfo.genType == wxT( "tuning_pattern" ) && genInfo.memberUuids.empty() )
6667 m_generatorInfos.pop_back();
6668}
6669
6670
6672{
6673 wxCHECK_MSG( CurTok() == T_arc, nullptr,
6674 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as ARC." ) );
6675
6676 VECTOR2I pt;
6677 T token;
6678
6679 std::unique_ptr<PCB_ARC> arc = std::make_unique<PCB_ARC>( m_board );
6680
6681 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
6682 {
6683 // Legacy locked
6684 if( token == T_locked )
6685 {
6686 arc->SetLocked( true );
6687 token = NextTok();
6688 }
6689
6690 if( token != T_LEFT )
6691 Expecting( T_LEFT );
6692
6693 token = NextTok();
6694
6695 switch( token )
6696 {
6697 case T_start:
6698 pt.x = parseBoardUnits( "start x" );
6699 pt.y = parseBoardUnits( "start y" );
6700 arc->SetStart( pt );
6701 NeedRIGHT();
6702 break;
6703
6704 case T_mid:
6705 pt.x = parseBoardUnits( "mid x" );
6706 pt.y = parseBoardUnits( "mid y" );
6707 arc->SetMid( pt );
6708 NeedRIGHT();
6709 break;
6710
6711 case T_end:
6712 pt.x = parseBoardUnits( "end x" );
6713 pt.y = parseBoardUnits( "end y" );
6714 arc->SetEnd( pt );
6715 NeedRIGHT();
6716 break;
6717
6718 case T_width:
6719 arc->SetWidth( parseBoardUnits( "width" ) );
6720 NeedRIGHT();
6721 break;
6722
6723 case T_layer:
6724 arc->SetLayer( parseBoardItemLayer() );
6725 NeedRIGHT();
6726 break;
6727
6728 case T_layers:
6729 arc->SetLayerSet( parseLayersForCuItemWithSoldermask() );
6730 break;
6731
6732 case T_solder_mask_margin:
6733 arc->SetLocalSolderMaskMargin( parseBoardUnits( "local solder mask margin value" ) );
6734 NeedRIGHT();
6735 break;
6736
6737 case T_net:
6738 if( !arc->SetNetCode( getNetCode( parseInt( "net number" ) ), /* aNoAssert */ true ) )
6739 {
6740 wxLogError( _( "Invalid net ID in\nfile: %s\nline: %d\noffset: %d." ),
6741 CurSource(), CurLineNumber(), CurOffset() );
6742 }
6743 NeedRIGHT();
6744 break;
6745
6746 case T_tstamp:
6747 case T_uuid:
6748 NextTok();
6749 const_cast<KIID&>( arc->m_Uuid ) = CurStrToKIID();
6750 NeedRIGHT();
6751 break;
6752
6753 // We continue to parse the status field but it is no longer written
6754 case T_status:
6755 parseHex();
6756 NeedRIGHT();
6757 break;
6758
6759 case T_locked:
6760 arc->SetLocked( parseMaybeAbsentBool( true ) );
6761 break;
6762
6763 default:
6764 Expecting( "start, mid, end, width, layer, solder_mask_margin, net, tstamp, uuid, "
6765 "or status" );
6766 }
6767 }
6768
6769 if( !IsCopperLayer( arc->GetLayer() ) )
6770 {
6771 // No point in asserting; these usually come from hand-edited boards
6772 return nullptr;
6773 }
6774
6775 return arc.release();
6776}
6777
6778
6780{
6781 wxCHECK_MSG( CurTok() == T_segment, nullptr,
6782 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as PCB_TRACK." ) );
6783
6784 VECTOR2I pt;
6785 T token;
6786
6787 std::unique_ptr<PCB_TRACK> track = std::make_unique<PCB_TRACK>( m_board );
6788
6789 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
6790 {
6791 // Legacy locked flag
6792 if( token == T_locked )
6793 {
6794 track->SetLocked( true );
6795 token = NextTok();
6796 }
6797
6798 if( token != T_LEFT )
6799 Expecting( T_LEFT );
6800
6801 token = NextTok();
6802
6803 switch( token )
6804 {
6805 case T_start:
6806 pt.x = parseBoardUnits( "start x" );
6807 pt.y = parseBoardUnits( "start y" );
6808 track->SetStart( pt );
6809 NeedRIGHT();
6810 break;
6811
6812 case T_end:
6813 pt.x = parseBoardUnits( "end x" );
6814 pt.y = parseBoardUnits( "end y" );
6815 track->SetEnd( pt );
6816 NeedRIGHT();
6817 break;
6818
6819 case T_width:
6820 track->SetWidth( parseBoardUnits( "width" ) );
6821 NeedRIGHT();
6822 break;
6823
6824 case T_layer:
6825 track->SetLayer( parseBoardItemLayer() );
6826 NeedRIGHT();
6827 break;
6828
6829 case T_layers:
6830 track->SetLayerSet( parseLayersForCuItemWithSoldermask() );
6831 break;
6832
6833 case T_solder_mask_margin:
6834 track->SetLocalSolderMaskMargin( parseBoardUnits( "local solder mask margin value" ) );
6835 NeedRIGHT();
6836 break;
6837
6838 case T_net:
6839 if( !track->SetNetCode( getNetCode( parseInt( "net number" ) ), /* aNoAssert */ true ) )
6840 {
6841 wxLogError( _( "Invalid net ID in\nfile: '%s'\nline: %d\noffset: %d." ),
6842 CurSource(), CurLineNumber(), CurOffset() );
6843 }
6844 NeedRIGHT();
6845 break;
6846
6847 case T_tstamp:
6848 case T_uuid:
6849 NextTok();
6850 const_cast<KIID&>( track->m_Uuid ) = CurStrToKIID();
6851 NeedRIGHT();
6852 break;
6853
6854 // We continue to parse the status field but it is no longer written
6855 case T_status:
6856 parseHex();
6857 NeedRIGHT();
6858 break;
6859
6860 case T_locked:
6861 track->SetLocked( parseMaybeAbsentBool( true ) );
6862 break;
6863
6864 default:
6865 Expecting( "start, end, width, layer, solder_mask_margin, net, tstamp, uuid, "
6866 "or locked" );
6867 }
6868 }
6869
6870 if( !IsCopperLayer( track->GetLayer() ) )
6871 {
6872 // No point in asserting; these usually come from hand-edited boards
6873 return nullptr;
6874 }
6875
6876 return track.release();
6877}
6878
6879
6881{
6882 wxCHECK_MSG( CurTok() == T_via, nullptr,
6883 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as PCB_VIA." ) );
6884
6885 VECTOR2I pt;
6886 T token;
6887
6888 std::unique_ptr<PCB_VIA> via = std::make_unique<PCB_VIA>( m_board );
6889
6890 // File format default is no-token == no-feature.
6891 via->Padstack().SetUnconnectedLayerMode( PADSTACK::UNCONNECTED_LAYER_MODE::KEEP_ALL );
6892
6893 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
6894 {
6895 // Legacy locked
6896 if( token == T_locked )
6897 {
6898 via->SetLocked( true );
6899 token = NextTok();
6900 }
6901
6902 if( token == T_LEFT )
6903 token = NextTok();
6904
6905 switch( token )
6906 {
6907 case T_blind:
6908 via->SetViaType( VIATYPE::BLIND );
6909 break;
6910
6911 case T_buried:
6912 via->SetViaType( VIATYPE::BURIED );
6913 break;
6914
6915 case T_micro:
6916 via->SetViaType( VIATYPE::MICROVIA );
6917 break;
6918
6919 case T_at:
6920 pt.x = parseBoardUnits( "start x" );
6921 pt.y = parseBoardUnits( "start y" );
6922 via->SetStart( pt );
6923 via->SetEnd( pt );
6924 NeedRIGHT();
6925 break;
6926
6927 case T_size:
6928 via->SetWidth( PADSTACK::ALL_LAYERS, parseBoardUnits( "via width" ) );
6929 NeedRIGHT();
6930 break;
6931
6932 case T_drill:
6933 via->SetDrill( parseBoardUnits( "drill diameter" ) );
6934 NeedRIGHT();
6935 break;
6936
6937 case T_layers:
6938 {
6939 PCB_LAYER_ID layer1, layer2;
6940 NextTok();
6941 layer1 = lookUpLayer( m_layerIndices );
6942 NextTok();
6943 layer2 = lookUpLayer( m_layerIndices );
6944 via->SetLayerPair( layer1, layer2 );
6945
6946 if( layer1 == UNDEFINED_LAYER || layer2 == UNDEFINED_LAYER )
6947 Expecting( "layer name" );
6948
6949 NeedRIGHT();
6950 break;
6951 }
6952
6953 case T_net:
6954 if( !via->SetNetCode( getNetCode( parseInt( "net number" ) ), /* aNoAssert */ true ) )
6955 {
6956 wxLogError( _( "Invalid net ID in\nfile: %s\nline: %d\noffset: %d" ),
6957 CurSource(), CurLineNumber(), CurOffset() );
6958 }
6959
6960 NeedRIGHT();
6961 break;
6962
6963 case T_remove_unused_layers:
6964 {
6965 bool remove = parseMaybeAbsentBool( true );
6966 via->SetRemoveUnconnected( remove );
6967 break;
6968 }
6969
6970 case T_keep_end_layers:
6971 {
6972 bool keep = parseMaybeAbsentBool( true );
6973 via->SetKeepStartEnd( keep );
6974 break;
6975 }
6976
6977 case T_start_end_only:
6978 {
6979 if( parseMaybeAbsentBool( true ) )
6980 via->Padstack().SetUnconnectedLayerMode( PADSTACK::UNCONNECTED_LAYER_MODE::START_END_ONLY );
6981
6982 break;
6983 }
6984
6985 case T_zone_layer_connections:
6986 {
6987 // Ensure only copper layers are stored int ZoneLayerOverride array
6988 LSET cuLayers = via->GetLayerSet() & LSET::AllCuMask();
6989
6990 for( PCB_LAYER_ID layer : cuLayers )
6991 {
6992 via->SetZoneLayerOverride( layer, ZLO_FORCE_NO_ZONE_CONNECTION );
6993 }
6994
6995 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
6996 {
6998
6999 if( !IsCopperLayer( layer ) )
7000 Expecting( "copper layer name" );
7001
7002 via->SetZoneLayerOverride( layer, ZLO_FORCE_FLASHED );
7003 }
7004 }
7005 break;
7006
7007 case T_padstack:
7008 parseViastack( via.get() );
7009 break;
7010
7011 case T_teardrops:
7012 parseTEARDROP_PARAMETERS( &via->GetTeardropParams() );
7013 break;
7014
7015 case T_tenting:
7016 {
7017 auto [front, back] = parseFrontBackOptBool( true );
7018 via->Padstack().FrontOuterLayers().has_solder_mask = front;
7019 via->Padstack().BackOuterLayers().has_solder_mask = back;
7020 break;
7021 }
7022 case T_covering:
7023 {
7024 auto [front, back] = parseFrontBackOptBool();
7025 via->Padstack().FrontOuterLayers().has_covering = front;
7026 via->Padstack().BackOuterLayers().has_covering = back;
7027 break;
7028 }
7029 case T_plugging:
7030 {
7031 auto [front, back] = parseFrontBackOptBool();
7032 via->Padstack().FrontOuterLayers().has_plugging = front;
7033 via->Padstack().BackOuterLayers().has_plugging = back;
7034 break;
7035 }
7036 case T_filling:
7037 {
7038 via->Padstack().Drill().is_filled = parseOptBool();
7039 NeedRIGHT();
7040 break;
7041 }
7042 case T_capping:
7043 {
7044 via->Padstack().Drill().is_capped = parseOptBool();
7045 NeedRIGHT();
7046 break;
7047 }
7048
7049 case T_tstamp:
7050 case T_uuid:
7051 NextTok();
7052 const_cast<KIID&>( via->m_Uuid ) = CurStrToKIID();
7053 NeedRIGHT();
7054 break;
7055
7056 // We continue to parse the status field but it is no longer written
7057 case T_status:
7058 parseHex();
7059 NeedRIGHT();
7060 break;
7061
7062 case T_locked:
7063 via->SetLocked( parseMaybeAbsentBool( true ) );
7064 break;
7065
7066 case T_free:
7067 via->SetIsFree( parseMaybeAbsentBool( true ) );
7068 break;
7069
7070 default:
7071 Expecting( "blind, micro, at, size, drill, layers, net, free, tstamp, uuid, status or "
7072 "teardrops" );
7073 }
7074 }
7075
7076 return via.release();
7077}
7078
7079
7080std::pair<std::optional<bool>, std::optional<bool>>
7082{
7083 T token = NextTok();
7084
7085 std::optional<bool> front( std::nullopt );
7086 std::optional<bool> back( std::nullopt );
7087
7088 if( token != T_LEFT && aLegacy )
7089 {
7090 // legacy format for tenting.
7091 if( token == T_front || token == T_back || token == T_none )
7092 {
7093 while( token != T_RIGHT )
7094 {
7095 if( token == T_front )
7096 {
7097 front = true;
7098 }
7099 else if( token == T_back )
7100 {
7101 back = true;
7102 }
7103 else if( token == T_none )
7104 {
7105 front.reset();
7106 back.reset();
7107 }
7108 else
7109 {
7110 Expecting( "front, back or none" );
7111 }
7112
7113 token = NextTok();
7114 }
7115
7116 return { front, back };
7117 }
7118 }
7119
7120 while( token != T_RIGHT )
7121 {
7122 if( token != T_LEFT )
7123 Expecting( "(" );
7124
7125 token = NextTok();
7126
7127 if( token == T_front )
7128 front = parseOptBool();
7129 else if( token == T_back )
7130 back = parseOptBool();
7131 else
7132 Expecting( "front or back" );
7133
7134 NeedRIGHT();
7135
7136 token = NextTok();
7137 }
7138
7139 return { front, back };
7140}
7141
7142
7144{
7145 PADSTACK& padstack = aVia->Padstack();
7146
7147 for( T token = NextTok(); token != T_RIGHT; token = NextTok() )
7148 {
7149 if( token != T_LEFT )
7150 Expecting( T_LEFT );
7151
7152 token = NextTok();
7153
7154 switch( token )
7155 {
7156 case T_mode:
7157 token = NextTok();
7158
7159 switch( token )
7160 {
7161 case T_front_inner_back:
7163 break;
7164
7165 case T_custom:
7166 padstack.SetMode( PADSTACK::MODE::CUSTOM );
7167 break;
7168
7169 default:
7170 Expecting( "front_inner_back or custom" );
7171 }
7172
7173 NeedRIGHT();
7174 break;
7175
7176 case T_layer:
7177 {
7178 NextTok();
7179 PCB_LAYER_ID curLayer = UNDEFINED_LAYER;
7180
7181 if( curText == "Inner" )
7182 {
7183 if( padstack.Mode() != PADSTACK::MODE::FRONT_INNER_BACK )
7184 {
7185 THROW_IO_ERROR( wxString::Format( _( "Invalid padstack layer in\nfile: %s\n"
7186 "line: %d\noffset: %d." ),
7187 CurSource(), CurLineNumber(), CurOffset() ) );
7188 }
7189
7190 curLayer = PADSTACK::INNER_LAYERS;
7191 }
7192 else
7193 {
7194 curLayer = lookUpLayer( m_layerIndices );
7195 }
7196
7197 if( !IsCopperLayer( curLayer ) )
7198 {
7199 wxString error;
7200 error.Printf( _( "Invalid padstack layer '%s' in file '%s' at line %d, offset %d." ),
7201 curText, CurSource().GetData(), CurLineNumber(), CurOffset() );
7202 THROW_IO_ERROR( error );
7203 }
7204
7205 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
7206 {
7207 if( token != T_LEFT )
7208 Expecting( T_LEFT );
7209
7210 token = NextTok();
7211
7212 switch( token )
7213 {
7214
7215 case T_size:
7216 {
7217 int diameter = parseBoardUnits( "via width" );
7218 padstack.SetSize( { diameter, diameter }, curLayer );
7219 NeedRIGHT();
7220 break;
7221 }
7222
7223 default:
7224 // Currently only supporting custom via diameter per layer, not other properties
7225 Expecting( "size" );
7226 }
7227 }
7228
7229 break;
7230 }
7231
7232 default:
7233 Expecting( "mode or layer" );
7234 break;
7235 }
7236 }
7237}
7238
7239
7241{
7242 wxCHECK_MSG( CurTok() == T_zone, nullptr,
7243 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as ZONE." ) );
7244
7246
7247 int hatchPitch = ZONE::GetDefaultHatchPitch();
7248 T token;
7249 int tmp;
7250 wxString netnameFromfile; // the zone net name find in file
7251
7252 // bigger scope since each filled_polygon is concatenated in here
7253 std::map<PCB_LAYER_ID, SHAPE_POLY_SET> pts;
7254 std::map<PCB_LAYER_ID, std::vector<SEG>> legacySegs;
7255 PCB_LAYER_ID filledLayer;
7256 bool addedFilledPolygons = false;
7257
7258 // This hasn't been supported since V6 or so, but we only stopped writing out the token
7259 // in V10.
7260 bool isStrokedFill = m_requiredVersion < 20250210;
7261
7262 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( aParent );
7263
7264 zone->SetAssignedPriority( 0 );
7265
7266 // This is the default for board files:
7267 zone->SetIslandRemovalMode( ISLAND_REMOVAL_MODE::ALWAYS );
7268
7269 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
7270 {
7271 // legacy locked
7272 if( token == T_locked )
7273 {
7274 zone->SetLocked( true );
7275 token = NextTok();
7276 }
7277
7278 if( token == T_LEFT )
7279 token = NextTok();
7280
7281 switch( token )
7282 {
7283 case T_net:
7284 // Init the net code only, not the netname, to be sure
7285 // the zone net name is the name read in file.
7286 // (When mismatch, the user will be prompted in DRC, to fix the actual name)
7287 tmp = getNetCode( parseInt( "net number" ) );
7288
7289 if( tmp < 0 )
7290 tmp = 0;
7291
7292 if( !zone->SetNetCode( tmp, /* aNoAssert */ true ) )
7293 {
7294 wxLogError( _( "Invalid net ID in\nfile: %s;\nline: %d\noffset: %d." ),
7295 CurSource(), CurLineNumber(), CurOffset() );
7296 }
7297
7298 NeedRIGHT();
7299 break;
7300
7301 case T_net_name:
7302 NeedSYMBOLorNUMBER();
7303 netnameFromfile = FromUTF8();
7304 NeedRIGHT();
7305 break;
7306
7307 case T_layer: // keyword for zones that are on only one layer
7308 zone->SetLayer( parseBoardItemLayer() );
7309 NeedRIGHT();
7310 break;
7311
7312 case T_layers: // keyword for zones that can live on a set of layers
7313 zone->SetLayerSet( parseBoardItemLayersAsMask() );
7314 break;
7315
7316 case T_property:
7317 parseZoneLayerProperty( zone->LayerProperties() );
7318 break;
7319
7320 case T_tstamp:
7321 case T_uuid:
7322 NextTok();
7323 const_cast<KIID&>( zone->m_Uuid ) = CurStrToKIID();
7324 NeedRIGHT();
7325 break;
7326
7327 case T_hatch:
7328 token = NextTok();
7329
7330 if( token != T_none && token != T_edge && token != T_full )
7331 Expecting( "none, edge, or full" );
7332
7333 switch( token )
7334 {
7335 default:
7336 case T_none: hatchStyle = ZONE_BORDER_DISPLAY_STYLE::NO_HATCH; break;
7337 case T_edge: hatchStyle = ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE; break;
7338 case T_full: hatchStyle = ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_FULL; break;
7339 }
7340
7341 hatchPitch = parseBoardUnits( "hatch pitch" );
7342 NeedRIGHT();
7343 break;
7344
7345 case T_priority:
7346 zone->SetAssignedPriority( parseInt( "zone priority" ) );
7347 NeedRIGHT();
7348 break;
7349
7350 case T_connect_pads:
7351 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
7352 {
7353 if( token == T_LEFT )
7354 token = NextTok();
7355
7356 switch( token )
7357 {
7358 case T_yes:
7359 zone->SetPadConnection( ZONE_CONNECTION::FULL );
7360 break;
7361
7362 case T_no:
7363 zone->SetPadConnection( ZONE_CONNECTION::NONE );
7364 break;
7365
7366 case T_thru_hole_only:
7367 zone->SetPadConnection( ZONE_CONNECTION::THT_THERMAL );
7368 break;
7369
7370 case T_clearance:
7371 zone->SetLocalClearance( parseBoardUnits( "zone clearance" ) );
7372 NeedRIGHT();
7373 break;
7374
7375 default:
7376 Expecting( "yes, no, or clearance" );
7377 }
7378 }
7379
7380 break;
7381
7382 case T_min_thickness:
7383 zone->SetMinThickness( parseBoardUnits( T_min_thickness ) );
7384 NeedRIGHT();
7385 break;
7386
7387 case T_filled_areas_thickness:
7388 // A new zone fill strategy was added in v6, so we need to know if we're parsing
7389 // a zone that was filled before that. Note that the change was implemented as
7390 // a new parameter, so we need to check for the presence of filled_areas_thickness
7391 // instead of just its value.
7392
7393 if( !parseBool() )
7394 isStrokedFill = false;
7395
7396 NeedRIGHT();
7397 break;
7398
7399 case T_fill:
7400 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
7401 {
7402 if( token == T_LEFT )
7403 token = NextTok();
7404
7405 switch( token )
7406 {
7407 case T_yes:
7408 zone->SetIsFilled( true );
7409 break;
7410
7411 case T_mode:
7412 token = NextTok();
7413
7414 if( token != T_segment && token != T_hatch && token != T_polygon )
7415 Expecting( "segment, hatch or polygon" );
7416
7417 switch( token )
7418 {
7419 case T_hatch:
7420 zone->SetFillMode( ZONE_FILL_MODE::HATCH_PATTERN );
7421 break;
7422
7423 case T_segment: // deprecated, convert to polygons
7424 case T_polygon:
7425 default:
7426 zone->SetFillMode( ZONE_FILL_MODE::POLYGONS );
7427 break;
7428 }
7429
7430 NeedRIGHT();
7431 break;
7432
7433 case T_hatch_thickness:
7434 zone->SetHatchThickness( parseBoardUnits( T_hatch_thickness ) );
7435 NeedRIGHT();
7436 break;
7437
7438 case T_hatch_gap:
7439 zone->SetHatchGap( parseBoardUnits( T_hatch_gap ) );
7440 NeedRIGHT();
7441 break;
7442
7443 case T_hatch_orientation:
7444 {
7445 EDA_ANGLE orientation( parseDouble( T_hatch_orientation ), DEGREES_T );
7446 zone->SetHatchOrientation( orientation );
7447 NeedRIGHT();
7448 break;
7449 }
7450
7451 case T_hatch_smoothing_level:
7452 zone->SetHatchSmoothingLevel( parseDouble( T_hatch_smoothing_level ) );
7453 NeedRIGHT();
7454 break;
7455
7456 case T_hatch_smoothing_value:
7457 zone->SetHatchSmoothingValue( parseDouble( T_hatch_smoothing_value ) );
7458 NeedRIGHT();
7459 break;
7460
7461 case T_hatch_border_algorithm:
7462 token = NextTok();
7463
7464 if( token != T_hatch_thickness && token != T_min_thickness )
7465 Expecting( "hatch_thickness or min_thickness" );
7466
7467 zone->SetHatchBorderAlgorithm( token == T_hatch_thickness ? 1 : 0 );
7468 NeedRIGHT();
7469 break;
7470
7471 case T_hatch_min_hole_area:
7472 zone->SetHatchHoleMinArea( parseDouble( T_hatch_min_hole_area ) );
7473 NeedRIGHT();
7474 break;
7475
7476 case T_arc_segments:
7477 ignore_unused( parseInt( "arc segment count" ) );
7478 NeedRIGHT();
7479 break;
7480
7481 case T_thermal_gap:
7482 zone->SetThermalReliefGap( parseBoardUnits( T_thermal_gap ) );
7483 NeedRIGHT();
7484 break;
7485
7486 case T_thermal_bridge_width:
7487 zone->SetThermalReliefSpokeWidth( parseBoardUnits( T_thermal_bridge_width ) );
7488 NeedRIGHT();
7489 break;
7490
7491 case T_smoothing:
7492 switch( NextTok() )
7493 {
7494 case T_none:
7495 zone->SetCornerSmoothingType( ZONE_SETTINGS::SMOOTHING_NONE );
7496 break;
7497
7498 case T_chamfer:
7499 if( !zone->GetIsRuleArea() ) // smoothing has meaning only for filled zones
7500 zone->SetCornerSmoothingType( ZONE_SETTINGS::SMOOTHING_CHAMFER );
7501
7502 break;
7503
7504 case T_fillet:
7505 if( !zone->GetIsRuleArea() ) // smoothing has meaning only for filled zones
7506 zone->SetCornerSmoothingType( ZONE_SETTINGS::SMOOTHING_FILLET );
7507
7508 break;
7509
7510 default:
7511 Expecting( "none, chamfer, or fillet" );
7512 }
7513
7514 NeedRIGHT();
7515 break;
7516
7517 case T_radius:
7518 tmp = parseBoardUnits( "corner radius" );
7519
7520 if( !zone->GetIsRuleArea() ) // smoothing has meaning only for filled zones
7521 zone->SetCornerRadius( tmp );
7522
7523 NeedRIGHT();
7524 break;
7525
7526 case T_island_removal_mode:
7527 tmp = parseInt( "island_removal_mode" );
7528
7529 if( tmp >= 0 && tmp <= 2 )
7530 zone->SetIslandRemovalMode( static_cast<ISLAND_REMOVAL_MODE>( tmp ) );
7531
7532 NeedRIGHT();
7533 break;
7534
7535 case T_island_area_min:
7536 {
7537 int area = parseBoardUnits( T_island_area_min );
7538 zone->SetMinIslandArea( area * pcbIUScale.IU_PER_MM );
7539 NeedRIGHT();
7540 break;
7541 }
7542
7543 default:
7544 Expecting( "mode, arc_segments, thermal_gap, thermal_bridge_width, "
7545 "hatch_thickness, hatch_gap, hatch_orientation, "
7546 "hatch_smoothing_level, hatch_smoothing_value, "
7547 "hatch_border_algorithm, hatch_min_hole_area, smoothing, radius, "
7548 "island_removal_mode, or island_area_min" );
7549 }
7550 }
7551
7552 break;
7553
7554 case T_placement:
7555 zone->SetIsRuleArea( true );
7556
7557 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
7558 {
7559 if( token == T_LEFT )
7560 token = NextTok();
7561
7562 switch( token )
7563 {
7564 case T_sheetname:
7565 {
7566 zone->SetPlacementAreaSourceType( PLACEMENT_SOURCE_T::SHEETNAME );
7567 NeedSYMBOL();
7568 zone->SetPlacementAreaSource( FromUTF8() );
7569 break;
7570 }
7571 case T_component_class:
7572 {
7573 zone->SetPlacementAreaSourceType( PLACEMENT_SOURCE_T::COMPONENT_CLASS );
7574 NeedSYMBOL();
7575 zone->SetPlacementAreaSource( FromUTF8() );
7576 break;
7577 }
7578 case T_group:
7579 {
7580 zone->SetPlacementAreaSourceType( PLACEMENT_SOURCE_T::GROUP_PLACEMENT );
7581 NeedSYMBOL();
7582 zone->SetPlacementAreaSource( FromUTF8() );
7583 break;
7584 }
7585 case T_enabled:
7586 {
7587 token = NextTok();
7588
7589 if( token == T_yes )
7590 zone->SetPlacementAreaEnabled( true );
7591 else if( token == T_no )
7592 zone->SetPlacementAreaEnabled( false );
7593 else
7594 Expecting( "yes or no" );
7595
7596 break;
7597 }
7598 default:
7599 {
7600 Expecting( "enabled, sheetname, component_class, or group" );
7601 break;
7602 }
7603 }
7604
7605 NeedRIGHT();
7606 }
7607
7608 break;
7609
7610 case T_keepout:
7611 // "keepout" now means rule area, but the file token stays the same
7612 zone->SetIsRuleArea( true );
7613
7614 // Initialize these two because their tokens won't appear in older files:
7615 zone->SetDoNotAllowPads( false );
7616 zone->SetDoNotAllowFootprints( false );
7617
7618 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
7619 {
7620 if( token == T_LEFT )
7621 token = NextTok();
7622
7623 switch( token )
7624 {
7625 case T_tracks:
7626 token = NextTok();
7627
7628 if( token != T_allowed && token != T_not_allowed )
7629 Expecting( "allowed or not_allowed" );
7630
7631 zone->SetDoNotAllowTracks( token == T_not_allowed );
7632 break;
7633
7634 case T_vias:
7635 token = NextTok();
7636
7637 if( token != T_allowed && token != T_not_allowed )
7638 Expecting( "allowed or not_allowed" );
7639
7640 zone->SetDoNotAllowVias( token == T_not_allowed );
7641 break;
7642
7643 case T_copperpour:
7644 token = NextTok();
7645
7646 if( token != T_allowed && token != T_not_allowed )
7647 Expecting( "allowed or not_allowed" );
7648
7649 zone->SetDoNotAllowZoneFills( token == T_not_allowed );
7650 break;
7651
7652 case T_pads:
7653 token = NextTok();
7654
7655 if( token != T_allowed && token != T_not_allowed )
7656 Expecting( "allowed or not_allowed" );
7657
7658 zone->SetDoNotAllowPads( token == T_not_allowed );
7659 break;
7660
7661 case T_footprints:
7662 token = NextTok();
7663
7664 if( token != T_allowed && token != T_not_allowed )
7665 Expecting( "allowed or not_allowed" );
7666
7667 zone->SetDoNotAllowFootprints( token == T_not_allowed );
7668 break;
7669
7670 default:
7671 Expecting( "tracks, vias or copperpour" );
7672 }
7673
7674 NeedRIGHT();
7675 }
7676
7677 break;
7678
7679 case T_polygon:
7680 {
7681 SHAPE_LINE_CHAIN outline;
7682
7683 NeedLEFT();
7684 token = NextTok();
7685
7686 if( token != T_pts )
7687 Expecting( T_pts );
7688
7689 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
7690 parseOutlinePoints( outline );
7691
7692 NeedRIGHT();
7693
7694 outline.SetClosed( true );
7695
7696 // Remark: The first polygon is the main outline.
7697 // Others are holes inside the main outline.
7698 zone->AddPolygon( outline );
7699 break;
7700 }
7701
7702 case T_filled_polygon:
7703 {
7704 // "(filled_polygon (pts"
7705 NeedLEFT();
7706 token = NextTok();
7707
7708 if( token == T_layer )
7709 {
7710 filledLayer = parseBoardItemLayer();
7711 NeedRIGHT();
7712 token = NextTok();
7713
7714 if( token != T_LEFT )
7715 Expecting( T_LEFT );
7716
7717 token = NextTok();
7718 }
7719 else
7720 {
7721 // for legacy, single-layer zones
7722 filledLayer = zone->GetFirstLayer();
7723 }
7724
7725 bool island = false;
7726
7727 if( token == T_island )
7728 {
7729 island = parseMaybeAbsentBool( true );
7730 NeedLEFT();
7731 token = NextTok();
7732 }
7733
7734 if( token != T_pts )
7735 Expecting( T_pts );
7736
7737 if( !pts.count( filledLayer ) )
7738 pts[filledLayer] = SHAPE_POLY_SET();
7739
7740 SHAPE_POLY_SET& poly = pts.at( filledLayer );
7741
7742 int idx = poly.NewOutline();
7743 SHAPE_LINE_CHAIN& chain = poly.Outline( idx );
7744
7745 if( island )
7746 zone->SetIsIsland( filledLayer, idx );
7747
7748 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
7750
7751 NeedRIGHT();
7752
7753 addedFilledPolygons |= !poly.IsEmpty();
7754 }
7755
7756 break;
7757
7758 case T_fill_segments:
7759 {
7760 // Legacy segment fill
7761
7762 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
7763 {
7764 if( token != T_LEFT )
7765 Expecting( T_LEFT );
7766
7767 token = NextTok();
7768
7769 if( token != T_pts )
7770 Expecting( T_pts );
7771
7772 // Legacy zones only had one layer
7773 filledLayer = zone->GetFirstLayer();
7774
7775 SEG fillSegment;
7776
7777 fillSegment.A = parseXY();
7778 fillSegment.B = parseXY();
7779
7780 legacySegs[filledLayer].push_back( fillSegment );
7781
7782 NeedRIGHT();
7783 }
7784
7785 break;
7786 }
7787
7788 case T_name:
7789 NextTok();
7790 zone->SetZoneName( FromUTF8() );
7791 NeedRIGHT();
7792 break;
7793
7794 case T_attr:
7795 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
7796 {
7797 if( token == T_LEFT )
7798 token = NextTok();
7799
7800 switch( token )
7801 {
7802 case T_teardrop:
7803 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
7804 {
7805 if( token == T_LEFT )
7806 token = NextTok();
7807
7808 switch( token )
7809 {
7810 case T_type:
7811 token = NextTok();
7812
7813 if( token == T_padvia )
7814 zone->SetTeardropAreaType( TEARDROP_TYPE::TD_VIAPAD );
7815 else if( token == T_track_end )
7816 zone->SetTeardropAreaType( TEARDROP_TYPE::TD_TRACKEND );
7817 else
7818 Expecting( "padvia or track_end" );
7819
7820 NeedRIGHT();
7821 break;
7822
7823 default:
7824 Expecting( "type" );
7825 }
7826 }
7827
7828 break;
7829
7830 default:
7831 Expecting( "teardrop" );
7832 }
7833 }
7834 break;
7835
7836 case T_locked:
7837 zone->SetLocked( parseBool() );
7838 NeedRIGHT();
7839 break;
7840
7841 default:
7842 Expecting( "net, layer/layers, tstamp, hatch, priority, connect_pads, min_thickness, "
7843 "fill, polygon, filled_polygon, fill_segments, attr, locked, uuid, or name" );
7844 }
7845 }
7846
7847 if( zone->GetNumCorners() > 2 )
7848 {
7849 if( !zone->IsOnCopperLayer() )
7850 {
7851 //zone->SetFillMode( ZONE_FILL_MODE::POLYGONS );
7852 zone->SetNetCode( NETINFO_LIST::UNCONNECTED );
7853 }
7854
7855 // Set hatch here, after outlines corners are read
7856 zone->SetBorderDisplayStyle( hatchStyle, hatchPitch, true );
7857 }
7858
7859 if( addedFilledPolygons )
7860 {
7861 if( isStrokedFill && !zone->GetIsRuleArea() )
7862 {
7864 {
7865 wxLogWarning( _( "Legacy zone fill strategy is not supported anymore.\n"
7866 "Zone fills will be converted on best-effort basis." ) );
7867
7869 }
7870
7871 if( zone->GetMinThickness() > 0 )
7872 {
7873 for( auto& [layer, polyset] : pts )
7874 {
7875 polyset.InflateWithLinkedHoles( zone->GetMinThickness() / 2,
7877 ARC_HIGH_DEF / 2 );
7878 }
7879 }
7880 }
7881
7882 for( auto& [layer, polyset] : pts )
7883 zone->SetFilledPolysList( layer, polyset );
7884
7885 zone->CalculateFilledArea();
7886 }
7887 else if( legacySegs.size() > 0 )
7888 {
7889 // No polygons, just segment fill?
7890 // Note RFB: This code might be removed if turns out this never existed for sexpr file
7891 // format or otherwise we should add a test case to the qa folder
7892
7894 {
7895 wxLogWarning( _( "The legacy segment zone fill mode is no longer supported.\n"
7896 "Zone fills will be converted on a best-effort basis." ) );
7897
7899 }
7900
7901
7902 for( const auto& [layer, segments] : legacySegs )
7903 {
7904 SHAPE_POLY_SET layerFill;
7905
7906 if( zone->HasFilledPolysForLayer( layer ) )
7907 layerFill = SHAPE_POLY_SET( *zone->GetFill( layer ) );
7908
7909 for( const auto& seg : segments )
7910 {
7911 SHAPE_POLY_SET segPolygon;
7912
7913 TransformOvalToPolygon( segPolygon, seg.A, seg.B, zone->GetMinThickness(),
7915
7916 layerFill.BooleanAdd( segPolygon );
7917 }
7918
7919
7920 zone->SetFilledPolysList( layer, layerFill );
7921 zone->CalculateFilledArea();
7922 }
7923 }
7924
7925
7926 // Ensure keepout and non copper zones do not have a net
7927 // (which have no sense for these zones)
7928 // the netcode 0 is used for these zones
7929 bool zone_has_net = zone->IsOnCopperLayer() && !zone->GetIsRuleArea();
7930
7931 if( !zone_has_net )
7932 zone->SetNetCode( NETINFO_LIST::UNCONNECTED );
7933
7934 // Ensure the zone net name is valid, and matches the net code, for copper zones
7935 if( zone_has_net
7936 && ( !zone->GetNet() || zone->GetNet()->GetNetname() != netnameFromfile ) )
7937 {
7938 // Can happens which old boards, with nonexistent nets ...
7939 // or after being edited by hand
7940 // We try to fix the mismatch.
7941 NETINFO_ITEM* net = m_board->FindNet( netnameFromfile );
7942
7943 if( net ) // An existing net has the same net name. use it for the zone
7944 {
7945 zone->SetNetCode( net->GetNetCode() );
7946 }
7947 else // Not existing net: add a new net to keep trace of the zone netname
7948 {
7949 int newnetcode = m_board->GetNetCount();
7950 net = new NETINFO_ITEM( m_board, netnameFromfile, newnetcode );
7951 m_board->Add( net, ADD_MODE::INSERT, true );
7952
7953 // Store the new code mapping
7954 pushValueIntoMap( newnetcode, net->GetNetCode() );
7955
7956 // and update the zone netcode
7957 zone->SetNetCode( net->GetNetCode() );
7958 }
7959 }
7960
7961 if( zone->IsTeardropArea() && m_requiredVersion < 20230517 )
7962 m_board->SetLegacyTeardrops( true );
7963
7964 // Clear flags used in zone edition:
7965 zone->SetNeedRefill( false );
7966
7967 return zone.release();
7968}
7969
7970
7972{
7973 wxCHECK_MSG( CurTok() == T_point, nullptr,
7974 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as PCB_POINT." ) );
7975
7976 std::unique_ptr<PCB_POINT> point = std::make_unique<PCB_POINT>( nullptr );
7977
7978 for( T token = NextTok(); token != T_RIGHT; token = NextTok() )
7979 {
7980 if( token == T_LEFT )
7981 token = NextTok();
7982
7983 switch( token )
7984 {
7985 case T_at:
7986 {
7987 VECTOR2I pt;
7988 pt.x = parseBoardUnits( "point x position" );
7989 pt.y = parseBoardUnits( "point y position" );
7990 point->SetPosition( pt );
7991 NeedRIGHT();
7992 break;
7993 }
7994 case T_size:
7995 {
7996 point->SetSize( parseBoardUnits( "point size" ) );
7997 NeedRIGHT();
7998 break;
7999 }
8000 case T_layer:
8001 {
8002 point->SetLayer( parseBoardItemLayer() );
8003 NeedRIGHT();
8004 break;
8005 }
8006 case T_uuid:
8007 {
8008 NextTok();
8009 const_cast<KIID&>( point->m_Uuid ) = CurStrToKIID();
8010 NeedRIGHT();
8011 break;
8012 }
8013 default: Expecting( "at, size, layer or uuid" );
8014 }
8015 }
8016
8017 return point.release();
8018}
8019
8020
8022{
8023 wxCHECK_MSG( CurTok() == T_target, nullptr,
8024 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as PCB_TARGET." ) );
8025
8026 VECTOR2I pt;
8027 T token;
8028
8029 std::unique_ptr<PCB_TARGET> target = std::make_unique<PCB_TARGET>( nullptr );
8030
8031 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
8032 {
8033 if( token == T_LEFT )
8034 token = NextTok();
8035
8036 switch( token )
8037 {
8038 case T_x:
8039 target->SetShape( 1 );
8040 break;
8041
8042 case T_plus:
8043 target->SetShape( 0 );
8044 break;
8045
8046 case T_at:
8047 pt.x = parseBoardUnits( "target x position" );
8048 pt.y = parseBoardUnits( "target y position" );
8049 target->SetPosition( pt );
8050 NeedRIGHT();
8051 break;
8052
8053 case T_size:
8054 target->SetSize( parseBoardUnits( "target size" ) );
8055 NeedRIGHT();
8056 break;
8057
8058 case T_width:
8059 target->SetWidth( parseBoardUnits( "target thickness" ) );
8060 NeedRIGHT();
8061 break;
8062
8063 case T_layer:
8064 target->SetLayer( parseBoardItemLayer() );
8065 NeedRIGHT();
8066 break;
8067
8068 case T_tstamp:
8069 case T_uuid:
8070 NextTok();
8071 const_cast<KIID&>( target->m_Uuid ) = CurStrToKIID();
8072 NeedRIGHT();
8073 break;
8074
8075 default:
8076 Expecting( "x, plus, at, size, width, layer, uuid, or tstamp" );
8077 }
8078 }
8079
8080 return target.release();
8081}
8082
8083
8085{
8086 KIID aId;
8087 std::string idStr( CurStr() );
8088
8089 // Older files did not quote UUIDs
8090 if( *idStr.begin() == '"' && *idStr.rbegin() == '"' )
8091 idStr = idStr.substr( 1, idStr.length() - 1 );
8092
8093 if( m_appendToExisting )
8094 {
8095 aId = KIID();
8096 m_resetKIIDMap.insert( std::make_pair( idStr, aId ) );
8097 }
8098 else
8099 {
8100 aId = KIID( idStr );
8101 }
8102
8103 return aId;
8104}
int color
const char * name
@ ERROR_OUTSIDE
constexpr int ARC_HIGH_DEF
Definition base_units.h:129
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:112
@ LT_UNDEFINED
Definition board.h:186
@ LAYER_CLASS_OTHERS
@ LAYER_CLASS_FAB
@ LAYER_CLASS_COURTYARD
@ LAYER_CLASS_SILK
@ LAYER_CLASS_COPPER
@ LAYER_CLASS_EDGES
#define DEFAULT_LINE_WIDTH
@ ZLO_FORCE_NO_ZONE_CONNECTION
Definition board_item.h:70
@ ZLO_FORCE_FLASHED
Definition board_item.h:69
@ BS_EDGE_CONNECTOR_BEVELLED
@ BS_EDGE_CONNECTOR_NONE
@ BS_EDGE_CONNECTOR_IN_USE
BOARD_STACKUP_ITEM_TYPE
@ BS_ITEM_TYPE_UNDEFINED
@ BS_ITEM_TYPE_COPPER
@ BS_ITEM_TYPE_SILKSCREEN
@ BS_ITEM_TYPE_DIELECTRIC
@ BS_ITEM_TYPE_SOLDERPASTE
@ BS_ITEM_TYPE_SOLDERMASK
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:990
BASE_SET & reset(size_t pos)
Definition base_set.h:143
BASE_SET & set(size_t pos)
Definition base_set.h:116
Container for design settings for a BOARD object.
DIM_PRECISION m_DimensionPrecision
Number of digits after the decimal.
std::shared_ptr< NET_SETTINGS > m_NetSettings
void SetGridOrigin(const VECTOR2I &aOrigin)
bool m_TextUpright[LAYER_CLASS_COUNT]
std::vector< DIFF_PAIR_DIMENSION > m_DiffPairDimensionsList
std::unique_ptr< PAD > m_Pad_Master
void SetAuxOrigin(const VECTOR2I &aOrigin)
BOARD_STACKUP & GetStackupDescriptor()
int m_TextThickness[LAYER_CLASS_COUNT]
std::vector< int > m_TrackWidthList
int m_LineThickness[LAYER_CLASS_COUNT]
ZONE_SETTINGS & GetDefaultZoneSettings()
VECTOR2I m_TextSize[LAYER_CLASS_COUNT]
bool m_TextItalic[LAYER_CLASS_COUNT]
std::vector< VIA_DIMENSION > m_ViasDimensionsList
Abstract interface for BOARD_ITEMs capable of storing other items inside.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:79
void SetLocked(bool aLocked) override
Definition board_item.h:323
virtual void SetIsKnockout(bool aKnockout)
Definition board_item.h:320
virtual void SetLayer(PCB_LAYER_ID aLayer)
Set the layer this item is on.
Definition board_item.h:280
FOOTPRINT * GetParentFootprint() const
virtual void RunOnChildren(const std::function< void(BOARD_ITEM *)> &aFunction, RECURSE_MODE aMode) const
Invoke a function on all children.
Definition board_item.h:208
BOARD_ITEM_CONTAINER * GetParent() const
Definition board_item.h:210
Manage one layer needed to make a physical board.
void AddDielectricPrms(int aDielectricPrmsIdx)
Add (insert) a DIELECTRIC_PRMS item to m_DielectricPrmsList all values are set to default.
void SetDielectricLayerId(int aLayerId)
void SetThickness(int aThickness, int aDielectricSubLayer=0)
void SetThicknessLocked(bool aLocked, int aDielectricSubLayer=0)
void SetMaterial(const wxString &aName, int aDielectricSubLayer=0)
void SetLossTangent(double aTg, int aDielectricSubLayer=0)
BOARD_STACKUP_ITEM_TYPE GetType() const
void SetBrdLayerId(PCB_LAYER_ID aBrdLayerId)
void SetTypeName(const wxString &aName)
void SetColor(const wxString &aColorName, int aDielectricSubLayer=0)
void SetEpsilonR(double aEpsilon, int aDielectricSubLayer=0)
Manage layers needed to make a physical board.
void RemoveAll()
Delete all items in list and clear the list.
bool m_HasDielectricConstrains
True if some layers have impedance controlled tracks or have specific constrains for micro-wave appli...
void Add(BOARD_STACKUP_ITEM *aItem)
Add a new item in stackup layer.
void BuildDefaultStackupList(const BOARD_DESIGN_SETTINGS *aSettings, int aActiveCopperLayersCount=0)
Create a default stackup, according to the current BOARD_DESIGN_SETTINGS settings.
bool m_EdgePlating
True if the edge board is plated.
BS_EDGE_CONNECTOR_CONSTRAINTS m_EdgeConnectorConstraints
If the board has edge connector cards, some constrains can be specified in job file: BS_EDGE_CONNECTO...
wxString m_FinishType
The name of external copper finish.
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:322
const KIID m_Uuid
Definition eda_item.h:516
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:110
virtual EMBEDDED_FILES * GetEmbeddedFiles()
Definition eda_item.h:468
SHAPE_POLY_SET & GetPolyShape()
Definition eda_shape.h:337
void SetStart(const VECTOR2I &aStart)
Definition eda_shape.h:177
void SetShape(SHAPE_T aShape)
Definition eda_shape.h:167
void SetEnd(const VECTOR2I &aEnd)
Definition eda_shape.h:219
A mix-in class (via multiple inheritance) that handles texts such as labels, parts,...
Definition eda_text.h:80
const EDA_ANGLE & GetTextAngle() const
Definition eda_text.h:147
void SetTextSize(VECTOR2I aNewSize, bool aEnforceMinTextSize=true)
Definition eda_text.cpp:544
void SetUnresolvedFontName(const wxString &aFontName)
Definition eda_text.h:249
virtual const wxString & GetText() const
Return the string associated with the text object.
Definition eda_text.h:98
void SetTextPos(const VECTOR2I &aPoint)
Definition eda_text.cpp:589
void SetMirrored(bool isMirrored)
Definition eda_text.cpp:404
void SetVertJustify(GR_TEXT_V_ALIGN_T aType)
Definition eda_text.cpp:428
void SetBoldFlag(bool aBold)
Set only the bold flag, without changing the font.
Definition eda_text.cpp:389
virtual void SetVisible(bool aVisible)
Definition eda_text.cpp:397
void SetLineSpacing(double aLineSpacing)
Definition eda_text.cpp:536
void SetTextThickness(int aWidth)
The TextThickness is that set by the user.
Definition eda_text.cpp:295
void SetItalicFlag(bool aItalic)
Set only the italic flag, without changing the font.
Definition eda_text.cpp:338
void SetKeepUpright(bool aKeepUpright)
Definition eda_text.cpp:436
virtual void SetText(const wxString &aText)
Definition eda_text.cpp:281
virtual void SetTextAngle(const EDA_ANGLE &aAngle)
Definition eda_text.cpp:310
void SetHorizJustify(GR_TEXT_H_ALIGN_T aType)
Definition eda_text.cpp:420
void ParseEmbedded(EMBEDDED_FILES *aFiles)
const std::vector< wxString > * UpdateFontFiles()
Helper function to get a list of fonts for fontconfig to add to the library.
void SetStackupLayers(LSET aLayers)
If the footprint has a non-default stackup, set the layers that should be used for the stackup.
EDA_ANGLE GetOrientation() const
Definition footprint.h:248
void SetStackupMode(FOOTPRINT_STACKUP aMode)
Set the stackup mode for this footprint.
void GetFields(std::vector< PCB_FIELD * > &aVector, bool aVisibleOnly) const
Populate a std::vector with PCB_TEXTs.
VECTOR2I GetPosition() const override
Definition footprint.h:245
VECTOR3D m_Offset
3D model offset (mm)
Definition footprint.h:119
double m_Opacity
Definition footprint.h:120
VECTOR3D m_Rotation
3D model rotation (degrees)
Definition footprint.h:118
VECTOR3D m_Scale
3D model scaling factor (dimensionless)
Definition footprint.h:117
wxString m_Filename
The 3D shape filename in 3D library.
Definition footprint.h:121
bool m_Show
Include model in rendering.
Definition footprint.h:122
static GAL_SET DefaultVisible()
Definition lset.cpp:769
A factory which returns an instance of a PCB_GENERATOR.
PCB_GENERATOR * CreateFromType(const wxString &aTypeStr)
static GENERATORS_MGR & Instance()
virtual const wxString What() const
A composite of Problem() and Where()
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:104
Definition kiid.h:49
wxString AsString() const
Definition kiid.cpp:246
A logical library item identifier and consists of various portions much like a URI.
Definition lib_id.h:49
int Parse(const UTF8 &aId, bool aFix=false)
Parse LIB_ID with the information from aId.
Definition lib_id.cpp:52
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
static int NameToLayer(wxString &aName)
Return the layer number from a layer name.
Definition lset.cpp:117
static const LSET & AllTechMask()
Return a mask holding all technical layers (no CU layer) on both side.
Definition lset.cpp:659
static LSET AllCuMask(int aCuLayerCount)
Return a mask holding the requested number of Cu PCB_LAYER_IDs.
Definition lset.cpp:582
static LSET AllCuMask()
return AllCuMask( MAX_CU_LAYERS );
Definition lset.cpp:591
static const LSET & InternalCuMask()
Return a complete set of internal copper layers which is all Cu layers except F_Cu and B_Cu.
Definition lset.cpp:560
static wxString Name(PCB_LAYER_ID aLayerId)
Return the fixed name association with aLayerId.
Definition lset.cpp:188
Handle the data for a net.
Definition netinfo.h:54
int GetNetCode() const
Definition netinfo.h:106
static const int UNCONNECTED
Constant that holds the "unconnected net" number (typically 0) all items "connected" to this net are ...
Definition netinfo.h:365
static const int ORPHANED
Constant that forces initialization of a netinfo item to the NETINFO_ITEM ORPHANED (typically -1) whe...
Definition netinfo.h:369
std::shared_ptr< NETCLASS > GetDefaultNetclass()
Gets the default netclass for the project.
A PADSTACK defines the characteristics of a single or multi-layer pad, in the IPC sense of the word.
Definition padstack.h:125
std::optional< int > & Clearance(PCB_LAYER_ID aLayer=F_Cu)
void AddPrimitive(PCB_SHAPE *aShape, PCB_LAYER_ID aLayer)
Adds a custom shape primitive to the padstack.
MASK_LAYER_PROPS & FrontOuterLayers()
Definition padstack.h:320
void SetThermalSpokeAngle(EDA_ANGLE aAngle, PCB_LAYER_ID aLayer=F_Cu)
void SetMode(MODE aMode)
Definition padstack.cpp:833
std::optional< int > & ThermalSpokeWidth(PCB_LAYER_ID aLayer=F_Cu)
std::optional< int > & ThermalGap(PCB_LAYER_ID aLayer=F_Cu)
void SetAnchorShape(PAD_SHAPE aShape, PCB_LAYER_ID aLayer)
@ CUSTOM
Shapes can be defined on arbitrary layers.
Definition padstack.h:141
@ FRONT_INNER_BACK
Up to three shapes can be defined (F_Cu, inner copper layers, B_Cu)
Definition padstack.h:140
MODE Mode() const
Definition padstack.h:295
MASK_LAYER_PROPS & BackOuterLayers()
Definition padstack.h:323
void SetSize(const VECTOR2I &aSize, PCB_LAYER_ID aLayer)
static constexpr PCB_LAYER_ID ALL_LAYERS
! Temporary layer identifier to identify code that is not padstack-aware
Definition padstack.h:145
static constexpr PCB_LAYER_ID INNER_LAYERS
! The layer identifier to use for "inner layers" on top/inner/bottom padstacks
Definition padstack.h:148
std::optional< ZONE_CONNECTION > & ZoneConnection(PCB_LAYER_ID aLayer=F_Cu)
Definition pad.h:54
void SetAnchorPadShape(PCB_LAYER_ID aLayer, PAD_SHAPE aShape)
Set the shape of the anchor pad for custom shaped pads.
Definition pad.h:242
void SetShape(PCB_LAYER_ID aLayer, PAD_SHAPE aShape)
Set the new shape of this pad.
Definition pad.h:186
void SetDelta(PCB_LAYER_ID aLayer, const VECTOR2I &aSize)
Definition pad.h:293
void SetOffset(PCB_LAYER_ID aLayer, const VECTOR2I &aOffset)
Definition pad.h:311
void SetChamferRectRatio(PCB_LAYER_ID aLayer, double aChamferScale)
Has meaning only for chamfered rectangular pads.
Definition pad.cpp:517
const PADSTACK & Padstack() const
Definition pad.h:321
void SetDrillSize(const VECTOR2I &aSize)
Definition pad.h:304
void SetSize(PCB_LAYER_ID aLayer, const VECTOR2I &aSize)
Definition pad.h:259
void SetChamferPositions(PCB_LAYER_ID aLayer, int aPositions)
Has meaning only for chamfered rectangular pads.
Definition pad.h:706
void SetCustomShapeInZoneOpt(PADSTACK::CUSTOM_SHAPE_ZONE_MODE aOption)
Set the option for the custom pad shape to use as clearance area in copper zones.
Definition pad.h:231
void SetRoundRectRadiusRatio(PCB_LAYER_ID aLayer, double aRadiusScale)
Has meaning only for rounded rectangle pads.
Definition pad.cpp:479
Describe the page size and margins of a paper page on which to eventually print or plot.
Definition page_info.h:79
void SetPortrait(bool aIsPortrait)
Rotate the paper page 90 degrees.
bool SetType(PAGE_SIZE_TYPE aPageSize, bool aIsPortrait=false)
Set the name of the page type and also the sizes and margins commonly associated with that type name.
void SetWidthMM(double aWidthInMM)
Definition page_info.h:140
void SetHeightMM(double aHeightInMM)
Definition page_info.h:145
const PAGE_SIZE_TYPE & GetType() const
Definition page_info.h:102
Abstract dimension API.
For better understanding of the points that make a dimension:
void SetExtensionHeight(int aHeight)
void UpdateHeight(const VECTOR2I &aCrossbarStart, const VECTOR2I &aCrossbarEnd)
Update the stored height basing on points coordinates.
void SetHeight(int aHeight)
Set the distance from the feature points to the crossbar line.
A leader is a dimension-like object pointing to a specific point.
void SetTextBorder(DIM_TEXT_BORDER aBorder)
An orthogonal dimension is like an aligned dimension, but the extension lines are locked to the X or ...
A radial dimension indicates either the radius or diameter of an arc or circle.
void SetLeaderLength(int aLength)
virtual void SetProperties(const STRING_ANY_MAP &aProps)
virtual void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
A set of BOARD_ITEMs (i.e., without duplicates).
Definition pcb_group.h:53
wxString m_generatorVersion
Set to the generator version this board requires.
PCB_TABLECELL * parsePCB_TABLECELL(BOARD_ITEM *aParent)
std::unordered_map< std::string, PCB_LAYER_ID > LAYER_ID_MAP
std::vector< int > m_netCodes
net codes mapping for boards being loaded
void parseOutlinePoints(SHAPE_LINE_CHAIN &aPoly)
Parses possible outline points and stores them into aPoly.
std::set< wxString > m_undefinedLayers
set of layers not defined in layers section
void parseZoneLayerProperty(std::map< PCB_LAYER_ID, ZONE_LAYER_PROPERTIES > &aProperties)
PROGRESS_REPORTER * m_progressReporter
optional; may be nullptr
void parseFootprintStackup(FOOTPRINT &aFootprint)
void createOldLayerMapping(std::unordered_map< std::string, std::string > &aMap)
Create a mapping from the (short-lived) bug where layer names were translated.
void parseZoneDefaults(ZONE_SETTINGS &aZoneSettings)
std::unordered_map< std::string, LSET > LSET_MAP
void parseEDA_TEXT(EDA_TEXT *aText)
Parse the common settings for any object derived from EDA_TEXT.
bool m_tooRecent
true if version parses as later than supported
PCB_LAYER_ID lookUpLayer(const LAYER_ID_MAP &aMap)
Parse the current token for the layer definition of a BOARD_ITEM object.
PCB_REFERENCE_IMAGE * parsePCB_REFERENCE_IMAGE(BOARD_ITEM *aParent)
LAYER_ID_MAP m_layerIndices
map layer name to it's index
void parseTextBoxContent(PCB_TEXTBOX *aTextBox)
FOOTPRINT * parseFOOTPRINT(wxArrayString *aInitialComments=nullptr)
void pushValueIntoMap(int aIndex, int aValue)
Add aValue value in netcode mapping (m_netCodes) at aIndex.
void init()
Clear and re-establish m_layerMap with the default layer names.
void skipCurrent()
Skip the current token level, i.e search for the RIGHT parenthesis which closes the current descripti...
void parseMargins(int &aLeft, int &aTop, int &aRight, int &aBottom)
PCB_LAYER_ID parseBoardItemLayer()
Parse the layer definition of a BOARD_ITEM object.
LSET parseLayersForCuItemWithSoldermask()
Parse the layers definition of a BOARD_ITEM object that has a single copper layer and optional solder...
void parseGENERATOR(BOARD_ITEM *aParent)
LSET parseBoardItemLayersAsMask()
Parse the layers definition of a BOARD_ITEM object.
std::pair< std::optional< bool >, std::optional< bool > > parseFrontBackOptBool(bool aLegacy=false)
void resolveGroups(BOARD_ITEM *aParent)
Called after parsing a footprint definition or board to build the group membership lists.
void parseDefaultTextDims(BOARD_DESIGN_SETTINGS &aSettings, int aLayer)
std::vector< GROUP_INFO > m_groupInfos
ZONE * parseZONE(BOARD_ITEM_CONTAINER *aParent)
PCB_TABLE * parsePCB_TABLE(BOARD_ITEM *aParent)
std::vector< GENERATOR_INFO > m_generatorInfos
PCB_TEXTBOX * parsePCB_TEXTBOX(BOARD_ITEM *aParent)
std::chrono::time_point< CLOCK > TIME_PT
PCB_TEXT * parsePCB_TEXT(BOARD_ITEM *aParent, PCB_TEXT *aBaseText=nullptr)
unsigned m_lineCount
for progress reporting
VECTOR2I parseXY()
Parse a coordinate pair (xy X Y) in board units (mm).
void parseTEARDROP_PARAMETERS(TEARDROP_PARAMETERS *tdParams)
int m_requiredVersion
set to the KiCad format version this board requires
PAD * parsePAD(FOOTPRINT *aParent=nullptr)
std::function< bool(wxString aTitle, int aIcon, wxString aMsg, wxString aAction)> m_queryUserCallback
FOOTPRINT * parseFOOTPRINT_unchecked(wxArrayString *aInitialComments=nullptr)
void parseRenderCache(EDA_TEXT *text)
Parse the render cache for any object derived from EDA_TEXT.
TIME_PT m_lastProgressTime
for progress reporting
void parseGROUP_members(GROUP_INFO &aGroupInfo)
bool IsValidBoardHeader()
Partially parse the input and check if it matches expected header.
std::pair< wxString, wxString > parseBoardProperty()
LSET_MAP m_layerMasks
map layer names to their masks
bool parseMaybeAbsentBool(bool aDefaultValue)
Parses a boolean flag inside a list that existed before boolean normalization.
int parseBoardUnits()
Parse the current token as an ASCII numeric string with possible leading whitespace into a double pre...
PCB_DIMENSION_BASE * parseDIMENSION(BOARD_ITEM *aParent)
LSET lookUpLayerSet(const LSET_MAP &aMap)
bool m_appendToExisting
reading into an existing board; reset UUIDs
void parsePCB_TEXT_effects(PCB_TEXT *aText, PCB_TEXT *aBaseText=nullptr)
PCB_SHAPE * parsePCB_SHAPE(BOARD_ITEM *aParent)
void parseDefaults(BOARD_DESIGN_SETTINGS &aSettings)
wxString GetRequiredVersion()
Return a string representing the version of KiCad required to open this file.
PCB_BARCODE * parsePCB_BARCODE(BOARD_ITEM *aParent)
The parser for PCB_PLOT_PARAMS.
Parameters and options when plotting/printing a board.
std::optional< bool > GetLegacyPlotViaOnMaskLayer() const
void Parse(PCB_PLOT_PARAMS_PARSER *aParser)
A PCB_POINT is a 0-dimensional point that is used to mark a position on a PCB, or more usually a foot...
Definition pcb_point.h:43
Object to handle a bitmap image that can be inserted in a PCB.
void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
void SetIsProxyItem(bool aIsProxy=true) override
void SetStroke(const STROKE_PARAMS &aStroke) override
Definition pcb_shape.h:92
void Rotate(const VECTOR2I &aRotCentre, const EDA_ANGLE &aAngle) override
Rotate this object.
void SetBorderEnabled(bool enabled)
void SetMarginTop(int aTop)
Definition pcb_textbox.h:85
void SetMarginLeft(int aLeft)
Definition pcb_textbox.h:84
void SetMarginBottom(int aBottom)
Definition pcb_textbox.h:87
void SetMarginRight(int aRight)
Definition pcb_textbox.h:86
void Move(const VECTOR2I &aMoveVector) override
Move this object.
int GetLegacyTextMargin() const
void StyleFromSettings(const BOARD_DESIGN_SETTINGS &settings, bool aCheckSide) override
Definition pcb_text.cpp:314
void Move(const VECTOR2I &aMoveVector) override
Move this object.
Definition pcb_text.h:94
void Rotate(const VECTOR2I &aRotCentre, const EDA_ANGLE &aAngle) override
Rotate this object.
Definition pcb_text.cpp:404
const PADSTACK & Padstack() const
Definition pcb_track.h:463
A REFERENCE_IMAGE is a wrapper around a BITMAP_IMAGE that is displayed in an editor as a reference fo...
bool ReadImageFile(const wxString &aFullFilename)
Read and store an image file.
double GetImageScale() const
void SetImageScale(double aScale)
Set the image "zoom" value.
Definition seg.h:42
VECTOR2I A
Definition seg.h:49
VECTOR2I B
Definition seg.h:50
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
void SetClosed(bool aClosed)
Mark the line chain as closed (i.e.
void Append(int aX, int aY, bool aAllowDuplication=false)
Append a new point at the end of the line chain.
Represent a set of closed polygons.
void RemoveAllContours()
Remove all outlines & holes (clears) the polygon set.
void BooleanAdd(const SHAPE_POLY_SET &b)
Perform boolean polyset union.
int AddOutline(const SHAPE_LINE_CHAIN &aOutline)
Adds a new outline to the set and returns its index.
bool IsEmpty() const
Return true if the set is empty (no polygons at all)
int AddHole(const SHAPE_LINE_CHAIN &aHole, int aOutline=-1)
Adds a new hole to the given outline (default: last) and returns its index.
SHAPE_LINE_CHAIN & Outline(int aIndex)
Return the reference to aIndex-th outline in the set.
int NewOutline()
Creates a new empty polygon in the set and returns its index.
int OutlineCount() const
Return the number of outlines in the set.
A name/value tuple with unique names and wxAny values.
void ParseStroke(STROKE_PARAMS &aStroke)
Simple container to manage line stroke parameters.
int GetWidth() const
void SetWidth(int aWidth)
TEARDROP_PARAMETARS is a helper class to handle parameters needed to build teardrops for a board thes...
double m_BestWidthRatio
The height of a teardrop as ratio between height and size of pad/via.
int m_TdMaxLen
max allowed length for teardrops in IU. <= 0 to disable
bool m_AllowUseTwoTracks
True to create teardrops using 2 track segments if the first in too small.
int m_TdMaxWidth
max allowed height for teardrops in IU. <= 0 to disable
double m_BestLengthRatio
The length of a teardrop as ratio between length and size of pad/via.
double m_WidthtoSizeFilterRatio
The ratio (H/D) between the via/pad size and the track width max value to create a teardrop 1....
bool m_TdOnPadsInZones
A filter to exclude pads inside zone fills.
bool m_Enabled
Flag to enable teardrops.
bool m_CurvedEdges
True if the teardrop should be curved.
Hold the information shown in the lower right corner of a plot, printout, or editing view.
Definition title_block.h:41
void SetRevision(const wxString &aRevision)
Definition title_block.h:81
void SetComment(int aIdx, const wxString &aComment)
void SetTitle(const wxString &aTitle)
Definition title_block.h:58
void SetCompany(const wxString &aCompany)
Definition title_block.h:91
void SetDate(const wxString &aDate)
Set the date field, and defaults to the current time and date.
Definition title_block.h:71
An 8 bit string that is assuredly encoded in UTF8, and supplies special conversion support to and fro...
Definition utf8.h:72
ZONE_SETTINGS handles zones parameters.
std::map< PCB_LAYER_ID, ZONE_LAYER_PROPERTIES > m_LayerProperties
Handle a list of polygons defining a copper zone.
Definition zone.h:74
virtual LSET GetLayerSet() const override
Return a std::bitset of all layers on which the item physically resides.
Definition zone.h:136
void SetLayerSetAndRemoveUnusedFills(const LSET &aLayerSet)
Set the zone to be on the aLayerSet layers and only remove the fill polygons from the unused layers,...
Definition zone.cpp:1613
static int GetDefaultHatchPitch()
Definition zone.cpp:1281
A type-safe container of any type.
Definition ki_any.h:93
constexpr any() noexcept
Default constructor, creates an empty object.
Definition ki_any.h:156
This file is part of the common library.
void TransformOvalToPolygon(SHAPE_POLY_SET &aBuffer, const VECTOR2I &aStart, const VECTOR2I &aEnd, int aWidth, int aError, ERROR_LOC aErrorLoc, int aMinSegCount=0)
Convert a oblong shape to a polygon, using multiple segments.
@ ROUND_ALL_CORNERS
All angles are rounded.
@ DSN_RIGHT
Definition dsnlexer.h:68
@ DSN_NUMBER
Definition dsnlexer.h:67
@ DSN_STRING
Definition dsnlexer.h:70
#define _(s)
static constexpr EDA_ANGLE ANGLE_0
Definition eda_angle.h:411
static constexpr EDA_ANGLE ANGLE_90
Definition eda_angle.h:413
@ DEGREES_T
Definition eda_angle.h:31
static constexpr EDA_ANGLE ANGLE_45
Definition eda_angle.h:412
@ RECURSE
Definition eda_item.h:51
@ NO_RECURSE
Definition eda_item.h:52
@ SEGMENT
Definition eda_shape.h:45
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:46
@ NO_FILL
Definition eda_shape.h:57
@ REVERSE_HATCH
Definition eda_shape.h:62
@ HATCH
Definition eda_shape.h:61
@ FILLED_SHAPE
Fill with object color.
Definition eda_shape.h:58
@ CROSS_HATCH
Definition eda_shape.h:63
EDA_UNITS
Definition eda_units.h:48
@ FP_SMD
Definition footprint.h:82
@ FP_DNP
Definition footprint.h:87
@ FP_EXCLUDE_FROM_POS_FILES
Definition footprint.h:83
@ FP_BOARD_ONLY
Definition footprint.h:85
@ FP_EXCLUDE_FROM_BOM
Definition footprint.h:84
@ FP_THROUGH_HOLE
Definition footprint.h:81
FOOTPRINT_STACKUP
Definition footprint.h:91
@ CUSTOM_LAYERS
Stackup handling where the footprint can have any number of copper layers, and objects on those layer...
Definition footprint.h:101
void ignore_unused(const T &)
Definition ignore.h:24
#define THROW_IO_ERROR(msg)
macro which captures the "call site" values of FILE_, __FUNCTION & LINE
#define THROW_PARSE_ERROR(aProblem, aSource, aInputLine, aLineNumber, aByteIndex)
#define MIN_VISIBILITY_MASK
Definition layer_ids.h:640
bool IsCopperLayer(int aLayerId)
Test whether a layer is a copper layer.
Definition layer_ids.h:676
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:60
@ F_CrtYd
Definition layer_ids.h:116
@ B_Adhes
Definition layer_ids.h:103
@ Edge_Cuts
Definition layer_ids.h:112
@ F_Paste
Definition layer_ids.h:104
@ Cmts_User
Definition layer_ids.h:108
@ F_Adhes
Definition layer_ids.h:102
@ B_Mask
Definition layer_ids.h:98
@ B_Cu
Definition layer_ids.h:65
@ F_Mask
Definition layer_ids.h:97
@ B_Paste
Definition layer_ids.h:105
@ In15_Cu
Definition layer_ids.h:80
@ F_Fab
Definition layer_ids.h:119
@ Margin
Definition layer_ids.h:113
@ F_SilkS
Definition layer_ids.h:100
@ B_CrtYd
Definition layer_ids.h:115
@ UNDEFINED_LAYER
Definition layer_ids.h:61
@ Rescue
Definition layer_ids.h:121
@ B_SilkS
Definition layer_ids.h:101
@ PCB_LAYER_ID_COUNT
Definition layer_ids.h:171
@ F_Cu
Definition layer_ids.h:64
@ B_Fab
Definition layer_ids.h:118
This file contains miscellaneous commonly used macros and functions.
KICOMMON_API bool FetchUnitsFromString(const wxString &aTextValue, EDA_UNITS &aUnits)
Write any unit info found in the string to aUnits.
Definition eda_units.cpp:88
bool contains(const _Container &__container, _Value __value)
Returns true if the container contains the given value.
Definition kicad_algo.h:100
@ NPTH
like PAD_PTH, but not plated mechanical use only, no connection allowed
Definition padstack.h:87
@ SMD
Smd pad, appears on the solder paste layer (default)
Definition padstack.h:83
@ PTH
Plated through hole pad.
Definition padstack.h:82
@ CONN
Like smd, does not appear on the solder paste layer (default) Note: also has a special attribute in G...
Definition padstack.h:84
@ CHAMFERED_RECT
Definition padstack.h:60
@ ROUNDRECT
Definition padstack.h:57
@ TRAPEZOID
Definition padstack.h:56
@ RECTANGLE
Definition padstack.h:54
@ FIDUCIAL_LOCAL
a fiducial (usually a smd) local to the parent footprint
Definition padstack.h:102
@ FIDUCIAL_GLBL
a fiducial (usually a smd) for the full board
Definition padstack.h:101
@ MECHANICAL
a pad used for mechanical support
Definition padstack.h:106
@ PRESSFIT
a PTH with a hole diameter with tight tolerances for press fit pin
Definition padstack.h:107
@ HEATSINK
a pad used as heat sink, usually in SMD footprints
Definition padstack.h:104
@ NONE
no special fabrication property
Definition padstack.h:99
@ TESTPOINT
a test point pad
Definition padstack.h:103
@ CASTELLATED
a pad with a castellated through hole
Definition padstack.h:105
@ BGA
Smd pad, used in BGA footprints.
Definition padstack.h:100
#define MAX_PAGE_SIZE_PCBNEW_MM
Definition page_info.h:40
#define MIN_PAGE_SIZE_MM
Min and max page sizes for clamping, in mm.
Definition page_info.h:39
BARCODE class definition.
DIM_TEXT_POSITION
Where to place the text on a dimension.
@ MANUAL
Text placement is manually set by the user.
DIM_UNITS_FORMAT
How to display the units in a dimension's text.
DIM_UNITS_MODE
Used for storing the units selection in the file because EDA_UNITS alone doesn't cut it.
DIM_TEXT_BORDER
Frame to show around dimension text.
DIM_PRECISION
Class to handle a set of BOARD_ITEMs.
#define SEXPR_BOARD_FILE_VERSION
Current s-expression file format version. 2 was the last legacy format version.
#define LEGACY_ARC_FORMATTING
These were the last to use old arc formatting.
#define LEGACY_NET_TIES
These were the last to use the keywords field to indicate a net-tie.
#define BOARD_FILE_HOST_VERSION
Earlier files than this include the host tag.
constexpr double INT_LIMIT
Pcbnew s-expression file format parser definition.
@ THROUGH
Definition pcb_track.h:68
@ MICROVIA
Definition pcb_track.h:71
PGM_BASE & Pgm()
The global program "get" accessor.
Definition pgm_base.cpp:913
see class PGM_BASE
int StrPrintf(std::string *result, const char *format,...)
This is like sprintf() but the output is appended to a std::string instead of to a character array.
Definition richio.cpp:71
double parseDouble(LINE_READER &aReader, const char *aLine, const char **aOutput)
Parses an ASCII point string with possible leading whitespace into a double precision floating point ...
#define DEFAULT_SOLDERMASK_OPACITY
wxString ConvertToNewOverbarNotation(const wxString &aOldStr)
Convert the old ~...~ overbar notation to the new ~{...} one.
wxString From_UTF8(const char *cstring)
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
Container to handle a stock of specific differential pairs each with unique track width,...
Variant of PARSE_ERROR indicating that a syntax or related error was likely caused by a file generate...
Container to hold information pertinent to a layer of a BOARD.
Definition board.h:201
void clear()
Definition board.h:207
LAYER_T m_type
The type of the layer.
Definition board.h:230
wxString m_name
The canonical name of the layer.
Definition board.h:228
wxString m_userName
The user defined name of the layer.
Definition board.h:229
bool m_visible
Definition board.h:231
int m_number
The layer ID.
Definition board.h:232
std::optional< bool > has_solder_mask
True if this outer layer has mask (is not tented)
Definition padstack.h:234
A filename or source description, a problem input line, a line number, a byte offset,...
Convert net code using the mapping table if available, otherwise returns unchanged net code if < 0 or...
Container to handle a stock of specific vias each with unique diameter and drill sizes in the BOARD c...
std::optional< VECTOR2I > hatching_offset
wxString GetUserFieldName(int aFieldNdx, bool aTranslateForHI)
#define DO_TRANSLATE
@ USER
The field ID hasn't been set yet; field is invalid.
@ DESCRIPTION
Field Description of part, i.e. "1/4W 1% Metal Film Resistor".
@ FOOTPRINT
Field Name Module PCB, i.e. "16DIP300".
@ REFERENCE
Field Reference of part, i.e. "IC21".
@ VALUE
Field Value of part, i.e. "3.3K".
const SHAPE_LINE_CHAIN chain
int delta
@ GR_TEXT_H_ALIGN_CENTER
@ GR_TEXT_H_ALIGN_RIGHT
@ GR_TEXT_H_ALIGN_LEFT
@ GR_TEXT_V_ALIGN_BOTTOM
@ GR_TEXT_V_ALIGN_CENTER
@ GR_TEXT_V_ALIGN_TOP
@ PCB_DIM_ORTHOGONAL_T
class PCB_DIM_ORTHOGONAL, a linear dimension constrained to x/y
Definition typeinfo.h:106
@ PCB_DIM_LEADER_T
class PCB_DIM_LEADER, a leader dimension (graphic item)
Definition typeinfo.h:103
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:97
@ PCB_TEXT_T
class PCB_TEXT, text on a layer
Definition typeinfo.h:92
@ PCB_FOOTPRINT_T
class FOOTPRINT, a footprint
Definition typeinfo.h:86
@ PCB_DIM_ALIGNED_T
class PCB_DIM_ALIGNED, a linear dimension (graphic item)
Definition typeinfo.h:102
@ PCB_DIM_RADIAL_T
class PCB_DIM_RADIAL, a radius or diameter dimension
Definition typeinfo.h:105
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:695
ISLAND_REMOVAL_MODE
Whether or not to remove isolated islands from a zone.
ZONE_BORDER_DISPLAY_STYLE
Zone border styles.
ZONE_CONNECTION
How pads are covered by copper in zone.
Definition zones.h:47
@ THT_THERMAL
Thermal relief only for THT pads.
Definition zones.h:52
@ NONE
Pads are not covered.
Definition zones.h:49
@ FULL
pads are covered by copper
Definition zones.h:51