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