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