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