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