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