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