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