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