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