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