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