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