KiCad PCB EDA Suite
Loading...
Searching...
No Matches
sprint_layout_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 The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 *
19 * Binary format knowledge derived from:
20 * https://github.com/sergey-raevskiy/xlay (lay6.h)
21 * https://github.com/OpenBoardView/OpenBoardView (LAYFile.cpp)
22 */
23
25
26#include <board.h>
29#include <footprint.h>
30#include <memory>
31#include <netinfo.h>
32#include <pad.h>
33#include <pcb_group.h>
34#include <pcb_shape.h>
35#include <pcb_text.h>
36#include <zone.h>
38#include <math/util.h>
39#include <math/box2.h>
40#include <font/fontconfig.h>
41
42#include <wx/filename.h>
43#include <wx/wfstream.h>
44#include <wx/log.h>
45#include <wx/strconv.h>
46#include <wx/fontenc.h>
47
48#include <algorithm>
49#include <cmath>
50#include <cstring>
51#include <limits>
52
53// All multi-byte reads below decode little-endian explicitly,
54// so this parser works correctly on any host byte order.
55
56static constexpr uint32_t MAX_OBJECTS = 1000000;
57static constexpr uint32_t MAX_GROUPS = 100000;
58static constexpr uint32_t MAX_CHILDREN = 10000;
59static constexpr uint32_t MAX_POINTS = 1000000;
60
61
63
64
66
67
68// ============================================================================
69// Binary reading helpers
70// ============================================================================
71
73{
74 if( m_pos + 1 > m_end )
75 THROW_IO_ERROR( _( "Unexpected end of Sprint Layout file" ) );
76
77 return *m_pos++;
78}
79
80
82{
83 if( m_pos + 2 > m_end )
84 THROW_IO_ERROR( _( "Unexpected end of Sprint Layout file" ) );
85
86 uint16_t v = static_cast<uint16_t>( m_pos[0] )
87 | ( static_cast<uint16_t>( m_pos[1] ) << 8 );
88 m_pos += 2;
89 return v;
90}
91
92
94{
95 return static_cast<int16_t>( readUint16() );
96}
97
98
100{
101 if( m_fileData.version >= 3 )
102 return readUint32();
103 else
104 return readUint16();
105}
106
107
109{
110 if( m_fileData.version >= 3 )
111 return readInt32();
112 else
113 return readInt16();
114}
115
116
118{
119 if( m_pos + 4 > m_end )
120 THROW_IO_ERROR( _( "Unexpected end of Sprint Layout file" ) );
121
122 uint32_t v = static_cast<uint32_t>( m_pos[0] )
123 | ( static_cast<uint32_t>( m_pos[1] ) << 8 )
124 | ( static_cast<uint32_t>( m_pos[2] ) << 16 )
125 | ( static_cast<uint32_t>( m_pos[3] ) << 24 );
126 m_pos += 4;
127 return v;
128}
129
130
132{
133 return static_cast<int32_t>( readUint32() );
134}
135
136
138{
139 uint32_t bits = readUint32();
140 float v;
141 std::memcpy( &v, &bits, sizeof( v ) );
142 return v;
143}
144
145
147{
148 if( m_pos + 8 > m_end )
149 THROW_IO_ERROR( _( "Unexpected end of Sprint Layout file" ) );
150
151 uint64_t bits = static_cast<uint64_t>( m_pos[0] )
152 | ( static_cast<uint64_t>( m_pos[1] ) << 8 )
153 | ( static_cast<uint64_t>( m_pos[2] ) << 16 )
154 | ( static_cast<uint64_t>( m_pos[3] ) << 24 )
155 | ( static_cast<uint64_t>( m_pos[4] ) << 32 )
156 | ( static_cast<uint64_t>( m_pos[5] ) << 40 )
157 | ( static_cast<uint64_t>( m_pos[6] ) << 48 )
158 | ( static_cast<uint64_t>( m_pos[7] ) << 56 );
159 m_pos += 8;
160
161 double v;
162 std::memcpy( &v, &bits, sizeof( v ) );
163 return v;
164}
165
166
168{
169 if( m_fileData.version >= 5 )
170 return readFloat();
171 else if( m_fileData.version >= 3 )
172 return static_cast<float>( readInt32() );
173 else
174 return static_cast<float>( readInt16() );
175}
176
177
178std::string SPRINT_LAYOUT_PARSER::readFixedString( size_t aMaxLen )
179{
180 size_t rawLen = readUint8();
181 size_t len = std::min( rawLen, aMaxLen );
182
183 if( m_pos + aMaxLen > m_end )
184 THROW_IO_ERROR( _( "Unexpected end of Sprint Layout file" ) );
185
186 std::string s( reinterpret_cast<const char*>( m_pos ), len );
187 m_pos += aMaxLen;
188 return s;
189}
190
191
193{
194 uint32_t len = readUint32();
195
196 if( len > 100000 )
197 THROW_IO_ERROR( _( "Invalid string length in Sprint Layout file" ) );
198
199 if( m_pos + len > m_end )
200 THROW_IO_ERROR( _( "Unexpected end of Sprint Layout file" ) );
201
202 std::string s( reinterpret_cast<const char*>( m_pos ), len );
203 m_pos += len;
204 return s;
205}
206
207
208void SPRINT_LAYOUT_PARSER::skip( size_t aBytes )
209{
210 if( m_pos + aBytes > m_end )
211 THROW_IO_ERROR( _( "Unexpected end of Sprint Layout file" ) );
212
213 m_pos += aBytes;
214}
215
216
218{
219 const uint8_t* seekTo = m_pos + aBytes;
220
221 if( seekTo > m_end || seekTo < m_start )
222 THROW_IO_ERROR( _( "Unexpected seek in Sprint Layout file" ) );
223
224 m_pos = seekTo;
225}
226
227
228// ============================================================================
229// Parsing
230// ============================================================================
231
232
233bool SPRINT_LAYOUT_PARSER::ParseBoard( const wxString& aFileName )
234{
235 m_parsingMacro = false;
236 parseFileStart( aFileName );
237
238 if( m_fileData.version >= 3 )
239 {
240 uint32_t numBoards = readUnsigned();
241
242 if( numBoards == 0 || numBoards > 100 )
243 THROW_IO_ERROR( _( "Invalid board count in Sprint Layout file" ) );
244
245 m_fileData.boards.resize( numBoards );
246 }
247 else
248 {
249 m_fileData.boards.resize( 1 );
250 }
251
252 for( uint32_t b = 0; b < m_fileData.boards.size(); b++ )
253 {
254 SPRINT_LAYOUT::BOARD_DATA& boardData = m_fileData.boards[b];
255 parseBoardHeader( boardData );
256
257 uint32_t numObjects = readUnsigned();
258
259 if( numObjects > MAX_OBJECTS )
260 THROW_IO_ERROR( _( "Too many objects in Sprint Layout board" ) );
261
262 boardData.objects.resize( numObjects );
263
264 for( uint32_t i = 0; i < numObjects; i++ )
265 parseObject( boardData.objects[i] );
266
267 if( m_fileData.version >= 3 )
268 {
269 uint32_t numConnections = 0;
270
271 for( auto& obj : boardData.objects )
272 {
273 if( obj.type == SPRINT_LAYOUT::OBJ_THT_PAD || obj.type == SPRINT_LAYOUT::OBJ_SMD_PAD )
274 numConnections++;
275 }
276
277 // Read connection records (one per pad object)
278 for( uint32_t c = 0; c < numConnections; c++ )
279 {
280 uint32_t connCount = readUnsigned();
281
282 // Skip the connection data for now
283 for( uint32_t i = 0; i < connCount; i++ )
284 (void) readUnsigned();
285 }
286 }
287 }
288
289 parseTrailer();
290
291 return true;
292}
293
294
295bool SPRINT_LAYOUT_PARSER::ParseMacroFile( const wxString& aFileName )
296{
297 // Parse the macro data into BOARD_DATA
299 data.name = wxFileNameFromPath( aFileName ).BeforeLast( '.' );
300
301 m_parsingMacro = true;
302 parseFileStart( aFileName );
303 parseObjectsList( data );
304
305 m_fileData.boards = { data };
306
307 return true;
308}
309
310
311void SPRINT_LAYOUT_PARSER::parseFileStart( const wxString& aFileName )
312{
313 wxFFileInputStream stream( aFileName );
314
315 if( !stream.IsOk() )
316 THROW_IO_ERRORF( _( "Cannot open file '%s'" ), aFileName );
317
318 size_t fileSize = stream.GetLength();
319
320 if( fileSize < 8 )
321 THROW_IO_ERRORF( _( "File '%s' is too small to be a Sprint Layout file" ), aFileName );
322
323 m_buffer.resize( fileSize );
324 stream.Read( m_buffer.data(), fileSize );
325
326 if( stream.LastRead() != fileSize )
327 THROW_IO_ERRORF( _( "Failed to read file '%s'" ), aFileName );
328
329 m_start = m_buffer.data();
330 m_pos = m_start;
331 m_end = m_start + fileSize;
332
333 // File header: version + magic bytes (0x33, 0xAA, 0xFF)
334 m_fileData.version = readUint8();
335 uint8_t magic1 = readUint8();
336 uint8_t magic2 = readUint8();
337 uint8_t magic3 = readUint8();
338
339 if( m_fileData.version > 6 || magic1 != 0x33 || magic2 != 0xAA || magic3 != 0xFF )
340 THROW_IO_ERROR( _( "Invalid Sprint Layout file header" ) );
341}
342
343
345{
346 if( m_fileData.version >= 3 )
347 {
348 // Board name (Pascal string, 30 bytes max)
349 aBoard.name = readFixedString( 30 );
350
351 // Unknown padding
352 skip( 4 );
353
354 aBoard.size_x = readUint32();
355 aBoard.size_y = readUint32();
356
357 // Ground plane enabled flag per layer (C1, S1, C2, S2, I1, I2, O)
358 for( int i = 0; i < 7; i++ )
359 aBoard.ground_plane[i] = readUint8();
360
361 if( m_fileData.version >= 5 )
362 {
363 // Grid and viewport (not needed for import)
364 readDouble(); // active_grid_val
365 readDouble(); // zoom
366 readUint32(); // viewport_offset_x
367 readUint32(); // viewport_offset_y
368
369 // Active layer + padding
370 skip( 4 );
371
372 // Layer visibility + scanned copy flags
373 skip( 7 ); // layer_visible[7]
374 skip( 1 ); // show_scanned_copy_top
375 skip( 1 ); // show_scanned_copy_bottom
376
377 // Scanned copy paths
378 readFixedString( 200 );
379 readFixedString( 200 );
380
381 // DPI and shift values for scanned copies
382 skip( 4 * 6 ); // dpi_top, dpi_bottom, shiftx/y_top, shiftx/y_bottom
383
384 // Unknown fields
385 skip( 4 * 2 );
386
387 aBoard.center_x = readInt32();
388 aBoard.center_y = readInt32();
389
390 aBoard.is_multilayer = readUint8();
391 }
392 else if( m_fileData.version >= 4 )
393 {
394 skip( 19 );
395 readUint32(); // active_layer
396 skip( 7 ); // layer_visible
397 skip( 400 ); // unknown_list: 100 * 4 bytes
398 skip( 33 );
399
400 aBoard.center_x = readInt32();
401 aBoard.center_y = readInt32();
402 }
403 else if( m_fileData.version >= 3 )
404 {
405 skip( 19 );
406 readUint32(); // active_layer
407 skip( 7 ); // layer_visible
408 skip( 400 ); // unknown_list: 100 * 4 bytes
409 skip( 33 );
410 }
411 }
412 else // Version 2 and older
413 {
414 aBoard.size_x = readUint32();
415 aBoard.size_y = readUint32();
416 }
417}
418
419
421{
422 uint32_t numObjects = readUnsigned();
423
424 if( numObjects > MAX_OBJECTS )
425 THROW_IO_ERROR( _( "Too many objects in Sprint Layout file" ) );
426
427 aBoard.objects.resize( numObjects );
428
429 for( uint32_t i = 0; i < numObjects; i++ )
430 parseObject( aBoard.objects[i], false );
431}
432
433
435{
436 uint32_t groupCount = readUnsigned();
437
438 if( groupCount > MAX_GROUPS )
439 THROW_IO_ERROR( _( "Too many groups in Sprint Layout object" ) );
440
441 aObj.groups.resize( groupCount );
442
443 for( uint32_t i = 0; i < groupCount; i++ )
444 aObj.groups[i] = readUnsigned();
445}
446
447
449{
450 uint32_t pointCount = readUnsigned();
451
452 if( pointCount > MAX_POINTS )
453 THROW_IO_ERROR( _( "Too many points in Sprint Layout object" ) );
454
455 for( uint32_t i = 0; i < pointCount; i++ )
456 {
458 pt.x = readCoord();
459 pt.y = readCoord();
460
461 if( pt.x == 0.0f || std::isnormal( pt.x ) )
462 {
463 aObj.points.emplace_back( pt );
464 }
465 else
466 {
467 seek( -8 );
468 }
469 }
470}
471
472
474{
475 aObj.type = readUint8();
476
481 {
482 THROW_IO_ERRORF( _( "Unknown object type %d in Sprint Layout file" ), aObj.type );
483 }
484
485 aObj.x = readCoord();
486 aObj.y = readCoord();
487 aObj.outer = readCoord();
488 aObj.inner = readCoord();
489 aObj.line_width = readSigned();
490 skip( 1 ); // padding
491 aObj.layer = readUint8();
492 aObj.tht_shape = readUint8();
493
494 if( m_fileData.version >= 5 )
495 {
496 skip( 4 ); // padding
497 aObj.component_id = readUint16();
498 skip( 1 ); // selected
499 aObj.start_angle = readInt32(); // also th_style[4]
500 skip( 5 ); // unknown
501 aObj.filled = readUint8();
502 aObj.clearance = readInt32();
503 skip( 5 ); // padding
504 aObj.mirror_h = readUint8();
505 aObj.mirror_v = readUint8();
506 aObj.keepout = readUint8();
507 aObj.rotation = readInt32();
508 aObj.plated = readUint8();
509 aObj.soldermask = readUint8();
510 skip( 18 );
511
512 if( !aIsTextChild )
513 {
514 aObj.text = readVarString();
515 aObj.identifier = readVarString();
516 }
517 }
518 else if( m_fileData.version >= 4 )
519 {
520 skip( 4 ); // padding
521 skip( 3 );
522 aObj.start_angle = readInt32();
523 skip( 5 );
524 aObj.filled = readUint8();
525 aObj.clearance = readInt32();
526 skip( 9 ); // padding
527 aObj.mirror_h = readUint8(); // text H mirror
528 aObj.mirror_v = readUint8(); // text V mirror
529 aObj.keepout = readUint8();
530 skip( 18 );
531
532 if( !aIsTextChild )
533 {
534 aObj.text = readVarString();
535 }
536 }
537 else if( m_fileData.version >= 3 )
538 {
539 // 50 bytes of data
541 {
542 aObj.text = readFixedString( 15 );
543 skip( 7 );
544 aObj.rotation = readInt16();
545 skip( 25 );
546 }
547 else
548 {
549 skip( 23 );
550 aObj.start_angle = readInt32();
551 skip( 3 );
552 aObj.mirror_h = readUint8();
553 aObj.mirror_v = readUint8();
554 skip( 1 );
555 aObj.clearance = readInt32();
556 skip( 13 );
557 }
558 }
559 else // Versions 1 and 2
560 {
561 // 35 bytes of data
563 {
564 aObj.text = readFixedString( 15 );
565 skip( 7 );
566 aObj.rotation = readInt16();
567 skip( 10 );
568 }
569 else
570 {
571 skip( 35 );
572 }
573 }
574
575 if( m_fileData.version >= 2 && !aIsTextChild )
576 parseGroups( aObj );
577
578 switch( aObj.type )
579 {
583 {
584 return;
585 }
586
589 {
590 parsePoints( aObj );
591 return;
592 }
593
595 {
596 if( m_fileData.version >= 5 )
597 parsePoints( aObj );
598 else if( m_fileData.version >= 3 )
599 skip( 4 ); // Usually 0xFFFFFFFF
600
601 return;
602 }
603
605 {
606 if( m_fileData.version >= 5 )
607 parsePoints( aObj );
608
609 return; // No points in older versions
610 }
611
613 {
614 // Only present since version 3
615 uint32_t childCount = readUint32();
616
617 if( childCount > MAX_CHILDREN )
618 THROW_IO_ERROR( _( "Too many text children in Sprint Layout object" ) );
619
620 aObj.text_children.resize( childCount );
621
622 for( uint32_t i = 0; i < childCount; i++ )
623 parseObject( aObj.text_children[i], true );
624
625 // In v6, component data follows for text objects that define a component
626 if( m_fileData.version >= 6 && aObj.tht_shape == 1 )
627 {
628 aObj.component.valid = true;
629 aObj.component.off_x = readCoord();
630 aObj.component.off_y = readCoord();
635 aObj.component.use = readUint8();
636 }
637
638 return;
639 }
640
641 default:
642 THROW_IO_ERRORF( _( "Unknown object type %d in Sprint Layout file" ), aObj.type );
643 }
644}
645
646
648{
649 if( m_fileData.version >= 4 )
650 {
651 readUint32(); // active_board_tab
652 m_fileData.project_name = readFixedString( 100 );
653 m_fileData.project_author = readFixedString( 100 );
654 m_fileData.project_company = readFixedString( 100 );
655 m_fileData.project_comment = readVarString();
656 }
657}
658
659
660// ============================================================================
661// Board construction
662// ============================================================================
663
664PCB_LAYER_ID SPRINT_LAYOUT_PARSER::mapLayer( uint8_t aSprintLayer ) const
665{
666 if( m_fileData.version >= 4 )
667 {
668 switch( aSprintLayer )
669 {
670 case SPRINT_LAYOUT::LAYER_C1: return F_Cu;
671 case SPRINT_LAYOUT::LAYER_S1: return F_SilkS;
672 case SPRINT_LAYOUT::LAYER_C2: return B_Cu;
673 case SPRINT_LAYOUT::LAYER_S2: return B_SilkS;
674 case SPRINT_LAYOUT::LAYER_I1: return In1_Cu;
675 case SPRINT_LAYOUT::LAYER_I2: return In2_Cu;
677 default: return F_Cu;
678 }
679 }
680 else
681 {
682 // In older Sprint Layout versions the meaning of C1/C2 is flipped
683 switch( aSprintLayer )
684 {
685 case SPRINT_LAYOUT::LAYER_C1: return m_fileData.version >= 3 ? F_Cu : B_Cu;
686 case SPRINT_LAYOUT::LAYER_S1: return F_SilkS;
687 case SPRINT_LAYOUT::LAYER_C2: return m_fileData.version >= 3 ? B_Cu : F_Cu;
688 case SPRINT_LAYOUT::LAYER_S2: return B_SilkS;
690
691 case SPRINT_LAYOUT::LAYER_I1: return B_Cu; // used for PTH pads and tracks inside macros
692 case SPRINT_LAYOUT::LAYER_I2: return F_SilkS; // used for graphics inside macros
693 default: return F_Cu;
694 }
695 }
696}
697
698
700{
701 // Sprint Layout 6 uses 1/10000 mm
702 // Older versions seem to use 1/100 mm
703 // KiCad uses nanometers (1 nm = 1e-6 mm)
704 double nm;
705
706 if( m_fileData.version >= 6 )
707 nm = static_cast<double>( aValue ) * 100.0; // 100 nm
708 else
709 nm = static_cast<double>( aValue ) * 10000.0; // 10 um
710
711 nm = std::clamp( nm, static_cast<double>( -pcbIUScale.mmToIU( 500 ) ),
712 static_cast<double>( pcbIUScale.mmToIU( 500 ) ) );
713
714 return KiROUND( nm );
715}
716
717
719{
720 // Sprint Layout uses Y-up (mathematical), KiCad uses Y-down (screen)
721 return VECTOR2I( sprintToKicadCoord( aX ), sprintToKicadCoord( -aY ) );
722}
723
724
725wxString SPRINT_LAYOUT_PARSER::convertString( const std::string& aStr ) const
726{
727 static wxCSConv convCP1251( wxFONTENCODING_CP1251 );
728 static wxCSConv convCP1252( wxFONTENCODING_CP1252 );
729
730 if( aStr.empty() )
731 return wxEmptyString;
732
733 wxString ret = wxString::FromUTF8( aStr );
734
735 if( ret.empty() && convCP1251.IsOk() && convCP1252.IsOk() )
736 {
737 // Statistically determine if the string is more likely to be CP1251 (Cyrillic) or CP1252 (Western European)
738 size_t extNonGermanCount = 0;
739
740 for( unsigned char c : aStr )
741 {
742 // Extended-range German characters in CP1252
743 switch( c )
744 {
745 case 0xC4: // Ä
746 case 0xD6: // Ö
747 case 0xDC: // Ü
748 case 0xE4: // ä
749 case 0xF6: // ö
750 case 0xFC: // ü
751 case 0xDF: // ß
752 break;
753
754 default:
755 if( c >= 0x80 )
756 extNonGermanCount++;
757 break;
758 }
759 }
760
761 if( extNonGermanCount > 0 )
762 ret = wxString( aStr.c_str(), convCP1251 );
763 else
764 ret = wxString( aStr.c_str(), convCP1252 );
765 }
766
767 return ret;
768}
769
770
771bool SPRINT_LAYOUT_PARSER::layerHasGroundPlane( PCB_LAYER_ID aLayer, const uint8_t aGroundPlane[7] ) const
772{
773 // Ground plane index map mirrors CreateBoard()'s groundPlaneMap
774 if( m_fileData.version >= 5 )
775 {
776 switch( aLayer )
777 {
778 case F_Cu: return aGroundPlane[0] != 0;
779 case B_Cu: return aGroundPlane[2] != 0;
780 case In1_Cu: return aGroundPlane[4] != 0;
781 case In2_Cu: return aGroundPlane[5] != 0;
782 default: return false;
783 }
784 }
785 else
786 {
787 switch( aLayer )
788 {
789 case F_Cu: return aGroundPlane[0] != 0;
790 case B_Cu: return aGroundPlane[1] != 0;
791 default: return false;
792 }
793 }
794}
795
796
798 PCB_LAYER_ID aLayer, const uint8_t aGroundPlane[7],
799 NETINFO_ITEM* aGndPlaneNet ) const
800{
801 if( !aBoard )
802 return nullptr;
803
804 bool isPad = aObj.type == SPRINT_LAYOUT::OBJ_THT_PAD || aObj.type == SPRINT_LAYOUT::OBJ_SMD_PAD;
805
806 // Override the net for ground plane connection. Note that the identifier string
807 // could specify anything (e.g. component value), not just the net name
808 if( aGndPlaneNet != nullptr && layerHasGroundPlane( aLayer, aGroundPlane ) )
809 {
810 if( aObj.clearance == 0 )
811 return aGndPlaneNet;
812
813 // If pad thermal reliefs are enabled, connect to the plane
814 if( m_fileData.version >= 5 && isPad && aObj.mirror_h != 0 )
815 return aGndPlaneNet;
816 }
817
818 // TODO: if a pad is connected through lines to the GND_PLANE, we don't want to set the pad's
819 // netname as this would update the nets of the lines, disconnecting them from the plane.
820 //
821 //if( !aObj.identifier.empty() )
822 //{
823 // wxString netName = convertString( aObj.identifier );
824 // NETINFO_ITEM* net = aBoard->FindNet( netName );
825
826 // if( !net )
827 // {
828 // net = new NETINFO_ITEM( aBoard, netName );
829 // aBoard->Add( net );
830 // }
831
832 // return net;
833 //}
834
835 return nullptr;
836}
837
838
839bool SPRINT_LAYOUT_PARSER::CreateBoard( BOARD& aBoard, std::map<wxString, std::unique_ptr<FOOTPRINT>>& aFootprintMap,
840 size_t aBoardIndex )
841{
842 if( aBoardIndex >= m_fileData.boards.size() )
843 return false;
844
845 BOARD* board = &aBoard;
846
847 // Set up copper layers based on whether inner layers are used
848 const SPRINT_LAYOUT::BOARD_DATA& boardData = m_fileData.boards[aBoardIndex];
849 bool hasInnerLayers = false;
850
851 for( const SPRINT_LAYOUT::OBJECT& obj : boardData.objects )
852 {
854 {
855 hasInnerLayers = true;
856 break;
857 }
858 }
859
860 if( hasInnerLayers || boardData.is_multilayer )
861 board->SetCopperLayerCount( 4 );
862 else
863 board->SetCopperLayerCount( 2 );
864
865 // Create ground plane zones for layers where ground plane is enabled.
866 // Sprint Layout stores a per-layer flag in the board header.
867 const wxString gndPlaneNetName( "GND_PLANE" );
868 std::map<int, PCB_LAYER_ID> groundPlaneMap;
869 LSET groundPlaneLayerSet;
870 NETINFO_ITEM* gndPlaneNet = nullptr;
871
872 if( m_fileData.version >= 5 )
873 {
874 groundPlaneMap = {
875 { 0, F_Cu },
876 { 2, B_Cu },
877 { 4, In1_Cu },
878 { 5, In2_Cu },
879 };
880 }
881 else
882 {
883 groundPlaneMap = {
884 { 0, F_Cu },
885 { 1, B_Cu },
886 };
887 }
888
889 for( const auto& [index, layer] : groundPlaneMap )
890 {
891 if( boardData.ground_plane[index] != 0 )
892 groundPlaneLayerSet.set( layer );
893 }
894
895 if( !groundPlaneLayerSet.empty() )
896 {
897 int w = sprintToKicadCoord( static_cast<float>( boardData.size_x ) );
898 int h = sprintToKicadCoord( static_cast<float>( boardData.size_y ) );
899
900 gndPlaneNet = new NETINFO_ITEM( board, gndPlaneNetName );
901 board->Add( gndPlaneNet );
902
903 ZONE* zone = new ZONE( board );
904 zone->SetLayerSet( groundPlaneLayerSet );
905 zone->SetIsRuleArea( false );
906 zone->SetZoneName( wxS( "GND_PLANE" ) );
907 zone->SetLocalClearance( std::optional<int>( pcbIUScale.mmToIU( 0.3 ) ) );
908 zone->SetThermalReliefGap( pcbIUScale.mmToIU( 0.5 ) );
909 zone->SetThermalReliefSpokeWidth( pcbIUScale.mmToIU( 0.5 ) );
910 zone->SetAssignedPriority( 0 );
912 zone->SetNet( gndPlaneNet );
913
914 SHAPE_POLY_SET outline( BOX2D( VECTOR2D( 0, 0 ), VECTOR2D( w, h ) ) );
915 zone->AddPolygon( outline.COutline( 0 ) );
917
918 board->Add( zone );
919 }
920
921 // Maps component_id to FOOTPRINT for grouping component-owned objects
922 std::map<uint16_t, FOOTPRINT*> componentMap;
923 std::vector<std::vector<VECTOR2I>> outlineSegments;
924
925 auto getOrCreateComponentFootprint = [&]( const SPRINT_LAYOUT::OBJECT& aObj ) -> FOOTPRINT*
926 {
927 if( aObj.component_id == 0 )
928 return nullptr;
929
930 auto it = componentMap.find( aObj.component_id );
931
932 if( it != componentMap.end() )
933 return it->second;
934
935 FOOTPRINT* fp = new FOOTPRINT( board );
936
937 if( aObj.type == SPRINT_LAYOUT::OBJ_STROKE_TEXT && !aObj.text.empty() )
938 {
939 fp->SetReference( convertString( aObj.text ) );
940 }
941 else
942 {
943 fp->SetReference( wxString::Format( wxS( "U%d" ), aObj.component_id ) );
944
945 for( PCB_FIELD* fd : fp->GetFields() )
946 fd->SetVisible( false );
947 }
948
949 if( aObj.type == SPRINT_LAYOUT::OBJ_STROKE_TEXT && aObj.component.valid )
950 {
951 if( !aObj.component.comment.empty() )
952 {
953 wxString comment = convertString( aObj.component.comment );
954 fp->GetField( FIELD_T::DESCRIPTION )->SetText( comment );
955 fp->SetValue( comment );
956 }
957 else if( !aObj.identifier.empty() )
958 {
959 fp->SetValue( convertString( aObj.identifier ) );
960 }
961
962 if( !aObj.component.package.empty() )
963 fp->SetLibDescription( convertString( aObj.component.package ) );
964
965 fp->SetOrientationDegrees( aObj.component.rotation );
966 }
967
968 PCB_LAYER_ID layer = mapLayer( aObj.layer );
969 fp->SetLayer( ( layer == B_Cu || layer == B_SilkS ) ? B_Cu : F_Cu );
970
971 componentMap[aObj.component_id] = fp;
972 board->Add( fp );
973 return fp;
974 };
975
976 // First pass: create footprints from component text records where available
977 for( const SPRINT_LAYOUT::OBJECT& obj : boardData.objects )
978 {
979 if( obj.type == SPRINT_LAYOUT::OBJ_STROKE_TEXT && obj.component_id > 0 && obj.component.valid )
980 getOrCreateComponentFootprint( obj );
981 }
982
983 std::map<uint32_t, std::set<BOARD_ITEM*>> gidToItems;
984
985 // Second pass: process all objects in board/footprint context
986 for( const SPRINT_LAYOUT::OBJECT& obj : boardData.objects )
987 {
988 BOARD_ITEM_CONTAINER* container = board;
989
990 if( FOOTPRINT* fp = getOrCreateComponentFootprint( obj ) )
991 container = fp;
992
993 // clang-format off
994 switch( obj.type )
995 {
998 processPad( container, obj, boardData.ground_plane, gndPlaneNet, gidToItems );
999 break;
1000
1002 processSegment( container, obj, outlineSegments, boardData.ground_plane, gndPlaneNet, gidToItems );
1003 break;
1004
1006 processLine( container, obj, outlineSegments, boardData.ground_plane, gndPlaneNet, gidToItems );
1007 break;
1008
1010 processPoly( container, obj, outlineSegments, boardData.ground_plane, gndPlaneNet, gidToItems );
1011 break;
1012
1014 processCircle( container, obj, outlineSegments, boardData.ground_plane, gndPlaneNet, gidToItems );
1015 break;
1016
1019 processText( container, obj, gidToItems );
1020 break;
1021
1022 default:
1023 break;
1024 }
1025 // clang-format on
1026 }
1027
1028 resolveGroups( board, gidToItems );
1029
1030 // Re-anchor footprints after all elements are added.
1031 for( FOOTPRINT* fp : board->Footprints() )
1032 {
1033 BOX2I fpBbox = fp->GetBoundingHull().BBox();
1034
1035 VECTOR2I anchor = fpBbox.GetCenter();
1036 fp->SetPosition( anchor );
1037
1038 VECTOR2I anchorShift( -anchor.x, -anchor.y );
1039 RotatePoint( anchorShift, fp->GetOrientation() );
1040 fp->MoveAnchorPosition( anchorShift );
1041 }
1042
1043 // Fill the footprint map
1044 for( const auto& [componentId, fp] : componentMap )
1045 {
1046 wxString fpKey = wxString::Format( wxS( "SprintLayout_%s" ), fp->GetReference() );
1047 FOOTPRINT* fpCopy = static_cast<FOOTPRINT*>( fp->Clone() );
1048 fpCopy->SetParent( nullptr );
1049 aFootprintMap[fpKey] = std::unique_ptr<FOOTPRINT>( fpCopy );
1050 }
1051
1052 buildOutline( board, outlineSegments, boardData );
1053
1054 // Center the board content on the page
1055 BOX2I bbox = board->ComputeBoundingBox( true );
1056
1057 if( bbox.GetWidth() > 0 && bbox.GetHeight() > 0 )
1058 {
1059 VECTOR2I pageSize = board->GetPageSettings().GetSizeIU( pcbIUScale.IU_PER_MILS );
1060 VECTOR2I centerOffset = VECTOR2I( pageSize.x / 2, pageSize.y / 2 ) - bbox.GetCenter();
1061
1062 for( FOOTPRINT* fp : board->Footprints() )
1063 fp->Move( centerOffset );
1064
1065 for( ZONE* zone : board->Zones() )
1066 zone->Move( centerOffset );
1067
1068 for( BOARD_ITEM* item : board->Drawings() )
1069 item->Move( centerOffset );
1070 }
1071
1072 return true;
1073}
1074
1075
1076std::unique_ptr<FOOTPRINT> SPRINT_LAYOUT_PARSER::CreateFootprint()
1077{
1078 if( m_fileData.boards.empty() )
1079 return nullptr;
1080
1081 const SPRINT_LAYOUT::BOARD_DATA& boardData = m_fileData.boards[0];
1082
1083 std::unique_ptr<FOOTPRINT> fp = std::make_unique<FOOTPRINT>( nullptr );
1084
1085 wxString fpName = convertString( boardData.name );
1086
1087 fp->SetFPID( LIB_ID( wxEmptyString, fpName ) );
1088 fp->SetReference( wxT( "REF**" ) );
1089 fp->SetValue( fpName );
1090 fp->Reference().SetVisible( true );
1091 fp->Value().SetVisible( true );
1092
1093 std::vector<std::vector<VECTOR2I>> outlineSegments;
1094 uint8_t groundPlane[7] = {};
1095 std::map<uint32_t, std::set<BOARD_ITEM*>> gidToItems;
1096
1097 for( const SPRINT_LAYOUT::OBJECT& obj : boardData.objects )
1098 {
1099 BOARD_ITEM_CONTAINER* container = fp.get();
1100
1101 // clang-format off
1102 switch( obj.type )
1103 {
1106 processPad( container, obj, groundPlane, nullptr, gidToItems );
1107 break;
1108
1110 processSegment( container, obj, outlineSegments, groundPlane, nullptr, gidToItems );
1111 break;
1112
1114 processLine( container, obj, outlineSegments, groundPlane, nullptr, gidToItems );
1115 break;
1116
1118 processPoly( container, obj, outlineSegments, groundPlane, nullptr, gidToItems );
1119 break;
1120
1122 processCircle( container, obj, outlineSegments, groundPlane, nullptr, gidToItems );
1123 break;
1124
1127 processText( container, obj, gidToItems );
1128 break;
1129
1130 default:
1131 break;
1132 }
1133 // clang-format on
1134 }
1135
1136 resolveGroups( fp.get(), gidToItems );
1137
1138 fp->AutoPositionFields();
1139
1140 // Generate basic courtyard rectangle
1141 BOX2I bbox = fp->GetBoundingHull().BBox();
1142 bbox.Inflate( pcbIUScale.mmToIU( 0.25 ) ); // Default courtyard clearance
1143
1144 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( fp.get(), SHAPE_T::RECTANGLE );
1145 shape->SetWidth( pcbIUScale.mmToIU( DEFAULT_COURTYARD_WIDTH ) );
1146 shape->SetLayer( F_CrtYd );
1147 shape->SetStart( bbox.GetOrigin() );
1148 shape->SetEnd( bbox.GetEnd() );
1149
1150 fp->Add( shape.release(), ADD_MODE::APPEND );
1151
1152 return fp;
1153}
1154
1155
1157 const uint8_t aGroundPlane[7], NETINFO_ITEM* aGndPlaneNet,
1158 std::map<uint32_t, std::set<BOARD_ITEM*>>& aGidToItems )
1159{
1160 BOARD* board = aContainer ? aContainer->GetBoard() : nullptr;
1161 FOOTPRINT* fp = dynamic_cast<FOOTPRINT*>( aContainer );
1162 bool standaloneFp = false;
1163
1164 if( !fp )
1165 {
1166 // Standalone pad without a component gets its own footprint
1167 standaloneFp = true;
1168 fp = new FOOTPRINT( board );
1169 fp->SetReference( wxString::Format( wxS( "PAD%d" ), static_cast<int>( board->Footprints().size() ) ) );
1170 fp->Reference().SetVisible( false );
1171 fp->SetLayer( F_Cu );
1172 aContainer->Add( fp );
1173 }
1174
1175 PAD* pad = new PAD( fp );
1176
1177 // SMD pad x,y may be a component-relative offset rather than an absolute
1178 // position (depends on the Sprint Layout version that created the file).
1179 // The points array always stores absolute coordinates, so derive the pad
1180 // center from the points when available.
1181 // The rotation field for pads (both TH and SMD) is unknown, so detect
1182 // the pad angle from the points when possible.
1183 VECTOR2I ptsCenter;
1184 EDA_ANGLE ptsAngle;
1185
1186 if( !aObj.points.empty() )
1187 {
1188 double cx = 0, cy = 0;
1189
1190 for( const auto& pt : aObj.points )
1191 {
1192 cx += pt.x;
1193 cy += pt.y;
1194 }
1195
1196 cx /= static_cast<double>( aObj.points.size() );
1197 cy /= static_cast<double>( aObj.points.size() );
1198 ptsCenter = sprintToKicadPos( static_cast<float>( cx ), static_cast<float>( cy ) );
1199
1200 std::vector<VECTOR2I> pts;
1201
1202 for( const SPRINT_LAYOUT::POINT& pt : aObj.points )
1203 pts.emplace_back( sprintToKicadPos( pt.x, pt.y ) );
1204
1205 if( pts.size() == 2 ) // Oval or circle
1206 {
1207 ptsAngle = EDA_ANGLE( pts[1] - pts[0] );
1208
1210 ptsAngle -= ANGLE_90;
1211 }
1212 else if( pts.size() == 4 ) // Rectangular
1213 {
1214 ptsAngle = EDA_ANGLE( pts[1] - pts[0] );
1215 }
1216 else if( pts.size() == 8 ) // Octagonal
1217 {
1218 ptsAngle = EDA_ANGLE( pts[2] - pts[1] );
1219 }
1220 else
1221 {
1222 wxFAIL_MSG( wxString::Format( "Unknown pad type %d shape %d with %zu points", int( aObj.type ),
1223 int( aObj.tht_shape ), aObj.points.size() ) );
1224 }
1225
1226 ptsAngle = ptsAngle.Round( 2 );
1227 }
1228
1229 PCB_LAYER_ID padLayer = mapLayer( aObj.layer );
1230 VECTOR2I padPos = sprintToKicadPos( aObj.x, aObj.y );
1231
1232 if( aObj.type == SPRINT_LAYOUT::OBJ_THT_PAD )
1233 {
1234 int outerDia = sprintToKicadCoord( aObj.outer * 2.0f );
1235 int drillDia = sprintToKicadCoord( aObj.inner * 2.0f );
1236
1237 bool isPTH = aObj.plated != 0 || ( m_fileData.version <= 3 && aObj.layer == 5 );
1238
1239 if( isPTH )
1240 {
1241 pad->SetAttribute( PAD_ATTRIB::PTH );
1242 pad->SetLayerSet( PAD::PTHMask() );
1243 }
1244 else
1245 {
1246 pad->SetAttribute( drillDia > 0 ? PAD_ATTRIB ::NPTH : PAD_ATTRIB::SMD );
1247
1248 if( padLayer == F_Cu )
1249 pad->SetLayerSet( LSET( { F_Cu, F_Mask } ) );
1250 else if( padLayer == B_Cu )
1251 pad->SetLayerSet( LSET( { B_Cu, B_Mask } ) );
1252 else
1253 pad->SetLayerSet( LSET( { padLayer } ) );
1254
1255 if( standaloneFp && IsBackLayer( padLayer ) )
1256 fp->SetLayer( B_Cu );
1257 }
1258
1259 VECTOR2I padSize( outerDia, outerDia );
1260 VECTOR2I drillSize( drillDia, drillDia );
1261
1262 switch( aObj.tht_shape )
1263 {
1267 padSize.x *= 2;
1268 break;
1269
1273 padSize.y *= 2;
1274 break;
1275
1276 default: break;
1277 }
1278
1279 pad->SetSize( PADSTACK::TEMP_ALL_LAYERS, padSize );
1280 pad->SetDrillSize( drillSize );
1281
1282 switch( aObj.tht_shape )
1283 {
1286 break;
1287
1291 break;
1292
1297 pad->SetChamferRectRatio( PADSTACK::TEMP_ALL_LAYERS, 0.25 );
1298 pad->SetChamferPositions( PADSTACK::TEMP_ALL_LAYERS,
1300 break;
1301
1306 break;
1307
1308 default:
1310 break;
1311 }
1312
1313 pad->SetPosition( padPos );
1314 pad->Rotate( padPos, -ptsAngle );
1315 }
1316 else
1317 {
1318 pad->SetAttribute( PAD_ATTRIB::SMD );
1319
1320 if( padLayer == F_Cu )
1321 pad->SetLayerSet( LSET( { F_Cu, F_Paste, F_Mask } ) );
1322 else if( padLayer == B_Cu )
1323 pad->SetLayerSet( LSET( { B_Cu, B_Paste, B_Mask } ) );
1324 else
1325 pad->SetLayerSet( LSET( { padLayer } ) );
1326
1327 if( standaloneFp && IsBackLayer( padLayer ) )
1328 fp->SetLayer( B_Cu );
1329
1331
1332 int width = sprintToKicadCoord( aObj.outer );
1333 int height = sprintToKicadCoord( aObj.inner );
1334
1335 if( height <= 0 )
1336 height = width;
1337
1338 if( !aObj.points.empty() )
1339 padPos = ptsCenter;
1340
1341 pad->SetSize( PADSTACK::TEMP_ALL_LAYERS, VECTOR2I( width, height ) );
1342 pad->SetPosition( padPos );
1343 pad->Rotate( padPos, -ptsAngle );
1344 }
1345
1346 // Solder mask: soldermask==0 means no mask opening (pad is tented/covered)
1347 if( aObj.soldermask == 0 )
1348 {
1349 pad->Padstack().FrontOuterLayers().has_solder_mask = false;
1350 pad->Padstack().BackOuterLayers().has_solder_mask = false;
1351 }
1352
1353 // Per-element ground plane clearance
1354 if( aObj.clearance > 0 )
1355 {
1356 int clearance = sprintToKicadCoord( static_cast<float>( aObj.clearance ) );
1357 pad->SetLocalClearance( std::optional<int>( clearance ) );
1358 pad->SetLocalThermalGapOverride( std::optional<int>( clearance ) );
1359 }
1360
1361 // Thermal reliefs
1362 if( m_fileData.version >= 5 && aObj.mirror_h != 0 )
1363 {
1364 int spokeWidth = aObj.rotation * 10000 / 2;
1365 pad->SetLocalThermalSpokeWidthOverride( spokeWidth );
1366
1367 // Each byte is the spoke directions for one copper layer (C1, C2, I1, I2).
1368 // 0x55 matches H/V directions, 0xAA matches diagonal directions
1369 uint32_t spokeMask = static_cast<uint32_t>( aObj.start_angle );
1370
1371 if( spokeMask != 0 )
1372 {
1373 pad->SetLocalZoneConnection( ZONE_CONNECTION::THERMAL );
1374
1375 if( spokeMask & 0x55555555 )
1376 pad->SetThermalSpokeAngle( ANGLE_90 );
1377 else if( spokeMask & 0xAAAAAAAA )
1378 pad->SetThermalSpokeAngle( ANGLE_45 );
1379 }
1380 else
1381 {
1382 pad->SetLocalZoneConnection( ZONE_CONNECTION::NONE );
1383 }
1384 }
1385 else
1386 {
1387 pad->SetLocalZoneConnection( ZONE_CONNECTION::FULL );
1388 pad->SetThermalSpokeAngle( ANGLE_90 );
1389 }
1390
1391 // Set net name. Plated THT pads span all copper layers, so accept the GND
1392 // default when any configured ground-plane layer is enabled; SMD and NPTH
1393 // pads only qualify on their own layer.
1394 PCB_LAYER_ID netLayer = padLayer;
1395
1396 if( aObj.type == SPRINT_LAYOUT::OBJ_THT_PAD && aObj.plated != 0
1397 && !layerHasGroundPlane( padLayer, aGroundPlane ) )
1398 {
1399 if( aGroundPlane[0] )
1400 netLayer = F_Cu;
1401 else if( aGroundPlane[2] )
1402 netLayer = B_Cu;
1403 else if( aGroundPlane[4] )
1404 netLayer = In1_Cu;
1405 else if( aGroundPlane[5] )
1406 netLayer = In2_Cu;
1407 }
1408
1409 if( NETINFO_ITEM* net = resolveItemNet( board, aObj, netLayer, aGroundPlane, aGndPlaneNet ) )
1410 pad->SetNet( net );
1411
1412 pad->SetNumber( wxString::Format( wxS( "%d" ), static_cast<int>( fp->Pads().size() + 1 ) ) );
1413
1414 fp->Add( pad );
1415
1416 if( standaloneFp )
1417 {
1418 for( PCB_FIELD* fd : fp->GetFields() )
1419 fd->SetTextPos( pad->GetPosition() );
1420
1421 processItemGroups( fp, aObj, aGidToItems );
1422 }
1423 else
1424 {
1425 processItemGroups( pad, aObj, aGidToItems );
1426 }
1427}
1428
1429
1431 std::vector<std::vector<VECTOR2I>>& aOutlineSegments,
1432 const uint8_t aGroundPlane[7], NETINFO_ITEM* aGndPlaneNet,
1433 std::map<uint32_t, std::set<BOARD_ITEM*>>& aGidToItems )
1434{
1435 if( aObj.points.size() < 2 )
1436 return;
1437
1438 BOARD* board = aContainer ? aContainer->GetBoard() : nullptr;
1439 PCB_LAYER_ID layer = mapLayer( aObj.layer );
1440
1441 if( layer == Edge_Cuts )
1442 {
1443 std::vector<VECTOR2I> segment;
1444
1445 for( const auto& pt : aObj.points )
1446 segment.push_back( sprintToKicadPos( pt.x, pt.y ) );
1447
1448 aOutlineSegments.push_back( std::move( segment ) );
1449 return;
1450 }
1451
1452 int width = sprintToKicadCoord( static_cast<float>( aObj.line_width ) );
1453
1454 if( width <= 0 )
1455 width = pcbIUScale.mmToIU( 0.25 );
1456
1457 for( size_t i = 0; i + 1 < aObj.points.size(); i++ )
1458 {
1459 PCB_SHAPE* shape = new PCB_SHAPE( aContainer );
1460 shape->SetShape( SHAPE_T::SEGMENT );
1461 shape->SetLayer( layer );
1462 shape->SetWidth( width );
1463 shape->SetStart( sprintToKicadPos( aObj.points[i].x, aObj.points[i].y ) );
1464 shape->SetEnd( sprintToKicadPos( aObj.points[i + 1].x, aObj.points[i + 1].y ) );
1465
1466 if( IsCopperLayer( layer ) )
1467 {
1468 if( NETINFO_ITEM* net = resolveItemNet( board, aObj, layer, aGroundPlane, aGndPlaneNet ) )
1469 shape->SetNet( net );
1470 }
1471
1472 aContainer->Add( shape );
1473 processItemGroups( shape, aObj, aGidToItems );
1474 }
1475}
1476
1477
1479 std::vector<std::vector<VECTOR2I>>& aOutlineSegments,
1480 const uint8_t aGroundPlane[7], NETINFO_ITEM* aGndPlaneNet,
1481 std::map<uint32_t, std::set<BOARD_ITEM*>>& aGidToItems )
1482{
1483 PCB_LAYER_ID layer = mapLayer( aObj.layer );
1484
1485 if( layer == Edge_Cuts )
1486 {
1487 std::vector<VECTOR2I> seg;
1488 seg.push_back( sprintToKicadPos( aObj.x, aObj.y ) );
1489 seg.push_back( sprintToKicadPos( aObj.outer, aObj.inner ) );
1490 aOutlineSegments.push_back( std::move( seg ) );
1491 return;
1492 }
1493
1494 VECTOR2I start = sprintToKicadPos( aObj.x, aObj.y );
1495 VECTOR2I end = sprintToKicadPos( aObj.outer, aObj.inner );
1496 int width = sprintToKicadCoord( static_cast<float>( aObj.line_width ) );
1497
1498 // Skip the dummy segment at 0,0 in version 1 files
1499 if( aObj.line_width == 0 && start == end )
1500 return;
1501
1502 PCB_SHAPE* shape = new PCB_SHAPE( aContainer );
1503 shape->SetShape( SHAPE_T::SEGMENT );
1504 shape->SetLayer( layer );
1505 shape->SetWidth( width );
1506 shape->SetStart( start );
1507 shape->SetEnd( end );
1508
1509 aContainer->Add( shape );
1510 processItemGroups( shape, aObj, aGidToItems );
1511}
1512
1513
1515 std::vector<std::vector<VECTOR2I>>& aOutlineSegments,
1516 const uint8_t aGroundPlane[7], NETINFO_ITEM* aGndPlaneNet,
1517 std::map<uint32_t, std::set<BOARD_ITEM*>>& aGidToItems )
1518{
1519 if( aObj.points.size() < 2 )
1520 return;
1521
1522 BOARD* board = aContainer ? aContainer->GetBoard() : nullptr;
1523 PCB_LAYER_ID layer = mapLayer( aObj.layer );
1524
1525 if( layer == Edge_Cuts )
1526 {
1527 std::vector<VECTOR2I> points;
1528
1529 for( const auto& pt : aObj.points )
1530 points.push_back( sprintToKicadPos( pt.x, pt.y ) );
1531
1532 points.push_back( points[0] );
1533
1534 aOutlineSegments.push_back( std::move( points ) );
1535 return;
1536 }
1537
1538 //bool isFilled = ( aObj.filled != 0 );
1539 bool isCutout = ( aObj.keepout != 0 );
1540
1541 int width = sprintToKicadCoord( static_cast<float>( aObj.line_width ) );
1542
1543 if( width < 0 )
1544 width = pcbIUScale.mmToIU( 0.25 );
1545
1546 SHAPE_LINE_CHAIN outline;
1547
1548 for( const auto& pt : aObj.points )
1549 {
1550 VECTOR2I pos = sprintToKicadPos( pt.x, pt.y );
1551 outline.Append( pos.x, pos.y );
1552 }
1553
1554 outline.SetClosed( true ); // Deduplicate the last point properly
1555
1556 if( isCutout && IsCopperLayer( layer ) && aObj.points.size() >= 3 )
1557 {
1558 // Cutout area for ground plane exclusion -> rule area (keepout zone)
1559 ZONE* zone = new ZONE( aContainer );
1560 zone->SetLayer( layer );
1561 zone->SetIsRuleArea( true );
1562 zone->SetDoNotAllowZoneFills( true );
1563 zone->SetDoNotAllowTracks( false );
1564 zone->SetDoNotAllowVias( false );
1565 zone->SetDoNotAllowPads( false );
1566 zone->SetDoNotAllowFootprints( false );
1567
1568 zone->AddPolygon( outline );
1569 aContainer->Add( zone );
1570 processItemGroups( zone, aObj, aGidToItems );
1571 }
1572 else if( aObj.points.size() >= 3 )
1573 {
1574 // Filled polygon on non-copper layer -> filled PCB_SHAPE
1575 PCB_SHAPE* shape = new PCB_SHAPE( aContainer );
1576 shape->SetShape( SHAPE_T::POLY );
1577 shape->SetFilled( true );
1578 shape->SetLayer( layer );
1579 shape->SetWidth( width );
1580
1581 shape->SetPolyShape( SHAPE_POLY_SET( outline ) );
1582
1583 if( NETINFO_ITEM* net = resolveItemNet( board, aObj, layer, aGroundPlane, aGndPlaneNet ) )
1584 shape->SetNet( net );
1585
1586 aContainer->Add( shape );
1587 processItemGroups( shape, aObj, aGidToItems );
1588 }
1589}
1590
1591
1593 std::vector<std::vector<VECTOR2I>>& aOutlineSegments,
1594 const uint8_t aGroundPlane[7], NETINFO_ITEM* aGndPlaneNet,
1595 std::map<uint32_t, std::set<BOARD_ITEM*>>& aGidToItems )
1596{
1597 BOARD* board = aContainer ? aContainer->GetBoard() : nullptr;
1598 PCB_LAYER_ID layer = mapLayer( aObj.layer );
1599 VECTOR2I center = sprintToKicadPos( aObj.x, aObj.y );
1600 float radius = ( aObj.outer + aObj.inner ) / 2.0f;
1601 int kiRadius = sprintToKicadCoord( radius );
1602 int width = sprintToKicadCoord( aObj.outer - aObj.inner );
1603
1604 if( width <= 0 )
1605 width = pcbIUScale.mmToIU( 0.25 );
1606
1607 bool isFullCircle = true;
1608 double startAngleDeg = 0, endAngleDeg = 0;
1609
1610 if( m_fileData.version >= 3 )
1611 {
1612 startAngleDeg = aObj.start_angle;
1613 endAngleDeg = aObj.line_width;
1614
1615 if( m_fileData.version >= 6 )
1616 {
1617 // There's nothing else in the format to specify the angle scale
1618 // It's either in 1 degree or 0.001 degree units
1619 if( startAngleDeg > 1000 || startAngleDeg < -1000 || endAngleDeg > 1000 || endAngleDeg < -1000 )
1620 {
1621 startAngleDeg /= 1000;
1622 endAngleDeg /= 1000;
1623 }
1624 }
1625 // Older versions always use 1 degree units
1626
1627 isFullCircle = ( startAngleDeg == 0 && endAngleDeg == 0 )
1628 || ( endAngleDeg - startAngleDeg >= 360 )
1629 || ( startAngleDeg == endAngleDeg );
1630 }
1631 // Older versions do not have arcs
1632
1633 if( layer == Edge_Cuts )
1634 {
1635 // Approximate arcs as line segments for outline reconstruction
1636 std::vector<VECTOR2I> segment;
1637
1638 if( isFullCircle )
1639 {
1640 for( int i = 0; i <= 24; i++ )
1641 {
1642 double angle = ( static_cast<double>( i ) / 24.0 ) * 2.0 * M_PI;
1643 int px = center.x + static_cast<int>( std::cos( angle ) * kiRadius );
1644 int py = center.y - static_cast<int>( std::sin( angle ) * kiRadius );
1645 segment.emplace_back( px, py );
1646 }
1647 }
1648 else
1649 {
1650 int32_t sa = startAngleDeg * 1000;
1651 int32_t ea = endAngleDeg * 1000;
1652
1653 if( ea <= sa )
1654 ea += 360000;
1655
1656 for( int32_t a = sa; a <= ea; a += 15000 )
1657 {
1658 double rad = ( static_cast<double>( a ) / 1000.0 ) * M_PI / 180.0;
1659 int px = center.x + static_cast<int>( std::cos( rad ) * kiRadius );
1660 int py = center.y - static_cast<int>( std::sin( rad ) * kiRadius );
1661 segment.emplace_back( px, py );
1662 }
1663
1664 double endRad = ( static_cast<double>( ea ) / 1000.0 ) * M_PI / 180.0;
1665 int epx = center.x + static_cast<int>( std::cos( endRad ) * kiRadius );
1666 int epy = center.y - static_cast<int>( std::sin( endRad ) * kiRadius );
1667 segment.emplace_back( epx, epy );
1668 }
1669
1670 aOutlineSegments.push_back( std::move( segment ) );
1671 return;
1672 }
1673
1674 PCB_SHAPE* shape = new PCB_SHAPE( aContainer );
1675 shape->SetLayer( layer );
1676 shape->SetWidth( width );
1677
1678 if( isFullCircle )
1679 {
1680 shape->SetShape( SHAPE_T::CIRCLE );
1681 shape->SetCenter( center );
1682 shape->SetEnd( VECTOR2I( center.x + kiRadius, center.y ) );
1683 }
1684 else
1685 {
1686 shape->SetShape( SHAPE_T::ARC );
1687 shape->SetCenter( center );
1688
1689 // Y-flip reverses angular direction, so negate start angle
1690 double startRad = startAngleDeg * M_PI / 180.0;
1691 int sx = center.x + static_cast<int>( std::cos( startRad ) * kiRadius );
1692 int sy = center.y - static_cast<int>( std::sin( startRad ) * kiRadius );
1693 shape->SetStart( VECTOR2I( sx, sy ) );
1694
1695 double newEndAngle = endAngleDeg;
1696
1697 if( newEndAngle < startAngleDeg )
1698 newEndAngle += 360;
1699
1700 // Negate arc angle for Y-flip (reverses sweep direction)
1701 double arcAngle = startAngleDeg - newEndAngle;
1702 shape->SetArcAngleAndEnd( EDA_ANGLE( arcAngle, DEGREES_T ), true );
1703 }
1704
1705 if( IsCopperLayer( layer ) )
1706 {
1707 if( NETINFO_ITEM* net = resolveItemNet( board, aObj, layer, aGroundPlane, aGndPlaneNet ) )
1708 shape->SetNet( net );
1709 }
1710
1711 aContainer->Add( shape );
1712 processItemGroups( shape, aObj, aGidToItems );
1713}
1714
1715
1717 std::map<uint32_t, std::set<BOARD_ITEM*>>& aGidToItems )
1718{
1719 FOOTPRINT* fp = dynamic_cast<FOOTPRINT*>( aContainer );
1720
1721 // Skip component reference/value text only when it is not attached to a footprint.
1722 if( aObj.component_id > 0 && !fp )
1723 return;
1724
1725 PCB_LAYER_ID layer = mapLayer( aObj.layer );
1726
1727 if( layer == Edge_Cuts )
1728 return;
1729
1730 PCB_TEXT* text = nullptr;
1731 bool add = false;
1732
1733 if( aObj.component_id > 0 && fp && aObj.type == SPRINT_LAYOUT::OBJ_STROKE_TEXT && aObj.tht_shape > 0
1734 && aObj.tht_shape <= 2 )
1735 {
1736 if( aObj.tht_shape == 1 )
1737 text = &fp->Reference();
1738 else if( aObj.tht_shape == 2 )
1739 text = &fp->Value();
1740 }
1741 else
1742 {
1743 if( aObj.text.empty() )
1744 return;
1745
1746 text = new PCB_TEXT( aContainer );
1747 add = true;
1748 }
1749
1750 if( !text )
1751 return;
1752
1753 // When inside a group, the rotation center seems to be at the group center.
1754 // Just so we don't have to do a complex fixup later, use points to detect
1755 // text center instead, they are always in absolute coordinates.
1756 VECTOR2I ptsCenter;
1757
1758 if( !aObj.text_children.empty() )
1759 {
1760 double cx = 0, cy = 0;
1761 size_t ptsCount = 0;
1762
1763 for( const SPRINT_LAYOUT::OBJECT& child : aObj.text_children )
1764 {
1765 if( child.type == SPRINT_LAYOUT::OBJ_LINE )
1766 {
1767 for( const SPRINT_LAYOUT::POINT& pt : child.points )
1768 {
1769 cx += pt.x;
1770 cy += pt.y;
1771 ptsCount += 1;
1772 }
1773 }
1774 else if( child.type == SPRINT_LAYOUT::OBJ_SEGMENT )
1775 {
1776 cx += child.x;
1777 cy += child.y;
1778 cx += child.outer;
1779 cy += child.inner;
1780 ptsCount += 2;
1781 }
1782 }
1783
1784 // Skip centering when the group contributed no points
1785 if( ptsCount > 0 )
1786 {
1787 cx /= static_cast<double>( ptsCount );
1788 cy /= static_cast<double>( ptsCount );
1789 ptsCenter = sprintToKicadPos( static_cast<float>( cx ), static_cast<float>( cy ) );
1790 }
1791 }
1792
1793 text->SetLayer( layer );
1794 text->SetText( convertString( aObj.text ) );
1795 text->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
1796 text->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM );
1797 text->SetKeepUpright( false );
1798 text->SetVisible( aObj.component_id == 0 || aObj.filled != 0 );
1799
1801 {
1802 int height = sprintToKicadCoord( aObj.outer ) * 0.75;
1803
1804 if( height <= 0 )
1805 height = pcbIUScale.mmToIU( 1.0 );
1806
1807 double widthScale = 0.8 + 0.2 * aObj.line_width;
1808 text->SetTextSize( VECTOR2I( height * widthScale, height ) );
1809
1810 double thicknessScale = 0.06 + 0.05 * aObj.inner;
1811 int thickness = height * thicknessScale;
1812
1813 if( thickness <= 0 )
1814 thickness = std::max( 1, height / 8 );
1815
1816 text->SetTextThickness( thickness );
1817 }
1818 else
1819 {
1820 // -133 maps to 1 mm height
1821 int normalized = std::abs( aObj.line_width ) * 100 / 133;
1822 int height = sprintToKicadCoord( normalized ) * 0.75;
1823
1824 if( aObj.line_width < 0 ) // Seems to be always
1825 text->SetVertJustify( GR_TEXT_V_ALIGN_TOP );
1826
1827 text->SetTextSize( VECTOR2I( height, height ) );
1828 text->SetTextThickness( height / 8 );
1829 }
1830
1831 VECTOR2I untransformedPos = sprintToKicadPos( aObj.x, aObj.y );
1832 text->SetTextPos( untransformedPos );
1833 VECTOR2I untransformedCenter = text->GetCenter();
1834
1835 VECTOR2I newCenter = !aObj.text_children.empty() ? ptsCenter : untransformedCenter;
1836 text->SetVertJustify( GR_TEXT_V_ALIGN_CENTER );
1837 text->SetHorizJustify( GR_TEXT_H_ALIGN_CENTER );
1838 text->SetTextPos( newCenter );
1839
1840 int rotation = 0;
1841
1842 if( m_fileData.version == 4 )
1843 rotation = aObj.start_angle * 90;
1844 else
1845 rotation = aObj.rotation;
1846
1847 bool mirrorH = aObj.mirror_h != 0;
1848 bool mirrorV = aObj.mirror_v != 0;
1849
1850 if( mirrorH ^ mirrorV )
1851 {
1852 text->SetMirrored( true );
1853 text->SetHorizJustify( (GR_TEXT_H_ALIGN_T) -text->GetHorizJustify() );
1854 rotation = -rotation;
1855 }
1856
1857 if( mirrorV )
1858 text->Rotate( newCenter, ANGLE_180 );
1859
1860 text->Rotate( newCenter, EDA_ANGLE( -rotation, DEGREES_T ) );
1861
1862 if( add )
1863 {
1864 aContainer->Add( text );
1865 processItemGroups( text, aObj, aGidToItems );
1866 }
1867}
1868
1869
1871 std::map<uint32_t, std::set<BOARD_ITEM*>>& aGidToItems )
1872{
1873 for( uint32_t gid : aObj.groups )
1874 aGidToItems[gid].insert( aItem );
1875}
1876
1877
1879 std::map<uint32_t, std::set<BOARD_ITEM*>>& aGidToItems )
1880{
1881 std::map<uint32_t, PCB_GROUP*> gidGroupMap;
1882 std::vector<uint32_t> gidAscBySize;
1883
1884 for( const auto& [gid, _] : aGidToItems )
1885 {
1886 PCB_GROUP* group = new PCB_GROUP( aContainer );
1887
1888 gidGroupMap[gid] = group;
1889 gidAscBySize.push_back( gid );
1890
1891 if( aContainer )
1892 aContainer->Add( group );
1893 }
1894
1895 // Process groups in ascending member-count order (tie-break by group id) so smaller
1896 // groups attach first and larger groups can adopt them, producing stable nesting.
1897 std::sort( gidAscBySize.begin(), gidAscBySize.end(),
1898 [&]( uint32_t gidA, uint32_t gidB )
1899 {
1900 size_t sa = aGidToItems.at( gidA ).size();
1901 size_t sb = aGidToItems.at( gidB ).size();
1902
1903 if( sa != sb )
1904 return sa < sb;
1905
1906 return gidA < gidB;
1907 } );
1908
1909 for( uint32_t gid : gidAscBySize )
1910 {
1911 PCB_GROUP* grp = gidGroupMap[gid];
1912
1913 for( BOARD_ITEM* item : aGidToItems[gid] )
1914 {
1915 if( PCB_GROUP* itemGroup = static_cast<PCB_GROUP*>( item->GetParentGroup() ) )
1916 {
1917 if( itemGroup != grp )
1918 grp->AddItem( itemGroup );
1919 }
1920 else
1921 {
1922 // Only add if we don't cross board-footprint boundaries
1923 if( item->GetParent() == grp->GetParent() )
1924 grp->AddItem( item );
1925 }
1926 }
1927 }
1928}
1929
1930
1931void SPRINT_LAYOUT_PARSER::buildOutline( BOARD* aBoard, std::vector<std::vector<VECTOR2I>>& aOutlineSegments,
1932 const SPRINT_LAYOUT::BOARD_DATA& aBoardData )
1933{
1934 // Try to join outline segments into closed polygons
1935 // Similar to OpenBoardView's outline_order_segments algorithm
1936 static const int PROXIMITY_DELTA = 100; // 100 nm tolerance
1937
1938 auto closeEnough = []( const VECTOR2I& a, const VECTOR2I& b, int delta ) -> bool
1939 {
1940 return std::abs( a.x - b.x ) < delta && std::abs( a.y - b.y ) < delta;
1941 };
1942
1943 // Try to join segments end-to-end. After each successful join, restart
1944 // the inner scan because seg.back() has changed.
1945 for( size_t iterations = 0; iterations < aOutlineSegments.size(); iterations++ )
1946 {
1947 bool joined = false;
1948
1949 for( auto& seg : aOutlineSegments )
1950 {
1951 if( seg.size() < 2 )
1952 continue;
1953
1954 for( auto& other : aOutlineSegments )
1955 {
1956 if( &seg == &other || other.empty() )
1957 continue;
1958
1959 bool frontMatch = closeEnough( seg.back(), other.front(), PROXIMITY_DELTA );
1960 bool backMatch = !frontMatch
1961 && closeEnough( seg.back(), other.back(), PROXIMITY_DELTA );
1962
1963 if( backMatch )
1964 {
1965 std::reverse( other.begin(), other.end() );
1966 frontMatch = true;
1967 }
1968
1969 if( !frontMatch )
1970 continue;
1971
1972 if( seg.back() == other.front() )
1973 seg.insert( seg.end(), other.begin() + 1, other.end() );
1974 else
1975 seg.insert( seg.end(), other.begin(), other.end() );
1976
1977 other.clear();
1978 joined = true;
1979 break;
1980 }
1981 }
1982
1983 if( !joined )
1984 break;
1985 }
1986
1987 bool hasOutline = false;
1988
1989 for( const auto& seg : aOutlineSegments )
1990 {
1991 if( seg.size() < 2 )
1992 continue;
1993
1994 hasOutline = true;
1995
1996 for( size_t i = 0; i + 1 < seg.size(); i++ )
1997 {
1998 PCB_SHAPE* shape = new PCB_SHAPE( aBoard );
1999 shape->SetShape( SHAPE_T::SEGMENT );
2000 shape->SetLayer( Edge_Cuts );
2001 shape->SetWidth( pcbIUScale.mmToIU( 0.1 ) );
2002 shape->SetStart( seg[i] );
2003 shape->SetEnd( seg[i + 1] );
2004 aBoard->Add( shape );
2005 }
2006 }
2007
2008 // Fallback: create rectangular outline from board dimensions
2009 if( !hasOutline )
2010 {
2011 int w = sprintToKicadCoord( static_cast<float>( aBoardData.size_x ) );
2012 int h = sprintToKicadCoord( static_cast<float>( aBoardData.size_y ) );
2013
2014 if( w > 0 && h > 0 )
2015 {
2016 VECTOR2I corners[4] = {
2017 { 0, 0 },
2018 { w, 0 },
2019 { w, h },
2020 { 0, h }
2021 };
2022
2023 for( int i = 0; i < 4; i++ )
2024 {
2025 PCB_SHAPE* shape = new PCB_SHAPE( aBoard );
2026 shape->SetShape( SHAPE_T::SEGMENT );
2027 shape->SetLayer( Edge_Cuts );
2028 shape->SetWidth( pcbIUScale.mmToIU( 0.1 ) );
2029 shape->SetStart( corners[i] );
2030 shape->SetEnd( corners[( i + 1 ) % 4] );
2031 aBoard->Add( shape );
2032 }
2033 }
2034 }
2035}
int index
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:121
#define DEFAULT_COURTYARD_WIDTH
BOX2< VECTOR2I > BOX2I
Definition box2.h:927
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:995
BOX2< VECTOR2D > BOX2D
Definition box2.h:928
BASE_SET & set(size_t pos)
Definition base_set.h:126
virtual void SetNet(NETINFO_ITEM *aNetInfo)
Set a NET_INFO object for the item.
Abstract interface for BOARD_ITEMs capable of storing other items inside.
virtual void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false)=0
Adds an item to the container.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:84
virtual const BOARD * GetBoard() const
Return the BOARD in which this BOARD_ITEM resides, or NULL if none.
BOARD_ITEM_CONTAINER * GetParent() const
Definition board_item.h:266
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:409
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
Definition board.cpp:1497
const PAGE_INFO & GetPageSettings() const
Definition board.h:1010
const ZONES & Zones() const
Definition board.h:467
const FOOTPRINTS & Footprints() const
Definition board.h:463
void SetCopperLayerCount(int aCount)
Definition board.cpp:1137
BOX2I ComputeBoundingBox(bool aBoardEdgesOnly=false, bool aPhysicalLayersOnly=false) const
Calculate the bounding box containing all board items (or board edge segments).
Definition board.cpp:2721
const DRAWINGS & Drawings() const
Definition board.h:465
constexpr BOX2< Vec > & Inflate(coord_type dx, coord_type dy)
Inflates the rectangle horizontally by dx and vertically by dy.
Definition box2.h:553
constexpr const Vec GetEnd() const
Definition box2.h:209
constexpr size_type GetWidth() const
Definition box2.h:211
constexpr const Vec GetCenter() const
Definition box2.h:227
constexpr size_type GetHeight() const
Definition box2.h:212
constexpr const Vec & GetOrigin() const
Definition box2.h:207
EDA_ANGLE Round(int digits) const
Definition eda_angle.h:303
void AddItem(EDA_ITEM *aItem)
Add item to group.
Definition eda_group.cpp:58
virtual void SetParent(EDA_ITEM *aParent)
Definition eda_item.cpp:153
void SetCenter(const VECTOR2I &aCenter)
virtual void SetFilled(bool aFlag)
Definition eda_shape.h:142
virtual void SetVisible(bool aVisible)
Definition eda_text.cpp:342
virtual void SetText(const wxString &aText)
Definition eda_text.cpp:231
PCB_FIELD & Value()
read/write accessors:
Definition footprint.h:939
void SetOrientationDegrees(double aOrientation)
Definition footprint.h:464
PCB_FIELD * GetField(FIELD_T aFieldType)
Return a mandatory field in this footprint.
std::deque< PAD * > & Pads()
Definition footprint.h:404
void SetReference(const wxString &aReference)
Definition footprint.h:907
void SetValue(const wxString &aValue)
Definition footprint.h:930
PCB_FIELD & Reference()
Definition footprint.h:940
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
void GetFields(std::vector< PCB_FIELD * > &aVector, bool aVisibleOnly) const
Populate a std::vector with PCB_TEXTs.
void SetLibDescription(const wxString &aDesc)
Definition footprint.h:491
A logical library item identifier and consists of various portions much like a URI.
Definition lib_id.h:45
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
Handle the data for a net.
Definition netinfo.h:50
static constexpr PCB_LAYER_ID TEMP_ALL_LAYERS
! Temporary layer identifier to identify code that is not padstack-aware
Definition padstack.h:176
Definition pad.h:61
static LSET PTHMask()
layer set for a through hole pad
Definition pad.cpp:606
const VECTOR2D GetSizeIU(double aIUScale) const
Gets the page size in internal units.
Definition page_info.h:173
A set of BOARD_ITEMs (i.e., without duplicates).
Definition pcb_group.h:51
void SetWidth(int aWidth) override
void SetArcAngleAndEnd(const EDA_ANGLE &aAngle, bool aCheckNegativeAngle=false)
Definition pcb_shape.h:107
void SetShape(SHAPE_T aShape) override
Definition pcb_shape.h:207
void SetEnd(const VECTOR2I &aEnd) override
void SetPolyShape(const SHAPE_POLY_SET &aShape) override
void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
void SetStart(const VECTOR2I &aStart) override
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.
const SHAPE_LINE_CHAIN & COutline(int aIndex) const
void processPoly(BOARD_ITEM_CONTAINER *aContainer, const SPRINT_LAYOUT::OBJECT &aObj, std::vector< std::vector< VECTOR2I > > &aOutlineSegments, const uint8_t aGroundPlane[7], NETINFO_ITEM *aGndPlaneNet, std::map< uint32_t, std::set< BOARD_ITEM * > > &aGidToItems)
std::unique_ptr< FOOTPRINT > CreateFootprint()
void parsePoints(SPRINT_LAYOUT::OBJECT &aObj)
wxString convertString(const std::string &aStr) const
void processCircle(BOARD_ITEM_CONTAINER *aContainer, const SPRINT_LAYOUT::OBJECT &aObj, std::vector< std::vector< VECTOR2I > > &aOutlineSegments, const uint8_t aGroundPlane[7], NETINFO_ITEM *aGndPlaneNet, std::map< uint32_t, std::set< BOARD_ITEM * > > &aGidToItems)
SPRINT_LAYOUT::FILE_DATA m_fileData
bool CreateBoard(BOARD &aBoard, std::map< wxString, std::unique_ptr< FOOTPRINT > > &aFootprintMap, size_t aBoardIndex=0)
PCB_LAYER_ID mapLayer(uint8_t aSprintLayer) const
void parseFileStart(const wxString &aFileName)
std::string readFixedString(size_t aMaxLen)
void processItemGroups(BOARD_ITEM *aItem, const SPRINT_LAYOUT::OBJECT &aObj, std::map< uint32_t, std::set< BOARD_ITEM * > > &aGidToItems)
NETINFO_ITEM * resolveItemNet(BOARD *aBoard, const SPRINT_LAYOUT::OBJECT &aObj, PCB_LAYER_ID aLayer, const uint8_t aGroundPlane[7], NETINFO_ITEM *aGndPlaneNet) const
void processSegment(BOARD_ITEM_CONTAINER *aContainer, const SPRINT_LAYOUT::OBJECT &aObj, std::vector< std::vector< VECTOR2I > > &aOutlineSegments, const uint8_t aGroundPlane[7], NETINFO_ITEM *aGndPlaneNet, std::map< uint32_t, std::set< BOARD_ITEM * > > &aGidToItems)
void buildOutline(BOARD *aBoard, std::vector< std::vector< VECTOR2I > > &aOutlineSegments, const SPRINT_LAYOUT::BOARD_DATA &aBoardData)
void processText(BOARD_ITEM_CONTAINER *aContainer, const SPRINT_LAYOUT::OBJECT &aObj, std::map< uint32_t, std::set< BOARD_ITEM * > > &aGidToItems)
void parseGroups(SPRINT_LAYOUT::OBJECT &aObj)
void resolveGroups(BOARD_ITEM_CONTAINER *aContainer, std::map< uint32_t, std::set< BOARD_ITEM * > > &aGidToItems)
void parseObject(SPRINT_LAYOUT::OBJECT &aObject, bool aIsTextChild=false)
void parseBoardHeader(SPRINT_LAYOUT::BOARD_DATA &aBoard)
void processPad(BOARD_ITEM_CONTAINER *aContainer, const SPRINT_LAYOUT::OBJECT &aObj, const uint8_t aGroundPlane[7], NETINFO_ITEM *aGndPlaneNet, std::map< uint32_t, std::set< BOARD_ITEM * > > &aGidToItems)
VECTOR2I sprintToKicadPos(float aX, float aY) const
int sprintToKicadCoord(float aValue) const
void parseObjectsList(SPRINT_LAYOUT::BOARD_DATA &aBoard)
bool ParseMacroFile(const wxString &aFileName)
std::vector< uint8_t > m_buffer
bool layerHasGroundPlane(PCB_LAYER_ID aLayer, const uint8_t aGroundPlane[7]) const
void processLine(BOARD_ITEM_CONTAINER *aContainer, const SPRINT_LAYOUT::OBJECT &aObj, std::vector< std::vector< VECTOR2I > > &aOutlineSegments, const uint8_t aGroundPlane[7], NETINFO_ITEM *aGndPlaneNet, std::map< uint32_t, std::set< BOARD_ITEM * > > &aGidToItems)
bool ParseBoard(const wxString &aFileName)
Handle a list of polygons defining a copper zone.
Definition zone.h:70
void SetDoNotAllowPads(bool aEnable)
Definition zone.h:826
void SetLocalClearance(std::optional< int > aClearance)
Definition zone.h:183
void AddPolygon(std::vector< VECTOR2I > &aPolygon)
Add a polygon to the zone outline.
Definition zone.cpp:1424
void SetThermalReliefSpokeWidth(int aThermalReliefSpokeWidth)
Definition zone.h:251
virtual void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
Definition zone.cpp:641
void SetIsRuleArea(bool aEnable)
Definition zone.h:808
void SetDoNotAllowTracks(bool aEnable)
Definition zone.h:825
void SetLayerSet(const LSET &aLayerSet) override
Definition zone.cpp:666
void SetDoNotAllowVias(bool aEnable)
Definition zone.h:824
void SetNet(NETINFO_ITEM *aNetInfo) override
Override that drops aNetInfo when this zone is in copper-thieving fill mode.
Definition zone.cpp:632
void SetThermalReliefGap(int aThermalReliefGap)
Definition zone.h:240
void SetDoNotAllowFootprints(bool aEnable)
Definition zone.h:827
void SetDoNotAllowZoneFills(bool aEnable)
Definition zone.h:823
void SetAssignedPriority(unsigned aPriority)
Definition zone.h:117
void SetZoneName(const wxString &aName)
Definition zone.h:161
void SetIslandRemovalMode(ISLAND_REMOVAL_MODE aRemove)
Definition zone.h:830
static int GetDefaultHatchPitch()
Definition zone.cpp:1617
void SetBorderDisplayStyle(ZONE_BORDER_DISPLAY_STYLE aBorderHatchStyle, int aBorderHatchPitch, bool aRebuilBorderHatch)
Set all hatch parameters for the zone.
Definition zone.cpp:1540
#define _(s)
static constexpr EDA_ANGLE ANGLE_90
Definition eda_angle.h:424
@ DEGREES_T
Definition eda_angle.h:31
static constexpr EDA_ANGLE ANGLE_45
Definition eda_angle.h:423
static constexpr EDA_ANGLE ANGLE_180
Definition eda_angle.h:426
@ SEGMENT
Definition eda_shape.h:56
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:57
#define THROW_IO_ERROR(msg)
macro which captures the "call site" values of FILE_, __FUNCTION & LINE
#define THROW_IO_ERRORF(msg,...)
bool IsBackLayer(PCB_LAYER_ID aLayerId)
Layer classification: check if it's a back layer.
Definition layer_ids.h:829
bool IsCopperLayer(int aLayerId)
Test whether a layer is a copper layer.
Definition layer_ids.h:703
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ F_CrtYd
Definition layer_ids.h:112
@ Edge_Cuts
Definition layer_ids.h:108
@ F_Paste
Definition layer_ids.h:100
@ B_Mask
Definition layer_ids.h:94
@ B_Cu
Definition layer_ids.h:61
@ F_Mask
Definition layer_ids.h:93
@ B_Paste
Definition layer_ids.h:101
@ In2_Cu
Definition layer_ids.h:63
@ F_SilkS
Definition layer_ids.h:96
@ In1_Cu
Definition layer_ids.h:62
@ B_SilkS
Definition layer_ids.h:97
@ F_Cu
Definition layer_ids.h:60
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:411
@ SMD
Smd pad, appears on the solder paste layer (default)
Definition padstack.h:98
@ PTH
Plated through hole pad.
Definition padstack.h:97
@ CHAMFERED_RECT
Definition padstack.h:59
@ RECTANGLE
Definition padstack.h:53
Class to handle a set of BOARD_ITEMs.
static constexpr uint32_t MAX_CHILDREN
static constexpr uint32_t MAX_GROUPS
static constexpr uint32_t MAX_OBJECTS
static constexpr uint32_t MAX_POINTS
std::vector< OBJECT > objects
std::vector< POINT > points
std::vector< OBJECT > text_children
std::vector< uint32_t > groups
@ DESCRIPTION
Field Description of part, i.e. "1/4W 1% Metal Film Resistor".
VECTOR2I center
int radius
VECTOR2I end
int clearance
int delta
GR_TEXT_H_ALIGN_T
This is API surface mapped to common.types.HorizontalAlignment.
@ GR_TEXT_H_ALIGN_CENTER
@ GR_TEXT_H_ALIGN_LEFT
@ GR_TEXT_V_ALIGN_BOTTOM
@ GR_TEXT_V_ALIGN_CENTER
@ GR_TEXT_V_ALIGN_TOP
#define M_PI
void RotatePoint(int *pX, int *pY, const EDA_ANGLE &aAngle)
Calculate the new point of coord coord pX, pY, for a rotation center 0, 0.
Definition trigo.cpp:225
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683
VECTOR2< double > VECTOR2D
Definition vector2d.h:682
@ THERMAL
Use thermal relief for pads.
Definition zones.h:46
@ NONE
Pads are not covered.
Definition zones.h:45
@ FULL
pads are covered by copper
Definition zones.h:47