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