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