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