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_ERROR( wxString::Format( _( "Cannot open file '%s'" ), aFileName ) );
316
317 size_t fileSize = stream.GetLength();
318
319 if( fileSize < 8 )
320 THROW_IO_ERROR( wxString::Format( _( "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_ERROR( wxString::Format( _( "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_ERROR( wxString::Format( _( "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 {
642 THROW_IO_ERROR( wxString::Format( _( "Unknown object type %d in Sprint Layout file" ), aObj.type ) );
643 }
644 }
645}
646
647
649{
650 if( m_fileData.version >= 4 )
651 {
652 readUint32(); // active_board_tab
653 m_fileData.project_name = readFixedString( 100 );
654 m_fileData.project_author = readFixedString( 100 );
655 m_fileData.project_company = readFixedString( 100 );
656 m_fileData.project_comment = readVarString();
657 }
658}
659
660
661// ============================================================================
662// Board construction
663// ============================================================================
664
665PCB_LAYER_ID SPRINT_LAYOUT_PARSER::mapLayer( uint8_t aSprintLayer ) const
666{
667 if( m_fileData.version >= 4 )
668 {
669 switch( aSprintLayer )
670 {
671 case SPRINT_LAYOUT::LAYER_C1: return F_Cu;
672 case SPRINT_LAYOUT::LAYER_S1: return F_SilkS;
673 case SPRINT_LAYOUT::LAYER_C2: return B_Cu;
674 case SPRINT_LAYOUT::LAYER_S2: return B_SilkS;
675 case SPRINT_LAYOUT::LAYER_I1: return In1_Cu;
676 case SPRINT_LAYOUT::LAYER_I2: return In2_Cu;
678 default: return F_Cu;
679 }
680 }
681 else
682 {
683 // In older Sprint Layout versions the meaning of C1/C2 is flipped
684 switch( aSprintLayer )
685 {
686 case SPRINT_LAYOUT::LAYER_C1: return m_fileData.version >= 3 ? F_Cu : B_Cu;
687 case SPRINT_LAYOUT::LAYER_S1: return F_SilkS;
688 case SPRINT_LAYOUT::LAYER_C2: return m_fileData.version >= 3 ? B_Cu : F_Cu;
689 case SPRINT_LAYOUT::LAYER_S2: return B_SilkS;
691
692 case SPRINT_LAYOUT::LAYER_I1: return B_Cu; // used for PTH pads and tracks inside macros
693 case SPRINT_LAYOUT::LAYER_I2: return F_SilkS; // used for graphics inside macros
694 default: return F_Cu;
695 }
696 }
697}
698
699
701{
702 // Sprint Layout 6 uses 1/10000 mm
703 // Older versions seem to use 1/100 mm
704 // KiCad uses nanometers (1 nm = 1e-6 mm)
705 double nm;
706
707 if( m_fileData.version >= 6 )
708 nm = static_cast<double>( aValue ) * 100.0; // 100 nm
709 else
710 nm = static_cast<double>( aValue ) * 10000.0; // 10 um
711
712 nm = std::clamp( nm, static_cast<double>( -pcbIUScale.mmToIU( 500 ) ),
713 static_cast<double>( pcbIUScale.mmToIU( 500 ) ) );
714
715 return KiROUND( nm );
716}
717
718
720{
721 // Sprint Layout uses Y-up (mathematical), KiCad uses Y-down (screen)
722 return VECTOR2I( sprintToKicadCoord( aX ), sprintToKicadCoord( -aY ) );
723}
724
725
726wxString SPRINT_LAYOUT_PARSER::convertString( const std::string& aStr ) const
727{
728 static wxCSConv convCP1251( wxFONTENCODING_CP1251 );
729 static wxCSConv convCP1252( wxFONTENCODING_CP1252 );
730
731 if( aStr.empty() )
732 return wxEmptyString;
733
734 wxString ret = wxString::FromUTF8( aStr );
735
736 if( ret.empty() && convCP1251.IsOk() && convCP1252.IsOk() )
737 {
738 // Statistically determine if the string is more likely to be CP1251 (Cyrillic) or CP1252 (Western European)
739 size_t extNonGermanCount = 0;
740
741 for( unsigned char c : aStr )
742 {
743 // Extended-range German characters in CP1252
744 switch( c )
745 {
746 case 0xC4: // Ä
747 case 0xD6: // Ö
748 case 0xDC: // Ü
749 case 0xE4: // ä
750 case 0xF6: // ö
751 case 0xFC: // ü
752 case 0xDF: // ß
753 break;
754
755 default:
756 if( c >= 0x80 )
757 extNonGermanCount++;
758 break;
759 }
760 }
761
762 if( extNonGermanCount > 0 )
763 ret = wxString( aStr.c_str(), convCP1251 );
764 else
765 ret = wxString( aStr.c_str(), convCP1252 );
766 }
767
768 return ret;
769}
770
771
772bool SPRINT_LAYOUT_PARSER::layerHasGroundPlane( PCB_LAYER_ID aLayer, const uint8_t aGroundPlane[7] ) const
773{
774 // Ground plane index map mirrors CreateBoard()'s groundPlaneMap
775 if( m_fileData.version >= 5 )
776 {
777 switch( aLayer )
778 {
779 case F_Cu: return aGroundPlane[0] != 0;
780 case B_Cu: return aGroundPlane[2] != 0;
781 case In1_Cu: return aGroundPlane[4] != 0;
782 case In2_Cu: return aGroundPlane[5] != 0;
783 default: return false;
784 }
785 }
786 else
787 {
788 switch( aLayer )
789 {
790 case F_Cu: return aGroundPlane[0] != 0;
791 case B_Cu: return aGroundPlane[1] != 0;
792 default: return false;
793 }
794 }
795}
796
797
799 PCB_LAYER_ID aLayer, const uint8_t aGroundPlane[7],
800 NETINFO_ITEM* aGndPlaneNet ) const
801{
802 if( !aBoard )
803 return nullptr;
804
805 bool isPad = aObj.type == SPRINT_LAYOUT::OBJ_THT_PAD || aObj.type == SPRINT_LAYOUT::OBJ_SMD_PAD;
806
807 // Override the net for ground plane connection. Note that the identifier string
808 // could specify anything (e.g. component value), not just the net name
809 if( aGndPlaneNet != nullptr && layerHasGroundPlane( aLayer, aGroundPlane ) )
810 {
811 if( aObj.clearance == 0 )
812 return aGndPlaneNet;
813
814 // If pad thermal reliefs are enabled, connect to the plane
815 if( m_fileData.version >= 5 && isPad && aObj.mirror_h != 0 )
816 return aGndPlaneNet;
817 }
818
819 // TODO: if a pad is connected through lines to the GND_PLANE, we don't want to set the pad's
820 // netname as this would update the nets of the lines, disconnecting them from the plane.
821 //
822 //if( !aObj.identifier.empty() )
823 //{
824 // wxString netName = convertString( aObj.identifier );
825 // NETINFO_ITEM* net = aBoard->FindNet( netName );
826
827 // if( !net )
828 // {
829 // net = new NETINFO_ITEM( aBoard, netName );
830 // aBoard->Add( net );
831 // }
832
833 // return net;
834 //}
835
836 return nullptr;
837}
838
839
840BOARD* SPRINT_LAYOUT_PARSER::CreateBoard( std::map<wxString, std::unique_ptr<FOOTPRINT>>& aFootprintMap,
841 size_t aBoardIndex )
842{
843 if( aBoardIndex >= m_fileData.boards.size() )
844 return nullptr;
845
846 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
847
848 // Set up copper layers based on whether inner layers are used
849 const SPRINT_LAYOUT::BOARD_DATA& boardData = m_fileData.boards[aBoardIndex];
850 bool hasInnerLayers = false;
851
852 for( const SPRINT_LAYOUT::OBJECT& obj : boardData.objects )
853 {
855 {
856 hasInnerLayers = true;
857 break;
858 }
859 }
860
861 if( hasInnerLayers || boardData.is_multilayer )
862 board->SetCopperLayerCount( 4 );
863 else
864 board->SetCopperLayerCount( 2 );
865
866 // Create ground plane zones for layers where ground plane is enabled.
867 // Sprint Layout stores a per-layer flag in the board header.
868 const wxString gndPlaneNetName( "GND_PLANE" );
869 std::map<int, PCB_LAYER_ID> groundPlaneMap;
870 LSET groundPlaneLayerSet;
871 NETINFO_ITEM* gndPlaneNet = nullptr;
872
873 if( m_fileData.version >= 5 )
874 {
875 groundPlaneMap = {
876 { 0, F_Cu },
877 { 2, B_Cu },
878 { 4, In1_Cu },
879 { 5, In2_Cu },
880 };
881 }
882 else
883 {
884 groundPlaneMap = {
885 { 0, F_Cu },
886 { 1, B_Cu },
887 };
888 }
889
890 for( const auto& [index, layer] : groundPlaneMap )
891 {
892 if( boardData.ground_plane[index] != 0 )
893 groundPlaneLayerSet.set( layer );
894 }
895
896 if( !groundPlaneLayerSet.empty() )
897 {
898 int w = sprintToKicadCoord( static_cast<float>( boardData.size_x ) );
899 int h = sprintToKicadCoord( static_cast<float>( boardData.size_y ) );
900
901 gndPlaneNet = new NETINFO_ITEM( board.get(), gndPlaneNetName );
902 board->Add( gndPlaneNet );
903
904 ZONE* zone = new ZONE( board.get() );
905 zone->SetLayerSet( groundPlaneLayerSet );
906 zone->SetIsRuleArea( false );
907 zone->SetZoneName( wxS( "GND_PLANE" ) );
908 zone->SetLocalClearance( std::optional<int>( pcbIUScale.mmToIU( 0.3 ) ) );
909 zone->SetThermalReliefGap( pcbIUScale.mmToIU( 0.5 ) );
910 zone->SetThermalReliefSpokeWidth( pcbIUScale.mmToIU( 0.5 ) );
911 zone->SetAssignedPriority( 0 );
913 zone->SetNet( gndPlaneNet );
914
915 SHAPE_POLY_SET outline( BOX2D( VECTOR2D( 0, 0 ), VECTOR2D( w, h ) ) );
916 zone->AddPolygon( outline.COutline( 0 ) );
918
919 board->Add( zone );
920 }
921
922 // Maps component_id to FOOTPRINT for grouping component-owned objects
923 std::map<uint16_t, FOOTPRINT*> componentMap;
924 std::vector<std::vector<VECTOR2I>> outlineSegments;
925
926 auto getOrCreateComponentFootprint = [&]( const SPRINT_LAYOUT::OBJECT& aObj ) -> FOOTPRINT*
927 {
928 if( aObj.component_id == 0 )
929 return nullptr;
930
931 auto it = componentMap.find( aObj.component_id );
932
933 if( it != componentMap.end() )
934 return it->second;
935
936 FOOTPRINT* fp = new FOOTPRINT( board.get() );
937
938 if( aObj.type == SPRINT_LAYOUT::OBJ_STROKE_TEXT && !aObj.text.empty() )
939 {
940 fp->SetReference( convertString( aObj.text ) );
941 }
942 else
943 {
944 fp->SetReference( wxString::Format( wxS( "U%d" ), aObj.component_id ) );
945
946 for( PCB_FIELD* fd : fp->GetFields() )
947 fd->SetVisible( false );
948 }
949
950 if( aObj.type == SPRINT_LAYOUT::OBJ_STROKE_TEXT && aObj.component.valid )
951 {
952 if( !aObj.component.comment.empty() )
953 {
954 wxString comment = convertString( aObj.component.comment );
955 fp->GetField( FIELD_T::DESCRIPTION )->SetText( comment );
956 fp->SetValue( comment );
957 }
958 else if( !aObj.identifier.empty() )
959 {
960 fp->SetValue( convertString( aObj.identifier ) );
961 }
962
963 if( !aObj.component.package.empty() )
964 fp->SetLibDescription( convertString( aObj.component.package ) );
965
966 fp->SetOrientationDegrees( aObj.component.rotation );
967 }
968
969 PCB_LAYER_ID layer = mapLayer( aObj.layer );
970 fp->SetLayer( ( layer == B_Cu || layer == B_SilkS ) ? B_Cu : F_Cu );
971
972 componentMap[aObj.component_id] = fp;
973 board->Add( fp );
974 return fp;
975 };
976
977 // First pass: create footprints from component text records where available
978 for( const SPRINT_LAYOUT::OBJECT& obj : boardData.objects )
979 {
980 if( obj.type == SPRINT_LAYOUT::OBJ_STROKE_TEXT && obj.component_id > 0 && obj.component.valid )
981 getOrCreateComponentFootprint( obj );
982 }
983
984 std::map<uint32_t, std::set<BOARD_ITEM*>> gidToItems;
985
986 // Second pass: process all objects in board/footprint context
987 for( const SPRINT_LAYOUT::OBJECT& obj : boardData.objects )
988 {
989 BOARD_ITEM_CONTAINER* container = board.get();
990
991 if( FOOTPRINT* fp = getOrCreateComponentFootprint( obj ) )
992 container = fp;
993
994 // clang-format off
995 switch( obj.type )
996 {
999 processPad( container, obj, boardData.ground_plane, gndPlaneNet, gidToItems );
1000 break;
1001
1003 processSegment( container, obj, outlineSegments, boardData.ground_plane, gndPlaneNet, gidToItems );
1004 break;
1005
1007 processLine( container, obj, outlineSegments, boardData.ground_plane, gndPlaneNet, gidToItems );
1008 break;
1009
1011 processPoly( container, obj, outlineSegments, boardData.ground_plane, gndPlaneNet, gidToItems );
1012 break;
1013
1015 processCircle( container, obj, outlineSegments, boardData.ground_plane, gndPlaneNet, gidToItems );
1016 break;
1017
1020 processText( container, obj, gidToItems );
1021 break;
1022
1023 default:
1024 break;
1025 }
1026 // clang-format on
1027 }
1028
1029 resolveGroups( board.get(), gidToItems );
1030
1031 // Re-anchor footprints after all elements are added.
1032 for( FOOTPRINT* fp : board->Footprints() )
1033 {
1034 BOX2I fpBbox = fp->GetBoundingHull().BBox();
1035
1036 VECTOR2I anchor = fpBbox.GetCenter();
1037 fp->SetPosition( anchor );
1038
1039 VECTOR2I anchorShift( -anchor.x, -anchor.y );
1040 RotatePoint( anchorShift, fp->GetOrientation() );
1041 fp->MoveAnchorPosition( anchorShift );
1042 }
1043
1044 // Fill the footprint map
1045 for( const auto& [componentId, fp] : componentMap )
1046 {
1047 wxString fpKey = wxString::Format( wxS( "SprintLayout_%s" ), fp->GetReference() );
1048 FOOTPRINT* fpCopy = static_cast<FOOTPRINT*>( fp->Clone() );
1049 fpCopy->SetParent( nullptr );
1050 aFootprintMap[fpKey] = std::unique_ptr<FOOTPRINT>( fpCopy );
1051 }
1052
1053 buildOutline( board.get(), outlineSegments, boardData );
1054
1055 // Center the board content on the page
1056 BOX2I bbox = board->ComputeBoundingBox( true );
1057
1058 if( bbox.GetWidth() > 0 && bbox.GetHeight() > 0 )
1059 {
1060 VECTOR2I pageSize = board->GetPageSettings().GetSizeIU( pcbIUScale.IU_PER_MILS );
1061 VECTOR2I centerOffset = VECTOR2I( pageSize.x / 2, pageSize.y / 2 ) - bbox.GetCenter();
1062
1063 for( FOOTPRINT* fp : board->Footprints() )
1064 fp->Move( centerOffset );
1065
1066 for( ZONE* zone : board->Zones() )
1067 zone->Move( centerOffset );
1068
1069 for( BOARD_ITEM* item : board->Drawings() )
1070 item->Move( centerOffset );
1071 }
1072
1073 return board.release();
1074}
1075
1076
1078{
1079 if( m_fileData.boards.empty() )
1080 return nullptr;
1081
1082 const SPRINT_LAYOUT::BOARD_DATA& boardData = m_fileData.boards[0];
1083
1084 std::unique_ptr<FOOTPRINT> fp = std::make_unique<FOOTPRINT>( nullptr );
1085
1086 wxString fpName = convertString( boardData.name );
1087
1088 fp->SetFPID( LIB_ID( wxEmptyString, fpName ) );
1089 fp->SetReference( wxT( "REF**" ) );
1090 fp->SetValue( fpName );
1091 fp->Reference().SetVisible( true );
1092 fp->Value().SetVisible( true );
1093
1094 std::vector<std::vector<VECTOR2I>> outlineSegments;
1095 uint8_t groundPlane[7] = {};
1096 std::map<uint32_t, std::set<BOARD_ITEM*>> gidToItems;
1097
1098 for( const SPRINT_LAYOUT::OBJECT& obj : boardData.objects )
1099 {
1100 BOARD_ITEM_CONTAINER* container = fp.get();
1101
1102 // clang-format off
1103 switch( obj.type )
1104 {
1107 processPad( container, obj, groundPlane, nullptr, gidToItems );
1108 break;
1109
1111 processSegment( container, obj, outlineSegments, groundPlane, nullptr, gidToItems );
1112 break;
1113
1115 processLine( container, obj, outlineSegments, groundPlane, nullptr, gidToItems );
1116 break;
1117
1119 processPoly( container, obj, outlineSegments, groundPlane, nullptr, gidToItems );
1120 break;
1121
1123 processCircle( container, obj, outlineSegments, groundPlane, nullptr, gidToItems );
1124 break;
1125
1128 processText( container, obj, gidToItems );
1129 break;
1130
1131 default:
1132 break;
1133 }
1134 // clang-format on
1135 }
1136
1137 resolveGroups( fp.get(), gidToItems );
1138
1139 fp->AutoPositionFields();
1140
1141 // Generate basic courtyard rectangle
1142 BOX2I bbox = fp->GetBoundingHull().BBox();
1143 bbox.Inflate( pcbIUScale.mmToIU( 0.25 ) ); // Default courtyard clearance
1144
1145 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( fp.get(), SHAPE_T::RECTANGLE );
1146 shape->SetWidth( pcbIUScale.mmToIU( DEFAULT_COURTYARD_WIDTH ) );
1147 shape->SetLayer( F_CrtYd );
1148 shape->SetStart( bbox.GetOrigin() );
1149 shape->SetEnd( bbox.GetEnd() );
1150
1151 fp->Add( shape.release(), ADD_MODE::APPEND );
1152
1153 return fp.release();
1154}
1155
1156
1158 const uint8_t aGroundPlane[7], NETINFO_ITEM* aGndPlaneNet,
1159 std::map<uint32_t, std::set<BOARD_ITEM*>>& aGidToItems )
1160{
1161 BOARD* board = aContainer ? aContainer->GetBoard() : nullptr;
1162 FOOTPRINT* fp = dynamic_cast<FOOTPRINT*>( aContainer );
1163 bool standaloneFp = false;
1164
1165 if( !fp )
1166 {
1167 // Standalone pad without a component gets its own footprint
1168 standaloneFp = true;
1169 fp = new FOOTPRINT( board );
1170 fp->SetReference( wxString::Format( wxS( "PAD%d" ), static_cast<int>( board->Footprints().size() ) ) );
1171 fp->Reference().SetVisible( false );
1172 fp->SetLayer( F_Cu );
1173 aContainer->Add( fp );
1174 }
1175
1176 PAD* pad = new PAD( fp );
1177
1178 // SMD pad x,y may be a component-relative offset rather than an absolute
1179 // position (depends on the Sprint Layout version that created the file).
1180 // The points array always stores absolute coordinates, so derive the pad
1181 // center from the points when available.
1182 // The rotation field for pads (both TH and SMD) is unknown, so detect
1183 // the pad angle from the points when possible.
1184 VECTOR2I ptsCenter;
1185 EDA_ANGLE ptsAngle;
1186
1187 if( !aObj.points.empty() )
1188 {
1189 double cx = 0, cy = 0;
1190
1191 for( const auto& pt : aObj.points )
1192 {
1193 cx += pt.x;
1194 cy += pt.y;
1195 }
1196
1197 cx /= static_cast<double>( aObj.points.size() );
1198 cy /= static_cast<double>( aObj.points.size() );
1199 ptsCenter = sprintToKicadPos( static_cast<float>( cx ), static_cast<float>( cy ) );
1200
1201 std::vector<VECTOR2I> pts;
1202
1203 for( const SPRINT_LAYOUT::POINT& pt : aObj.points )
1204 pts.emplace_back( sprintToKicadPos( pt.x, pt.y ) );
1205
1206 if( pts.size() == 2 ) // Oval or circle
1207 {
1208 ptsAngle = EDA_ANGLE( pts[1] - pts[0] );
1209
1211 ptsAngle -= ANGLE_90;
1212 }
1213 else if( pts.size() == 4 ) // Rectangular
1214 {
1215 ptsAngle = EDA_ANGLE( pts[1] - pts[0] );
1216 }
1217 else if( pts.size() == 8 ) // Octagonal
1218 {
1219 ptsAngle = EDA_ANGLE( pts[2] - pts[1] );
1220 }
1221 else
1222 {
1223 wxFAIL_MSG( wxString::Format( "Unknown pad type %d shape %d with %zu points", int( aObj.type ),
1224 int( aObj.tht_shape ), aObj.points.size() ) );
1225 }
1226
1227 ptsAngle = ptsAngle.Round( 2 );
1228 }
1229
1230 PCB_LAYER_ID padLayer = mapLayer( aObj.layer );
1231 VECTOR2I padPos = sprintToKicadPos( aObj.x, aObj.y );
1232
1233 if( aObj.type == SPRINT_LAYOUT::OBJ_THT_PAD )
1234 {
1235 int outerDia = sprintToKicadCoord( aObj.outer * 2.0f );
1236 int drillDia = sprintToKicadCoord( aObj.inner * 2.0f );
1237
1238 bool isPTH = aObj.plated != 0 || ( m_fileData.version <= 3 && aObj.layer == 5 );
1239
1240 if( isPTH )
1241 {
1242 pad->SetAttribute( PAD_ATTRIB::PTH );
1243 pad->SetLayerSet( PAD::PTHMask() );
1244 }
1245 else
1246 {
1247 pad->SetAttribute( drillDia > 0 ? PAD_ATTRIB ::NPTH : PAD_ATTRIB::SMD );
1248
1249 if( padLayer == F_Cu )
1250 pad->SetLayerSet( LSET( { F_Cu, F_Mask } ) );
1251 else if( padLayer == B_Cu )
1252 pad->SetLayerSet( LSET( { B_Cu, B_Mask } ) );
1253 else
1254 pad->SetLayerSet( LSET( { padLayer } ) );
1255
1256 if( standaloneFp && IsBackLayer( padLayer ) )
1257 fp->SetLayer( B_Cu );
1258 }
1259
1260 VECTOR2I padSize( outerDia, outerDia );
1261 VECTOR2I drillSize( drillDia, drillDia );
1262
1263 switch( aObj.tht_shape )
1264 {
1268 padSize.x *= 2;
1269 break;
1270
1274 padSize.y *= 2;
1275 break;
1276
1277 default: break;
1278 }
1279
1280 pad->SetSize( PADSTACK::ALL_LAYERS, padSize );
1281 pad->SetDrillSize( drillSize );
1282
1283 switch( aObj.tht_shape )
1284 {
1287 break;
1288
1292 break;
1293
1298 pad->SetChamferRectRatio( PADSTACK::ALL_LAYERS, 0.25 );
1299 pad->SetChamferPositions( PADSTACK::ALL_LAYERS,
1301 break;
1302
1307 break;
1308
1309 default:
1311 break;
1312 }
1313
1314 pad->SetPosition( padPos );
1315 pad->Rotate( padPos, -ptsAngle );
1316 }
1317 else
1318 {
1319 pad->SetAttribute( PAD_ATTRIB::SMD );
1320
1321 if( padLayer == F_Cu )
1322 pad->SetLayerSet( LSET( { F_Cu, F_Paste, F_Mask } ) );
1323 else if( padLayer == B_Cu )
1324 pad->SetLayerSet( LSET( { B_Cu, B_Paste, B_Mask } ) );
1325 else
1326 pad->SetLayerSet( LSET( { padLayer } ) );
1327
1328 if( standaloneFp && IsBackLayer( padLayer ) )
1329 fp->SetLayer( B_Cu );
1330
1332
1333 int width = sprintToKicadCoord( aObj.outer );
1334 int height = sprintToKicadCoord( aObj.inner );
1335
1336 if( height <= 0 )
1337 height = width;
1338
1339 if( !aObj.points.empty() )
1340 padPos = ptsCenter;
1341
1342 pad->SetSize( PADSTACK::ALL_LAYERS, VECTOR2I( width, height ) );
1343 pad->SetPosition( padPos );
1344 pad->Rotate( padPos, -ptsAngle );
1345 }
1346
1347 // Solder mask: soldermask==0 means no mask opening (pad is tented/covered)
1348 if( aObj.soldermask == 0 )
1349 {
1350 pad->Padstack().FrontOuterLayers().has_solder_mask = false;
1351 pad->Padstack().BackOuterLayers().has_solder_mask = false;
1352 }
1353
1354 // Per-element ground plane clearance
1355 if( aObj.clearance > 0 )
1356 {
1357 int clearance = sprintToKicadCoord( static_cast<float>( aObj.clearance ) );
1358 pad->SetLocalClearance( std::optional<int>( clearance ) );
1359 pad->SetLocalThermalGapOverride( std::optional<int>( clearance ) );
1360 }
1361
1362 // Thermal reliefs
1363 if( m_fileData.version >= 5 && aObj.mirror_h != 0 )
1364 {
1365 int spokeWidth = aObj.rotation * 10000 / 2;
1366 pad->SetLocalThermalSpokeWidthOverride( spokeWidth );
1367
1368 // Each byte is the spoke directions for one copper layer (C1, C2, I1, I2).
1369 // 0x55 matches H/V directions, 0xAA matches diagonal directions
1370 uint32_t spokeMask = static_cast<uint32_t>( aObj.start_angle );
1371
1372 if( spokeMask != 0 )
1373 {
1374 pad->SetLocalZoneConnection( ZONE_CONNECTION::THERMAL );
1375
1376 if( spokeMask & 0x55555555 )
1377 pad->SetThermalSpokeAngle( ANGLE_90 );
1378 else if( spokeMask & 0xAAAAAAAA )
1379 pad->SetThermalSpokeAngle( ANGLE_45 );
1380 }
1381 else
1382 {
1383 pad->SetLocalZoneConnection( ZONE_CONNECTION::NONE );
1384 }
1385 }
1386 else
1387 {
1388 pad->SetLocalZoneConnection( ZONE_CONNECTION::FULL );
1389 pad->SetThermalSpokeAngle( ANGLE_90 );
1390 }
1391
1392 // Set net name. Plated THT pads span all copper layers, so accept the GND
1393 // default when any configured ground-plane layer is enabled; SMD and NPTH
1394 // pads only qualify on their own layer.
1395 PCB_LAYER_ID netLayer = padLayer;
1396
1397 if( aObj.type == SPRINT_LAYOUT::OBJ_THT_PAD && aObj.plated != 0
1398 && !layerHasGroundPlane( padLayer, aGroundPlane ) )
1399 {
1400 if( aGroundPlane[0] )
1401 netLayer = F_Cu;
1402 else if( aGroundPlane[2] )
1403 netLayer = B_Cu;
1404 else if( aGroundPlane[4] )
1405 netLayer = In1_Cu;
1406 else if( aGroundPlane[5] )
1407 netLayer = In2_Cu;
1408 }
1409
1410 if( NETINFO_ITEM* net = resolveItemNet( board, aObj, netLayer, aGroundPlane, aGndPlaneNet ) )
1411 pad->SetNet( net );
1412
1413 pad->SetNumber( wxString::Format( wxS( "%d" ), static_cast<int>( fp->Pads().size() + 1 ) ) );
1414
1415 fp->Add( pad );
1416
1417 if( standaloneFp )
1418 {
1419 for( PCB_FIELD* fd : fp->GetFields() )
1420 fd->SetTextPos( pad->GetPosition() );
1421
1422 processItemGroups( fp, aObj, aGidToItems );
1423 }
1424 else
1425 {
1426 processItemGroups( pad, aObj, aGidToItems );
1427 }
1428}
1429
1430
1432 std::vector<std::vector<VECTOR2I>>& aOutlineSegments,
1433 const uint8_t aGroundPlane[7], NETINFO_ITEM* aGndPlaneNet,
1434 std::map<uint32_t, std::set<BOARD_ITEM*>>& aGidToItems )
1435{
1436 if( aObj.points.size() < 2 )
1437 return;
1438
1439 BOARD* board = aContainer ? aContainer->GetBoard() : nullptr;
1440 PCB_LAYER_ID layer = mapLayer( aObj.layer );
1441
1442 if( layer == Edge_Cuts )
1443 {
1444 std::vector<VECTOR2I> segment;
1445
1446 for( const auto& pt : aObj.points )
1447 segment.push_back( sprintToKicadPos( pt.x, pt.y ) );
1448
1449 aOutlineSegments.push_back( std::move( segment ) );
1450 return;
1451 }
1452
1453 int width = sprintToKicadCoord( static_cast<float>( aObj.line_width ) );
1454
1455 if( width <= 0 )
1456 width = pcbIUScale.mmToIU( 0.25 );
1457
1458 for( size_t i = 0; i + 1 < aObj.points.size(); i++ )
1459 {
1460 PCB_SHAPE* shape = new PCB_SHAPE( aContainer );
1461 shape->SetShape( SHAPE_T::SEGMENT );
1462 shape->SetLayer( layer );
1463 shape->SetWidth( width );
1464 shape->SetStart( sprintToKicadPos( aObj.points[i].x, aObj.points[i].y ) );
1465 shape->SetEnd( sprintToKicadPos( aObj.points[i + 1].x, aObj.points[i + 1].y ) );
1466
1467 if( IsCopperLayer( layer ) )
1468 {
1469 if( NETINFO_ITEM* net = resolveItemNet( board, aObj, layer, aGroundPlane, aGndPlaneNet ) )
1470 shape->SetNet( net );
1471 }
1472
1473 aContainer->Add( shape );
1474 processItemGroups( shape, aObj, aGidToItems );
1475 }
1476}
1477
1478
1480 std::vector<std::vector<VECTOR2I>>& aOutlineSegments,
1481 const uint8_t aGroundPlane[7], NETINFO_ITEM* aGndPlaneNet,
1482 std::map<uint32_t, std::set<BOARD_ITEM*>>& aGidToItems )
1483{
1484 PCB_LAYER_ID layer = mapLayer( aObj.layer );
1485
1486 if( layer == Edge_Cuts )
1487 {
1488 std::vector<VECTOR2I> seg;
1489 seg.push_back( sprintToKicadPos( aObj.x, aObj.y ) );
1490 seg.push_back( sprintToKicadPos( aObj.outer, aObj.inner ) );
1491 aOutlineSegments.push_back( std::move( seg ) );
1492 return;
1493 }
1494
1495 VECTOR2I start = sprintToKicadPos( aObj.x, aObj.y );
1496 VECTOR2I end = sprintToKicadPos( aObj.outer, aObj.inner );
1497 int width = sprintToKicadCoord( static_cast<float>( aObj.line_width ) );
1498
1499 // Skip the dummy segment at 0,0 in version 1 files
1500 if( aObj.line_width == 0 && start == end )
1501 return;
1502
1503 PCB_SHAPE* shape = new PCB_SHAPE( aContainer );
1504 shape->SetShape( SHAPE_T::SEGMENT );
1505 shape->SetLayer( layer );
1506 shape->SetWidth( width );
1507 shape->SetStart( start );
1508 shape->SetEnd( end );
1509
1510 aContainer->Add( shape );
1511 processItemGroups( shape, aObj, aGidToItems );
1512}
1513
1514
1516 std::vector<std::vector<VECTOR2I>>& aOutlineSegments,
1517 const uint8_t aGroundPlane[7], NETINFO_ITEM* aGndPlaneNet,
1518 std::map<uint32_t, std::set<BOARD_ITEM*>>& aGidToItems )
1519{
1520 if( aObj.points.size() < 2 )
1521 return;
1522
1523 BOARD* board = aContainer ? aContainer->GetBoard() : nullptr;
1524 PCB_LAYER_ID layer = mapLayer( aObj.layer );
1525
1526 if( layer == Edge_Cuts )
1527 {
1528 std::vector<VECTOR2I> points;
1529
1530 for( const auto& pt : aObj.points )
1531 points.push_back( sprintToKicadPos( pt.x, pt.y ) );
1532
1533 points.push_back( points[0] );
1534
1535 aOutlineSegments.push_back( std::move( points ) );
1536 return;
1537 }
1538
1539 //bool isFilled = ( aObj.filled != 0 );
1540 bool isCutout = ( aObj.keepout != 0 );
1541
1542 int width = sprintToKicadCoord( static_cast<float>( aObj.line_width ) );
1543
1544 if( width < 0 )
1545 width = pcbIUScale.mmToIU( 0.25 );
1546
1547 SHAPE_LINE_CHAIN outline;
1548
1549 for( const auto& pt : aObj.points )
1550 {
1551 VECTOR2I pos = sprintToKicadPos( pt.x, pt.y );
1552 outline.Append( pos.x, pos.y );
1553 }
1554
1555 outline.SetClosed( true ); // Deduplicate the last point properly
1556
1557 if( isCutout && IsCopperLayer( layer ) && aObj.points.size() >= 3 )
1558 {
1559 // Cutout area for ground plane exclusion -> rule area (keepout zone)
1560 ZONE* zone = new ZONE( aContainer );
1561 zone->SetLayer( layer );
1562 zone->SetIsRuleArea( true );
1563 zone->SetDoNotAllowZoneFills( true );
1564 zone->SetDoNotAllowTracks( false );
1565 zone->SetDoNotAllowVias( false );
1566 zone->SetDoNotAllowPads( false );
1567 zone->SetDoNotAllowFootprints( false );
1568
1569 zone->AddPolygon( outline );
1570 aContainer->Add( zone );
1571 processItemGroups( zone, aObj, aGidToItems );
1572 }
1573 else if( aObj.points.size() >= 3 )
1574 {
1575 // Filled polygon on non-copper layer -> filled PCB_SHAPE
1576 PCB_SHAPE* shape = new PCB_SHAPE( aContainer );
1577 shape->SetShape( SHAPE_T::POLY );
1578 shape->SetFilled( true );
1579 shape->SetLayer( layer );
1580 shape->SetWidth( width );
1581
1582 shape->SetPolyShape( SHAPE_POLY_SET( outline ) );
1583
1584 if( NETINFO_ITEM* net = resolveItemNet( board, aObj, layer, aGroundPlane, aGndPlaneNet ) )
1585 shape->SetNet( net );
1586
1587 aContainer->Add( shape );
1588 processItemGroups( shape, aObj, aGidToItems );
1589 }
1590}
1591
1592
1594 std::vector<std::vector<VECTOR2I>>& aOutlineSegments,
1595 const uint8_t aGroundPlane[7], NETINFO_ITEM* aGndPlaneNet,
1596 std::map<uint32_t, std::set<BOARD_ITEM*>>& aGidToItems )
1597{
1598 BOARD* board = aContainer ? aContainer->GetBoard() : nullptr;
1599 PCB_LAYER_ID layer = mapLayer( aObj.layer );
1600 VECTOR2I center = sprintToKicadPos( aObj.x, aObj.y );
1601 float radius = ( aObj.outer + aObj.inner ) / 2.0f;
1602 int kiRadius = sprintToKicadCoord( radius );
1603 int width = sprintToKicadCoord( aObj.outer - aObj.inner );
1604
1605 if( width <= 0 )
1606 width = pcbIUScale.mmToIU( 0.25 );
1607
1608 bool isFullCircle = true;
1609 double startAngleDeg = 0, endAngleDeg = 0;
1610
1611 if( m_fileData.version >= 3 )
1612 {
1613 startAngleDeg = aObj.start_angle;
1614 endAngleDeg = aObj.line_width;
1615
1616 if( m_fileData.version >= 6 )
1617 {
1618 // There's nothing else in the format to specify the angle scale
1619 // It's either in 1 degree or 0.001 degree units
1620 if( startAngleDeg > 1000 || startAngleDeg < -1000 || endAngleDeg > 1000 || endAngleDeg < -1000 )
1621 {
1622 startAngleDeg /= 1000;
1623 endAngleDeg /= 1000;
1624 }
1625 }
1626 // Older versions always use 1 degree units
1627
1628 isFullCircle = ( startAngleDeg == 0 && endAngleDeg == 0 )
1629 || ( endAngleDeg - startAngleDeg >= 360 )
1630 || ( startAngleDeg == endAngleDeg );
1631 }
1632 // Older versions do not have arcs
1633
1634 if( layer == Edge_Cuts )
1635 {
1636 // Approximate arcs as line segments for outline reconstruction
1637 std::vector<VECTOR2I> segment;
1638
1639 if( isFullCircle )
1640 {
1641 for( int i = 0; i <= 24; i++ )
1642 {
1643 double angle = ( static_cast<double>( i ) / 24.0 ) * 2.0 * M_PI;
1644 int px = center.x + static_cast<int>( std::cos( angle ) * kiRadius );
1645 int py = center.y - static_cast<int>( std::sin( angle ) * kiRadius );
1646 segment.emplace_back( px, py );
1647 }
1648 }
1649 else
1650 {
1651 int32_t sa = startAngleDeg * 1000;
1652 int32_t ea = endAngleDeg * 1000;
1653
1654 if( ea <= sa )
1655 ea += 360000;
1656
1657 for( int32_t a = sa; a <= ea; a += 15000 )
1658 {
1659 double rad = ( static_cast<double>( a ) / 1000.0 ) * M_PI / 180.0;
1660 int px = center.x + static_cast<int>( std::cos( rad ) * kiRadius );
1661 int py = center.y - static_cast<int>( std::sin( rad ) * kiRadius );
1662 segment.emplace_back( px, py );
1663 }
1664
1665 double endRad = ( static_cast<double>( ea ) / 1000.0 ) * M_PI / 180.0;
1666 int epx = center.x + static_cast<int>( std::cos( endRad ) * kiRadius );
1667 int epy = center.y - static_cast<int>( std::sin( endRad ) * kiRadius );
1668 segment.emplace_back( epx, epy );
1669 }
1670
1671 aOutlineSegments.push_back( std::move( segment ) );
1672 return;
1673 }
1674
1675 PCB_SHAPE* shape = new PCB_SHAPE( aContainer );
1676 shape->SetLayer( layer );
1677 shape->SetWidth( width );
1678
1679 if( isFullCircle )
1680 {
1681 shape->SetShape( SHAPE_T::CIRCLE );
1682 shape->SetCenter( center );
1683 shape->SetEnd( VECTOR2I( center.x + kiRadius, center.y ) );
1684 }
1685 else
1686 {
1687 shape->SetShape( SHAPE_T::ARC );
1688 shape->SetCenter( center );
1689
1690 // Y-flip reverses angular direction, so negate start angle
1691 double startRad = startAngleDeg * M_PI / 180.0;
1692 int sx = center.x + static_cast<int>( std::cos( startRad ) * kiRadius );
1693 int sy = center.y - static_cast<int>( std::sin( startRad ) * kiRadius );
1694 shape->SetStart( VECTOR2I( sx, sy ) );
1695
1696 double newEndAngle = endAngleDeg;
1697
1698 if( newEndAngle < startAngleDeg )
1699 newEndAngle += 360;
1700
1701 // Negate arc angle for Y-flip (reverses sweep direction)
1702 double arcAngle = startAngleDeg - newEndAngle;
1703 shape->SetArcAngleAndEnd( EDA_ANGLE( arcAngle, DEGREES_T ), true );
1704 }
1705
1706 if( IsCopperLayer( layer ) )
1707 {
1708 if( NETINFO_ITEM* net = resolveItemNet( board, aObj, layer, aGroundPlane, aGndPlaneNet ) )
1709 shape->SetNet( net );
1710 }
1711
1712 aContainer->Add( shape );
1713 processItemGroups( shape, aObj, aGidToItems );
1714}
1715
1716
1718 std::map<uint32_t, std::set<BOARD_ITEM*>>& aGidToItems )
1719{
1720 FOOTPRINT* fp = dynamic_cast<FOOTPRINT*>( aContainer );
1721
1722 // Skip component reference/value text only when it is not attached to a footprint.
1723 if( aObj.component_id > 0 && !fp )
1724 return;
1725
1726 PCB_LAYER_ID layer = mapLayer( aObj.layer );
1727
1728 if( layer == Edge_Cuts )
1729 return;
1730
1731 PCB_TEXT* text = nullptr;
1732 bool add = false;
1733
1734 if( aObj.component_id > 0 && fp && aObj.type == SPRINT_LAYOUT::OBJ_STROKE_TEXT && aObj.tht_shape > 0
1735 && aObj.tht_shape <= 2 )
1736 {
1737 if( aObj.tht_shape == 1 )
1738 text = &fp->Reference();
1739 else if( aObj.tht_shape == 2 )
1740 text = &fp->Value();
1741 }
1742 else
1743 {
1744 if( aObj.text.empty() )
1745 return;
1746
1747 text = new PCB_TEXT( aContainer );
1748 add = true;
1749 }
1750
1751 if( !text )
1752 return;
1753
1754 // When inside a group, the rotation center seems to be at the group center.
1755 // Just so we don't have to do a complex fixup later, use points to detect
1756 // text center instead, they are always in absolute coordinates.
1757 VECTOR2I ptsCenter;
1758
1759 if( !aObj.text_children.empty() )
1760 {
1761 double cx = 0, cy = 0;
1762 size_t ptsCount = 0;
1763
1764 for( const SPRINT_LAYOUT::OBJECT& child : aObj.text_children )
1765 {
1766 if( child.type == SPRINT_LAYOUT::OBJ_LINE )
1767 {
1768 for( const SPRINT_LAYOUT::POINT& pt : child.points )
1769 {
1770 cx += pt.x;
1771 cy += pt.y;
1772 ptsCount += 1;
1773 }
1774 }
1775 else if( child.type == SPRINT_LAYOUT::OBJ_SEGMENT )
1776 {
1777 cx += child.x;
1778 cy += child.y;
1779 cx += child.outer;
1780 cy += child.inner;
1781 ptsCount += 2;
1782 }
1783 }
1784
1785 cx /= static_cast<double>( ptsCount );
1786 cy /= static_cast<double>( ptsCount );
1787 ptsCenter = sprintToKicadPos( static_cast<float>( cx ), static_cast<float>( cy ) );
1788 }
1789
1790 text->SetLayer( layer );
1791 text->SetText( convertString( aObj.text ) );
1792 text->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
1793 text->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM );
1794 text->SetKeepUpright( false );
1795 text->SetVisible( aObj.component_id == 0 || aObj.filled != 0 );
1796
1798 {
1799 int height = sprintToKicadCoord( aObj.outer ) * 0.75;
1800
1801 if( height <= 0 )
1802 height = pcbIUScale.mmToIU( 1.0 );
1803
1804 double widthScale = 0.8 + 0.2 * aObj.line_width;
1805 text->SetTextSize( VECTOR2I( height * widthScale, height ) );
1806
1807 double thicknessScale = 0.06 + 0.05 * aObj.inner;
1808 int thickness = height * thicknessScale;
1809
1810 if( thickness <= 0 )
1811 thickness = std::max( 1, height / 8 );
1812
1813 text->SetTextThickness( thickness );
1814 }
1815 else
1816 {
1817 // -133 maps to 1 mm height
1818 int normalized = std::abs( aObj.line_width ) * 100 / 133;
1819 int height = sprintToKicadCoord( normalized ) * 0.75;
1820
1821 if( aObj.line_width < 0 ) // Seems to be always
1822 text->SetVertJustify( GR_TEXT_V_ALIGN_TOP );
1823
1824 text->SetTextSize( VECTOR2I( height, height ) );
1825 text->SetTextThickness( height / 8 );
1826 }
1827
1828 VECTOR2I untransformedPos = sprintToKicadPos( aObj.x, aObj.y );
1829 text->SetTextPos( untransformedPos );
1830 VECTOR2I untransformedCenter = text->GetCenter();
1831
1832 VECTOR2I newCenter = !aObj.text_children.empty() ? ptsCenter : untransformedCenter;
1833 text->SetVertJustify( GR_TEXT_V_ALIGN_CENTER );
1834 text->SetHorizJustify( GR_TEXT_H_ALIGN_CENTER );
1835 text->SetTextPos( newCenter );
1836
1837 int rotation = 0;
1838
1839 if( m_fileData.version == 4 )
1840 rotation = aObj.start_angle * 90;
1841 else
1842 rotation = aObj.rotation;
1843
1844 bool mirrorH = aObj.mirror_h != 0;
1845 bool mirrorV = aObj.mirror_v != 0;
1846
1847 if( mirrorH ^ mirrorV )
1848 {
1849 text->SetMirrored( true );
1850 text->SetHorizJustify( (GR_TEXT_H_ALIGN_T) -text->GetHorizJustify() );
1851 rotation = -rotation;
1852 }
1853
1854 if( mirrorV )
1855 text->Rotate( newCenter, ANGLE_180 );
1856
1857 text->Rotate( newCenter, EDA_ANGLE( -rotation, DEGREES_T ) );
1858
1859 if( add )
1860 {
1861 aContainer->Add( text );
1862 processItemGroups( text, aObj, aGidToItems );
1863 }
1864}
1865
1866
1868 std::map<uint32_t, std::set<BOARD_ITEM*>>& aGidToItems )
1869{
1870 for( uint32_t gid : aObj.groups )
1871 aGidToItems[gid].insert( aItem );
1872}
1873
1874
1876 std::map<uint32_t, std::set<BOARD_ITEM*>>& aGidToItems )
1877{
1878 std::map<uint32_t, PCB_GROUP*> gidGroupMap;
1879 std::vector<uint32_t> gidAscBySize;
1880
1881 for( const auto& [gid, _] : aGidToItems )
1882 {
1883 PCB_GROUP* group = new PCB_GROUP( aContainer );
1884
1885 gidGroupMap[gid] = group;
1886 gidAscBySize.push_back( gid );
1887
1888 if( aContainer )
1889 aContainer->Add( group );
1890 }
1891
1892 // Process groups in ascending member-count order (tie-break by group id) so smaller
1893 // groups attach first and larger groups can adopt them, producing stable nesting.
1894 std::sort( gidAscBySize.begin(), gidAscBySize.end(),
1895 [&]( uint32_t gidA, uint32_t gidB )
1896 {
1897 size_t sa = aGidToItems.at( gidA ).size();
1898 size_t sb = aGidToItems.at( gidB ).size();
1899
1900 if( sa != sb )
1901 return sa < sb;
1902
1903 return gidA < gidB;
1904 } );
1905
1906 for( uint32_t gid : gidAscBySize )
1907 {
1908 PCB_GROUP* grp = gidGroupMap[gid];
1909
1910 for( BOARD_ITEM* item : aGidToItems[gid] )
1911 {
1912 if( PCB_GROUP* itemGroup = static_cast<PCB_GROUP*>( item->GetParentGroup() ) )
1913 {
1914 if( itemGroup != grp )
1915 grp->AddItem( itemGroup );
1916 }
1917 else
1918 {
1919 // Only add if we don't cross board-footprint boundaries
1920 if( item->GetParent() == grp->GetParent() )
1921 grp->AddItem( item );
1922 }
1923 }
1924 }
1925}
1926
1927
1928void SPRINT_LAYOUT_PARSER::buildOutline( BOARD* aBoard, std::vector<std::vector<VECTOR2I>>& aOutlineSegments,
1929 const SPRINT_LAYOUT::BOARD_DATA& aBoardData )
1930{
1931 // Try to join outline segments into closed polygons
1932 // Similar to OpenBoardView's outline_order_segments algorithm
1933 static const int PROXIMITY_DELTA = 100; // 100 nm tolerance
1934
1935 auto closeEnough = []( const VECTOR2I& a, const VECTOR2I& b, int delta ) -> bool
1936 {
1937 return std::abs( a.x - b.x ) < delta && std::abs( a.y - b.y ) < delta;
1938 };
1939
1940 // Try to join segments end-to-end. After each successful join, restart
1941 // the inner scan because seg.back() has changed.
1942 for( size_t iterations = 0; iterations < aOutlineSegments.size(); iterations++ )
1943 {
1944 bool joined = false;
1945
1946 for( auto& seg : aOutlineSegments )
1947 {
1948 if( seg.size() < 2 )
1949 continue;
1950
1951 for( auto& other : aOutlineSegments )
1952 {
1953 if( &seg == &other || other.empty() )
1954 continue;
1955
1956 bool frontMatch = closeEnough( seg.back(), other.front(), PROXIMITY_DELTA );
1957 bool backMatch = !frontMatch
1958 && closeEnough( seg.back(), other.back(), PROXIMITY_DELTA );
1959
1960 if( backMatch )
1961 {
1962 std::reverse( other.begin(), other.end() );
1963 frontMatch = true;
1964 }
1965
1966 if( !frontMatch )
1967 continue;
1968
1969 if( seg.back() == other.front() )
1970 seg.insert( seg.end(), other.begin() + 1, other.end() );
1971 else
1972 seg.insert( seg.end(), other.begin(), other.end() );
1973
1974 other.clear();
1975 joined = true;
1976 break;
1977 }
1978 }
1979
1980 if( !joined )
1981 break;
1982 }
1983
1984 bool hasOutline = false;
1985
1986 for( const auto& seg : aOutlineSegments )
1987 {
1988 if( seg.size() < 2 )
1989 continue;
1990
1991 hasOutline = true;
1992
1993 for( size_t i = 0; i + 1 < seg.size(); i++ )
1994 {
1995 PCB_SHAPE* shape = new PCB_SHAPE( aBoard );
1996 shape->SetShape( SHAPE_T::SEGMENT );
1997 shape->SetLayer( Edge_Cuts );
1998 shape->SetWidth( pcbIUScale.mmToIU( 0.1 ) );
1999 shape->SetStart( seg[i] );
2000 shape->SetEnd( seg[i + 1] );
2001 aBoard->Add( shape );
2002 }
2003 }
2004
2005 // Fallback: create rectangular outline from board dimensions
2006 if( !hasOutline )
2007 {
2008 int w = sprintToKicadCoord( static_cast<float>( aBoardData.size_x ) );
2009 int h = sprintToKicadCoord( static_cast<float>( aBoardData.size_y ) );
2010
2011 if( w > 0 && h > 0 )
2012 {
2013 VECTOR2I corners[4] = {
2014 { 0, 0 },
2015 { w, 0 },
2016 { w, h },
2017 { 0, h }
2018 };
2019
2020 for( int i = 0; i < 4; i++ )
2021 {
2022 PCB_SHAPE* shape = new PCB_SHAPE( aBoard );
2023 shape->SetShape( SHAPE_T::SEGMENT );
2024 shape->SetLayer( Edge_Cuts );
2025 shape->SetWidth( pcbIUScale.mmToIU( 0.1 ) );
2026 shape->SetStart( corners[i] );
2027 shape->SetEnd( corners[( i + 1 ) % 4] );
2028 aBoard->Add( shape );
2029 }
2030 }
2031 }
2032}
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:81
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:231
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:372
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
Definition board.cpp:1295
const FOOTPRINTS & Footprints() const
Definition board.h:420
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:877
void SetOrientationDegrees(double aOrientation)
Definition footprint.h:432
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:847
void SetValue(const wxString &aValue)
Definition footprint.h:868
PCB_FIELD & Reference()
Definition footprint.h:878
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:459
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:49
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:830
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:1350
void SetBorderDisplayStyle(ZONE_BORDER_DISPLAY_STYLE aBorderHatchStyle, int aBorderHatchPitch, bool aRebuilBorderdHatch)
Set all hatch parameters for the zone.
Definition zone.cpp:1458
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:599
void SetIsRuleArea(bool aEnable)
Definition zone.h:812
void SetDoNotAllowTracks(bool aEnable)
Definition zone.h:829
void SetLayerSet(const LSET &aLayerSet) override
Definition zone.cpp:624
void SetDoNotAllowVias(bool aEnable)
Definition zone.h:828
void SetNet(NETINFO_ITEM *aNetInfo) override
Override that drops aNetInfo when this zone is in copper-thieving fill mode.
Definition zone.cpp:590
void SetThermalReliefGap(int aThermalReliefGap)
Definition zone.h:240
void SetDoNotAllowFootprints(bool aEnable)
Definition zone.h:831
void SetDoNotAllowZoneFills(bool aEnable)
Definition zone.h:827
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:834
static int GetDefaultHatchPitch()
Definition zone.cpp:1535
#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
bool IsBackLayer(PCB_LAYER_ID aLayerId)
Layer classification: check if it's a back layer.
Definition layer_ids.h:801
bool IsCopperLayer(int aLayerId)
Test whether a layer is a copper layer.
Definition layer_ids.h:675
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