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
2617{
2618 T token;
2619
2620 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
2621 {
2622 if( token != T_LEFT )
2623 {
2624 Expecting( T_LEFT );
2625 }
2626
2627 token = NextTok();
2628
2629 switch( token )
2630 {
2631 case T_property:
2633 break;
2634 default:
2635 Unexpected( CurText() );
2636 }
2637 }
2638}
2639
2640
2642 std::map<PCB_LAYER_ID, ZONE_LAYER_PROPERTIES>& aProperties )
2643{
2644 T token;
2645
2647 ZONE_LAYER_PROPERTIES properties;
2648
2649 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
2650 {
2651 if( token != T_LEFT )
2652 {
2653 Expecting( T_LEFT );
2654 }
2655
2656 token = NextTok();
2657
2658 switch( token )
2659 {
2660 case T_layer:
2661 layer = parseBoardItemLayer();
2662 NeedRIGHT();
2663 break;
2664 case T_hatch_position:
2665 {
2666 properties.hatching_offset = parseXY();
2667 NeedRIGHT();
2668 break;
2669 }
2670 default:
2671 Unexpected( CurText() );
2672 break;
2673 }
2674 }
2675
2676 aProperties.emplace( layer, properties );
2677}
2678
2679
2681{
2682 T token;
2683
2684 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
2685 {
2686 if( token != T_LEFT )
2687 Expecting( T_LEFT );
2688
2689 token = NextTok();
2690
2691 switch( token )
2692 {
2693 case T_edge_clearance:
2694 designSettings.m_CopperEdgeClearance = parseBoardUnits( T_edge_clearance );
2695 m_board->m_LegacyCopperEdgeClearanceLoaded = true;
2696 NeedRIGHT();
2697 break;
2698
2699 case T_copper_line_width:
2700 designSettings.m_LineThickness[ LAYER_CLASS_COPPER ] = parseBoardUnits( token );
2701 NeedRIGHT();
2702 break;
2703
2704 case T_copper_text_dims:
2705 parseDefaultTextDims( designSettings, LAYER_CLASS_COPPER );
2706 break;
2707
2708 case T_courtyard_line_width:
2709 designSettings.m_LineThickness[ LAYER_CLASS_COURTYARD ] = parseBoardUnits( token );
2710 NeedRIGHT();
2711 break;
2712
2713 case T_edge_cuts_line_width:
2714 designSettings.m_LineThickness[ LAYER_CLASS_EDGES ] = parseBoardUnits( token );
2715 NeedRIGHT();
2716 break;
2717
2718 case T_silk_line_width:
2719 designSettings.m_LineThickness[ LAYER_CLASS_SILK ] = parseBoardUnits( token );
2720 NeedRIGHT();
2721 break;
2722
2723 case T_silk_text_dims:
2724 parseDefaultTextDims( designSettings, LAYER_CLASS_SILK );
2725 break;
2726
2727 case T_fab_layers_line_width:
2728 designSettings.m_LineThickness[ LAYER_CLASS_FAB ] = parseBoardUnits( token );
2729 NeedRIGHT();
2730 break;
2731
2732 case T_fab_layers_text_dims:
2733 parseDefaultTextDims( designSettings, LAYER_CLASS_FAB );
2734 break;
2735
2736 case T_other_layers_line_width:
2737 designSettings.m_LineThickness[ LAYER_CLASS_OTHERS ] = parseBoardUnits( token );
2738 NeedRIGHT();
2739 break;
2740
2741 case T_other_layers_text_dims:
2742 parseDefaultTextDims( designSettings, LAYER_CLASS_OTHERS );
2743 break;
2744
2745 case T_dimension_units:
2746 designSettings.m_DimensionUnitsMode =
2747 static_cast<DIM_UNITS_MODE>( parseInt( "dimension units" ) );
2748 NeedRIGHT();
2749 break;
2750
2751 case T_dimension_precision:
2752 designSettings.m_DimensionPrecision =
2753 static_cast<DIM_PRECISION>( parseInt( "dimension precision" ) );
2754 NeedRIGHT();
2755 break;
2756
2757 default:
2758 Unexpected( CurText() );
2759 }
2760 }
2761}
2762
2763
2765{
2766 T token;
2767
2768 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
2769 {
2770 if( token == T_LEFT )
2771 token = NextTok();
2772
2773 switch( token )
2774 {
2775 case T_size:
2776 aSettings.m_TextSize[ aLayer ].x = parseBoardUnits( "default text size X" );
2777 aSettings.m_TextSize[ aLayer ].y = parseBoardUnits( "default text size Y" );
2778 NeedRIGHT();
2779 break;
2780
2781 case T_thickness:
2782 aSettings.m_TextThickness[ aLayer ] = parseBoardUnits( "default text width" );
2783 NeedRIGHT();
2784 break;
2785
2786 case T_italic:
2787 aSettings.m_TextItalic[ aLayer ] = true;
2788 break;
2789
2790 case T_keep_upright:
2791 aSettings.m_TextUpright[ aLayer ] = true;
2792 break;
2793
2794 default:
2795 Expecting( "size, thickness, italic or keep_upright" );
2796 }
2797 }
2798}
2799
2800
2802{
2803 wxCHECK_RET( CurTok() == T_net,
2804 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as net." ) );
2805
2806 int netCode = parseInt( "net number" );
2807
2808 NeedSYMBOLorNUMBER();
2809 wxString name = FromUTF8();
2810
2811 // Convert overbar syntax from `~...~` to `~{...}`. These were left out of the first merge
2812 // so the version is a bit later.
2813 if( m_requiredVersion < 20210606 )
2815
2816 NeedRIGHT();
2817
2818 // net 0 should be already in list, so store this net
2819 // if it is not the net 0, or if the net 0 does not exists.
2820 // (TODO: a better test.)
2821 if( netCode > NETINFO_LIST::UNCONNECTED || !m_board->FindNet( NETINFO_LIST::UNCONNECTED ) )
2822 {
2823 NETINFO_ITEM* net = new NETINFO_ITEM( m_board, name, netCode );
2824 m_board->Add( net, ADD_MODE::INSERT, true );
2825
2826 // Store the new code mapping
2827 pushValueIntoMap( netCode, net->GetNetCode() );
2828 }
2829}
2830
2831
2833{
2834 wxCHECK_RET( CurTok() == T_net_class,
2835 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as net class." ) );
2836
2837 T token;
2838
2839 std::shared_ptr<NETCLASS> nc = std::make_shared<NETCLASS>( wxEmptyString );
2840
2841 // Read netclass name (can be a name or just a number like track width)
2842 NeedSYMBOLorNUMBER();
2843 nc->SetName( FromUTF8() );
2844 NeedSYMBOL();
2845 nc->SetDescription( FromUTF8() );
2846
2847 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
2848 {
2849 if( token != T_LEFT )
2850 Expecting( T_LEFT );
2851
2852 token = NextTok();
2853
2854 switch( token )
2855 {
2856 case T_clearance:
2857 nc->SetClearance( parseBoardUnits( T_clearance ) );
2858 break;
2859
2860 case T_trace_width:
2861 nc->SetTrackWidth( parseBoardUnits( T_trace_width ) );
2862 break;
2863
2864 case T_via_dia:
2865 nc->SetViaDiameter( parseBoardUnits( T_via_dia ) );
2866 break;
2867
2868 case T_via_drill:
2869 nc->SetViaDrill( parseBoardUnits( T_via_drill ) );
2870 break;
2871
2872 case T_uvia_dia:
2873 nc->SetuViaDiameter( parseBoardUnits( T_uvia_dia ) );
2874 break;
2875
2876 case T_uvia_drill:
2877 nc->SetuViaDrill( parseBoardUnits( T_uvia_drill ) );
2878 break;
2879
2880 case T_diff_pair_width:
2881 nc->SetDiffPairWidth( parseBoardUnits( T_diff_pair_width ) );
2882 break;
2883
2884 case T_diff_pair_gap:
2885 nc->SetDiffPairGap( parseBoardUnits( T_diff_pair_gap ) );
2886 break;
2887
2888 case T_add_net:
2889 {
2890 NeedSYMBOLorNUMBER();
2891
2892 wxString netName = FromUTF8();
2893
2894 // Convert overbar syntax from `~...~` to `~{...}`. These were left out of the
2895 // first merge so the version is a bit later.
2896 if( m_requiredVersion < 20210606 )
2897 netName = ConvertToNewOverbarNotation( FromUTF8() );
2898
2899 m_board->GetDesignSettings().m_NetSettings->SetNetclassPatternAssignment(
2900 netName, nc->GetName() );
2901
2902 break;
2903 }
2904
2905 default:
2906 Expecting( "clearance, trace_width, via_dia, via_drill, uvia_dia, uvia_drill, "
2907 "diff_pair_width, diff_pair_gap or add_net" );
2908 }
2909
2910 NeedRIGHT();
2911 }
2912
2913 std::shared_ptr<NET_SETTINGS>& netSettings = m_board->GetDesignSettings().m_NetSettings;
2914
2915 if( netSettings->HasNetclass( nc->GetName() ) )
2916 {
2917 // Must have been a name conflict, this is a bad board file.
2918 // User may have done a hand edit to the file.
2919 wxString error;
2920 error.Printf( _( "Duplicate NETCLASS name '%s' in file '%s' at line %d, offset %d." ),
2921 nc->GetName().GetData(), CurSource().GetData(), CurLineNumber(),
2922 CurOffset() );
2923 THROW_IO_ERROR( error );
2924 }
2925 else if( nc->GetName() == netSettings->GetDefaultNetclass()->GetName() )
2926 {
2927 netSettings->SetDefaultNetclass( nc );
2928 }
2929 else
2930 {
2931 netSettings->SetNetclass( nc->GetName(), nc );
2932 }
2933}
2934
2935
2937{
2938 wxCHECK_MSG( CurTok() == T_fp_arc || CurTok() == T_fp_circle || CurTok() == T_fp_curve ||
2939 CurTok() == T_fp_rect || CurTok() == T_fp_line || CurTok() == T_fp_poly ||
2940 CurTok() == T_gr_arc || CurTok() == T_gr_circle || CurTok() == T_gr_curve ||
2941 CurTok() == T_gr_rect || CurTok() == T_gr_bbox || CurTok() == T_gr_line ||
2942 CurTok() == T_gr_poly || CurTok() == T_gr_vector, nullptr,
2943 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as PCB_SHAPE." ) );
2944
2945 T token;
2946 VECTOR2I pt;
2947 STROKE_PARAMS stroke( 0, LINE_STYLE::SOLID );
2948 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( aParent );
2949
2950 switch( CurTok() )
2951 {
2952 case T_gr_arc:
2953 case T_fp_arc:
2954 shape->SetShape( SHAPE_T::ARC );
2955 token = NextTok();
2956
2957 if( token == T_locked )
2958 {
2959 shape->SetLocked( true );
2960 token = NextTok();
2961 }
2962
2963 if( token != T_LEFT )
2964 Expecting( T_LEFT );
2965
2966 token = NextTok();
2967
2969 {
2970 // In legacy files the start keyword actually gives the arc center...
2971 if( token != T_start )
2972 Expecting( T_start );
2973
2974 pt.x = parseBoardUnits( "X coordinate" );
2975 pt.y = parseBoardUnits( "Y coordinate" );
2976 shape->SetCenter( pt );
2977 NeedRIGHT();
2978 NeedLEFT();
2979 token = NextTok();
2980
2981 // ... and the end keyword gives the start point of the arc
2982 if( token != T_end )
2983 Expecting( T_end );
2984
2985 pt.x = parseBoardUnits( "X coordinate" );
2986 pt.y = parseBoardUnits( "Y coordinate" );
2987 shape->SetStart( pt );
2988 NeedRIGHT();
2989 NeedLEFT();
2990 token = NextTok();
2991
2992 if( token != T_angle )
2993 Expecting( T_angle );
2994
2995 shape->SetArcAngleAndEnd( EDA_ANGLE( parseDouble( "arc angle" ), DEGREES_T ), true );
2996 NeedRIGHT();
2997 }
2998 else
2999 {
3000 VECTOR2I arc_start, arc_mid, arc_end;
3001
3002 if( token != T_start )
3003 Expecting( T_start );
3004
3005 arc_start.x = parseBoardUnits( "X coordinate" );
3006 arc_start.y = parseBoardUnits( "Y coordinate" );
3007 NeedRIGHT();
3008 NeedLEFT();
3009 token = NextTok();
3010
3011 if( token != T_mid )
3012 Expecting( T_mid );
3013
3014 arc_mid.x = parseBoardUnits( "X coordinate" );
3015 arc_mid.y = parseBoardUnits( "Y coordinate" );
3016 NeedRIGHT();
3017 NeedLEFT();
3018 token = NextTok();
3019
3020 if( token != T_end )
3021 Expecting( T_end );
3022
3023 arc_end.x = parseBoardUnits( "X coordinate" );
3024 arc_end.y = parseBoardUnits( "Y coordinate" );
3025 NeedRIGHT();
3026
3027 shape->SetArcGeometry( arc_start, arc_mid, arc_end );
3028 }
3029
3030 break;
3031
3032 case T_gr_circle:
3033 case T_fp_circle:
3034 shape->SetShape( SHAPE_T::CIRCLE );
3035 token = NextTok();
3036
3037 if( token == T_locked )
3038 {
3039 shape->SetLocked( true );
3040 token = NextTok();
3041 }
3042
3043 if( token != T_LEFT )
3044 Expecting( T_LEFT );
3045
3046 token = NextTok();
3047
3048 if( token != T_center )
3049 Expecting( T_center );
3050
3051 pt.x = parseBoardUnits( "X coordinate" );
3052 pt.y = parseBoardUnits( "Y coordinate" );
3053 shape->SetStart( pt );
3054 NeedRIGHT();
3055 NeedLEFT();
3056
3057 token = NextTok();
3058
3059 if( token != T_end )
3060 Expecting( T_end );
3061
3062 pt.x = parseBoardUnits( "X coordinate" );
3063 pt.y = parseBoardUnits( "Y coordinate" );
3064 shape->SetEnd( pt );
3065 NeedRIGHT();
3066 break;
3067
3068 case T_gr_curve:
3069 case T_fp_curve:
3070 shape->SetShape( SHAPE_T::BEZIER );
3071 token = NextTok();
3072
3073 if( token == T_locked )
3074 {
3075 shape->SetLocked( true );
3076 token = NextTok();
3077 }
3078
3079 if( token != T_LEFT )
3080 Expecting( T_LEFT );
3081
3082 token = NextTok();
3083
3084 if( token != T_pts )
3085 Expecting( T_pts );
3086
3087 shape->SetStart( parseXY() );
3088 shape->SetBezierC1( parseXY());
3089 shape->SetBezierC2( parseXY());
3090 shape->SetEnd( parseXY() );
3091
3092 if( m_board )
3093 shape->RebuildBezierToSegmentsPointsList( m_board->GetDesignSettings().m_MaxError );
3094 else
3095 shape->RebuildBezierToSegmentsPointsList( ARC_HIGH_DEF );
3096
3097 NeedRIGHT();
3098 break;
3099
3100 case T_gr_bbox:
3101 case T_gr_rect:
3102 case T_fp_rect:
3103 shape->SetShape( SHAPE_T::RECTANGLE );
3104 token = NextTok();
3105
3106 if( token == T_locked )
3107 {
3108 shape->SetLocked( true );
3109 token = NextTok();
3110 }
3111
3112 if( token != T_LEFT )
3113 Expecting( T_LEFT );
3114
3115 token = NextTok();
3116
3117 if( token != T_start )
3118 Expecting( T_start );
3119
3120 pt.x = parseBoardUnits( "X coordinate" );
3121 pt.y = parseBoardUnits( "Y coordinate" );
3122 shape->SetStart( pt );
3123 NeedRIGHT();
3124 NeedLEFT();
3125 token = NextTok();
3126
3127 if( token != T_end )
3128 Expecting( T_end );
3129
3130 pt.x = parseBoardUnits( "X coordinate" );
3131 pt.y = parseBoardUnits( "Y coordinate" );
3132 shape->SetEnd( pt );
3133
3134 if( aParent && aParent->Type() == PCB_FOOTPRINT_T )
3135 {
3136 // Footprint shapes are stored in board-relative coordinates, but we want the
3137 // normalization to remain in footprint-relative coordinates.
3138 }
3139 else
3140 {
3141 shape->Normalize();
3142 }
3143
3144 NeedRIGHT();
3145 break;
3146
3147 case T_gr_vector:
3148 case T_gr_line:
3149 case T_fp_line:
3150 shape->SetShape( SHAPE_T::SEGMENT );
3151 token = NextTok();
3152
3153 if( token == T_locked )
3154 {
3155 shape->SetLocked( true );
3156 token = NextTok();
3157 }
3158
3159 if( token != T_LEFT )
3160 Expecting( T_LEFT );
3161
3162 token = NextTok();
3163
3164 if( token != T_start )
3165 Expecting( T_start );
3166
3167 pt.x = parseBoardUnits( "X coordinate" );
3168 pt.y = parseBoardUnits( "Y coordinate" );
3169 shape->SetStart( pt );
3170 NeedRIGHT();
3171 NeedLEFT();
3172 token = NextTok();
3173
3174 if( token != T_end )
3175 Expecting( T_end );
3176
3177 pt.x = parseBoardUnits( "X coordinate" );
3178 pt.y = parseBoardUnits( "Y coordinate" );
3179 shape->SetEnd( pt );
3180 NeedRIGHT();
3181 break;
3182
3183 case T_gr_poly:
3184 case T_fp_poly:
3185 {
3186 shape->SetShape( SHAPE_T::POLY );
3187 shape->SetPolyPoints( {} );
3188
3189 SHAPE_LINE_CHAIN& outline = shape->GetPolyShape().Outline( 0 );
3190
3191 token = NextTok();
3192
3193 if( token == T_locked )
3194 {
3195 shape->SetLocked( true );
3196 token = NextTok();
3197 }
3198
3199 if( token != T_LEFT )
3200 Expecting( T_LEFT );
3201
3202 token = NextTok();
3203
3204 if( token != T_pts )
3205 Expecting( T_pts );
3206
3207 while( (token = NextTok() ) != T_RIGHT )
3208 parseOutlinePoints( outline );
3209
3210 break;
3211 }
3212
3213 default:
3214 if( aParent && aParent->Type() == PCB_FOOTPRINT_T )
3215 {
3216 Expecting( "fp_arc, fp_circle, fp_curve, fp_line, fp_poly or fp_rect" );
3217 }
3218 else
3219 {
3220 Expecting( "gr_arc, gr_circle, gr_curve, gr_vector, gr_line, gr_poly, gr_rect or "
3221 "gr_bbox" );
3222 }
3223 }
3224
3225 bool foundFill = false;
3226
3227 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
3228 {
3229 if( token != T_LEFT )
3230 Expecting( T_LEFT );
3231
3232 token = NextTok();
3233
3234 switch( token )
3235 {
3236 case T_angle: // legacy token; ignore value
3237 parseDouble( "arc angle" );
3238 NeedRIGHT();
3239 break;
3240
3241 case T_layer:
3242 shape->SetLayer( parseBoardItemLayer() );
3243 NeedRIGHT();
3244 break;
3245
3246 case T_layers:
3247 shape->SetLayerSet( parseBoardItemLayersAsMask() );
3248 break;
3249
3250 case T_solder_mask_margin:
3251 shape->SetLocalSolderMaskMargin( parseBoardUnits( "local solder mask margin value" ) );
3252 NeedRIGHT();
3253 break;
3254
3255 case T_width: // legacy token
3256 stroke.SetWidth( parseBoardUnits( T_width ) );
3257 NeedRIGHT();
3258 break;
3259
3260 case T_radius:
3261 shape->SetCornerRadius( parseBoardUnits( "corner radius" ) );
3262 NeedRIGHT();
3263 break;
3264
3265 case T_stroke:
3266 {
3267 STROKE_PARAMS_PARSER strokeParser( reader, pcbIUScale.IU_PER_MM );
3268 strokeParser.SyncLineReaderWith( *this );
3269
3270 strokeParser.ParseStroke( stroke );
3271 SyncLineReaderWith( strokeParser );
3272 break;
3273 }
3274
3275 case T_tstamp:
3276 case T_uuid:
3277 NextTok();
3278 const_cast<KIID&>( shape->m_Uuid ) = CurStrToKIID();
3279 NeedRIGHT();
3280 break;
3281
3282 case T_fill:
3283 foundFill = true;
3284
3285 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
3286 {
3287 if( token == T_LEFT )
3288 token = NextTok();
3289
3290 switch( token )
3291 {
3292 // T_yes was used to indicate filling when first introduced, so treat it like a
3293 // solid fill since that was the only fill available at the time.
3294 case T_yes:
3295 case T_solid: shape->SetFillMode( FILL_T::FILLED_SHAPE ); break;
3296
3297 case T_none:
3298 case T_no: shape->SetFillMode( FILL_T::NO_FILL ); break;
3299
3300 case T_hatch: shape->SetFillMode( FILL_T::HATCH ); break;
3301 case T_reverse_hatch: shape->SetFillMode( FILL_T::REVERSE_HATCH ); break;
3302 case T_cross_hatch: shape->SetFillMode( FILL_T::CROSS_HATCH ); break;
3303
3304 default: Expecting( "yes, no, solid, none, hatch, reverse_hatch or cross_hatch" );
3305 }
3306 }
3307
3308 break;
3309
3310 case T_status: // legacy token; ignore value
3311 parseHex();
3312 NeedRIGHT();
3313 break;
3314
3315 // Handle "(locked)" from 5.99 development, and "(locked yes)" from modern times
3316 case T_locked:
3317 shape->SetLocked( parseMaybeAbsentBool( true ) );
3318 break;
3319
3320 case T_net:
3321 parseNet( shape.get() );
3322 break;
3323
3324 default:
3325 Expecting( "layer, width, fill, tstamp, uuid, locked, net, status, "
3326 "or solder_mask_margin" );
3327 }
3328 }
3329
3330 if( !foundFill )
3331 {
3332 // Legacy versions didn't have a filled flag but allowed some shapes to indicate they
3333 // should be filled by specifying a 0 stroke-width.
3334 if( stroke.GetWidth() == 0
3335 && ( shape->GetShape() == SHAPE_T::RECTANGLE || shape->GetShape() == SHAPE_T::CIRCLE ) )
3336 {
3337 shape->SetFilled( true );
3338 }
3339 else if( shape->GetShape() == SHAPE_T::POLY && shape->GetLayer() != Edge_Cuts )
3340 {
3341 // Polygons on non-Edge_Cuts layers were always filled.
3342 shape->SetFilled( true );
3343 }
3344 }
3345
3346 // Only filled shapes may have a zero line-width. This is not permitted in KiCad but some
3347 // external tools can generate invalid files.
3348 if( stroke.GetWidth() <= 0 && !shape->IsAnyFill() )
3349 {
3350 stroke.SetWidth( pcbIUScale.mmToIU( DEFAULT_LINE_WIDTH ) );
3351 }
3352
3353 shape->SetStroke( stroke );
3354
3355 if( FOOTPRINT* parentFP = shape->GetParentFootprint() )
3356 {
3357 shape->Rotate( { 0, 0 }, parentFP->GetOrientation() );
3358 shape->Move( parentFP->GetPosition() );
3359 }
3360
3361 return shape.release();
3362}
3363
3364
3366{
3367 wxCHECK_MSG( CurTok() == T_image, nullptr,
3368 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a reference image." ) );
3369
3370 T token;
3371 std::unique_ptr<PCB_REFERENCE_IMAGE> bitmap = std::make_unique<PCB_REFERENCE_IMAGE>( aParent );
3372
3373 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
3374 {
3375 if( token != T_LEFT )
3376 Expecting( T_LEFT );
3377
3378 token = NextTok();
3379
3380 switch( token )
3381 {
3382 case T_at:
3383 {
3384 VECTOR2I pos;
3385 pos.x = parseBoardUnits( "X coordinate" );
3386 pos.y = parseBoardUnits( "Y coordinate" );
3387 bitmap->SetPosition( pos );
3388 NeedRIGHT();
3389 break;
3390 }
3391
3392 case T_layer:
3393 bitmap->SetLayer( parseBoardItemLayer() );
3394 NeedRIGHT();
3395 break;
3396
3397 case T_scale:
3398 {
3399 REFERENCE_IMAGE& refImage = bitmap->GetReferenceImage();
3400 refImage.SetImageScale( parseDouble( "image scale factor" ) );
3401
3402 if( !std::isnormal( refImage.GetImageScale() ) )
3403 refImage.SetImageScale( 1.0 );
3404
3405 NeedRIGHT();
3406 break;
3407 }
3408 case T_data:
3409 {
3410 token = NextTok();
3411
3412 wxString data;
3413
3414 // Reserve 512K because most image files are going to be larger than the default
3415 // 1K that wxString reserves.
3416 data.reserve( 1 << 19 );
3417
3418 while( token != T_RIGHT )
3419 {
3420 if( !IsSymbol( token ) )
3421 Expecting( "base64 image data" );
3422
3423 data += FromUTF8();
3424 token = NextTok();
3425 }
3426
3427 wxMemoryBuffer buffer = wxBase64Decode( data );
3428
3429 REFERENCE_IMAGE& refImage = bitmap->GetReferenceImage();
3430 if( !refImage.ReadImageFile( buffer ) )
3431 THROW_IO_ERROR( _( "Failed to read image data." ) );
3432
3433 break;
3434 }
3435
3436 case T_locked:
3437 {
3438 // This has only ever been (locked yes) format
3439 const bool locked = parseBool();
3440 bitmap->SetLocked( locked );
3441
3442 NeedRIGHT();
3443 break;
3444 }
3445
3446 case T_uuid:
3447 {
3448 NextTok();
3449 const_cast<KIID&>( bitmap->m_Uuid ) = CurStrToKIID();
3450 NeedRIGHT();
3451 break;
3452 }
3453
3454 default:
3455 Expecting( "at, layer, scale, data, locked or uuid" );
3456 }
3457 }
3458
3459 return bitmap.release();
3460}
3461
3462
3464{
3465 wxCHECK_MSG( CurTok() == T_gr_text || CurTok() == T_fp_text, nullptr,
3466 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as PCB_TEXT." ) );
3467
3468 FOOTPRINT* parentFP = dynamic_cast<FOOTPRINT*>( aParent );
3469 std::unique_ptr<PCB_TEXT> text;
3470
3471 T token = NextTok();
3472
3473 // If a base text is provided, we have a derived text already parsed and just need to update it
3474 if( aBaseText )
3475 {
3476 text = std::unique_ptr<PCB_TEXT>( aBaseText );
3477 }
3478 else if( parentFP )
3479 {
3480 switch( token )
3481 {
3482 case T_reference:
3483 text = std::make_unique<PCB_FIELD>( parentFP, FIELD_T::REFERENCE );
3484 break;
3485
3486 case T_value:
3487 text = std::make_unique<PCB_FIELD>( parentFP, FIELD_T::VALUE );
3488 break;
3489
3490 case T_user:
3491 text = std::make_unique<PCB_TEXT>( parentFP );
3492 break;
3493
3494 default:
3495 THROW_IO_ERROR( wxString::Format( _( "Cannot handle footprint text type %s" ),
3496 FromUTF8() ) );
3497 }
3498
3499 token = NextTok();
3500 }
3501 else
3502 {
3503 text = std::make_unique<PCB_TEXT>( aParent );
3504 }
3505
3506 // Legacy bare locked token
3507 if( token == T_locked )
3508 {
3509 text->SetLocked( true );
3510 token = NextTok();
3511 }
3512
3513 if( !IsSymbol( token ) && (int) token != DSN_NUMBER )
3514 Expecting( "text value" );
3515
3516 wxString value = FromUTF8();
3517 value.Replace( wxT( "%V" ), wxT( "${VALUE}" ) );
3518 value.Replace( wxT( "%R" ), wxT( "${REFERENCE}" ) );
3519 text->SetText( value );
3520
3521 NeedLEFT();
3522
3523 parsePCB_TEXT_effects( text.get(), aBaseText );
3524
3525 if( parentFP )
3526 {
3527 // Convert hidden footprint text (which is no longer supported) into a hidden field
3528 if( !text->IsVisible() && text->Type() == PCB_TEXT_T )
3529 {
3530 wxString fieldName = GetUserFieldName( parentFP->GetFields().size(), !DO_TRANSLATE );
3531 return new PCB_FIELD( *text.get(), FIELD_T::USER, fieldName );
3532 }
3533 }
3534 else
3535 {
3536 // Hidden PCB text is no longer supported
3537 text->SetVisible( true );
3538 }
3539
3540 return text.release();
3541}
3542
3543
3545{
3546 FOOTPRINT* parentFP = dynamic_cast<FOOTPRINT*>( aText->GetParent() );
3547 bool hasAngle = false; // Old files do not have a angle specified.
3548 // in this case it is 0 expected to be 0
3549 bool hasPos = false;
3550
3551 // By default, texts in footprints have a locked rotation (i.e. rot = -90 ... 90 deg)
3552 if( parentFP )
3553 aText->SetKeepUpright( true );
3554
3555 for( T token = NextTok(); token != T_RIGHT; token = NextTok() )
3556 {
3557 if( token == T_LEFT )
3558 token = NextTok();
3559
3560 switch( token )
3561 {
3562 case T_at:
3563 {
3564 VECTOR2I pt;
3565
3566 hasPos = true;
3567 pt.x = parseBoardUnits( "X coordinate" );
3568 pt.y = parseBoardUnits( "Y coordinate" );
3569 aText->SetTextPos( pt );
3570 token = NextTok();
3571
3572 if( CurTok() == T_NUMBER )
3573 {
3575 hasAngle = true;
3576 token = NextTok();
3577 }
3578
3579 // Legacy location of this token; presence implies true
3580 if( parentFP && CurTok() == T_unlocked )
3581 {
3582 aText->SetKeepUpright( false );
3583 token = NextTok();
3584 }
3585
3586 if( (int) token != DSN_RIGHT )
3587 Expecting( DSN_RIGHT );
3588
3589 break;
3590 }
3591
3592 case T_layer:
3593 aText->SetLayer( parseBoardItemLayer() );
3594
3595 token = NextTok();
3596
3597 if( token == T_knockout )
3598 {
3599 aText->SetIsKnockout( true );
3600 token = NextTok();
3601 }
3602
3603 if( (int) token != DSN_RIGHT )
3604 Expecting( DSN_RIGHT );
3605
3606 break;
3607
3608 case T_tstamp:
3609 case T_uuid:
3610 NextTok();
3611 const_cast<KIID&>( aText->m_Uuid ) = CurStrToKIID();
3612 NeedRIGHT();
3613 break;
3614
3615 case T_hide:
3616 {
3617 // In older files, the hide token appears bare, and indicates hide==true.
3618 // In newer files, it will be an explicit bool in a list like (hide yes)
3619 bool hide = parseMaybeAbsentBool( true );
3620
3621 if( parentFP )
3622 aText->SetVisible( !hide );
3623 else
3624 Expecting( "layer, effects, locked, render_cache, uuid or tstamp" );
3625
3626 break;
3627 }
3628
3629 case T_locked:
3630 // Newer list-enclosed locked
3631 aText->SetLocked( parseBool() );
3632 NeedRIGHT();
3633 break;
3634
3635 // Confusingly, "unlocked" is not the opposite of "locked", but refers to "keep upright"
3636 case T_unlocked:
3637 if( parentFP )
3638 aText->SetKeepUpright( !parseBool() );
3639 else
3640 Expecting( "layer, effects, locked, render_cache or tstamp" );
3641
3642 NeedRIGHT();
3643 break;
3644
3645 case T_effects:
3646 parseEDA_TEXT( static_cast<EDA_TEXT*>( aText ) );
3647 break;
3648
3649 case T_render_cache:
3650 parseRenderCache( static_cast<EDA_TEXT*>( aText ) );
3651 break;
3652
3653 default:
3654 if( parentFP )
3655 Expecting( "layer, hide, effects, locked, render_cache or tstamp" );
3656 else
3657 Expecting( "layer, effects, locked, render_cache or tstamp" );
3658 }
3659 }
3660
3661 // If there is no orientation defined, then it is the default value of 0 degrees.
3662 if( !hasAngle )
3663 aText->SetTextAngle( ANGLE_0 );
3664
3665 if( parentFP && !dynamic_cast<PCB_DIMENSION_BASE*>( aBaseText ) )
3666 {
3667 // make PCB_TEXT rotation relative to the parent footprint.
3668 // It was read as absolute rotation from file
3669 // Note: this is not rue for PCB_DIMENSION items that use the board
3670 // coordinates
3671 aText->SetTextAngle( aText->GetTextAngle() - parentFP->GetOrientation() );
3672
3673 // Move and rotate the text to its board coordinates
3674 aText->Rotate( { 0, 0 }, parentFP->GetOrientation() );
3675
3676 // Only move offset from parent position if we read a position from the file.
3677 // These positions are relative to the parent footprint. If we don't have a position
3678 // then the text defaults to the parent position and moving again will double it.
3679 if (hasPos)
3680 aText->Move( parentFP->GetPosition() );
3681 }
3682}
3683
3684
3686{
3687 wxCHECK_MSG( CurTok() == T_barcode, nullptr,
3688 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as PCB_BARCODE." ) );
3689
3690 std::unique_ptr<PCB_BARCODE> barcode = std::make_unique<PCB_BARCODE>( aParent );
3691
3692 for( T token = NextTok(); token != T_RIGHT; token = NextTok() )
3693 {
3694 if( token != T_LEFT )
3695 Expecting( T_LEFT );
3696
3697 token = NextTok();
3698
3699 switch( token )
3700 {
3701 case T_at:
3702 {
3703 VECTOR2I pos;
3704 pos.x = parseBoardUnits( "X coordinate" );
3705 pos.y = parseBoardUnits( "Y coordinate" );
3706 barcode->SetPosition( pos );
3707 token = NextTok();
3708
3709 if( CurTok() == T_NUMBER )
3710 barcode->SetOrientation( parseDouble() );
3711
3712 NeedRIGHT();
3713 break;
3714 }
3715
3716 case T_layer:
3717 barcode->SetLayer( parseBoardItemLayer() );
3718 NeedRIGHT();
3719 break;
3720
3721 case T_size:
3722 {
3723 int w = parseBoardUnits( "barcode width" );
3724 int h = parseBoardUnits( "barcode height" );
3725 barcode->SetWidth( w );
3726 barcode->SetHeight( h );
3727 NeedRIGHT();
3728 break;
3729 }
3730
3731 case T_text:
3732
3733 if( NextTok() != T_STRING )
3734 Expecting( T_STRING );
3735
3736 barcode->SetText( FromUTF8() );
3737 NeedRIGHT();
3738 break;
3739
3740 case T_text_height:
3741 {
3742 int h = parseBoardUnits( "barcode text height" );
3743 barcode->SetTextSize( h );
3744 NeedRIGHT();
3745 break;
3746 }
3747
3748 case T_type:
3749 NeedSYMBOL();
3750 {
3751 std::string kind = CurText();
3752 if( kind == "code39" )
3753 barcode->SetKind( BARCODE_T::CODE_39 );
3754 else if( kind == "code128" )
3755 barcode->SetKind( BARCODE_T::CODE_128 );
3756 else if( kind == "datamatrix" || kind == "data_matrix" )
3757 barcode->SetKind( BARCODE_T::DATA_MATRIX );
3758 else if( kind == "qr" || kind == "qrcode" )
3759 barcode->SetKind( BARCODE_T::QR_CODE );
3760 else if( kind == "microqr" || kind == "micro_qr" )
3761 barcode->SetKind( BARCODE_T::MICRO_QR_CODE );
3762 else
3763 Expecting( "barcode type" );
3764 }
3765 NeedRIGHT();
3766 break;
3767
3768 case T_ecc_level:
3769 NeedSYMBOL();
3770 {
3771 std::string ecc = CurText();
3772 if( ecc == "L" || ecc == "l" )
3773 barcode->SetErrorCorrection( BARCODE_ECC_T::L );
3774 else if( ecc == "M" || ecc == "m" )
3775 barcode->SetErrorCorrection( BARCODE_ECC_T::M );
3776 else if( ecc == "Q" || ecc == "q" )
3777 barcode->SetErrorCorrection( BARCODE_ECC_T::Q );
3778 else if( ecc == "H" || ecc == "h" )
3779 barcode->SetErrorCorrection( BARCODE_ECC_T::H );
3780 else
3781 Expecting( "ecc level" );
3782 }
3783 NeedRIGHT();
3784 break;
3785
3786
3787 case T_locked:
3788 barcode->SetLocked( parseMaybeAbsentBool( true ) );
3789 break;
3790
3791 case T_tstamp:
3792 case T_uuid:
3793 NextTok();
3794 const_cast<KIID&>( barcode->m_Uuid ) = CurStrToKIID();
3795 NeedRIGHT();
3796 break;
3797
3798 default:
3799 Expecting( "at, layer, size, text, text_height, type, ecc_level, locked or uuid" );
3800 }
3801 }
3802
3803 barcode->AssembleBarcode();
3804
3805 return barcode.release();
3806}
3807
3808
3810{
3811 wxCHECK_MSG( CurTok() == T_gr_text_box || CurTok() == T_fp_text_box, nullptr,
3812 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as PCB_TEXTBOX." ) );
3813
3814 std::unique_ptr<PCB_TEXTBOX> textbox = std::make_unique<PCB_TEXTBOX>( aParent );
3815
3816 parseTextBoxContent( textbox.get() );
3817
3818 return textbox.release();
3819}
3820
3821
3823{
3824 wxCHECK_MSG( CurTok() == T_table_cell, nullptr,
3825 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a table cell." ) );
3826
3827 std::unique_ptr<PCB_TABLECELL> cell = std::make_unique<PCB_TABLECELL>( aParent );
3828
3829 parseTextBoxContent( cell.get() );
3830
3831 return cell.release();
3832}
3833
3834
3836{
3837 int left;
3838 int top;
3839 int right;
3840 int bottom;
3841 STROKE_PARAMS stroke( -1, LINE_STYLE::SOLID );
3842 bool foundMargins = false;
3843
3844 T token = NextTok();
3845
3846 // Legacy locked
3847 if( token == T_locked )
3848 {
3849 aTextBox->SetLocked( true );
3850 token = NextTok();
3851 }
3852
3853 if( !IsSymbol( token ) && (int) token != DSN_NUMBER )
3854 Expecting( "text value" );
3855
3856 aTextBox->SetText( FromUTF8() );
3857
3858 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
3859 {
3860 if( token != T_LEFT )
3861 Expecting( T_LEFT );
3862
3863 token = NextTok();
3864
3865 switch( token )
3866 {
3867 case T_locked:
3868 aTextBox->SetLocked( parseMaybeAbsentBool( true ) );
3869 break;
3870
3871 case T_start:
3872 {
3873 int x = parseBoardUnits( "X coordinate" );
3874 int y = parseBoardUnits( "Y coordinate" );
3875 aTextBox->SetStart( VECTOR2I( x, y ) );
3876 NeedRIGHT();
3877
3878 NeedLEFT();
3879 token = NextTok();
3880
3881 if( token != T_end )
3882 Expecting( T_end );
3883
3884 x = parseBoardUnits( "X coordinate" );
3885 y = parseBoardUnits( "Y coordinate" );
3886 aTextBox->SetEnd( VECTOR2I( x, y ) );
3887 NeedRIGHT();
3888 break;
3889 }
3890
3891 case T_pts:
3892 {
3893 aTextBox->SetShape( SHAPE_T::POLY );
3894 aTextBox->GetPolyShape().RemoveAllContours();
3895 aTextBox->GetPolyShape().NewOutline();
3896
3897 while( (token = NextTok() ) != T_RIGHT )
3898 parseOutlinePoints( aTextBox->GetPolyShape().Outline( 0 ) );
3899
3900 break;
3901 }
3902
3903 case T_angle:
3904 // Set the angle of the text only, the coordinates of the box (a polygon) are
3905 // already at the right position, and must not be rotated
3906 aTextBox->EDA_TEXT::SetTextAngle( EDA_ANGLE( parseDouble( "text box angle" ), DEGREES_T ) );
3907 NeedRIGHT();
3908 break;
3909
3910 case T_stroke:
3911 {
3912 STROKE_PARAMS_PARSER strokeParser( reader, pcbIUScale.IU_PER_MM );
3913 strokeParser.SyncLineReaderWith( *this );
3914
3915 strokeParser.ParseStroke( stroke );
3916 SyncLineReaderWith( strokeParser );
3917 break;
3918 }
3919
3920 case T_border:
3921 aTextBox->SetBorderEnabled( parseBool() );
3922 NeedRIGHT();
3923 break;
3924
3925 case T_margins:
3926 parseMargins( left, top, right, bottom );
3927 aTextBox->SetMarginLeft( left );
3928 aTextBox->SetMarginTop( top );
3929 aTextBox->SetMarginRight( right );
3930 aTextBox->SetMarginBottom( bottom );
3931 foundMargins = true;
3932 NeedRIGHT();
3933 break;
3934
3935 case T_layer:
3936 aTextBox->SetLayer( parseBoardItemLayer() );
3937 NeedRIGHT();
3938 break;
3939
3940 case T_knockout:
3941 if( [[maybe_unused]] PCB_TABLECELL* cell = dynamic_cast<PCB_TABLECELL*>( aTextBox ) )
3942 {
3943 Expecting( "locked, start, pts, angle, width, margins, layer, effects, span, "
3944 "render_cache, uuid or tstamp" );
3945 }
3946 else
3947 {
3948 aTextBox->SetIsKnockout( parseBool() );
3949 }
3950
3951 NeedRIGHT();
3952 break;
3953
3954 case T_span:
3955 if( PCB_TABLECELL* cell = dynamic_cast<PCB_TABLECELL*>( aTextBox ) )
3956 {
3957 cell->SetColSpan( parseInt( "column span" ) );
3958 cell->SetRowSpan( parseInt( "row span" ) );
3959 }
3960 else
3961 {
3962 Expecting( "locked, start, pts, angle, width, stroke, border, margins, knockout, "
3963 "layer, effects, render_cache, uuid or tstamp" );
3964 }
3965
3966 NeedRIGHT();
3967 break;
3968
3969 case T_tstamp:
3970 case T_uuid:
3971 NextTok();
3972 const_cast<KIID&>( aTextBox->m_Uuid ) = CurStrToKIID();
3973 NeedRIGHT();
3974 break;
3975
3976 case T_effects:
3977 parseEDA_TEXT( static_cast<EDA_TEXT*>( aTextBox ) );
3978 break;
3979
3980 case T_render_cache:
3981 parseRenderCache( static_cast<EDA_TEXT*>( aTextBox ) );
3982 break;
3983
3984 default:
3985 if( dynamic_cast<PCB_TABLECELL*>( aTextBox ) != nullptr )
3986 {
3987 Expecting( "locked, start, pts, angle, width, margins, layer, effects, span, "
3988 "render_cache, uuid or tstamp" );
3989 }
3990 else
3991 {
3992 Expecting( "locked, start, pts, angle, width, stroke, border, margins, knockout,"
3993 "layer, effects, render_cache, uuid or tstamp" );
3994 }
3995 }
3996 }
3997
3998 aTextBox->SetStroke( stroke );
3999
4000 if( m_requiredVersion < 20230825 ) // compat, we move to an explicit flag
4001 aTextBox->SetBorderEnabled( stroke.GetWidth() >= 0 );
4002
4003 if( !foundMargins )
4004 {
4005 int margin = aTextBox->GetLegacyTextMargin();
4006 aTextBox->SetMarginLeft( margin );
4007 aTextBox->SetMarginTop( margin );
4008 aTextBox->SetMarginRight( margin );
4009 aTextBox->SetMarginBottom( margin );
4010 }
4011
4012 if( FOOTPRINT* parentFP = aTextBox->GetParentFootprint() )
4013 {
4014 aTextBox->Rotate( { 0, 0 }, parentFP->GetOrientation() );
4015 aTextBox->Move( parentFP->GetPosition() );
4016 }
4017}
4018
4019
4021{
4022 wxCHECK_MSG( CurTok() == T_table, nullptr,
4023 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a table." ) );
4024
4025 T token;
4026 STROKE_PARAMS borderStroke( -1, LINE_STYLE::SOLID );
4027 STROKE_PARAMS separatorsStroke( -1, LINE_STYLE::SOLID );
4028 std::unique_ptr<PCB_TABLE> table = std::make_unique<PCB_TABLE>( aParent, -1 );
4029
4030 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
4031 {
4032 if( token != T_LEFT )
4033 Expecting( T_LEFT );
4034
4035 token = NextTok();
4036
4037 switch( token )
4038 {
4039 case T_column_count:
4040 table->SetColCount( parseInt( "column count" ) );
4041 NeedRIGHT();
4042 break;
4043
4044 case T_uuid:
4045 NextTok();
4046 const_cast<KIID&>( table->m_Uuid ) = CurStrToKIID();
4047 NeedRIGHT();
4048 break;
4049
4050 case T_locked:
4051 table->SetLocked( parseBool() );
4052 NeedRIGHT();
4053 break;
4054
4055 case T_angle: // legacy token no longer used
4056 NeedRIGHT();
4057 break;
4058
4059 case T_layer:
4060 table->SetLayer( parseBoardItemLayer() );
4061 NeedRIGHT();
4062 break;
4063
4064 case T_column_widths:
4065 {
4066 int col = 0;
4067
4068 while( ( token = NextTok() ) != T_RIGHT )
4069 table->SetColWidth( col++, parseBoardUnits() );
4070
4071 break;
4072 }
4073
4074 case T_row_heights:
4075 {
4076 int row = 0;
4077
4078 while( ( token = NextTok() ) != T_RIGHT )
4079 table->SetRowHeight( row++, parseBoardUnits() );
4080
4081 break;
4082 }
4083
4084 case T_cells:
4085 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
4086 {
4087 if( token != T_LEFT )
4088 Expecting( T_LEFT );
4089
4090 token = NextTok();
4091
4092 if( token != T_table_cell )
4093 Expecting( "table_cell" );
4094
4095 table->AddCell( parsePCB_TABLECELL( table.get() ) );
4096 }
4097
4098 break;
4099
4100 case T_border:
4101 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
4102 {
4103 if( token != T_LEFT )
4104 Expecting( T_LEFT );
4105
4106 token = NextTok();
4107
4108 switch( token )
4109 {
4110 case T_external:
4111 table->SetStrokeExternal( parseBool() );
4112 NeedRIGHT();
4113 break;
4114
4115 case T_header:
4116 table->SetStrokeHeaderSeparator( parseBool() );
4117 NeedRIGHT();
4118 break;
4119
4120 case T_stroke:
4121 {
4122 STROKE_PARAMS_PARSER strokeParser( reader, pcbIUScale.IU_PER_MM );
4123 strokeParser.SyncLineReaderWith( *this );
4124
4125 strokeParser.ParseStroke( borderStroke );
4126 SyncLineReaderWith( strokeParser );
4127
4128 table->SetBorderStroke( borderStroke );
4129 break;
4130 }
4131
4132 default:
4133 Expecting( "external, header or stroke" );
4134 break;
4135 }
4136 }
4137
4138 break;
4139
4140 case T_separators:
4141 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
4142 {
4143 if( token != T_LEFT )
4144 Expecting( T_LEFT );
4145
4146 token = NextTok();
4147
4148 switch( token )
4149 {
4150 case T_rows:
4151 table->SetStrokeRows( parseBool() );
4152 NeedRIGHT();
4153 break;
4154
4155 case T_cols:
4156 table->SetStrokeColumns( parseBool() );
4157 NeedRIGHT();
4158 break;
4159
4160 case T_stroke:
4161 {
4162 STROKE_PARAMS_PARSER strokeParser( reader, pcbIUScale.IU_PER_MM );
4163 strokeParser.SyncLineReaderWith( *this );
4164
4165 strokeParser.ParseStroke( separatorsStroke );
4166 SyncLineReaderWith( strokeParser );
4167
4168 table->SetSeparatorsStroke( separatorsStroke );
4169 break;
4170 }
4171
4172 default:
4173 Expecting( "rows, cols, or stroke" );
4174 break;
4175 }
4176 }
4177
4178 break;
4179
4180 default:
4181 Expecting( "columns, layer, col_widths, row_heights, border, separators, header or "
4182 "cells" );
4183 }
4184 }
4185
4186 return table.release();
4187}
4188
4189
4191{
4192 wxCHECK_MSG( CurTok() == T_dimension, nullptr,
4193 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as DIMENSION." ) );
4194
4195 T token;
4196 bool locked = false;
4197 std::unique_ptr<PCB_DIMENSION_BASE> dim;
4198
4199 token = NextTok();
4200
4201 // Free 'locked' token from 6.0/7.0 formats
4202 if( token == T_locked )
4203 {
4204 locked = true;
4205 token = NextTok();
4206 }
4207
4208 // skip value that used to be saved
4209 if( token != T_LEFT )
4210 NeedLEFT();
4211
4212 token = NextTok();
4213
4214 bool isLegacyDimension = false;
4215 bool isStyleKnown = false;
4216
4217 // Old format
4218 if( token == T_width )
4219 {
4220 isLegacyDimension = true;
4221 dim = std::make_unique<PCB_DIM_ALIGNED>( aParent );
4222 dim->SetLineThickness( parseBoardUnits( "dimension width value" ) );
4223 NeedRIGHT();
4224 }
4225 else
4226 {
4227 if( token != T_type )
4228 Expecting( T_type );
4229
4230 switch( NextTok() )
4231 {
4232 case T_aligned: dim = std::make_unique<PCB_DIM_ALIGNED>( aParent ); break;
4233 case T_orthogonal: dim = std::make_unique<PCB_DIM_ORTHOGONAL>( aParent ); break;
4234 case T_leader: dim = std::make_unique<PCB_DIM_LEADER>( aParent ); break;
4235 case T_center: dim = std::make_unique<PCB_DIM_CENTER>( aParent ); break;
4236 case T_radial: dim = std::make_unique<PCB_DIM_RADIAL>( aParent ); break;
4237 default: wxFAIL_MSG( wxT( "Cannot parse unknown dimension type " )
4238 + GetTokenString( CurTok() ) );
4239 }
4240
4241 NeedRIGHT();
4242
4243 // Before parsing further, set default properites for old KiCad file
4244 // versions that didnt have these properties:
4245 dim->SetArrowDirection( DIM_ARROW_DIRECTION::OUTWARD );
4246 }
4247
4248 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
4249 {
4250 if( token != T_LEFT )
4251 Expecting( T_LEFT );
4252
4253 token = NextTok();
4254
4255 switch( token )
4256 {
4257 case T_layer:
4258 dim->SetLayer( parseBoardItemLayer() );
4259 NeedRIGHT();
4260 break;
4261
4262 case T_tstamp:
4263 case T_uuid:
4264 NextTok();
4265 const_cast<KIID&>( dim->m_Uuid ) = CurStrToKIID();
4266 NeedRIGHT();
4267 break;
4268
4269 case T_gr_text:
4270 {
4271 // In old pcb files, when parsing the text we do not yet know
4272 // if the text is kept aligned or not, and its DIM_TEXT_POSITION option.
4273 // Leave the text not aligned for now to read the text angle, and no
4274 // constraint for DIM_TEXT_POSITION in this case.
4275 // It will be set aligned (or not) later
4276 bool is_aligned = dim->GetKeepTextAligned();
4277 DIM_TEXT_POSITION t_dim_pos = dim->GetTextPositionMode();
4278
4279 if( !isStyleKnown )
4280 {
4281 dim->SetTextPositionMode( DIM_TEXT_POSITION::MANUAL );
4282 dim->SetKeepTextAligned( false );
4283 }
4284
4285 parsePCB_TEXT( m_board, dim.get() );
4286
4287 if( isLegacyDimension )
4288 {
4289 EDA_UNITS units = EDA_UNITS::MM;
4290
4291 if( !EDA_UNIT_UTILS::FetchUnitsFromString( dim->GetText(), units ) )
4292 dim->SetAutoUnits( true ); //Not determined => use automatic units
4293
4294 dim->SetUnits( units );
4295 }
4296
4297 if( !isStyleKnown )
4298 {
4299 dim->SetKeepTextAligned( is_aligned );
4300 dim->SetTextPositionMode( t_dim_pos );
4301 }
4302 break;
4303 }
4304
4305 // New format: feature points
4306 case T_pts:
4307 {
4308 VECTOR2I point;
4309
4310 parseXY( &point.x, &point.y );
4311 dim->SetStart( point );
4312 parseXY( &point.x, &point.y );
4313 dim->SetEnd( point );
4314
4315 NeedRIGHT();
4316 break;
4317 }
4318
4319 case T_height:
4320 {
4321 int height = parseBoardUnits( "dimension height value" );
4322 NeedRIGHT();
4323
4324 if( dim->Type() == PCB_DIM_ORTHOGONAL_T || dim->Type() == PCB_DIM_ALIGNED_T )
4325 {
4326 PCB_DIM_ALIGNED* aligned = static_cast<PCB_DIM_ALIGNED*>( dim.get() );
4327 aligned->SetHeight( height );
4328 }
4329
4330 break;
4331 }
4332
4333 case T_leader_length:
4334 {
4335 int length = parseBoardUnits( "leader length value" );
4336 NeedRIGHT();
4337
4338 if( dim->Type() == PCB_DIM_RADIAL_T )
4339 {
4340 PCB_DIM_RADIAL* radial = static_cast<PCB_DIM_RADIAL*>( dim.get() );
4341 radial->SetLeaderLength( length );
4342 }
4343
4344 break;
4345 }
4346
4347 case T_orientation:
4348 {
4349 int orientation = parseInt( "orthogonal dimension orientation" );
4350 NeedRIGHT();
4351
4352 if( dim->Type() == PCB_DIM_ORTHOGONAL_T )
4353 {
4354 PCB_DIM_ORTHOGONAL* ortho = static_cast<PCB_DIM_ORTHOGONAL*>( dim.get() );
4355 orientation = std::clamp( orientation, 0, 1 );
4356 ortho->SetOrientation( static_cast<PCB_DIM_ORTHOGONAL::DIR>( orientation ) );
4357 }
4358
4359 break;
4360 }
4361
4362 case T_format:
4363 {
4364 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
4365 {
4366 switch( token )
4367 {
4368 case T_LEFT:
4369 continue;
4370
4371 case T_prefix:
4372 NeedSYMBOLorNUMBER();
4373 dim->SetPrefix( FromUTF8() );
4374 NeedRIGHT();
4375 break;
4376
4377 case T_suffix:
4378 NeedSYMBOLorNUMBER();
4379 dim->SetSuffix( FromUTF8() );
4380 NeedRIGHT();
4381 break;
4382
4383 case T_units:
4384 {
4385 int mode = parseInt( "dimension units mode" );
4386 mode = std::max( 0, std::min( 4, mode ) );
4387 dim->SetUnitsMode( static_cast<DIM_UNITS_MODE>( mode ) );
4388 NeedRIGHT();
4389 break;
4390 }
4391
4392 case T_units_format:
4393 {
4394 int format = parseInt( "dimension units format" );
4395 format = std::clamp( format, 0, 3 );
4396 dim->SetUnitsFormat( static_cast<DIM_UNITS_FORMAT>( format ) );
4397 NeedRIGHT();
4398 break;
4399 }
4400
4401 case T_precision:
4402 dim->SetPrecision( static_cast<DIM_PRECISION>( parseInt( "dimension precision" ) ) );
4403 NeedRIGHT();
4404 break;
4405
4406 case T_override_value:
4407 NeedSYMBOLorNUMBER();
4408 dim->SetOverrideTextEnabled( true );
4409 dim->SetOverrideText( FromUTF8() );
4410 NeedRIGHT();
4411 break;
4412
4413 case T_suppress_zeroes:
4414 dim->SetSuppressZeroes( parseMaybeAbsentBool( true ) );
4415 break;
4416
4417 default:
4418 std::cerr << "Unknown format token: " << GetTokenString( token ) << std::endl;
4419 Expecting( "prefix, suffix, units, units_format, precision, override_value, "
4420 "suppress_zeroes" );
4421 }
4422 }
4423 break;
4424 }
4425
4426 case T_style:
4427 {
4428 isStyleKnown = true;
4429
4430 // new format: default to keep text aligned off unless token is present
4431 dim->SetKeepTextAligned( false );
4432
4433 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
4434 {
4435 switch( token )
4436 {
4437 case T_LEFT:
4438 continue;
4439
4440 case T_thickness:
4441 dim->SetLineThickness( parseBoardUnits( "extension line thickness value" ) );
4442 NeedRIGHT();
4443 break;
4444
4445 case T_arrow_direction:
4446 {
4447 token = NextTok();
4448
4449 if( token == T_inward )
4450 dim->ChangeArrowDirection( DIM_ARROW_DIRECTION::INWARD );
4451 else if( token == T_outward )
4452 dim->ChangeArrowDirection( DIM_ARROW_DIRECTION::OUTWARD );
4453 else
4454 Expecting( "inward or outward" );
4455
4456 NeedRIGHT();
4457 break;
4458 }
4459 case T_arrow_length:
4460
4461 dim->SetArrowLength( parseBoardUnits( "arrow length value" ) );
4462 NeedRIGHT();
4463 break;
4464
4465 case T_text_position_mode:
4466 {
4467 int mode = parseInt( "text position mode" );
4468 mode = std::max( 0, std::min( 3, mode ) );
4469 dim->SetTextPositionMode( static_cast<DIM_TEXT_POSITION>( mode ) );
4470 NeedRIGHT();
4471 break;
4472 }
4473
4474 case T_extension_height:
4475 {
4476 PCB_DIM_ALIGNED* aligned = dynamic_cast<PCB_DIM_ALIGNED*>( dim.get() );
4477 wxCHECK_MSG( aligned, nullptr, wxT( "Invalid extension_height token" ) );
4478 aligned->SetExtensionHeight( parseBoardUnits( "extension height value" ) );
4479 NeedRIGHT();
4480 break;
4481 }
4482
4483 case T_extension_offset:
4484 dim->SetExtensionOffset( parseBoardUnits( "extension offset value" ) );
4485 NeedRIGHT();
4486 break;
4487
4488 case T_keep_text_aligned:
4489 dim->SetKeepTextAligned( parseMaybeAbsentBool( true ) );
4490 break;
4491
4492 case T_text_frame:
4493 {
4494 wxCHECK_MSG( dim->Type() == PCB_DIM_LEADER_T, nullptr,
4495 wxT( "Invalid text_frame token" ) );
4496
4497 PCB_DIM_LEADER* leader = static_cast<PCB_DIM_LEADER*>( dim.get() );
4498
4499 int textFrame = parseInt( "text frame mode" );
4500 textFrame = std::clamp( textFrame, 0, 3 );
4501 leader->SetTextBorder( static_cast<DIM_TEXT_BORDER>( textFrame ));
4502 NeedRIGHT();
4503 break;
4504 }
4505
4506 default:
4507 Expecting( "thickness, arrow_length, arrow_direction, text_position_mode, "
4508 "extension_height, extension_offset" );
4509 }
4510 }
4511
4512 break;
4513 }
4514
4515 // Old format: feature1 stores a feature line. We only care about the origin.
4516 case T_feature1:
4517 {
4518 NeedLEFT();
4519 token = NextTok();
4520
4521 if( token != T_pts )
4522 Expecting( T_pts );
4523
4524 VECTOR2I point;
4525
4526 parseXY( &point.x, &point.y );
4527 dim->SetStart( point );
4528
4529 parseXY( nullptr, nullptr ); // Ignore second point
4530 NeedRIGHT();
4531 NeedRIGHT();
4532 break;
4533 }
4534
4535 // Old format: feature2 stores a feature line. We only care about the end point.
4536 case T_feature2:
4537 {
4538 NeedLEFT();
4539 token = NextTok();
4540
4541 if( token != T_pts )
4542 Expecting( T_pts );
4543
4544 VECTOR2I point;
4545
4546 parseXY( &point.x, &point.y );
4547 dim->SetEnd( point );
4548
4549 parseXY( nullptr, nullptr ); // Ignore second point
4550
4551 NeedRIGHT();
4552 NeedRIGHT();
4553 break;
4554 }
4555
4556 case T_crossbar:
4557 {
4558 NeedLEFT();
4559 token = NextTok();
4560
4561 if( token == T_pts )
4562 {
4563 // If we have a crossbar, we know we're an old aligned dim
4564 PCB_DIM_ALIGNED* aligned = static_cast<PCB_DIM_ALIGNED*>( dim.get() );
4565
4566 // Old style: calculate height from crossbar
4567 VECTOR2I point1, point2;
4568 parseXY( &point1.x, &point1.y );
4569 parseXY( &point2.x, &point2.y );
4570 aligned->UpdateHeight( point2, point1 ); // Yes, backwards intentionally
4571 NeedRIGHT();
4572 }
4573
4574 NeedRIGHT();
4575 break;
4576 }
4577
4578 // Arrow: no longer saved; no-op
4579 case T_arrow1a:
4580 NeedLEFT();
4581 token = NextTok();
4582
4583 if( token != T_pts )
4584 Expecting( T_pts );
4585
4586 parseXY( nullptr, nullptr );
4587 parseXY( nullptr, nullptr );
4588 NeedRIGHT();
4589 NeedRIGHT();
4590 break;
4591
4592 // Arrow: no longer saved; no-op
4593 case T_arrow1b:
4594 NeedLEFT();
4595 token = NextTok();
4596
4597 if( token != T_pts )
4598 Expecting( T_pts );
4599
4600 parseXY( nullptr, nullptr );
4601 parseXY( nullptr, nullptr );
4602 NeedRIGHT();
4603 NeedRIGHT();
4604 break;
4605
4606 // Arrow: no longer saved; no-op
4607 case T_arrow2a:
4608 NeedLEFT();
4609 token = NextTok();
4610
4611 if( token != T_pts )
4612 Expecting( T_pts );
4613
4614 parseXY( nullptr, nullptr );
4615 parseXY( nullptr, nullptr );
4616 NeedRIGHT();
4617 NeedRIGHT();
4618 break;
4619
4620 // Arrow: no longer saved; no-op
4621 case T_arrow2b:
4622 NeedLEFT();
4623 token = NextTok();
4624
4625 if( token != T_pts )
4626 Expecting( T_pts );
4627
4628 parseXY( nullptr, nullptr );
4629 parseXY( nullptr, nullptr );
4630 NeedRIGHT();
4631 NeedRIGHT();
4632 break;
4633
4634 // Handle (locked yes) from modern times
4635 case T_locked:
4636 {
4637 // Unsure if we ever wrote out (locked) for dimensions, so use maybeAbsent just in case
4638 bool isLocked = parseMaybeAbsentBool( true );
4639 dim->SetLocked( isLocked );
4640 break;
4641 }
4642
4643 default:
4644 Expecting( "layer, tstamp, uuid, gr_text, feature1, feature2, crossbar, arrow1a, "
4645 "arrow1b, arrow2a, or arrow2b" );
4646 }
4647 }
4648
4649 if( locked )
4650 dim->SetLocked( true );
4651
4652 dim->Update();
4653
4654 return dim.release();
4655}
4656
4657
4658FOOTPRINT* PCB_IO_KICAD_SEXPR_PARSER::parseFOOTPRINT( wxArrayString* aInitialComments )
4659{
4660 try
4661 {
4662 return parseFOOTPRINT_unchecked( aInitialComments );
4663 }
4664 catch( const PARSE_ERROR& parse_error )
4665 {
4666 if( m_tooRecent )
4667 throw FUTURE_FORMAT_ERROR( parse_error, GetRequiredVersion() );
4668 else
4669 throw;
4670 }
4671}
4672
4673
4675{
4676 wxCHECK_MSG( CurTok() == T_module || CurTok() == T_footprint, nullptr,
4677 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as FOOTPRINT." ) );
4678
4679 wxString name;
4680 VECTOR2I pt;
4681 T token;
4682 LIB_ID fpid;
4683 int attributes = 0;
4684
4685 std::unique_ptr<FOOTPRINT> footprint = std::make_unique<FOOTPRINT>( m_board );
4686
4687 footprint->SetInitialComments( aInitialComments );
4688
4689 if( m_board )
4690 {
4691 footprint->SetStaticComponentClass(
4692 m_board->GetComponentClassManager().GetNoneComponentClass() );
4693 }
4694
4695 token = NextTok();
4696
4697 if( !IsSymbol( token ) && token != T_NUMBER )
4698 Expecting( "symbol|number" );
4699
4700 name = FromUTF8();
4701
4702 if( !name.IsEmpty() && fpid.Parse( name, true ) >= 0 )
4703 {
4704 THROW_IO_ERROR( wxString::Format( _( "Invalid footprint ID in\nfile: %s\nline: %d\n"
4705 "offset: %d." ),
4706 CurSource(), CurLineNumber(), CurOffset() ) );
4707 }
4708
4709 auto checkVersion =
4710 [&]()
4711 {
4713 {
4714 throw FUTURE_FORMAT_ERROR( fmt::format( "{}", m_requiredVersion ),
4716 }
4717 };
4718
4719 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
4720 {
4721 if( token == T_LEFT )
4722 token = NextTok();
4723
4724 switch( token )
4725 {
4726 case T_version:
4727 {
4728 // Theoretically a footprint nested in a PCB could declare its own version, though
4729 // as of writing this comment we don't do that. Just in case, take the greater
4730 // version.
4731 int this_version = parseInt( FromUTF8().mb_str( wxConvUTF8 ) );
4732 NeedRIGHT();
4733 m_requiredVersion = std::max( m_requiredVersion, this_version );
4735 SetKnowsBar( m_requiredVersion >= 20240706 ); // Bar token is known from this version
4736 footprint->SetFileFormatVersionAtLoad( this_version );
4737 break;
4738 }
4739
4740 case T_generator:
4741 // We currently ignore the generator when parsing. It is included in the file for manual
4742 // indication of where the footprint came from.
4743 NeedSYMBOL();
4744 NeedRIGHT();
4745 break;
4746
4747 case T_generator_version:
4748 {
4749 NeedSYMBOL();
4750 m_generatorVersion = FromUTF8();
4751 NeedRIGHT();
4752
4753 // If the format includes a generator version, by this point we have enough info to
4754 // do the version check here
4755 checkVersion();
4756
4757 break;
4758 }
4759
4760 case T_locked:
4761 footprint->SetLocked( parseMaybeAbsentBool( true ) );
4762 break;
4763
4764 case T_placed:
4765 footprint->SetIsPlaced( parseMaybeAbsentBool( true ) );
4766 break;
4767
4768 case T_layer:
4769 {
4770 // Footprints can be only on the front side or the back side.
4771 // but because we can find some stupid layer in file, ensure a
4772 // acceptable layer is set for the footprint
4774 footprint->SetLayer( layer == B_Cu ? B_Cu : F_Cu );
4775 NeedRIGHT();
4776 break;
4777 }
4778
4779 case T_stackup:
4780 {
4781 parseFootprintStackup( *footprint );
4782 break;
4783 }
4784
4785 case T_tedit:
4786 parseHex();
4787 NeedRIGHT();
4788 break;
4789
4790 case T_tstamp:
4791 case T_uuid:
4792 NextTok();
4793 const_cast<KIID&>( footprint->m_Uuid ) = CurStrToKIID();
4794 NeedRIGHT();
4795 break;
4796
4797 case T_at:
4798 pt.x = parseBoardUnits( "X coordinate" );
4799 pt.y = parseBoardUnits( "Y coordinate" );
4800 footprint->SetPosition( pt );
4801 token = NextTok();
4802
4803 if( token == T_NUMBER )
4804 {
4805 footprint->SetOrientation( EDA_ANGLE( parseDouble(), DEGREES_T ) );
4806 NeedRIGHT();
4807 }
4808 else if( token != T_RIGHT )
4809 {
4810 Expecting( T_RIGHT );
4811 }
4812
4813 break;
4814
4815 case T_descr:
4816 NeedSYMBOLorNUMBER(); // some symbols can be 0508, so a number is also a symbol here
4817 footprint->SetLibDescription( FromUTF8() );
4818 NeedRIGHT();
4819 break;
4820
4821 case T_tags:
4822 NeedSYMBOLorNUMBER(); // some symbols can be 0508, so a number is also a symbol here
4823 footprint->SetKeywords( FromUTF8() );
4824 NeedRIGHT();
4825 break;
4826
4827 case T_property:
4828 {
4829 NeedSYMBOL();
4830 wxString pName = FromUTF8();
4831 NeedSYMBOL();
4832 wxString pValue = FromUTF8();
4833
4834 // Prior to PCB fields, we used to use properties for special values instead of
4835 // using (keyword_example "value")
4836 if( m_requiredVersion < 20230620 )
4837 {
4838 // Skip legacy non-field properties sent from symbols that should not be kept
4839 // in footprints.
4840 if( pName == "ki_keywords" || pName == "ki_locked" )
4841 {
4842 NeedRIGHT();
4843 break;
4844 }
4845
4846 // Description from symbol (not the fooprint library description stored in (descr) )
4847 // used to be stored as a reserved key value
4848 if( pName == "ki_description" )
4849 {
4850 footprint->GetField( FIELD_T::DESCRIPTION )->SetText( pValue );
4851 NeedRIGHT();
4852 break;
4853 }
4854
4855 // Sheet file and name used to be stored as properties invisible to the user
4856 if( pName == "Sheetfile" || pName == "Sheet file" )
4857 {
4858 footprint->SetSheetfile( pValue );
4859 NeedRIGHT();
4860 break;
4861 }
4862
4863 if( pName == "Sheetname" || pName == "Sheet name" )
4864 {
4865 footprint->SetSheetname( pValue );
4866 NeedRIGHT();
4867 break;
4868 }
4869 }
4870
4871 PCB_FIELD* field = nullptr;
4872 std::unique_ptr<PCB_FIELD> unusedField;
4873
4874 // 8.0.0rc3 had a bug where these properties were mistakenly added to the footprint as
4875 // fields, this will remove them as fields but still correctly set the footprint filters
4876 if( pName == "ki_fp_filters" )
4877 {
4878 footprint->SetFilters( pValue );
4879
4880 // Use the text effect parsing function because it will handle ki_fp_filters as a
4881 // property with no text effects, but will also handle parsing the text effects.
4882 // We just drop the effects if they're present.
4883 unusedField = std::make_unique<PCB_FIELD>( footprint.get(), FIELD_T::USER );
4884 field = unusedField.get();
4885 }
4886 else if( pName == "Footprint" )
4887 {
4888 // Until V9, footprints had a Footprint field that usually (but not always)
4889 // duplicated the footprint's LIB_ID. In V9 this was removed. Parse it
4890 // like any other, but don't add it to anything.
4891 unusedField = std::make_unique<PCB_FIELD>( footprint.get(), FIELD_T::FOOTPRINT );
4892 field = unusedField.get();
4893 }
4894 else if( footprint->HasField( pName ) )
4895 {
4896 field = footprint->GetField( pName );
4897 field->SetText( pValue );
4898 }
4899 else
4900 {
4901 field = new PCB_FIELD( footprint.get(), FIELD_T::USER, pName );
4902 footprint->Add( field );
4903
4904 field->SetText( pValue );
4905 field->SetLayer( footprint->GetLayer() == F_Cu ? F_Fab : B_Fab );
4906
4907 if( m_board ) // can be null when reading a lib
4908 field->StyleFromSettings( m_board->GetDesignSettings(), true );
4909 }
4910
4911 // Hide the field by default if it is a legacy field that did not have
4912 // text effects applied, since hide is a negative effect
4913 if( m_requiredVersion < 20230620 )
4914 field->SetVisible( false );
4915 else
4916 field->SetVisible( true );
4917
4918 parsePCB_TEXT_effects( field );
4919 }
4920 break;
4921
4922 case T_path:
4923 NeedSYMBOLorNUMBER(); // Paths can be numerical so a number is also a symbol here
4924 footprint->SetPath( KIID_PATH( FromUTF8() ) );
4925 NeedRIGHT();
4926 break;
4927
4928 case T_sheetname:
4929 NeedSYMBOL();
4930 footprint->SetSheetname( FromUTF8() );
4931 NeedRIGHT();
4932 break;
4933
4934 case T_sheetfile:
4935 NeedSYMBOL();
4936 footprint->SetSheetfile( FromUTF8() );
4937 NeedRIGHT();
4938 break;
4939
4940 case T_units:
4941 {
4942 std::vector<FOOTPRINT::FP_UNIT_INFO> unitInfos;
4943
4944 // (units (unit (name "A") (pins "1" "2" ...)) ...)
4945 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
4946 {
4947 if( token == T_LEFT )
4948 token = NextTok();
4949
4950 if( token == T_unit )
4951 {
4953
4954 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
4955 {
4956 if( token == T_LEFT )
4957 token = NextTok();
4958
4959 if( token == T_name )
4960 {
4961 NeedSYMBOLorNUMBER();
4962 info.m_unitName = FromUTF8();
4963 NeedRIGHT();
4964 }
4965 else if( token == T_pins )
4966 {
4967 // Parse a flat list of quoted numbers or symbols until ')'
4968 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
4969 {
4970 if( token == T_STRING || token == T_NUMBER )
4971 {
4972 info.m_pins.emplace_back( FromUTF8() );
4973 }
4974 else
4975 {
4976 Expecting( "pin number" );
4977 }
4978 }
4979 }
4980 else
4981 {
4982 // Unknown sub-token inside unit; skip its list if any
4983 skipCurrent();
4984 }
4985 }
4986
4987 unitInfos.push_back( info );
4988 }
4989 else
4990 {
4991 // Unknown entry under units; skip
4992 skipCurrent();
4993 }
4994 }
4995
4996 if( !unitInfos.empty() )
4997 footprint->SetUnitInfo( unitInfos );
4998
4999 break;
5000 }
5001
5002 case T_autoplace_cost90:
5003 case T_autoplace_cost180:
5004 parseInt( "legacy auto-place cost" );
5005 NeedRIGHT();
5006 break;
5007
5008 case T_private_layers:
5009 {
5010 LSET privateLayers;
5011
5012 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
5013 {
5014 auto it = m_layerIndices.find( CurStr() );
5015
5016 if( it != m_layerIndices.end() )
5017 privateLayers.set( it->second );
5018 else
5019 Expecting( "layer name" );
5020 }
5021
5022 if( m_requiredVersion < 20220427 )
5023 {
5024 privateLayers.set( Edge_Cuts, false );
5025 privateLayers.set( Margin, false );
5026 }
5027
5028 footprint->SetPrivateLayers( privateLayers );
5029 break;
5030 }
5031
5032 case T_net_tie_pad_groups:
5033 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
5034 footprint->AddNetTiePadGroup( CurStr() );
5035
5036 break;
5037
5038 case T_duplicate_pad_numbers_are_jumpers:
5039 footprint->SetDuplicatePadNumbersAreJumpers( parseBool() );
5040 NeedRIGHT();
5041 break;
5042
5043 case T_jumper_pad_groups:
5044 {
5045 // This should only be formatted if there is at least one group
5046 std::vector<std::set<wxString>>& groups = footprint->JumperPadGroups();
5047 std::set<wxString>* currentGroup = nullptr;
5048
5049 for( token = NextTok(); currentGroup || token != T_RIGHT; token = NextTok() )
5050 {
5051 switch( static_cast<int>( token ) )
5052 {
5053 case T_LEFT:
5054 currentGroup = &groups.emplace_back();
5055 break;
5056
5057 case DSN_STRING:
5058 if( currentGroup )
5059 currentGroup->insert( FromUTF8() );
5060
5061 break;
5062
5063 case T_RIGHT:
5064 currentGroup = nullptr;
5065 break;
5066
5067 default:
5068 Expecting( "list of pad names" );
5069 }
5070 }
5071
5072 break;
5073 }
5074
5075 case T_solder_mask_margin:
5076 footprint->SetLocalSolderMaskMargin( parseBoardUnits( "local solder mask margin value" ) );
5077 NeedRIGHT();
5078
5079 // In pre-9.0 files "0" meant inherit.
5080 if( m_requiredVersion <= 20240201 && footprint->GetLocalSolderMaskMargin() == 0 )
5081 footprint->SetLocalSolderMaskMargin( {} );
5082
5083 break;
5084
5085 case T_solder_paste_margin:
5086 footprint->SetLocalSolderPasteMargin( parseBoardUnits( "local solder paste margin value" ) );
5087 NeedRIGHT();
5088
5089 // In pre-9.0 files "0" meant inherit.
5090 if( m_requiredVersion <= 20240201 && footprint->GetLocalSolderPasteMargin() == 0 )
5091 footprint->SetLocalSolderPasteMargin( {} );
5092
5093 break;
5094
5095 case T_solder_paste_ratio: // legacy token
5096 case T_solder_paste_margin_ratio:
5097 footprint->SetLocalSolderPasteMarginRatio( parseDouble( "local solder paste margin ratio value" ) );
5098 NeedRIGHT();
5099
5100 // In pre-9.0 files "0" meant inherit.
5101 if( m_requiredVersion <= 20240201 && footprint->GetLocalSolderPasteMarginRatio() == 0 )
5102 footprint->SetLocalSolderPasteMarginRatio( {} );
5103
5104 break;
5105
5106 case T_clearance:
5107 footprint->SetLocalClearance( parseBoardUnits( "local clearance value" ) );
5108 NeedRIGHT();
5109
5110 // In pre-9.0 files "0" meant inherit.
5111 if( m_requiredVersion <= 20240201 && footprint->GetLocalClearance() == 0 )
5112 footprint->SetLocalClearance( {} );
5113
5114 break;
5115
5116 case T_zone_connect:
5117 footprint->SetLocalZoneConnection((ZONE_CONNECTION) parseInt( "zone connection value" ) );
5118 NeedRIGHT();
5119 break;
5120
5121 case T_thermal_width:
5122 case T_thermal_gap:
5123 // Interestingly, these have never been exposed in the GUI
5124 parseBoardUnits( token );
5125 NeedRIGHT();
5126 break;
5127
5128 case T_attr:
5129 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
5130 {
5131 switch( token )
5132 {
5133 case T_virtual: // legacy token prior to version 20200826
5135 break;
5136
5137 case T_through_hole:
5138 attributes |= FP_THROUGH_HOLE;
5139 break;
5140
5141 case T_smd:
5142 attributes |= FP_SMD;
5143 break;
5144
5145 case T_board_only:
5146 attributes |= FP_BOARD_ONLY;
5147 break;
5148
5149 case T_exclude_from_pos_files:
5150 attributes |= FP_EXCLUDE_FROM_POS_FILES;
5151 break;
5152
5153 case T_exclude_from_bom:
5154 attributes |= FP_EXCLUDE_FROM_BOM;
5155 break;
5156
5157 case T_allow_missing_courtyard:
5158 footprint->SetAllowMissingCourtyard( true );
5159 break;
5160
5161 case T_dnp:
5162 attributes |= FP_DNP;
5163 break;
5164
5165 case T_allow_soldermask_bridges:
5166 footprint->SetAllowSolderMaskBridges( true );
5167 break;
5168
5169 default:
5170 Expecting( "through_hole, smd, virtual, board_only, exclude_from_pos_files, "
5171 "exclude_from_bom or allow_solder_mask_bridges" );
5172 }
5173 }
5174
5175 break;
5176
5177 case T_fp_text:
5178 {
5179 PCB_TEXT* text = parsePCB_TEXT( footprint.get() );
5180
5181 if( PCB_FIELD* field = dynamic_cast<PCB_FIELD*>( text ) )
5182 {
5183 switch( field->GetId() )
5184 {
5185 case FIELD_T::REFERENCE:
5186 footprint->Reference() = PCB_FIELD( *text, FIELD_T::REFERENCE );
5187 const_cast<KIID&>( footprint->Reference().m_Uuid ) = text->m_Uuid;
5188 delete text;
5189 break;
5190
5191 case FIELD_T::VALUE:
5192 footprint->Value() = PCB_FIELD( *text, FIELD_T::VALUE );
5193 const_cast<KIID&>( footprint->Value().m_Uuid ) = text->m_Uuid;
5194 delete text;
5195 break;
5196
5197 default:
5198 // Fields other than reference and value aren't treated specially,
5199 // and can be created if the fp_text was hidden on the board,
5200 // so just add those to the footprint as normal.
5201 footprint->Add(text, ADD_MODE::APPEND, true );
5202 break;
5203 }
5204 }
5205 else
5206 {
5207 footprint->Add( text, ADD_MODE::APPEND, true );
5208 }
5209
5210 break;
5211 }
5212
5213 case T_fp_text_box:
5214 {
5215 PCB_TEXTBOX* textbox = parsePCB_TEXTBOX( footprint.get() );
5216 footprint->Add( textbox, ADD_MODE::APPEND, true );
5217 break;
5218 }
5219
5220 case T_table:
5221 {
5222 PCB_TABLE* table = parsePCB_TABLE( footprint.get() );
5223 footprint->Add( table, ADD_MODE::APPEND, true );
5224 break;
5225 }
5226
5227 case T_fp_arc:
5228 case T_fp_circle:
5229 case T_fp_curve:
5230 case T_fp_rect:
5231 case T_fp_line:
5232 case T_fp_poly:
5233 {
5234 PCB_SHAPE* shape = parsePCB_SHAPE( footprint.get() );
5235 footprint->Add( shape, ADD_MODE::APPEND, true );
5236 break;
5237 }
5238
5239 case T_image:
5240 {
5242 footprint->Add( image, ADD_MODE::APPEND, true );
5243 break;
5244 }
5245
5246 case T_barcode:
5247 {
5248 PCB_BARCODE* barcode = parsePCB_BARCODE( footprint.get() );
5249 footprint->Add( barcode, ADD_MODE::APPEND, true );
5250 break;
5251 }
5252
5253 case T_dimension:
5254 {
5255 PCB_DIMENSION_BASE* dimension = parseDIMENSION( footprint.get() );
5256 footprint->Add( dimension, ADD_MODE::APPEND, true );
5257 break;
5258 }
5259
5260 case T_pad:
5261 {
5262 PAD* pad = parsePAD( footprint.get() );
5263 footprint->Add( pad, ADD_MODE::APPEND, true );
5264 break;
5265 }
5266
5267 case T_model:
5268 {
5269 FP_3DMODEL* model = parse3DModel();
5270 footprint->Add3DModel( model );
5271 delete model;
5272 break;
5273 }
5274
5275 case T_zone:
5276 {
5277 ZONE* zone = parseZONE( footprint.get() );
5278 footprint->Add( zone, ADD_MODE::APPEND, true );
5279 break;
5280 }
5281
5282 case T_group:
5283 parseGROUP( footprint.get() );
5284 break;
5285
5286 case T_point:
5287 {
5288 PCB_POINT* point = parsePCB_POINT();
5289 footprint->Add( point, ADD_MODE::APPEND, true );
5290 break;
5291 }
5292 case T_embedded_fonts:
5293 {
5294 footprint->GetEmbeddedFiles()->SetAreFontsEmbedded( parseBool() );
5295 NeedRIGHT();
5296 break;
5297 }
5298
5299 case T_embedded_files:
5300 {
5301 EMBEDDED_FILES_PARSER embeddedFilesParser( reader );
5302 embeddedFilesParser.SyncLineReaderWith( *this );
5303
5304 try
5305 {
5306 embeddedFilesParser.ParseEmbedded( footprint->GetEmbeddedFiles() );
5307 }
5308 catch( const PARSE_ERROR& e )
5309 {
5310 wxLogError( e.What() );
5311 }
5312
5313 SyncLineReaderWith( embeddedFilesParser );
5314 break;
5315 }
5316
5317 case T_component_classes:
5318 {
5319 std::unordered_set<wxString> componentClassNames;
5320
5321 while( ( token = NextTok() ) != T_RIGHT )
5322 {
5323 if( token != T_LEFT )
5324 Expecting( T_LEFT );
5325
5326 if( ( token = NextTok() ) != T_class )
5327 Expecting( T_class );
5328
5329 NeedSYMBOLorNUMBER();
5330 componentClassNames.insert( From_UTF8( CurText() ) );
5331 NeedRIGHT();
5332 }
5333
5334 footprint->SetTransientComponentClassNames( componentClassNames );
5335
5336 if( m_board )
5337 footprint->ResolveComponentClassNames( m_board, componentClassNames );
5338
5339 break;
5340 }
5341
5342 default:
5343 Expecting( "at, descr, locked, placed, tedit, tstamp, uuid, "
5344 "autoplace_cost90, autoplace_cost180, attr, clearance, "
5345 "embedded_files, fp_arc, fp_circle, fp_curve, fp_line, fp_poly, "
5346 "fp_rect, fp_text, pad, group, generator, model, path, solder_mask_margin, "
5347 "solder_paste_margin, solder_paste_margin_ratio, tags, thermal_gap, "
5348 "version, zone, zone_connect, or component_classes" );
5349 }
5350 }
5351
5352 // In legacy files the lack of attributes indicated a through-hole component which was by
5353 // default excluded from pos files. However there was a hack to look for SMD pads and
5354 // consider those "mislabeled through-hole components" and therefore include them in place
5355 // files. We probably don't want to get into that game so we'll just include them by
5356 // default and let the user change it if required.
5357 if( m_requiredVersion < 20200826 && attributes == 0 )
5358 attributes |= FP_THROUGH_HOLE;
5359
5361 {
5362 if( footprint->GetKeywords().StartsWith( wxT( "net tie" ) ) )
5363 {
5364 wxString padGroup;
5365
5366 for( PAD* pad : footprint->Pads() )
5367 {
5368 if( !padGroup.IsEmpty() )
5369 padGroup += wxS( ", " );
5370
5371 padGroup += pad->GetNumber();
5372 }
5373
5374 if( !padGroup.IsEmpty() )
5375 footprint->AddNetTiePadGroup( padGroup );
5376 }
5377 }
5378
5379 footprint->SetAttributes( attributes );
5380
5381 footprint->SetFPID( fpid );
5382
5383 return footprint.release();
5384}
5385
5386
5388{
5389 wxCHECK_RET( CurTok() == T_stackup, "Expected stackup token" );
5390
5391 // If we have a stackup list at all, we must be in custom layer mode
5393 LSET layers = LSET{};
5394
5395 for( T token = NextTok(); token != T_RIGHT; token = NextTok() )
5396 {
5397 if( CurTok() != T_LEFT )
5398 Expecting( T_LEFT );
5399
5400 token = NextTok();
5401
5402 switch( token )
5403 {
5404 case T_layer:
5405 {
5406 NeedSYMBOLorNUMBER();
5407
5408 const auto it = m_layerIndices.find( CurStr() );
5409 if( it == m_layerIndices.end() )
5410 {
5411 Expecting( "layer name" );
5412 }
5413 else
5414 {
5415 layers.set( it->second );
5416 }
5417
5418 NeedRIGHT();
5419 break;
5420 }
5421 default:
5422 {
5423 Expecting( "layer" );
5424 break;
5425 }
5426 }
5427 }
5428
5429 // Check that the copper layers are sensible and contiguous
5430 const LSET gotCuLayers = layers & LSET::AllCuMask();
5431
5432 // Remove this check when we support odd copper layer stackups
5433 if( gotCuLayers.count() % 2 != 0 )
5434 {
5435 THROW_IO_ERROR( wxString::Format( _( "Invalid stackup in footprint: "
5436 "odd number of copper layers (%d)." ),
5437 gotCuLayers.count() ) );
5438 }
5439
5440 const LSET expectedCuLayers = LSET::AllCuMask( gotCuLayers.count() );
5441 if( gotCuLayers != expectedCuLayers )
5442 {
5443 THROW_IO_ERROR( wxString::Format( _( "Invalid stackup in footprint: "
5444 "copper layers are not contiguous." ) ) );
5445 }
5446
5447 if( ( layers & LSET::AllTechMask() ).count() > 0 )
5448 {
5449 THROW_IO_ERROR( wxString::Format( _( "Invalid stackup in footprint: "
5450 "technology layers are implicit in footprints and "
5451 "should not be specified in the stackup." ) ) );
5452 }
5453
5454 // Set the mode first, so that the layer count is unlocked if needed
5455 aFootprint.SetStackupMode( stackupMode );
5456 aFootprint.SetStackupLayers( std::move( layers ) );
5457}
5458
5459
5461{
5462 wxCHECK_MSG( CurTok() == T_pad, nullptr,
5463 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as PAD." ) );
5464
5465 VECTOR2I sz;
5466 VECTOR2I pt;
5467 bool foundNet = false;
5468 bool foundNetcode = false;
5469
5470 std::unique_ptr<PAD> pad = std::make_unique<PAD>( aParent );
5471
5472 NeedSYMBOLorNUMBER();
5473 pad->SetNumber( FromUTF8() );
5474
5475 T token = NextTok();
5476
5477 switch( token )
5478 {
5479 case T_thru_hole:
5480 pad->SetAttribute( PAD_ATTRIB::PTH );
5481
5482 // The drill token is usually missing if 0 drill size is specified.
5483 // Emulate it using 1 nm drill size to avoid errors.
5484 // Drill size cannot be set to 0 in newer versions.
5485 pad->SetDrillSize( VECTOR2I( 1, 1 ) );
5486 break;
5487
5488 case T_smd:
5489 pad->SetAttribute( PAD_ATTRIB::SMD );
5490
5491 // Default PAD object is thru hole with drill.
5492 // SMD pads have no hole
5493 pad->SetDrillSize( VECTOR2I( 0, 0 ) );
5494 break;
5495
5496 case T_connect:
5497 pad->SetAttribute( PAD_ATTRIB::CONN );
5498
5499 // Default PAD object is thru hole with drill.
5500 // CONN pads have no hole
5501 pad->SetDrillSize( VECTOR2I( 0, 0 ) );
5502 break;
5503
5504 case T_np_thru_hole:
5505 pad->SetAttribute( PAD_ATTRIB::NPTH );
5506 break;
5507
5508 default:
5509 Expecting( "thru_hole, smd, connect, or np_thru_hole" );
5510 }
5511
5512 token = NextTok();
5513
5514 switch( token )
5515 {
5516 case T_circle:
5518 break;
5519
5520 case T_rect:
5522 break;
5523
5524 case T_oval:
5526 break;
5527
5528 case T_trapezoid:
5530 break;
5531
5532 case T_roundrect:
5533 // Note: the shape can be PAD_SHAPE::ROUNDRECT or PAD_SHAPE::CHAMFERED_RECT
5534 // (if chamfer parameters are found later in pad descr.)
5536 break;
5537
5538 case T_custom:
5540 break;
5541
5542 default:
5543 Expecting( "circle, rectangle, roundrect, oval, trapezoid or custom" );
5544 }
5545
5546 std::optional<EDA_ANGLE> thermalBrAngleOverride;
5547
5548 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
5549 {
5550 if( token == T_locked )
5551 {
5552 // Pad locking is now a session preference
5553 token = NextTok();
5554 }
5555
5556 if( token != T_LEFT )
5557 Expecting( T_LEFT );
5558
5559 token = NextTok();
5560
5561 switch( token )
5562 {
5563 case T_size:
5564 sz.x = parseBoardUnits( "width value" );
5565 sz.y = parseBoardUnits( "height value" );
5566 pad->SetSize( PADSTACK::ALL_LAYERS, sz );
5567 NeedRIGHT();
5568 break;
5569
5570 case T_at:
5571 pt.x = parseBoardUnits( "X coordinate" );
5572 pt.y = parseBoardUnits( "Y coordinate" );
5573 pad->SetFPRelativePosition( pt );
5574 token = NextTok();
5575
5576 if( token == T_NUMBER )
5577 {
5578 pad->SetOrientation( EDA_ANGLE( parseDouble(), DEGREES_T ) );
5579 NeedRIGHT();
5580 }
5581 else if( token != T_RIGHT )
5582 {
5583 Expecting( ") or angle value" );
5584 }
5585
5586 break;
5587
5588 case T_rect_delta:
5589 {
5591 delta.x = parseBoardUnits( "rectangle delta width" );
5592 delta.y = parseBoardUnits( "rectangle delta height" );
5593 pad->SetDelta( PADSTACK::ALL_LAYERS, delta );
5594 NeedRIGHT();
5595 break;
5596 }
5597
5598 case T_drill:
5599 {
5600 bool haveWidth = false;
5601 VECTOR2I drillSize = pad->GetDrillSize();
5602
5603 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
5604 {
5605 if( token == T_LEFT )
5606 token = NextTok();
5607
5608 switch( token )
5609 {
5610 case T_oval: pad->SetDrillShape( PAD_DRILL_SHAPE::OBLONG ); break;
5611
5612 case T_NUMBER:
5613 {
5614 if( !haveWidth )
5615 {
5616 drillSize.x = parseBoardUnits();
5617
5618 // If height is not defined the width and height are the same.
5619 drillSize.y = drillSize.x;
5620 haveWidth = true;
5621 }
5622 else
5623 {
5624 drillSize.y = parseBoardUnits();
5625 }
5626 }
5627
5628 break;
5629
5630 case T_offset:
5631 pt.x = parseBoardUnits( "drill offset x" );
5632 pt.y = parseBoardUnits( "drill offset y" );
5633 pad->SetOffset( PADSTACK::ALL_LAYERS, pt );
5634 NeedRIGHT();
5635 break;
5636
5637 default:
5638 Expecting( "oval, size, or offset" );
5639 }
5640 }
5641
5642 // This fixes a bug caused by setting the default PAD drill size to a value other
5643 // than 0 used to fix a bunch of debug assertions even though it is defined as a
5644 // through hole pad. Wouldn't a though hole pad with no drill be a surface mount
5645 // pad (or a conn pad which is a smd pad with no solder paste)?
5646 if( pad->GetAttribute() != PAD_ATTRIB::SMD && pad->GetAttribute() != PAD_ATTRIB::CONN )
5647 pad->SetDrillSize( drillSize );
5648 else
5649 pad->SetDrillSize( VECTOR2I( 0, 0 ) );
5650
5651 break;
5652 }
5653
5654 case T_layers:
5655 {
5656 LSET layerMask = parseBoardItemLayersAsMask();
5657
5658 // We force this mask to include all copper layers if the pad is a PTH pad.
5659 // This is because PTH pads are always drawn on all copper layers, even if the
5660 // padstack has inner layers that are smaller than the hole. There was a corner
5661 // case in the past where a PTH pad was defined with NPTH layer set (F&B.Cu) and
5662 // could not be reset without effort
5663 if( pad->GetAttribute() == PAD_ATTRIB::PTH && m_board )
5664 layerMask |= LSET::AllCuMask( m_board->GetCopperLayerCount() );
5665
5666 pad->SetLayerSet( layerMask );
5667 break;
5668 }
5669
5670 case T_net:
5671 foundNet = true;
5672
5673 token = NextTok();
5674
5675 // Legacy files (pre-10.0) will have a netcode written before the netname. This netcode
5676 // is authoratative (though may be mapped by getNetCode() to prevent collisions).
5677 if( IsNumber( token ) )
5678 {
5679 if( !pad->SetNetCode( getNetCode( parseInt() ), /* aNoAssert */ true ) )
5680 {
5681 wxLogError( _( "Invalid net ID in\nfile: %s\nline: %d offset: %d" ),
5682 CurSource(), CurLineNumber(), CurOffset() );
5683 }
5684
5685 foundNetcode = true;
5686 token = NextTok();
5687 }
5688
5689 if( !IsSymbol( token ) )
5690 {
5691 Expecting( "net name" );
5692 break;
5693 }
5694
5695 if( m_board )
5696 {
5697 wxString netName( FromUTF8() );
5698
5699 // Convert overbar syntax from `~...~` to `~{...}`. These were left out of the
5700 // first merge so the version is a bit later.
5701 if( m_requiredVersion < 20210606 )
5702 netName = ConvertToNewOverbarNotation( netName );
5703
5704 if( foundNetcode )
5705 {
5706 if( netName != m_board->FindNet( pad->GetNetCode() )->GetNetname() )
5707 {
5708 pad->SetNetCode( NETINFO_LIST::ORPHANED, /* aNoAssert */ true );
5709 wxLogError( _( "Net name doesn't match ID in\nfile: %s\nline: %d offset: %d" ),
5710 CurSource(), CurLineNumber(), CurOffset() );
5711 }
5712 }
5713 else
5714 {
5715 NETINFO_ITEM* netinfo = m_board->FindNet( netName );
5716
5717 if( !netinfo )
5718 {
5719 netinfo = new NETINFO_ITEM( m_board, netName );
5720 m_board->Add( netinfo, ADD_MODE::INSERT, true );
5721 }
5722
5723 pad->SetNet( netinfo );
5724 }
5725 }
5726
5727 NeedRIGHT();
5728 break;
5729
5730 case T_pinfunction:
5731 NeedSYMBOLorNUMBER();
5732 pad->SetPinFunction( FromUTF8() );
5733 NeedRIGHT();
5734 break;
5735
5736 case T_pintype:
5737 NeedSYMBOLorNUMBER();
5738 pad->SetPinType( FromUTF8() );
5739 NeedRIGHT();
5740 break;
5741
5742 case T_die_length:
5743 pad->SetPadToDieLength( parseBoardUnits( T_die_length ) );
5744 NeedRIGHT();
5745 break;
5746
5747 case T_die_delay:
5748 {
5749 if( m_requiredVersion <= 20250926 )
5750 pad->SetPadToDieDelay( parseBoardUnits( T_die_delay ) );
5751 else
5752 pad->SetPadToDieDelay( parseBoardUnits( T_die_delay, EDA_DATA_TYPE::TIME ) );
5753
5754 NeedRIGHT();
5755 break;
5756 }
5757
5758 case T_solder_mask_margin:
5759 pad->SetLocalSolderMaskMargin( parseBoardUnits( "local solder mask margin value" ) );
5760 NeedRIGHT();
5761
5762 // In pre-9.0 files "0" meant inherit.
5763 if( m_requiredVersion <= 20240201 && pad->GetLocalSolderMaskMargin() == 0 )
5764 pad->SetLocalSolderMaskMargin( {} );
5765
5766 break;
5767
5768 case T_solder_paste_margin:
5769 pad->SetLocalSolderPasteMargin( parseBoardUnits( "local solder paste margin value" ) );
5770 NeedRIGHT();
5771
5772 // In pre-9.0 files "0" meant inherit.
5773 if( m_requiredVersion <= 20240201 && pad->GetLocalSolderPasteMargin() == 0 )
5774 pad->SetLocalSolderPasteMargin( {} );
5775
5776 break;
5777
5778 case T_solder_paste_margin_ratio:
5779 pad->SetLocalSolderPasteMarginRatio( parseDouble( "local solder paste margin ratio value" ) );
5780 NeedRIGHT();
5781
5782 // In pre-9.0 files "0" meant inherit.
5783 if( m_requiredVersion <= 20240201 && pad->GetLocalSolderPasteMarginRatio() == 0 )
5784 pad->SetLocalSolderPasteMarginRatio( {} );
5785
5786 break;
5787
5788 case T_clearance:
5789 pad->SetLocalClearance( parseBoardUnits( "local clearance value" ) );
5790 NeedRIGHT();
5791
5792 // In pre-9.0 files "0" meant inherit.
5793 if( m_requiredVersion <= 20240201 && pad->GetLocalClearance() == 0 )
5794 pad->SetLocalClearance( {} );
5795
5796 break;
5797
5798 case T_teardrops:
5799 parseTEARDROP_PARAMETERS( &pad->GetTeardropParams() );
5800 break;
5801
5802 case T_zone_connect:
5803 pad->SetLocalZoneConnection( (ZONE_CONNECTION) parseInt( "zone connection value" ) );
5804 NeedRIGHT();
5805 break;
5806
5807 case T_thermal_width: // legacy token
5808 case T_thermal_bridge_width:
5809 pad->SetLocalThermalSpokeWidthOverride( parseBoardUnits( token ) );
5810 NeedRIGHT();
5811 break;
5812
5813 case T_thermal_bridge_angle:
5814 thermalBrAngleOverride = EDA_ANGLE( parseDouble( "thermal spoke angle" ), DEGREES_T );
5815 NeedRIGHT();
5816 break;
5817
5818
5819 case T_thermal_gap:
5820 pad->SetThermalGap( parseBoardUnits( "thermal relief gap value" ) );
5821 NeedRIGHT();
5822 break;
5823
5824 case T_roundrect_rratio:
5825 pad->SetRoundRectRadiusRatio( PADSTACK::ALL_LAYERS,
5826 parseDouble( "roundrect radius ratio" ) );
5827 NeedRIGHT();
5828 break;
5829
5830 case T_chamfer_ratio:
5831 pad->SetChamferRectRatio( PADSTACK::ALL_LAYERS, parseDouble( "chamfer ratio" ) );
5832
5833 if( pad->GetChamferRectRatio( PADSTACK::ALL_LAYERS ) > 0 )
5835
5836 NeedRIGHT();
5837 break;
5838
5839 case T_chamfer:
5840 {
5841 int chamfers = 0;
5842 bool end_list = false;
5843
5844 while( !end_list )
5845 {
5846 token = NextTok();
5847
5848 switch( token )
5849 {
5850 case T_top_left:
5851 chamfers |= RECT_CHAMFER_TOP_LEFT;
5852 break;
5853
5854 case T_top_right:
5855 chamfers |= RECT_CHAMFER_TOP_RIGHT;
5856 break;
5857
5858 case T_bottom_left:
5859 chamfers |= RECT_CHAMFER_BOTTOM_LEFT;
5860 break;
5861
5862 case T_bottom_right:
5863 chamfers |= RECT_CHAMFER_BOTTOM_RIGHT;
5864 break;
5865
5866 case T_RIGHT:
5867 pad->SetChamferPositions( PADSTACK::ALL_LAYERS, chamfers );
5868 end_list = true;
5869 break;
5870
5871 default:
5872 Expecting( "chamfer_top_left chamfer_top_right chamfer_bottom_left or "
5873 "chamfer_bottom_right" );
5874 }
5875 }
5876
5877 if( pad->GetChamferPositions( PADSTACK::ALL_LAYERS ) != RECT_NO_CHAMFER )
5879
5880 break;
5881 }
5882
5883 case T_property:
5884 while( token != T_RIGHT )
5885 {
5886 token = NextTok();
5887
5888 switch( token )
5889 {
5890 case T_pad_prop_bga: pad->SetProperty( PAD_PROP::BGA ); break;
5891 case T_pad_prop_fiducial_glob: pad->SetProperty( PAD_PROP::FIDUCIAL_GLBL ); break;
5892 case T_pad_prop_fiducial_loc: pad->SetProperty( PAD_PROP::FIDUCIAL_LOCAL ); break;
5893 case T_pad_prop_testpoint: pad->SetProperty( PAD_PROP::TESTPOINT ); break;
5894 case T_pad_prop_castellated: pad->SetProperty( PAD_PROP::CASTELLATED ); break;
5895 case T_pad_prop_heatsink: pad->SetProperty( PAD_PROP::HEATSINK ); break;
5896 case T_pad_prop_mechanical: pad->SetProperty( PAD_PROP::MECHANICAL ); break;
5897 case T_pad_prop_pressfit: pad->SetProperty( PAD_PROP::PRESSFIT ); break;
5898 case T_none: pad->SetProperty( PAD_PROP::NONE ); break;
5899 case T_RIGHT: break;
5900
5901 default:
5902#if 0 // Currently: skip unknown property
5903 Expecting( "pad_prop_bga pad_prop_fiducial_glob pad_prop_fiducial_loc"
5904 " pad_prop_heatsink or pad_prop_castellated" );
5905#endif
5906 break;
5907 }
5908 }
5909
5910 break;
5911
5912 case T_options:
5913 parsePAD_option( pad.get() );
5914 break;
5915
5916 case T_padstack:
5917 parsePadstack( pad.get() );
5918 break;
5919
5920 case T_primitives:
5921 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
5922 {
5923 if( token == T_LEFT )
5924 token = NextTok();
5925
5926 switch( token )
5927 {
5928 case T_gr_arc:
5929 case T_gr_line:
5930 case T_gr_circle:
5931 case T_gr_rect:
5932 case T_gr_poly:
5933 case T_gr_curve:
5934 pad->AddPrimitive( PADSTACK::ALL_LAYERS, parsePCB_SHAPE( nullptr ) );
5935 break;
5936
5937 case T_gr_bbox:
5938 {
5939 PCB_SHAPE* numberBox = parsePCB_SHAPE( nullptr );
5940 numberBox->SetIsProxyItem();
5941 pad->AddPrimitive( PADSTACK::ALL_LAYERS, numberBox );
5942 break;
5943 }
5944
5945 case T_gr_vector:
5946 {
5947 PCB_SHAPE* spokeTemplate = parsePCB_SHAPE( nullptr );
5948 spokeTemplate->SetIsProxyItem();
5949 pad->AddPrimitive( PADSTACK::ALL_LAYERS, spokeTemplate );
5950 break;
5951 }
5952
5953 default:
5954 Expecting( "gr_line, gr_arc, gr_circle, gr_curve, gr_rect, gr_bbox or gr_poly" );
5955 break;
5956 }
5957 }
5958
5959 break;
5960
5961 case T_remove_unused_layers:
5962 {
5963 bool remove = parseMaybeAbsentBool( true );
5964 pad->SetRemoveUnconnected( remove );
5965 break;
5966 }
5967
5968 case T_keep_end_layers:
5969 {
5970 bool keep = parseMaybeAbsentBool( true );
5971 pad->SetKeepTopBottom( keep );
5972 break;
5973 }
5974
5975 case T_tenting:
5976 {
5977 auto [front, back] = parseFrontBackOptBool( true );
5978 pad->Padstack().FrontOuterLayers().has_solder_mask = front;
5979 pad->Padstack().BackOuterLayers().has_solder_mask = back;
5980 break;
5981 }
5982
5983 case T_zone_layer_connections:
5984 {
5985 LSET cuLayers = pad->GetLayerSet() & LSET::AllCuMask();
5986
5987 for( PCB_LAYER_ID layer : cuLayers )
5988 pad->SetZoneLayerOverride( layer, ZLO_FORCE_NO_ZONE_CONNECTION );
5989
5990 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
5991 {
5993
5994 if( !IsCopperLayer( layer ) )
5995 Expecting( "copper layer name" );
5996
5997 pad->SetZoneLayerOverride( layer, ZLO_FORCE_FLASHED );
5998 }
5999
6000 break;
6001 }
6002
6003 // Continue to process "(locked)" format which was output during 5.99 development
6004 case T_locked:
6005 // Pad locking is now a session preference
6006 parseMaybeAbsentBool( true );
6007 break;
6008
6009 case T_tstamp:
6010 case T_uuid:
6011 NextTok();
6012 const_cast<KIID&>( pad->m_Uuid ) = CurStrToKIID();
6013 NeedRIGHT();
6014 break;
6015
6016 default:
6017 Expecting( "at, locked, drill, layers, net, die_length, roundrect_rratio, "
6018 "solder_mask_margin, solder_paste_margin, solder_paste_margin_ratio, uuid, "
6019 "clearance, tstamp, primitives, remove_unused_layers, keep_end_layers, "
6020 "pinfunction, pintype, zone_connect, thermal_width, thermal_gap, padstack or "
6021 "teardrops" );
6022 }
6023 }
6024
6025 if( !foundNet )
6026 {
6027 // Make sure default netclass is correctly assigned to pads that don't define a net.
6028 pad->SetNetCode( 0, /* aNoAssert */ true );
6029 }
6030
6031 if( thermalBrAngleOverride )
6032 {
6033 pad->SetThermalSpokeAngle( *thermalBrAngleOverride );
6034 }
6035 else
6036 {
6037 // This is here because custom pad anchor shape isn't known before reading (options
6038 if( pad->GetShape( PADSTACK::ALL_LAYERS ) == PAD_SHAPE::CIRCLE )
6039 {
6040 pad->SetThermalSpokeAngle( ANGLE_45 );
6041 }
6042 else if( pad->GetShape( PADSTACK::ALL_LAYERS ) == PAD_SHAPE::CUSTOM
6043 && pad->GetAnchorPadShape( PADSTACK::ALL_LAYERS ) == PAD_SHAPE::CIRCLE )
6044 {
6045 if( m_requiredVersion <= 20211014 ) // 6.0
6046 pad->SetThermalSpokeAngle( ANGLE_90 );
6047 else
6048 pad->SetThermalSpokeAngle( ANGLE_45 );
6049 }
6050 else
6051 {
6052 pad->SetThermalSpokeAngle( ANGLE_90 );
6053 }
6054 }
6055
6056 if( !pad->CanHaveNumber() )
6057 {
6058 // At some point it was possible to assign a number to aperture pads so we need to clean
6059 // those out here.
6060 pad->SetNumber( wxEmptyString );
6061 }
6062
6063 // Zero-sized pads are likely algorithmically unsafe.
6064 if( pad->GetSizeX() <= 0 || pad->GetSizeY() <= 0 )
6065 {
6066 pad->SetSize( PADSTACK::ALL_LAYERS,
6067 VECTOR2I( pcbIUScale.mmToIU( 0.001 ), pcbIUScale.mmToIU( 0.001 ) ) );
6068
6069 wxLogWarning( _( "Invalid zero-sized pad pinned to %s in\nfile: %s\nline: %d\noffset: %d" ),
6070 wxT( "1µm" ), CurSource(), CurLineNumber(), CurOffset() );
6071 }
6072
6073 return pad.release();
6074}
6075
6076
6078{
6079 // Parse only the (option ...) inside a pad description
6080 for( T token = NextTok(); token != T_RIGHT; token = NextTok() )
6081 {
6082 if( token != T_LEFT )
6083 Expecting( T_LEFT );
6084
6085 token = NextTok();
6086
6087 switch( token )
6088 {
6089 case T_anchor:
6090 token = NextTok();
6091 // Custom shaped pads have a "anchor pad", which is the reference
6092 // for connection calculations.
6093 // Because this is an anchor, only the 2 very basic shapes are managed:
6094 // circle and rect.
6095 switch( token )
6096 {
6097 case T_circle:
6099 break;
6100
6101 case T_rect:
6103 break;
6104
6105 default:
6106 // Currently, because pad options is a moving target
6107 // just skip unknown keywords
6108 break;
6109 }
6110 NeedRIGHT();
6111 break;
6112
6113 case T_clearance:
6114 token = NextTok();
6115 // Custom shaped pads have a clearance area that is the pad shape
6116 // (like usual pads) or the convex hull of the pad shape.
6117 switch( token )
6118 {
6119 case T_outline:
6121 break;
6122
6123 case T_convexhull:
6125 break;
6126
6127 default:
6128 // Currently, because pad options is a moving target
6129 // just skip unknown keywords
6130 break;
6131 }
6132
6133 NeedRIGHT();
6134 break;
6135
6136 default:
6137 // Currently, because pad options is a moving target
6138 // just skip unknown keywords
6139 while( (token = NextTok() ) != T_RIGHT )
6140 {}
6141
6142 break;
6143 }
6144 }
6145
6146 return true;
6147}
6148
6149
6151{
6152 PADSTACK& padstack = aPad->Padstack();
6153
6154 for( T token = NextTok(); token != T_RIGHT; token = NextTok() )
6155 {
6156 if( token != T_LEFT )
6157 Expecting( T_LEFT );
6158
6159 token = NextTok();
6160
6161 switch( token )
6162 {
6163 case T_mode:
6164 token = NextTok();
6165
6166 switch( token )
6167 {
6168 case T_front_inner_back:
6170 break;
6171
6172 case T_custom:
6173 padstack.SetMode( PADSTACK::MODE::CUSTOM );
6174 break;
6175
6176 default:
6177 Expecting( "front_inner_back or custom" );
6178 }
6179
6180 NeedRIGHT();
6181 break;
6182
6183 case T_layer:
6184 {
6185 NextTok();
6186 PCB_LAYER_ID curLayer = UNDEFINED_LAYER;
6187
6188 if( curText == "Inner" )
6189 {
6190 if( padstack.Mode() != PADSTACK::MODE::FRONT_INNER_BACK )
6191 {
6192 THROW_IO_ERROR( wxString::Format( _( "Invalid padstack layer in\nfile: %s\n"
6193 "line: %d\noffset: %d." ),
6194 CurSource(), CurLineNumber(), CurOffset() ) );
6195 }
6196
6197 curLayer = PADSTACK::INNER_LAYERS;
6198 }
6199 else
6200 {
6201 curLayer = lookUpLayer( m_layerIndices );
6202 }
6203
6204 if( !IsCopperLayer( curLayer ) )
6205 {
6206 wxString error;
6207 error.Printf( _( "Invalid padstack layer '%s' in file '%s' at line %d, offset %d." ),
6208 curText, CurSource().GetData(), CurLineNumber(), CurOffset() );
6209 THROW_IO_ERROR( error );
6210 }
6211
6212 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
6213 {
6214 if( token != T_LEFT )
6215 Expecting( T_LEFT );
6216
6217 token = NextTok();
6218
6219 switch( token )
6220 {
6221 case T_shape:
6222 token = NextTok();
6223
6224 switch( token )
6225 {
6226 case T_circle:
6227 aPad->SetShape( curLayer, PAD_SHAPE::CIRCLE );
6228 break;
6229
6230 case T_rect:
6231 aPad->SetShape( curLayer, PAD_SHAPE::RECTANGLE );
6232 break;
6233
6234 case T_oval:
6235 aPad->SetShape( curLayer, PAD_SHAPE::OVAL );
6236 break;
6237
6238 case T_trapezoid:
6239 aPad->SetShape( curLayer, PAD_SHAPE::TRAPEZOID );
6240 break;
6241
6242 case T_roundrect:
6243 // Note: the shape can be PAD_SHAPE::ROUNDRECT or PAD_SHAPE::CHAMFERED_RECT
6244 // (if chamfer parameters are found later in pad descr.)
6245 aPad->SetShape( curLayer, PAD_SHAPE::ROUNDRECT );
6246 break;
6247
6248 case T_custom:
6249 aPad->SetShape( curLayer, PAD_SHAPE::CUSTOM );
6250 break;
6251
6252 default:
6253 Expecting( "circle, rectangle, roundrect, oval, trapezoid or custom" );
6254 }
6255
6256 NeedRIGHT();
6257 break;
6258
6259 case T_size:
6260 {
6261 VECTOR2I sz;
6262 sz.x = parseBoardUnits( "width value" );
6263 sz.y = parseBoardUnits( "height value" );
6264 aPad->SetSize( curLayer, sz );
6265 NeedRIGHT();
6266 break;
6267 }
6268
6269 case T_offset:
6270 {
6271 VECTOR2I pt;
6272 pt.x = parseBoardUnits( "drill offset x" );
6273 pt.y = parseBoardUnits( "drill offset y" );
6274 aPad->SetOffset( curLayer, pt );
6275 NeedRIGHT();
6276 break;
6277 }
6278
6279 case T_rect_delta:
6280 {
6282 delta.x = parseBoardUnits( "rectangle delta width" );
6283 delta.y = parseBoardUnits( "rectangle delta height" );
6284 aPad->SetDelta( curLayer, delta );
6285 NeedRIGHT();
6286 break;
6287 }
6288
6289 case T_roundrect_rratio:
6290 aPad->SetRoundRectRadiusRatio( curLayer,
6291 parseDouble( "roundrect radius ratio" ) );
6292 NeedRIGHT();
6293 break;
6294
6295 case T_chamfer_ratio:
6296 {
6297 double ratio = parseDouble( "chamfer ratio" );
6298 aPad->SetChamferRectRatio( curLayer, ratio );
6299
6300 if( ratio > 0 )
6301 aPad->SetShape( curLayer, PAD_SHAPE::CHAMFERED_RECT );
6302
6303 NeedRIGHT();
6304 break;
6305 }
6306
6307 case T_chamfer:
6308 {
6309 int chamfers = 0;
6310 bool end_list = false;
6311
6312 while( !end_list )
6313 {
6314 token = NextTok();
6315
6316 switch( token )
6317 {
6318 case T_top_left:
6319 chamfers |= RECT_CHAMFER_TOP_LEFT;
6320 break;
6321
6322 case T_top_right:
6323 chamfers |= RECT_CHAMFER_TOP_RIGHT;
6324 break;
6325
6326 case T_bottom_left:
6327 chamfers |= RECT_CHAMFER_BOTTOM_LEFT;
6328 break;
6329
6330 case T_bottom_right:
6331 chamfers |= RECT_CHAMFER_BOTTOM_RIGHT;
6332 break;
6333
6334 case T_RIGHT:
6335 aPad->SetChamferPositions( curLayer, chamfers );
6336 end_list = true;
6337 break;
6338
6339 default:
6340 Expecting( "chamfer_top_left chamfer_top_right chamfer_bottom_left or "
6341 "chamfer_bottom_right" );
6342 }
6343 }
6344
6345 if( end_list && chamfers != RECT_NO_CHAMFER )
6346 aPad->SetShape( curLayer, PAD_SHAPE::CHAMFERED_RECT );
6347
6348 break;
6349 }
6350
6351 case T_thermal_bridge_width:
6352 padstack.ThermalSpokeWidth( curLayer ) =
6353 parseBoardUnits( "thermal relief spoke width" );
6354 NeedRIGHT();
6355 break;
6356
6357 case T_thermal_gap:
6358 padstack.ThermalGap( curLayer ) = parseBoardUnits( "thermal relief gap value" );
6359 NeedRIGHT();
6360 break;
6361
6362 case T_thermal_bridge_angle:
6363 padstack.SetThermalSpokeAngle(
6364 EDA_ANGLE( parseDouble( "thermal spoke angle" ), DEGREES_T ) );
6365 NeedRIGHT();
6366 break;
6367
6368 case T_zone_connect:
6369 padstack.ZoneConnection( curLayer ) = magic_enum::enum_cast<ZONE_CONNECTION>(
6370 parseInt( "zone connection value" ) );
6371 NeedRIGHT();
6372 break;
6373
6374 case T_clearance:
6375 padstack.Clearance( curLayer ) = parseBoardUnits( "local clearance value" );
6376 NeedRIGHT();
6377 break;
6378
6379 case T_tenting:
6380 {
6381 auto [front, back] = parseFrontBackOptBool( true );
6382 padstack.FrontOuterLayers().has_solder_mask = front;
6383 padstack.BackOuterLayers().has_solder_mask = back;
6384 break;
6385 }
6386
6387 // TODO: refactor parsePAD_options to work on padstacks too
6388 case T_options:
6389 {
6390 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
6391 {
6392 if( token != T_LEFT )
6393 Expecting( T_LEFT );
6394
6395 token = NextTok();
6396
6397 switch( token )
6398 {
6399 case T_anchor:
6400 token = NextTok();
6401 // Custom shaped pads have a "anchor pad", which is the reference
6402 // for connection calculations.
6403 // Because this is an anchor, only the 2 very basic shapes are managed:
6404 // circle and rect.
6405 switch( token )
6406 {
6407 case T_circle:
6408 padstack.SetAnchorShape( PAD_SHAPE::CIRCLE, curLayer );
6409 break;
6410
6411 case T_rect:
6412 padstack.SetAnchorShape( PAD_SHAPE::RECTANGLE, curLayer );
6413 break;
6414
6415 default:
6416 // Currently, because pad options is a moving target
6417 // just skip unknown keywords
6418 break;
6419 }
6420 NeedRIGHT();
6421 break;
6422
6423 case T_clearance:
6424 token = NextTok();
6425 // TODO: m_customShapeInZoneMode is not per-layer at the moment
6426 NeedRIGHT();
6427 break;
6428
6429 default:
6430 // Currently, because pad options is a moving target
6431 // just skip unknown keywords
6432 while( ( token = NextTok() ) != T_RIGHT )
6433 {
6434 }
6435
6436 break;
6437 }
6438 }
6439
6440 break;
6441 }
6442
6443 // TODO: deduplicate with non-padstack parser
6444 case T_primitives:
6445 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
6446 {
6447 if( token == T_LEFT )
6448 token = NextTok();
6449
6450 switch( token )
6451 {
6452 case T_gr_arc:
6453 case T_gr_line:
6454 case T_gr_circle:
6455 case T_gr_rect:
6456 case T_gr_poly:
6457 case T_gr_curve:
6458 padstack.AddPrimitive( parsePCB_SHAPE( nullptr ), curLayer );
6459 break;
6460
6461 case T_gr_bbox:
6462 {
6463 PCB_SHAPE* numberBox = parsePCB_SHAPE( nullptr );
6464 numberBox->SetIsProxyItem();
6465 padstack.AddPrimitive( numberBox, curLayer );
6466 break;
6467 }
6468
6469 case T_gr_vector:
6470 {
6471 PCB_SHAPE* spokeTemplate = parsePCB_SHAPE( nullptr );
6472 spokeTemplate->SetIsProxyItem();
6473 padstack.AddPrimitive( spokeTemplate, curLayer );
6474 break;
6475 }
6476
6477 default:
6478 Expecting( "gr_line, gr_arc, gr_circle, gr_curve, gr_rect, gr_bbox or gr_poly" );
6479 break;
6480 }
6481 }
6482
6483 break;
6484
6485 default:
6486 // Not strict-parsing padstack layers yet
6487 continue;
6488 }
6489 }
6490
6491 break;
6492 }
6493
6494 default:
6495 Expecting( "mode or layer" );
6496 break;
6497 }
6498 }
6499}
6500
6501
6503{
6504 T token;
6505
6506 while( ( token = NextTok() ) != T_RIGHT )
6507 {
6508 // This token is the Uuid of the item in the group.
6509 // Since groups are serialized at the end of the file/footprint, the Uuid should already
6510 // have been seen and exist in the board.
6511 KIID uuid( CurStr() );
6512 aGroupInfo.memberUuids.push_back( uuid );
6513 }
6514}
6515
6516
6518{
6519 wxCHECK_RET( CurTok() == T_group,
6520 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as PCB_GROUP." ) );
6521
6522 T token;
6523
6524 m_groupInfos.push_back( GROUP_INFO() );
6525 GROUP_INFO& groupInfo = m_groupInfos.back();
6526 groupInfo.parent = aParent;
6527
6528 while( ( token = NextTok() ) != T_LEFT )
6529 {
6530 if( token == T_STRING )
6531 groupInfo.name = FromUTF8();
6532 else if( token == T_locked )
6533 groupInfo.locked = true;
6534 else
6535 Expecting( "group name or locked" );
6536 }
6537
6538 for( ; token != T_RIGHT; token = NextTok() )
6539 {
6540 if( token != T_LEFT )
6541 Expecting( T_LEFT );
6542
6543 token = NextTok();
6544
6545 switch( token )
6546 {
6547 // From formats [20200811, 20231215), 'id' was used instead of 'uuid'
6548 case T_id:
6549 case T_uuid:
6550 NextTok();
6551 groupInfo.uuid = CurStrToKIID();
6552 NeedRIGHT();
6553 break;
6554
6555 case T_lib_id:
6556 {
6557 token = NextTok();
6558
6559 if( !IsSymbol( token ) && token != T_NUMBER )
6560 Expecting( "symbol|number" );
6561
6562 wxString name = FromUTF8();
6563 // Some symbol LIB_IDs have the '/' character escaped which can break
6564 // symbol links. The '/' character is no longer an illegal LIB_ID character so
6565 // it doesn't need to be escaped.
6566 name.Replace( "{slash}", "/" );
6567
6568 int bad_pos = groupInfo.libId.Parse( name );
6569
6570 if( bad_pos >= 0 )
6571 {
6572 if( static_cast<int>( name.size() ) > bad_pos )
6573 {
6574 wxString msg = wxString::Format( _( "Group library link %s contains invalid character '%c'" ),
6575 name,
6576 name[bad_pos] );
6577
6578 THROW_PARSE_ERROR( msg, CurSource(), CurLine(), CurLineNumber(), CurOffset() );
6579 }
6580
6581 THROW_PARSE_ERROR( _( "Invalid library ID" ), CurSource(), CurLine(), CurLineNumber(), CurOffset() );
6582 }
6583
6584 NeedRIGHT();
6585 break;
6586 }
6587
6588 case T_locked:
6589 groupInfo.locked = parseBool();
6590 NeedRIGHT();
6591 break;
6592
6593 case T_members:
6594 {
6595 parseGROUP_members( groupInfo );
6596 break;
6597 }
6598
6599 default:
6600 Expecting( "uuid, locked, lib_id, or members" );
6601 }
6602 }
6603}
6604
6605
6607{
6608 wxCHECK_RET( CurTok() == T_generated,
6609 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as PCB_GENERATOR." ) );
6610
6611 T token;
6612
6613 m_generatorInfos.push_back( GENERATOR_INFO() );
6614 GENERATOR_INFO& genInfo = m_generatorInfos.back();
6615
6616 genInfo.layer = F_Cu;
6617 genInfo.parent = aParent;
6618 genInfo.properties = STRING_ANY_MAP( pcbIUScale.IU_PER_MM );
6619
6620 NeedLEFT();
6621 token = NextTok();
6622
6623 // For formats [20231007, 20231215), 'id' was used instead of 'uuid'
6624 if( token != T_uuid && token != T_id )
6625 Expecting( T_uuid );
6626
6627 NextTok();
6628 genInfo.uuid = CurStrToKIID();
6629 NeedRIGHT();
6630
6631 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
6632 {
6633 if( token != T_LEFT )
6634 Expecting( T_LEFT );
6635
6636 token = NextTok();
6637
6638 switch( token )
6639 {
6640 case T_type:
6641 NeedSYMBOL();
6642 genInfo.genType = FromUTF8();
6643 NeedRIGHT();
6644 break;
6645
6646 case T_name:
6647 NeedSYMBOL();
6648 genInfo.name = FromUTF8();
6649 NeedRIGHT();
6650 break;
6651
6652 case T_locked:
6653 token = NextTok();
6654 genInfo.locked = token == T_yes;
6655 NeedRIGHT();
6656 break;
6657
6658 case T_layer:
6659 genInfo.layer = parseBoardItemLayer();
6660 NeedRIGHT();
6661 break;
6662
6663 case T_members:
6664 parseGROUP_members( genInfo );
6665 break;
6666
6667 default:
6668 {
6669 wxString pName = FromUTF8();
6670 T tok1 = NextTok();
6671
6672 switch( tok1 )
6673 {
6674 case T_yes:
6675 {
6676 genInfo.properties.emplace( pName, wxAny( true ) );
6677 NeedRIGHT();
6678 break;
6679 }
6680 case T_no:
6681 {
6682 genInfo.properties.emplace( pName, wxAny( false ) );
6683 NeedRIGHT();
6684 break;
6685 }
6686 case T_NUMBER:
6687 {
6688 double pValue = parseDouble();
6689 genInfo.properties.emplace( pName, wxAny( pValue ) );
6690 NeedRIGHT();
6691 break;
6692 }
6693 case T_STRING: // Quoted string
6694 {
6695 wxString pValue = FromUTF8();
6696 genInfo.properties.emplace( pName, pValue );
6697 NeedRIGHT();
6698 break;
6699 }
6700 case T_LEFT:
6701 {
6702 NeedSYMBOL();
6703 T tok2 = CurTok();
6704
6705 switch( tok2 )
6706 {
6707 case T_xy:
6708 {
6709 VECTOR2I pt;
6710
6711 pt.x = parseBoardUnits( "X coordinate" );
6712 pt.y = parseBoardUnits( "Y coordinate" );
6713
6714 genInfo.properties.emplace( pName, wxAny( pt ) );
6715 NeedRIGHT();
6716 NeedRIGHT();
6717
6718 break;
6719 }
6720 case T_pts:
6721 {
6723
6724 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
6726
6727 NeedRIGHT();
6728
6729 genInfo.properties.emplace( pName, wxAny( chain ) );
6730 break;
6731 }
6732 default: Expecting( "xy or pts" );
6733 }
6734
6735 break;
6736 }
6737 default: Expecting( "a number, symbol, string or (" );
6738 }
6739
6740 break;
6741 }
6742 }
6743 }
6744
6745 // Previous versions had bugs which could save ghost tuning patterns. Ignore them.
6746 if( genInfo.genType == wxT( "tuning_pattern" ) && genInfo.memberUuids.empty() )
6747 m_generatorInfos.pop_back();
6748}
6749
6750
6752{
6753 wxCHECK_MSG( CurTok() == T_arc, nullptr,
6754 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as ARC." ) );
6755
6756 VECTOR2I pt;
6757 T token;
6758
6759 std::unique_ptr<PCB_ARC> arc = std::make_unique<PCB_ARC>( m_board );
6760
6761 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
6762 {
6763 // Legacy locked
6764 if( token == T_locked )
6765 {
6766 arc->SetLocked( true );
6767 token = NextTok();
6768 }
6769
6770 if( token != T_LEFT )
6771 Expecting( T_LEFT );
6772
6773 token = NextTok();
6774
6775 switch( token )
6776 {
6777 case T_start:
6778 pt.x = parseBoardUnits( "start x" );
6779 pt.y = parseBoardUnits( "start y" );
6780 arc->SetStart( pt );
6781 NeedRIGHT();
6782 break;
6783
6784 case T_mid:
6785 pt.x = parseBoardUnits( "mid x" );
6786 pt.y = parseBoardUnits( "mid y" );
6787 arc->SetMid( pt );
6788 NeedRIGHT();
6789 break;
6790
6791 case T_end:
6792 pt.x = parseBoardUnits( "end x" );
6793 pt.y = parseBoardUnits( "end y" );
6794 arc->SetEnd( pt );
6795 NeedRIGHT();
6796 break;
6797
6798 case T_width:
6799 arc->SetWidth( parseBoardUnits( "width" ) );
6800 NeedRIGHT();
6801 break;
6802
6803 case T_layer:
6804 arc->SetLayer( parseBoardItemLayer() );
6805 NeedRIGHT();
6806 break;
6807
6808 case T_layers:
6809 arc->SetLayerSet( parseLayersForCuItemWithSoldermask() );
6810 break;
6811
6812 case T_solder_mask_margin:
6813 arc->SetLocalSolderMaskMargin( parseBoardUnits( "local solder mask margin value" ) );
6814 NeedRIGHT();
6815 break;
6816
6817 case T_net:
6818 parseNet( arc.get() );
6819 break;
6820
6821 case T_tstamp:
6822 case T_uuid:
6823 NextTok();
6824 const_cast<KIID&>( arc->m_Uuid ) = CurStrToKIID();
6825 NeedRIGHT();
6826 break;
6827
6828 // We continue to parse the status field but it is no longer written
6829 case T_status:
6830 parseHex();
6831 NeedRIGHT();
6832 break;
6833
6834 case T_locked:
6835 arc->SetLocked( parseMaybeAbsentBool( true ) );
6836 break;
6837
6838 default:
6839 Expecting( "start, mid, end, width, layer, solder_mask_margin, net, tstamp, uuid, "
6840 "or status" );
6841 }
6842 }
6843
6844 if( !IsCopperLayer( arc->GetLayer() ) )
6845 {
6846 // No point in asserting; these usually come from hand-edited boards
6847 return nullptr;
6848 }
6849
6850 return arc.release();
6851}
6852
6853
6855{
6856 wxCHECK_MSG( CurTok() == T_segment, nullptr,
6857 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as PCB_TRACK." ) );
6858
6859 VECTOR2I pt;
6860 T token;
6861
6862 std::unique_ptr<PCB_TRACK> track = std::make_unique<PCB_TRACK>( m_board );
6863
6864 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
6865 {
6866 // Legacy locked flag
6867 if( token == T_locked )
6868 {
6869 track->SetLocked( true );
6870 token = NextTok();
6871 }
6872
6873 if( token != T_LEFT )
6874 Expecting( T_LEFT );
6875
6876 token = NextTok();
6877
6878 switch( token )
6879 {
6880 case T_start:
6881 pt.x = parseBoardUnits( "start x" );
6882 pt.y = parseBoardUnits( "start y" );
6883 track->SetStart( pt );
6884 NeedRIGHT();
6885 break;
6886
6887 case T_end:
6888 pt.x = parseBoardUnits( "end x" );
6889 pt.y = parseBoardUnits( "end y" );
6890 track->SetEnd( pt );
6891 NeedRIGHT();
6892 break;
6893
6894 case T_width:
6895 track->SetWidth( parseBoardUnits( "width" ) );
6896 NeedRIGHT();
6897 break;
6898
6899 case T_layer:
6900 track->SetLayer( parseBoardItemLayer() );
6901 NeedRIGHT();
6902 break;
6903
6904 case T_layers:
6905 track->SetLayerSet( parseLayersForCuItemWithSoldermask() );
6906 break;
6907
6908 case T_solder_mask_margin:
6909 track->SetLocalSolderMaskMargin( parseBoardUnits( "local solder mask margin value" ) );
6910 NeedRIGHT();
6911 break;
6912
6913 case T_net:
6914 parseNet( track.get() );
6915 break;
6916
6917 case T_tstamp:
6918 case T_uuid:
6919 NextTok();
6920 const_cast<KIID&>( track->m_Uuid ) = CurStrToKIID();
6921 NeedRIGHT();
6922 break;
6923
6924 // We continue to parse the status field but it is no longer written
6925 case T_status:
6926 parseHex();
6927 NeedRIGHT();
6928 break;
6929
6930 case T_locked:
6931 track->SetLocked( parseMaybeAbsentBool( true ) );
6932 break;
6933
6934 default:
6935 Expecting( "start, end, width, layer, solder_mask_margin, net, tstamp, uuid, "
6936 "or locked" );
6937 }
6938 }
6939
6940 if( !IsCopperLayer( track->GetLayer() ) )
6941 {
6942 // No point in asserting; these usually come from hand-edited boards
6943 return nullptr;
6944 }
6945
6946 return track.release();
6947}
6948
6949
6951{
6952 wxCHECK_MSG( CurTok() == T_via, nullptr,
6953 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as PCB_VIA." ) );
6954
6955 VECTOR2I pt;
6956 T token;
6957
6958 std::unique_ptr<PCB_VIA> via = std::make_unique<PCB_VIA>( m_board );
6959
6960 // File format default is no-token == no-feature.
6961 via->Padstack().SetUnconnectedLayerMode( PADSTACK::UNCONNECTED_LAYER_MODE::KEEP_ALL );
6962
6963 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
6964 {
6965 // Legacy locked
6966 if( token == T_locked )
6967 {
6968 via->SetLocked( true );
6969 token = NextTok();
6970 }
6971
6972 if( token == T_LEFT )
6973 token = NextTok();
6974
6975 switch( token )
6976 {
6977 case T_blind:
6978 via->SetViaType( VIATYPE::BLIND );
6979 break;
6980
6981 case T_buried:
6982 via->SetViaType( VIATYPE::BURIED );
6983 break;
6984
6985 case T_micro:
6986 via->SetViaType( VIATYPE::MICROVIA );
6987 break;
6988
6989 case T_at:
6990 pt.x = parseBoardUnits( "start x" );
6991 pt.y = parseBoardUnits( "start y" );
6992 via->SetStart( pt );
6993 via->SetEnd( pt );
6994 NeedRIGHT();
6995 break;
6996
6997 case T_size:
6998 via->SetWidth( PADSTACK::ALL_LAYERS, parseBoardUnits( "via width" ) );
6999 NeedRIGHT();
7000 break;
7001
7002 case T_drill:
7003 via->SetDrill( parseBoardUnits( "drill diameter" ) );
7004 NeedRIGHT();
7005 break;
7006
7007 case T_layers:
7008 {
7009 PCB_LAYER_ID layer1, layer2;
7010 NextTok();
7011 layer1 = lookUpLayer( m_layerIndices );
7012 NextTok();
7013 layer2 = lookUpLayer( m_layerIndices );
7014 via->SetLayerPair( layer1, layer2 );
7015
7016 if( layer1 == UNDEFINED_LAYER || layer2 == UNDEFINED_LAYER )
7017 Expecting( "layer name" );
7018
7019 NeedRIGHT();
7020 break;
7021 }
7022
7023 case T_net:
7024 parseNet( via.get() );
7025 break;
7026
7027 case T_remove_unused_layers:
7028 {
7029 bool remove = parseMaybeAbsentBool( true );
7030 via->SetRemoveUnconnected( remove );
7031 break;
7032 }
7033
7034 case T_keep_end_layers:
7035 {
7036 bool keep = parseMaybeAbsentBool( true );
7037 via->SetKeepStartEnd( keep );
7038 break;
7039 }
7040
7041 case T_start_end_only:
7042 {
7043 if( parseMaybeAbsentBool( true ) )
7044 via->Padstack().SetUnconnectedLayerMode( PADSTACK::UNCONNECTED_LAYER_MODE::START_END_ONLY );
7045
7046 break;
7047 }
7048
7049 case T_zone_layer_connections:
7050 {
7051 // Ensure only copper layers are stored int ZoneLayerOverride array
7052 LSET cuLayers = via->GetLayerSet() & LSET::AllCuMask();
7053
7054 for( PCB_LAYER_ID layer : cuLayers )
7055 {
7056 via->SetZoneLayerOverride( layer, ZLO_FORCE_NO_ZONE_CONNECTION );
7057 }
7058
7059 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
7060 {
7062
7063 if( !IsCopperLayer( layer ) )
7064 Expecting( "copper layer name" );
7065
7066 via->SetZoneLayerOverride( layer, ZLO_FORCE_FLASHED );
7067 }
7068 }
7069 break;
7070
7071 case T_padstack:
7072 parseViastack( via.get() );
7073 break;
7074
7075 case T_teardrops:
7076 parseTEARDROP_PARAMETERS( &via->GetTeardropParams() );
7077 break;
7078
7079 case T_tenting:
7080 {
7081 auto [front, back] = parseFrontBackOptBool( true );
7082 via->Padstack().FrontOuterLayers().has_solder_mask = front;
7083 via->Padstack().BackOuterLayers().has_solder_mask = back;
7084 break;
7085 }
7086 case T_covering:
7087 {
7088 auto [front, back] = parseFrontBackOptBool();
7089 via->Padstack().FrontOuterLayers().has_covering = front;
7090 via->Padstack().BackOuterLayers().has_covering = back;
7091 break;
7092 }
7093 case T_plugging:
7094 {
7095 auto [front, back] = parseFrontBackOptBool();
7096 via->Padstack().FrontOuterLayers().has_plugging = front;
7097 via->Padstack().BackOuterLayers().has_plugging = back;
7098 break;
7099 }
7100 case T_filling:
7101 {
7102 via->Padstack().Drill().is_filled = parseOptBool();
7103 NeedRIGHT();
7104 break;
7105 }
7106 case T_capping:
7107 {
7108 via->Padstack().Drill().is_capped = parseOptBool();
7109 NeedRIGHT();
7110 break;
7111 }
7112
7113 case T_tstamp:
7114 case T_uuid:
7115 NextTok();
7116 const_cast<KIID&>( via->m_Uuid ) = CurStrToKIID();
7117 NeedRIGHT();
7118 break;
7119
7120 // We continue to parse the status field but it is no longer written
7121 case T_status:
7122 parseHex();
7123 NeedRIGHT();
7124 break;
7125
7126 case T_locked:
7127 via->SetLocked( parseMaybeAbsentBool( true ) );
7128 break;
7129
7130 case T_free:
7131 via->SetIsFree( parseMaybeAbsentBool( true ) );
7132 break;
7133
7134 default:
7135 Expecting( "blind, micro, at, size, drill, layers, net, free, tstamp, uuid, status or "
7136 "teardrops" );
7137 }
7138 }
7139
7140 return via.release();
7141}
7142
7143
7144std::pair<std::optional<bool>, std::optional<bool>>
7146{
7147 T token = NextTok();
7148
7149 std::optional<bool> front( std::nullopt );
7150 std::optional<bool> back( std::nullopt );
7151
7152 if( token != T_LEFT && aLegacy )
7153 {
7154 // legacy format for tenting.
7155 if( token == T_front || token == T_back || token == T_none )
7156 {
7157 while( token != T_RIGHT )
7158 {
7159 if( token == T_front )
7160 {
7161 front = true;
7162 }
7163 else if( token == T_back )
7164 {
7165 back = true;
7166 }
7167 else if( token == T_none )
7168 {
7169 front.reset();
7170 back.reset();
7171 }
7172 else
7173 {
7174 Expecting( "front, back or none" );
7175 }
7176
7177 token = NextTok();
7178 }
7179
7180 return { front, back };
7181 }
7182 }
7183
7184 while( token != T_RIGHT )
7185 {
7186 if( token != T_LEFT )
7187 Expecting( "(" );
7188
7189 token = NextTok();
7190
7191 if( token == T_front )
7192 front = parseOptBool();
7193 else if( token == T_back )
7194 back = parseOptBool();
7195 else
7196 Expecting( "front or back" );
7197
7198 NeedRIGHT();
7199
7200 token = NextTok();
7201 }
7202
7203 return { front, back };
7204}
7205
7206
7208{
7209 PADSTACK& padstack = aVia->Padstack();
7210
7211 for( T token = NextTok(); token != T_RIGHT; token = NextTok() )
7212 {
7213 if( token != T_LEFT )
7214 Expecting( T_LEFT );
7215
7216 token = NextTok();
7217
7218 switch( token )
7219 {
7220 case T_mode:
7221 token = NextTok();
7222
7223 switch( token )
7224 {
7225 case T_front_inner_back:
7227 break;
7228
7229 case T_custom:
7230 padstack.SetMode( PADSTACK::MODE::CUSTOM );
7231 break;
7232
7233 default:
7234 Expecting( "front_inner_back or custom" );
7235 }
7236
7237 NeedRIGHT();
7238 break;
7239
7240 case T_layer:
7241 {
7242 NextTok();
7243 PCB_LAYER_ID curLayer = UNDEFINED_LAYER;
7244
7245 if( curText == "Inner" )
7246 {
7247 if( padstack.Mode() != PADSTACK::MODE::FRONT_INNER_BACK )
7248 {
7249 THROW_IO_ERROR( wxString::Format( _( "Invalid padstack layer in\nfile: %s\n"
7250 "line: %d\noffset: %d." ),
7251 CurSource(), CurLineNumber(), CurOffset() ) );
7252 }
7253
7254 curLayer = PADSTACK::INNER_LAYERS;
7255 }
7256 else
7257 {
7258 curLayer = lookUpLayer( m_layerIndices );
7259 }
7260
7261 if( !IsCopperLayer( curLayer ) )
7262 {
7263 wxString error;
7264 error.Printf( _( "Invalid padstack layer '%s' in file '%s' at line %d, offset %d." ),
7265 curText, CurSource().GetData(), CurLineNumber(), CurOffset() );
7266 THROW_IO_ERROR( error );
7267 }
7268
7269 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
7270 {
7271 if( token != T_LEFT )
7272 Expecting( T_LEFT );
7273
7274 token = NextTok();
7275
7276 switch( token )
7277 {
7278
7279 case T_size:
7280 {
7281 int diameter = parseBoardUnits( "via width" );
7282 padstack.SetSize( { diameter, diameter }, curLayer );
7283 NeedRIGHT();
7284 break;
7285 }
7286
7287 default:
7288 // Currently only supporting custom via diameter per layer, not other properties
7289 Expecting( "size" );
7290 }
7291 }
7292
7293 break;
7294 }
7295
7296 default:
7297 Expecting( "mode or layer" );
7298 break;
7299 }
7300 }
7301}
7302
7303
7305{
7306 wxCHECK_MSG( CurTok() == T_zone, nullptr,
7307 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as ZONE." ) );
7308
7310
7311 int hatchPitch = ZONE::GetDefaultHatchPitch();
7312 T token;
7313 int tmp;
7314 wxString legacyNetnameFromFile; // the (non-authoratative) zone net name found in a legacy file
7315
7316 // bigger scope since each filled_polygon is concatenated in here
7317 std::map<PCB_LAYER_ID, SHAPE_POLY_SET> pts;
7318 std::map<PCB_LAYER_ID, std::vector<SEG>> legacySegs;
7319 PCB_LAYER_ID filledLayer;
7320 bool addedFilledPolygons = false;
7321
7322 // This hasn't been supported since V6 or so, but we only stopped writing out the token
7323 // in V10.
7324 bool isStrokedFill = m_requiredVersion < 20250210;
7325
7326 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( aParent );
7327
7328 zone->SetAssignedPriority( 0 );
7329
7330 // This is the default for board files:
7331 zone->SetIslandRemovalMode( ISLAND_REMOVAL_MODE::ALWAYS );
7332
7333 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
7334 {
7335 // legacy locked
7336 if( token == T_locked )
7337 {
7338 zone->SetLocked( true );
7339 token = NextTok();
7340 }
7341
7342 if( token == T_LEFT )
7343 token = NextTok();
7344
7345 switch( token )
7346 {
7347 case T_net:
7348 parseNet( zone.get() );
7349 break;
7350
7351 case T_net_name:
7352 NeedSYMBOLorNUMBER();
7353 legacyNetnameFromFile = FromUTF8();
7354 NeedRIGHT();
7355 break;
7356
7357 case T_layer: // keyword for zones that are on only one layer
7358 zone->SetLayer( parseBoardItemLayer() );
7359 NeedRIGHT();
7360 break;
7361
7362 case T_layers: // keyword for zones that can live on a set of layers
7363 zone->SetLayerSet( parseBoardItemLayersAsMask() );
7364 break;
7365
7366 case T_property:
7367 parseZoneLayerProperty( zone->LayerProperties() );
7368 break;
7369
7370 case T_tstamp:
7371 case T_uuid:
7372 NextTok();
7373 const_cast<KIID&>( zone->m_Uuid ) = CurStrToKIID();
7374 NeedRIGHT();
7375 break;
7376
7377 case T_hatch:
7378 token = NextTok();
7379
7380 if( token != T_none && token != T_edge && token != T_full )
7381 Expecting( "none, edge, or full" );
7382
7383 switch( token )
7384 {
7385 default:
7386 case T_none: hatchStyle = ZONE_BORDER_DISPLAY_STYLE::NO_HATCH; break;
7387 case T_edge: hatchStyle = ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE; break;
7388 case T_full: hatchStyle = ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_FULL; break;
7389 }
7390
7391 hatchPitch = parseBoardUnits( "hatch pitch" );
7392 NeedRIGHT();
7393 break;
7394
7395 case T_priority:
7396 zone->SetAssignedPriority( parseInt( "zone priority" ) );
7397 NeedRIGHT();
7398 break;
7399
7400 case T_connect_pads:
7401 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
7402 {
7403 if( token == T_LEFT )
7404 token = NextTok();
7405
7406 switch( token )
7407 {
7408 case T_yes:
7409 zone->SetPadConnection( ZONE_CONNECTION::FULL );
7410 break;
7411
7412 case T_no:
7413 zone->SetPadConnection( ZONE_CONNECTION::NONE );
7414 break;
7415
7416 case T_thru_hole_only:
7417 zone->SetPadConnection( ZONE_CONNECTION::THT_THERMAL );
7418 break;
7419
7420 case T_clearance:
7421 zone->SetLocalClearance( parseBoardUnits( "zone clearance" ) );
7422 NeedRIGHT();
7423 break;
7424
7425 default:
7426 Expecting( "yes, no, or clearance" );
7427 }
7428 }
7429
7430 break;
7431
7432 case T_min_thickness:
7433 zone->SetMinThickness( parseBoardUnits( T_min_thickness ) );
7434 NeedRIGHT();
7435 break;
7436
7437 case T_filled_areas_thickness:
7438 // A new zone fill strategy was added in v6, so we need to know if we're parsing
7439 // a zone that was filled before that. Note that the change was implemented as
7440 // a new parameter, so we need to check for the presence of filled_areas_thickness
7441 // instead of just its value.
7442
7443 if( !parseBool() )
7444 isStrokedFill = false;
7445
7446 NeedRIGHT();
7447 break;
7448
7449 case T_fill:
7450 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
7451 {
7452 if( token == T_LEFT )
7453 token = NextTok();
7454
7455 switch( token )
7456 {
7457 case T_yes:
7458 zone->SetIsFilled( true );
7459 break;
7460
7461 case T_mode:
7462 token = NextTok();
7463
7464 if( token != T_segment && token != T_hatch && token != T_polygon )
7465 Expecting( "segment, hatch or polygon" );
7466
7467 switch( token )
7468 {
7469 case T_hatch:
7470 zone->SetFillMode( ZONE_FILL_MODE::HATCH_PATTERN );
7471 break;
7472
7473 case T_segment: // deprecated, convert to polygons
7474 case T_polygon:
7475 default:
7476 zone->SetFillMode( ZONE_FILL_MODE::POLYGONS );
7477 break;
7478 }
7479
7480 NeedRIGHT();
7481 break;
7482
7483 case T_hatch_thickness:
7484 zone->SetHatchThickness( parseBoardUnits( T_hatch_thickness ) );
7485 NeedRIGHT();
7486 break;
7487
7488 case T_hatch_gap:
7489 zone->SetHatchGap( parseBoardUnits( T_hatch_gap ) );
7490 NeedRIGHT();
7491 break;
7492
7493 case T_hatch_orientation:
7494 {
7495 EDA_ANGLE orientation( parseDouble( T_hatch_orientation ), DEGREES_T );
7496 zone->SetHatchOrientation( orientation );
7497 NeedRIGHT();
7498 break;
7499 }
7500
7501 case T_hatch_smoothing_level:
7502 zone->SetHatchSmoothingLevel( parseDouble( T_hatch_smoothing_level ) );
7503 NeedRIGHT();
7504 break;
7505
7506 case T_hatch_smoothing_value:
7507 zone->SetHatchSmoothingValue( parseDouble( T_hatch_smoothing_value ) );
7508 NeedRIGHT();
7509 break;
7510
7511 case T_hatch_border_algorithm:
7512 token = NextTok();
7513
7514 if( token != T_hatch_thickness && token != T_min_thickness )
7515 Expecting( "hatch_thickness or min_thickness" );
7516
7517 zone->SetHatchBorderAlgorithm( token == T_hatch_thickness ? 1 : 0 );
7518 NeedRIGHT();
7519 break;
7520
7521 case T_hatch_min_hole_area:
7522 zone->SetHatchHoleMinArea( parseDouble( T_hatch_min_hole_area ) );
7523 NeedRIGHT();
7524 break;
7525
7526 case T_arc_segments:
7527 ignore_unused( parseInt( "arc segment count" ) );
7528 NeedRIGHT();
7529 break;
7530
7531 case T_thermal_gap:
7532 zone->SetThermalReliefGap( parseBoardUnits( T_thermal_gap ) );
7533 NeedRIGHT();
7534 break;
7535
7536 case T_thermal_bridge_width:
7537 zone->SetThermalReliefSpokeWidth( parseBoardUnits( T_thermal_bridge_width ) );
7538 NeedRIGHT();
7539 break;
7540
7541 case T_smoothing:
7542 switch( NextTok() )
7543 {
7544 case T_none:
7545 zone->SetCornerSmoothingType( ZONE_SETTINGS::SMOOTHING_NONE );
7546 break;
7547
7548 case T_chamfer:
7549 if( !zone->GetIsRuleArea() ) // smoothing has meaning only for filled zones
7550 zone->SetCornerSmoothingType( ZONE_SETTINGS::SMOOTHING_CHAMFER );
7551
7552 break;
7553
7554 case T_fillet:
7555 if( !zone->GetIsRuleArea() ) // smoothing has meaning only for filled zones
7556 zone->SetCornerSmoothingType( ZONE_SETTINGS::SMOOTHING_FILLET );
7557
7558 break;
7559
7560 default:
7561 Expecting( "none, chamfer, or fillet" );
7562 }
7563
7564 NeedRIGHT();
7565 break;
7566
7567 case T_radius:
7568 tmp = parseBoardUnits( "corner radius" );
7569
7570 if( !zone->GetIsRuleArea() ) // smoothing has meaning only for filled zones
7571 zone->SetCornerRadius( tmp );
7572
7573 NeedRIGHT();
7574 break;
7575
7576 case T_island_removal_mode:
7577 tmp = parseInt( "island_removal_mode" );
7578
7579 if( tmp >= 0 && tmp <= 2 )
7580 zone->SetIslandRemovalMode( static_cast<ISLAND_REMOVAL_MODE>( tmp ) );
7581
7582 NeedRIGHT();
7583 break;
7584
7585 case T_island_area_min:
7586 {
7587 int area = parseBoardUnits( T_island_area_min );
7588 zone->SetMinIslandArea( area * pcbIUScale.IU_PER_MM );
7589 NeedRIGHT();
7590 break;
7591 }
7592
7593 default:
7594 Expecting( "mode, arc_segments, thermal_gap, thermal_bridge_width, "
7595 "hatch_thickness, hatch_gap, hatch_orientation, "
7596 "hatch_smoothing_level, hatch_smoothing_value, "
7597 "hatch_border_algorithm, hatch_min_hole_area, smoothing, radius, "
7598 "island_removal_mode, or island_area_min" );
7599 }
7600 }
7601
7602 break;
7603
7604 case T_placement:
7605 zone->SetIsRuleArea( true );
7606
7607 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
7608 {
7609 if( token == T_LEFT )
7610 token = NextTok();
7611
7612 switch( token )
7613 {
7614 case T_sheetname:
7615 {
7616 zone->SetPlacementAreaSourceType( PLACEMENT_SOURCE_T::SHEETNAME );
7617 NeedSYMBOL();
7618 zone->SetPlacementAreaSource( FromUTF8() );
7619 break;
7620 }
7621 case T_component_class:
7622 {
7623 zone->SetPlacementAreaSourceType( PLACEMENT_SOURCE_T::COMPONENT_CLASS );
7624 NeedSYMBOL();
7625 zone->SetPlacementAreaSource( FromUTF8() );
7626 break;
7627 }
7628 case T_group:
7629 {
7630 zone->SetPlacementAreaSourceType( PLACEMENT_SOURCE_T::GROUP_PLACEMENT );
7631 NeedSYMBOL();
7632 zone->SetPlacementAreaSource( FromUTF8() );
7633 break;
7634 }
7635 case T_enabled:
7636 {
7637 token = NextTok();
7638
7639 if( token == T_yes )
7640 zone->SetPlacementAreaEnabled( true );
7641 else if( token == T_no )
7642 zone->SetPlacementAreaEnabled( false );
7643 else
7644 Expecting( "yes or no" );
7645
7646 break;
7647 }
7648 default:
7649 {
7650 Expecting( "enabled, sheetname, component_class, or group" );
7651 break;
7652 }
7653 }
7654
7655 NeedRIGHT();
7656 }
7657
7658 break;
7659
7660 case T_keepout:
7661 // "keepout" now means rule area, but the file token stays the same
7662 zone->SetIsRuleArea( true );
7663
7664 // Initialize these two because their tokens won't appear in older files:
7665 zone->SetDoNotAllowPads( false );
7666 zone->SetDoNotAllowFootprints( false );
7667
7668 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
7669 {
7670 if( token == T_LEFT )
7671 token = NextTok();
7672
7673 switch( token )
7674 {
7675 case T_tracks:
7676 token = NextTok();
7677
7678 if( token != T_allowed && token != T_not_allowed )
7679 Expecting( "allowed or not_allowed" );
7680
7681 zone->SetDoNotAllowTracks( token == T_not_allowed );
7682 break;
7683
7684 case T_vias:
7685 token = NextTok();
7686
7687 if( token != T_allowed && token != T_not_allowed )
7688 Expecting( "allowed or not_allowed" );
7689
7690 zone->SetDoNotAllowVias( token == T_not_allowed );
7691 break;
7692
7693 case T_copperpour:
7694 token = NextTok();
7695
7696 if( token != T_allowed && token != T_not_allowed )
7697 Expecting( "allowed or not_allowed" );
7698
7699 zone->SetDoNotAllowZoneFills( token == T_not_allowed );
7700 break;
7701
7702 case T_pads:
7703 token = NextTok();
7704
7705 if( token != T_allowed && token != T_not_allowed )
7706 Expecting( "allowed or not_allowed" );
7707
7708 zone->SetDoNotAllowPads( token == T_not_allowed );
7709 break;
7710
7711 case T_footprints:
7712 token = NextTok();
7713
7714 if( token != T_allowed && token != T_not_allowed )
7715 Expecting( "allowed or not_allowed" );
7716
7717 zone->SetDoNotAllowFootprints( token == T_not_allowed );
7718 break;
7719
7720 default:
7721 Expecting( "tracks, vias or copperpour" );
7722 }
7723
7724 NeedRIGHT();
7725 }
7726
7727 break;
7728
7729 case T_polygon:
7730 {
7731 SHAPE_LINE_CHAIN outline;
7732
7733 NeedLEFT();
7734 token = NextTok();
7735
7736 if( token != T_pts )
7737 Expecting( T_pts );
7738
7739 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
7740 parseOutlinePoints( outline );
7741
7742 NeedRIGHT();
7743
7744 outline.SetClosed( true );
7745
7746 // Remark: The first polygon is the main outline.
7747 // Others are holes inside the main outline.
7748 zone->AddPolygon( outline );
7749 break;
7750 }
7751
7752 case T_filled_polygon:
7753 {
7754 // "(filled_polygon (pts"
7755 NeedLEFT();
7756 token = NextTok();
7757
7758 if( token == T_layer )
7759 {
7760 filledLayer = parseBoardItemLayer();
7761 NeedRIGHT();
7762 token = NextTok();
7763
7764 if( token != T_LEFT )
7765 Expecting( T_LEFT );
7766
7767 token = NextTok();
7768 }
7769 else
7770 {
7771 // for legacy, single-layer zones
7772 filledLayer = zone->GetFirstLayer();
7773 }
7774
7775 bool island = false;
7776
7777 if( token == T_island )
7778 {
7779 island = parseMaybeAbsentBool( true );
7780 NeedLEFT();
7781 token = NextTok();
7782 }
7783
7784 if( token != T_pts )
7785 Expecting( T_pts );
7786
7787 if( !pts.count( filledLayer ) )
7788 pts[filledLayer] = SHAPE_POLY_SET();
7789
7790 SHAPE_POLY_SET& poly = pts.at( filledLayer );
7791
7792 int idx = poly.NewOutline();
7793 SHAPE_LINE_CHAIN& chain = poly.Outline( idx );
7794
7795 if( island )
7796 zone->SetIsIsland( filledLayer, idx );
7797
7798 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
7800
7801 NeedRIGHT();
7802
7803 addedFilledPolygons |= !poly.IsEmpty();
7804 }
7805
7806 break;
7807
7808 case T_fill_segments:
7809 {
7810 // Legacy segment fill
7811
7812 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
7813 {
7814 if( token != T_LEFT )
7815 Expecting( T_LEFT );
7816
7817 token = NextTok();
7818
7819 if( token != T_pts )
7820 Expecting( T_pts );
7821
7822 // Legacy zones only had one layer
7823 filledLayer = zone->GetFirstLayer();
7824
7825 SEG fillSegment;
7826
7827 fillSegment.A = parseXY();
7828 fillSegment.B = parseXY();
7829
7830 legacySegs[filledLayer].push_back( fillSegment );
7831
7832 NeedRIGHT();
7833 }
7834
7835 break;
7836 }
7837
7838 case T_name:
7839 NextTok();
7840 zone->SetZoneName( FromUTF8() );
7841 NeedRIGHT();
7842 break;
7843
7844 case T_attr:
7845 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
7846 {
7847 if( token == T_LEFT )
7848 token = NextTok();
7849
7850 switch( token )
7851 {
7852 case T_teardrop:
7853 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
7854 {
7855 if( token == T_LEFT )
7856 token = NextTok();
7857
7858 switch( token )
7859 {
7860 case T_type:
7861 token = NextTok();
7862
7863 if( token == T_padvia )
7864 zone->SetTeardropAreaType( TEARDROP_TYPE::TD_VIAPAD );
7865 else if( token == T_track_end )
7866 zone->SetTeardropAreaType( TEARDROP_TYPE::TD_TRACKEND );
7867 else
7868 Expecting( "padvia or track_end" );
7869
7870 NeedRIGHT();
7871 break;
7872
7873 default:
7874 Expecting( "type" );
7875 }
7876 }
7877
7878 break;
7879
7880 default:
7881 Expecting( "teardrop" );
7882 }
7883 }
7884 break;
7885
7886 case T_locked:
7887 zone->SetLocked( parseBool() );
7888 NeedRIGHT();
7889 break;
7890
7891 default:
7892 Expecting( "net, layer/layers, tstamp, hatch, priority, connect_pads, min_thickness, "
7893 "fill, polygon, filled_polygon, fill_segments, attr, locked, uuid, or name" );
7894 }
7895 }
7896
7897 if( zone->GetNumCorners() > 2 )
7898 {
7899 if( !zone->IsOnCopperLayer() )
7900 {
7901 //zone->SetFillMode( ZONE_FILL_MODE::POLYGONS );
7902 zone->SetNetCode( NETINFO_LIST::UNCONNECTED );
7903 }
7904
7905 // Set hatch here, after outlines corners are read
7906 zone->SetBorderDisplayStyle( hatchStyle, hatchPitch, true );
7907 }
7908
7909 if( addedFilledPolygons )
7910 {
7911 if( isStrokedFill && !zone->GetIsRuleArea() )
7912 {
7914 {
7915 wxLogWarning( _( "Legacy zone fill strategy is not supported anymore.\n"
7916 "Zone fills will be converted on best-effort basis." ) );
7917
7919 }
7920
7921 if( zone->GetMinThickness() > 0 )
7922 {
7923 for( auto& [layer, polyset] : pts )
7924 {
7925 polyset.InflateWithLinkedHoles( zone->GetMinThickness() / 2,
7927 ARC_HIGH_DEF / 2 );
7928 }
7929 }
7930 }
7931
7932 for( auto& [layer, polyset] : pts )
7933 zone->SetFilledPolysList( layer, polyset );
7934
7935 zone->CalculateFilledArea();
7936 }
7937 else if( legacySegs.size() > 0 )
7938 {
7939 // No polygons, just segment fill?
7940 // Note RFB: This code might be removed if turns out this never existed for sexpr file
7941 // format or otherwise we should add a test case to the qa folder
7942
7944 {
7945 wxLogWarning( _( "The legacy segment zone fill mode is no longer supported.\n"
7946 "Zone fills will be converted on a best-effort basis." ) );
7947
7949 }
7950
7951
7952 for( const auto& [layer, segments] : legacySegs )
7953 {
7954 SHAPE_POLY_SET layerFill;
7955
7956 if( zone->HasFilledPolysForLayer( layer ) )
7957 layerFill = SHAPE_POLY_SET( *zone->GetFill( layer ) );
7958
7959 for( const auto& seg : segments )
7960 {
7961 SHAPE_POLY_SET segPolygon;
7962
7963 TransformOvalToPolygon( segPolygon, seg.A, seg.B, zone->GetMinThickness(),
7965
7966 layerFill.BooleanAdd( segPolygon );
7967 }
7968
7969
7970 zone->SetFilledPolysList( layer, layerFill );
7971 zone->CalculateFilledArea();
7972 }
7973 }
7974
7975
7976 // Ensure keepout and non copper zones do not have a net
7977 // (which have no sense for these zones)
7978 // the netcode 0 is used for these zones
7979 bool zone_has_net = zone->IsOnCopperLayer() && !zone->GetIsRuleArea();
7980
7981 if( !zone_has_net )
7982 zone->SetNetCode( NETINFO_LIST::UNCONNECTED );
7983
7984 // In legacy files, ensure the zone net name is valid, and matches the net code
7985 if( !legacyNetnameFromFile.IsEmpty() && zone->GetNetname() != legacyNetnameFromFile )
7986 {
7987 // Can happens which old boards, with nonexistent nets ...
7988 // or after being edited by hand
7989 // We try to fix the mismatch.
7990 NETINFO_ITEM* net = m_board->FindNet( legacyNetnameFromFile );
7991
7992 if( net ) // An existing net has the same net name. use it for the zone
7993 {
7994 zone->SetNetCode( net->GetNetCode() );
7995 }
7996 else // Not existing net: add a new net to keep track of the zone netname
7997 {
7998 int newnetcode = m_board->GetNetCount();
7999 net = new NETINFO_ITEM( m_board, legacyNetnameFromFile, newnetcode );
8000 m_board->Add( net, ADD_MODE::INSERT, true );
8001
8002 // Store the new code mapping
8003 pushValueIntoMap( newnetcode, net->GetNetCode() );
8004
8005 // and update the zone netcode
8006 zone->SetNetCode( net->GetNetCode() );
8007 }
8008 }
8009
8010 if( zone->IsTeardropArea() && m_requiredVersion < 20230517 )
8011 m_board->SetLegacyTeardrops( true );
8012
8013 // Clear flags used in zone edition:
8014 zone->SetNeedRefill( false );
8015
8016 return zone.release();
8017}
8018
8019
8021{
8022 wxCHECK_MSG( CurTok() == T_point, nullptr,
8023 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as PCB_POINT." ) );
8024
8025 std::unique_ptr<PCB_POINT> point = std::make_unique<PCB_POINT>( nullptr );
8026
8027 for( T token = NextTok(); token != T_RIGHT; token = NextTok() )
8028 {
8029 if( token == T_LEFT )
8030 token = NextTok();
8031
8032 switch( token )
8033 {
8034 case T_at:
8035 {
8036 VECTOR2I pt;
8037 pt.x = parseBoardUnits( "point x position" );
8038 pt.y = parseBoardUnits( "point y position" );
8039 point->SetPosition( pt );
8040 NeedRIGHT();
8041 break;
8042 }
8043 case T_size:
8044 {
8045 point->SetSize( parseBoardUnits( "point size" ) );
8046 NeedRIGHT();
8047 break;
8048 }
8049 case T_layer:
8050 {
8051 point->SetLayer( parseBoardItemLayer() );
8052 NeedRIGHT();
8053 break;
8054 }
8055 case T_uuid:
8056 {
8057 NextTok();
8058 const_cast<KIID&>( point->m_Uuid ) = CurStrToKIID();
8059 NeedRIGHT();
8060 break;
8061 }
8062 default: Expecting( "at, size, layer or uuid" );
8063 }
8064 }
8065
8066 return point.release();
8067}
8068
8069
8071{
8072 wxCHECK_MSG( CurTok() == T_target, nullptr,
8073 wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as PCB_TARGET." ) );
8074
8075 VECTOR2I pt;
8076 T token;
8077
8078 std::unique_ptr<PCB_TARGET> target = std::make_unique<PCB_TARGET>( nullptr );
8079
8080 for( token = NextTok(); token != T_RIGHT; token = NextTok() )
8081 {
8082 if( token == T_LEFT )
8083 token = NextTok();
8084
8085 switch( token )
8086 {
8087 case T_x:
8088 target->SetShape( 1 );
8089 break;
8090
8091 case T_plus:
8092 target->SetShape( 0 );
8093 break;
8094
8095 case T_at:
8096 pt.x = parseBoardUnits( "target x position" );
8097 pt.y = parseBoardUnits( "target y position" );
8098 target->SetPosition( pt );
8099 NeedRIGHT();
8100 break;
8101
8102 case T_size:
8103 target->SetSize( parseBoardUnits( "target size" ) );
8104 NeedRIGHT();
8105 break;
8106
8107 case T_width:
8108 target->SetWidth( parseBoardUnits( "target thickness" ) );
8109 NeedRIGHT();
8110 break;
8111
8112 case T_layer:
8113 target->SetLayer( parseBoardItemLayer() );
8114 NeedRIGHT();
8115 break;
8116
8117 case T_tstamp:
8118 case T_uuid:
8119 NextTok();
8120 const_cast<KIID&>( target->m_Uuid ) = CurStrToKIID();
8121 NeedRIGHT();
8122 break;
8123
8124 default:
8125 Expecting( "x, plus, at, size, width, layer, uuid, or tstamp" );
8126 }
8127 }
8128
8129 return target.release();
8130}
8131
8132
8134{
8135 KIID aId;
8136 std::string idStr( CurStr() );
8137
8138 // Older files did not quote UUIDs
8139 if( *idStr.begin() == '"' && *idStr.rbegin() == '"' )
8140 idStr = idStr.substr( 1, idStr.length() - 1 );
8141
8142 if( m_appendToExisting )
8143 {
8144 aId = KIID();
8145 m_resetKIIDMap.insert( std::make_pair( idStr, aId ) );
8146 }
8147 else
8148 {
8149 aId = KIID( idStr );
8150 }
8151
8152 return aId;
8153}
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
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(ZONE_SETTINGS &aZoneSettings)
std::unordered_map< std::string, LSET > LSET_MAP
void parseEDA_TEXT(EDA_TEXT *aText)
Parse the common settings for any object derived from EDA_TEXT.
bool m_tooRecent
true if version parses as later than supported
PCB_LAYER_ID lookUpLayer(const LAYER_ID_MAP &aMap)
Parse the current token for the layer definition of a BOARD_ITEM object.
PCB_REFERENCE_IMAGE * parsePCB_REFERENCE_IMAGE(BOARD_ITEM *aParent)
LAYER_ID_MAP m_layerIndices
map layer name to it's index
void parseTextBoxContent(PCB_TEXTBOX *aTextBox)
FOOTPRINT * parseFOOTPRINT(wxArrayString *aInitialComments=nullptr)
void pushValueIntoMap(int aIndex, int aValue)
Add aValue value in netcode mapping (m_netCodes) at aIndex.
void init()
Clear and re-establish m_layerMap with the default layer names.
void skipCurrent()
Skip the current token level, i.e search for the RIGHT parenthesis which closes the current descripti...
void parseMargins(int &aLeft, int &aTop, int &aRight, int &aBottom)
PCB_LAYER_ID parseBoardItemLayer()
Parse the layer definition of a BOARD_ITEM object.
LSET parseLayersForCuItemWithSoldermask()
Parse the layers definition of a BOARD_ITEM object that has a single copper layer and optional solder...
void parseGENERATOR(BOARD_ITEM *aParent)
LSET parseBoardItemLayersAsMask()
Parse the layers definition of a BOARD_ITEM object.
std::pair< std::optional< bool >, std::optional< bool > > parseFrontBackOptBool(bool aLegacy=false)
void resolveGroups(BOARD_ITEM *aParent)
Called after parsing a footprint definition or board to build the group membership lists.
void parseDefaultTextDims(BOARD_DESIGN_SETTINGS &aSettings, int aLayer)
std::vector< GROUP_INFO > m_groupInfos
ZONE * parseZONE(BOARD_ITEM_CONTAINER *aParent)
PCB_TABLE * parsePCB_TABLE(BOARD_ITEM *aParent)
std::vector< GENERATOR_INFO > m_generatorInfos
PCB_TEXTBOX * parsePCB_TEXTBOX(BOARD_ITEM *aParent)
std::chrono::time_point< CLOCK > TIME_PT
PCB_TEXT * parsePCB_TEXT(BOARD_ITEM *aParent, PCB_TEXT *aBaseText=nullptr)
unsigned m_lineCount
for progress reporting
VECTOR2I parseXY()
Parse a coordinate pair (xy X Y) in board units (mm).
void parseTEARDROP_PARAMETERS(TEARDROP_PARAMETERS *tdParams)
int m_requiredVersion
set to the KiCad format version this board requires
PAD * parsePAD(FOOTPRINT *aParent=nullptr)
std::function< bool(wxString aTitle, int aIcon, wxString aMsg, wxString aAction)> m_queryUserCallback
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.
std::map< PCB_LAYER_ID, ZONE_LAYER_PROPERTIES > m_LayerProperties
Handle a list of polygons defining a copper zone.
Definition zone.h:74
virtual LSET GetLayerSet() const override
Return a std::bitset of all layers on which the item physically resides.
Definition zone.h:136
void SetLayerSetAndRemoveUnusedFills(const LSET &aLayerSet)
Set the zone to be on the aLayerSet layers and only remove the fill polygons from the unused layers,...
Definition zone.cpp:1605
static int GetDefaultHatchPitch()
Definition zone.cpp:1273
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