KiCad PCB EDA Suite
Loading...
Searching...
No Matches
sprint_layout_parser.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19 * or you may search the http://www.gnu.org website for the version 2 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 *
23 * Binary format knowledge derived from:
24 * https://github.com/sergey-raevskiy/xlay (lay6.h)
25 * https://github.com/OpenBoardView/OpenBoardView (LAYFile.cpp)
26 */
27
29
30#include <board.h>
32#include <footprint.h>
33#include <netinfo.h>
34#include <pad.h>
35#include <pcb_shape.h>
36#include <pcb_text.h>
37#include <zone.h>
39#include <math/util.h>
40#include <font/fontconfig.h>
41
42#include <wx/filename.h>
43#include <wx/wfstream.h>
44#include <wx/log.h>
45
46#include <algorithm>
47#include <cmath>
48#include <cstring>
49#include <limits>
50
51// All multi-byte reads below decode little-endian explicitly,
52// so this parser works correctly on any host byte order.
53
54static constexpr uint32_t MAX_OBJECTS = 1000000;
55static constexpr uint32_t MAX_GROUPS = 100000;
56static constexpr uint32_t MAX_CHILDREN = 10000;
57static constexpr uint32_t MAX_POINTS = 1000000;
58
59
61
62
64
65
66// ============================================================================
67// Binary reading helpers
68// ============================================================================
69
71{
72 if( m_pos + 1 > m_end )
73 THROW_IO_ERROR( _( "Unexpected end of Sprint Layout file" ) );
74
75 return *m_pos++;
76}
77
78
80{
81 if( m_pos + 2 > m_end )
82 THROW_IO_ERROR( _( "Unexpected end of Sprint Layout file" ) );
83
84 uint16_t v = static_cast<uint16_t>( m_pos[0] )
85 | ( static_cast<uint16_t>( m_pos[1] ) << 8 );
86 m_pos += 2;
87 return v;
88}
89
90
92{
93 if( m_pos + 4 > m_end )
94 THROW_IO_ERROR( _( "Unexpected end of Sprint Layout file" ) );
95
96 uint32_t v = static_cast<uint32_t>( m_pos[0] )
97 | ( static_cast<uint32_t>( m_pos[1] ) << 8 )
98 | ( static_cast<uint32_t>( m_pos[2] ) << 16 )
99 | ( static_cast<uint32_t>( m_pos[3] ) << 24 );
100 m_pos += 4;
101 return v;
102}
103
104
106{
107 return static_cast<int32_t>( readUint32() );
108}
109
110
112{
113 uint32_t bits = readUint32();
114 float v;
115 std::memcpy( &v, &bits, sizeof( v ) );
116 return v;
117}
118
119
121{
122 if( m_pos + 8 > m_end )
123 THROW_IO_ERROR( _( "Unexpected end of Sprint Layout file" ) );
124
125 uint64_t bits = static_cast<uint64_t>( m_pos[0] )
126 | ( static_cast<uint64_t>( m_pos[1] ) << 8 )
127 | ( static_cast<uint64_t>( m_pos[2] ) << 16 )
128 | ( static_cast<uint64_t>( m_pos[3] ) << 24 )
129 | ( static_cast<uint64_t>( m_pos[4] ) << 32 )
130 | ( static_cast<uint64_t>( m_pos[5] ) << 40 )
131 | ( static_cast<uint64_t>( m_pos[6] ) << 48 )
132 | ( static_cast<uint64_t>( m_pos[7] ) << 56 );
133 m_pos += 8;
134
135 double v;
136 std::memcpy( &v, &bits, sizeof( v ) );
137 return v;
138}
139
140
141std::string SPRINT_LAYOUT_PARSER::readFixedString( size_t aMaxLen )
142{
143 size_t rawLen = readUint8();
144 size_t len = std::min( rawLen, aMaxLen );
145
146 if( m_pos + aMaxLen > m_end )
147 THROW_IO_ERROR( _( "Unexpected end of Sprint Layout file" ) );
148
149 std::string s( reinterpret_cast<const char*>( m_pos ), len );
150 m_pos += aMaxLen;
151 return s;
152}
153
154
156{
157 uint32_t len = readUint32();
158
159 if( len > 100000 )
160 THROW_IO_ERROR( _( "Invalid string length in Sprint Layout file" ) );
161
162 if( m_pos + len > m_end )
163 THROW_IO_ERROR( _( "Unexpected end of Sprint Layout file" ) );
164
165 std::string s( reinterpret_cast<const char*>( m_pos ), len );
166 m_pos += len;
167 return s;
168}
169
170
171void SPRINT_LAYOUT_PARSER::skip( size_t aBytes )
172{
173 if( m_pos + aBytes > m_end )
174 THROW_IO_ERROR( _( "Unexpected end of Sprint Layout file" ) );
175
176 m_pos += aBytes;
177}
178
179
180// ============================================================================
181// Parsing
182// ============================================================================
183
184bool SPRINT_LAYOUT_PARSER::Parse( const wxString& aFileName )
185{
186 wxFFileInputStream stream( aFileName );
187
188 if( !stream.IsOk() )
189 THROW_IO_ERROR( wxString::Format( _( "Cannot open file '%s'" ), aFileName ) );
190
191 size_t fileSize = stream.GetLength();
192
193 if( fileSize < 8 )
194 THROW_IO_ERROR( wxString::Format( _( "File '%s' is too small to be a Sprint Layout file" ), aFileName ) );
195
196 m_buffer.resize( fileSize );
197 stream.Read( m_buffer.data(), fileSize );
198
199 if( stream.LastRead() != fileSize )
200 THROW_IO_ERROR( wxString::Format( _( "Failed to read file '%s'" ), aFileName ) );
201
202 m_start = m_buffer.data();
203 m_pos = m_start;
204 m_end = m_start + fileSize;
205
206 // File header: version + magic bytes (0x33, 0xAA, 0xFF)
207 m_fileData.version = readUint8();
208 uint8_t magic1 = readUint8();
209 uint8_t magic2 = readUint8();
210 uint8_t magic3 = readUint8();
211
212 if( m_fileData.version > 6 || magic1 != 0x33 || magic2 != 0xAA || magic3 != 0xFF )
213 THROW_IO_ERROR( _( "Invalid Sprint Layout file header" ) );
214
215 uint32_t numBoards = readUint32();
216
217 if( numBoards == 0 || numBoards > 100 )
218 THROW_IO_ERROR( _( "Invalid board count in Sprint Layout file" ) );
219
220 m_fileData.boards.resize( numBoards );
221
222 for( uint32_t b = 0; b < numBoards; b++ )
223 {
224 parseBoardHeader( m_fileData.boards[b] );
225
226 uint32_t numConnections = 0;
227
228 for( auto& obj : m_fileData.boards[b].objects )
229 {
230 if( obj.type == SPRINT_LAYOUT::OBJ_THT_PAD || obj.type == SPRINT_LAYOUT::OBJ_SMD_PAD )
231 numConnections++;
232 }
233
234 // Read connection records (one per pad object)
235 for( uint32_t c = 0; c < numConnections; c++ )
236 {
237 uint32_t connCount = readUint32();
238
239 // Skip the connection data for now
240 skip( connCount * sizeof( uint32_t ) );
241 }
242 }
243
244 parseTrailer();
245
246 return true;
247}
248
249
251{
252 // Board name (Pascal string, 30 bytes max)
253 aBoard.name = readFixedString( 30 );
254
255 // Unknown padding
256 skip( 4 );
257
258 aBoard.size_x = readUint32();
259 aBoard.size_y = readUint32();
260
261 // Ground plane enabled flag per layer (C1, S1, C2, S2, I1, I2, O)
262 for( int i = 0; i < 7; i++ )
263 aBoard.ground_plane[i] = readUint8();
264
265 // Grid and viewport (not needed for import)
266 readDouble(); // active_grid_val
267 readDouble(); // zoom
268 readUint32(); // viewport_offset_x
269 readUint32(); // viewport_offset_y
270
271 // Active layer + padding
272 skip( 4 );
273
274 // Layer visibility + scanned copy flags
275 skip( 7 ); // layer_visible[7]
276 skip( 1 ); // show_scanned_copy_top
277 skip( 1 ); // show_scanned_copy_bottom
278
279 // Scanned copy paths
280 readFixedString( 200 );
281 readFixedString( 200 );
282
283 // DPI and shift values for scanned copies
284 skip( 4 * 6 ); // dpi_top, dpi_bottom, shiftx/y_top, shiftx/y_bottom
285
286 // Unknown fields
287 skip( 4 * 2 );
288
289 aBoard.center_x = readInt32();
290 aBoard.center_y = readInt32();
291
292 aBoard.is_multilayer = readUint8();
293
294 uint32_t numObjects = readUint32();
295
296 if( numObjects > MAX_OBJECTS )
297 THROW_IO_ERROR( _( "Too many objects in Sprint Layout board" ) );
298
299 aBoard.objects.resize( numObjects );
300
301 for( uint32_t i = 0; i < numObjects; i++ )
302 parseObject( aBoard.objects[i] );
303}
304
305
307{
308 aObj.type = readUint8();
309
310 // Type 0 entries are deleted objects with zeroed type bytes. The original
311 // application has no special case for type 0 either -- its signed group-count
312 // loop and broad exception handler mask the parse failures. The 117-byte fixed
313 // size (1 type + 116 data) was validated against ku14194revb.lay6 (10 type-0
314 // objects, 0 bytes remaining).
315 if( aObj.type == 0 )
316 {
317 skip( 116 );
318 return;
319 }
320
324 {
325 THROW_IO_ERROR( wxString::Format( _( "Unknown object type %d in Sprint Layout file" ),
326 aObj.type ) );
327 }
328
329 aObj.x = readFloat();
330 aObj.y = readFloat();
331 aObj.outer = readFloat();
332 aObj.inner = readFloat();
333 aObj.line_width = readUint32();
334 skip( 1 ); // padding
335 aObj.layer = readUint8();
336 aObj.tht_shape = readUint8();
337 skip( 4 ); // padding
338 aObj.component_id = readUint16();
339 skip( 1 ); // selected
340 aObj.start_angle = readInt32(); // also th_style[4]
341 skip( 5 ); // unknown
342 aObj.filled = readUint8();
343 aObj.clearance = readInt32();
344 skip( 5 ); // unknown
345 aObj.thermal_width = readUint8();
346 aObj.mirror = readUint8();
347 aObj.keepout = readUint8();
348 aObj.rotation = readInt32(); // thzise
349 aObj.plated = readUint8();
350 aObj.soldermask = readUint8();
351 skip( 18 ); // unknown padding
352
353 // Variable-length data after header
354 if( !aIsTextChild )
355 {
356 aObj.text = readVarString();
357 aObj.net_name = readVarString();
358
359 uint32_t groupCount = readUint32();
360
361 if( groupCount > MAX_GROUPS )
362 THROW_IO_ERROR( _( "Too many groups in Sprint Layout object" ) );
363
364 aObj.groups.resize( groupCount );
365
366 for( uint32_t i = 0; i < groupCount; i++ )
367 aObj.groups[i] = readUint32();
368 }
369
370 switch( aObj.type )
371 {
373 // Circles have no points list
374 return;
375
377 {
378 uint32_t childCount = readUint32();
379
380 if( childCount > MAX_CHILDREN )
381 THROW_IO_ERROR( _( "Too many text children in Sprint Layout object" ) );
382
383 aObj.text_children.resize( childCount );
384
385 for( uint32_t i = 0; i < childCount; i++ )
386 parseObject( aObj.text_children[i], true );
387
388 // Component data follows for text objects that define a component
389 if( aObj.tht_shape == 1 )
390 {
391 aObj.component.valid = true;
392 aObj.component.off_x = readFloat();
393 aObj.component.off_y = readFloat();
398 aObj.component.use = readUint8();
399 }
400
401 return;
402 }
403
404 default:
405 break;
406 }
407
408 // Points list for pads, lines, and polygons
409 uint32_t pointCount = readUint32();
410
411 if( pointCount > MAX_POINTS )
412 THROW_IO_ERROR( _( "Too many points in Sprint Layout object" ) );
413
414 aObj.points.resize( pointCount );
415
416 for( uint32_t i = 0; i < pointCount; i++ )
417 {
418 aObj.points[i].x = readFloat();
419 aObj.points[i].y = readFloat();
420 }
421}
422
423
425{
426 readUint32(); // active_board_tab
427 m_fileData.project_name = readFixedString( 100 );
428 m_fileData.project_author = readFixedString( 100 );
429 m_fileData.project_company = readFixedString( 100 );
430 m_fileData.project_comment = readVarString();
431}
432
433
434// ============================================================================
435// Board construction
436// ============================================================================
437
438PCB_LAYER_ID SPRINT_LAYOUT_PARSER::mapLayer( uint8_t aSprintLayer ) const
439{
440 switch( aSprintLayer )
441 {
442 case SPRINT_LAYOUT::LAYER_C1: return F_Cu;
443 case SPRINT_LAYOUT::LAYER_S1: return F_SilkS;
444 case SPRINT_LAYOUT::LAYER_C2: return B_Cu;
445 case SPRINT_LAYOUT::LAYER_S2: return B_SilkS;
446 case SPRINT_LAYOUT::LAYER_I1: return In1_Cu;
447 case SPRINT_LAYOUT::LAYER_I2: return In2_Cu;
449 default: return F_Cu;
450 }
451}
452
453
455{
456 // Sprint Layout uses 1/10000 mm, KiCad uses nanometers (1 nm = 1e-6 mm)
457 // 1/10000 mm = 100 nm
458 double nm = static_cast<double>( aValue ) * 100.0;
459
460 if( nm > std::numeric_limits<int>::max() || nm < std::numeric_limits<int>::min() )
461 THROW_IO_ERROR( _( "Coordinate value out of range in Sprint Layout file" ) );
462
463 return KiROUND( nm );
464}
465
466
468{
469 // Sprint Layout uses Y-up (mathematical), KiCad uses Y-down (screen)
470 return VECTOR2I( sprintToKicadCoord( aX ), sprintToKicadCoord( -aY ) );
471}
472
473
475 std::map<wxString, std::unique_ptr<FOOTPRINT>>& aFootprintMap, size_t aBoardIndex )
476{
477 if( aBoardIndex >= m_fileData.boards.size() )
478 return nullptr;
479
480 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
481
482 // Set up copper layers based on whether inner layers are used
483 const SPRINT_LAYOUT::BOARD_DATA& boardData = m_fileData.boards[aBoardIndex];
484 bool hasInnerLayers = false;
485
486 for( const auto& obj : boardData.objects )
487 {
488 if( obj.layer == SPRINT_LAYOUT::LAYER_I1 || obj.layer == SPRINT_LAYOUT::LAYER_I2 )
489 {
490 hasInnerLayers = true;
491 break;
492 }
493 }
494
495 if( hasInnerLayers || boardData.is_multilayer )
496 board->SetCopperLayerCount( 4 );
497 else
498 board->SetCopperLayerCount( 2 );
499
500 // Maps component_id to FOOTPRINT for grouping pads into components
501 std::map<uint16_t, FOOTPRINT*> componentMap;
502 std::vector<std::vector<VECTOR2I>> outlineSegments;
503
504 // First pass: create footprints for all component IDs referenced by text objects
505 for( const auto& obj : boardData.objects )
506 {
507 if( obj.type == SPRINT_LAYOUT::OBJ_TEXT && obj.component_id > 0
508 && obj.component.valid )
509 {
510 if( componentMap.find( obj.component_id ) == componentMap.end() )
511 {
512 FOOTPRINT* fp = new FOOTPRINT( board.get() );
513 fp->SetReference( wxString::FromUTF8( obj.text ) );
514 fp->SetValue( wxString::FromUTF8( obj.component.comment ) );
515
516 if( !obj.component.package.empty() )
517 {
518 fp->SetLibDescription( wxString::FromUTF8( obj.component.package ) );
519 }
520
521 double rotDeg = obj.component.rotation;
522 fp->SetOrientationDegrees( rotDeg );
523
524 PCB_LAYER_ID layer = mapLayer( obj.layer );
525
526 if( layer == B_Cu || layer == B_SilkS )
527 fp->SetLayer( B_Cu );
528 else
529 fp->SetLayer( F_Cu );
530
531 componentMap[obj.component_id] = fp;
532 board->Add( fp );
533
534 wxString fpKey = wxString::Format( wxS( "SprintLayout_%s" ),
535 wxString::FromUTF8( obj.text ) );
536 aFootprintMap[fpKey] = std::unique_ptr<FOOTPRINT>(
537 static_cast<FOOTPRINT*>( fp->Clone() ) );
538 }
539 }
540 }
541
542 // Second pass: process all objects
543 for( const auto& obj : boardData.objects )
544 {
545 switch( obj.type )
546 {
549 addPadToBoard( board.get(), obj, componentMap, aFootprintMap );
550 break;
551
553 addLineToBoard( board.get(), obj, outlineSegments );
554 break;
555
557 addPolyToBoard( board.get(), obj, outlineSegments );
558 break;
559
561 addCircleToBoard( board.get(), obj, outlineSegments );
562 break;
563
565 addTextToBoard( board.get(), obj );
566 break;
567
568 default:
569 break;
570 }
571 }
572
573 buildOutline( board.get(), outlineSegments, boardData );
574
575 // Create ground plane zones for layers where ground plane is enabled.
576 // Sprint Layout stores a per-layer flag in the board header.
577 // Indices: 0=C1(F.Cu), 2=C2(B.Cu), 4=I1(In1.Cu), 5=I2(In2.Cu)
578 static const struct
579 {
580 int index;
581 PCB_LAYER_ID layer;
582 } groundPlaneMap[] = {
583 { 0, F_Cu },
584 { 2, B_Cu },
585 { 4, In1_Cu },
586 { 5, In2_Cu },
587 };
588
589 for( const auto& gp : groundPlaneMap )
590 {
591 if( boardData.ground_plane[gp.index] == 0 )
592 continue;
593
594 int w = sprintToKicadCoord( static_cast<float>( boardData.size_x ) );
595 int h = sprintToKicadCoord( static_cast<float>( boardData.size_y ) );
596
597 if( w <= 0 || h <= 0 )
598 continue;
599
600 ZONE* zone = new ZONE( board.get() );
601 zone->SetLayer( gp.layer );
602 zone->SetIsRuleArea( false );
603 zone->SetZoneName( wxString::Format( wxS( "GND_PLANE_%s" ),
604 board->GetLayerName( gp.layer ) ) );
605 zone->SetLocalClearance( std::optional<int>( pcbIUScale.mmToIU( 0.3 ) ) );
606 zone->SetThermalReliefGap( pcbIUScale.mmToIU( 0.5 ) );
607 zone->SetThermalReliefSpokeWidth( pcbIUScale.mmToIU( 0.5 ) );
608 zone->SetAssignedPriority( 0 );
609
610 SHAPE_POLY_SET outline;
611 outline.NewOutline();
612 outline.Append( 0, 0 );
613 outline.Append( w, 0 );
614 outline.Append( w, h );
615 outline.Append( 0, h );
616
617 zone->AddPolygon( outline.COutline( 0 ) );
618 board->Add( zone );
619 }
620
621 // Center the board content on the page
622 BOX2I bbox = board->ComputeBoundingBox( true );
623
624 if( bbox.GetWidth() > 0 && bbox.GetHeight() > 0 )
625 {
626 VECTOR2I pageSize = board->GetPageSettings().GetSizeIU( pcbIUScale.IU_PER_MILS );
627 VECTOR2I centerOffset = VECTOR2I( pageSize.x / 2, pageSize.y / 2 ) - bbox.GetCenter();
628
629 for( FOOTPRINT* fp : board->Footprints() )
630 fp->Move( centerOffset );
631
632 for( ZONE* zone : board->Zones() )
633 zone->Move( centerOffset );
634
635 for( BOARD_ITEM* item : board->Drawings() )
636 item->Move( centerOffset );
637 }
638
639 return board.release();
640}
641
642
644 BOARD* aBoard, const SPRINT_LAYOUT::OBJECT& aObj,
645 std::map<uint16_t, FOOTPRINT*>& aComponentMap,
646 std::map<wxString, std::unique_ptr<FOOTPRINT>>& aFootprintMap )
647{
648 FOOTPRINT* fp = nullptr;
649
650 // Find or create the parent footprint
651 if( aObj.component_id > 0 )
652 {
653 auto it = aComponentMap.find( aObj.component_id );
654
655 if( it != aComponentMap.end() )
656 {
657 fp = it->second;
658 }
659 else
660 {
661 // Create an anonymous footprint for this component
662 fp = new FOOTPRINT( aBoard );
663 fp->SetReference( wxString::Format( wxS( "U%d" ), aObj.component_id ) );
664 fp->SetLayer( F_Cu );
665 aComponentMap[aObj.component_id] = fp;
666 aBoard->Add( fp );
667 }
668 }
669 else
670 {
671 // Standalone pad without a component gets its own footprint
672 fp = new FOOTPRINT( aBoard );
673 fp->SetReference( wxString::Format( wxS( "PAD%d" ),
674 static_cast<int>( aBoard->Footprints().size() ) ) );
675 fp->SetLayer( F_Cu );
676 aBoard->Add( fp );
677 }
678
679 PAD* pad = new PAD( fp );
680
681 // SMD pad x,y may be a component-relative offset rather than an absolute
682 // position (depends on the Sprint Layout version that created the file).
683 // The points array always stores absolute coordinates, so derive the pad
684 // center from the points when available.
685 VECTOR2I pos;
686
687 if( aObj.type == SPRINT_LAYOUT::OBJ_SMD_PAD && !aObj.points.empty() )
688 {
689 double cx = 0, cy = 0;
690
691 for( const auto& pt : aObj.points )
692 {
693 cx += pt.x;
694 cy += pt.y;
695 }
696
697 cx /= static_cast<double>( aObj.points.size() );
698 cy /= static_cast<double>( aObj.points.size() );
699 pos = sprintToKicadPos( static_cast<float>( cx ), static_cast<float>( cy ) );
700 }
701 else
702 {
703 pos = sprintToKicadPos( aObj.x, aObj.y );
704 }
705
706 pad->SetPosition( pos );
707
708 if( fp->Pads().empty() )
709 fp->SetPosition( pos );
710
711 if( aObj.type == SPRINT_LAYOUT::OBJ_THT_PAD )
712 {
713 if( aObj.plated == 0 )
714 pad->SetAttribute( PAD_ATTRIB::NPTH );
715 else
716 pad->SetAttribute( PAD_ATTRIB::PTH );
717
718 pad->SetLayerSet( PAD::PTHMask() );
719
720 int outerDia = sprintToKicadCoord( aObj.outer * 2.0f );
721 int drillDia = sprintToKicadCoord( aObj.inner * 2.0f );
722
723 pad->SetSize( PADSTACK::ALL_LAYERS, VECTOR2I( outerDia, outerDia ) );
724 pad->SetDrillSize( VECTOR2I( drillDia, drillDia ) );
725
726 switch( aObj.tht_shape )
727 {
730 break;
731
734 pad->SetChamferRectRatio( PADSTACK::ALL_LAYERS, 0.25 );
735 pad->SetChamferPositions( PADSTACK::ALL_LAYERS,
737 break;
738
741 break;
742
743 default:
745 break;
746 }
747
748 // The start_angle field is a union with th_style[4] for pad objects.
749 // Each byte is the thermal style for one copper layer (C1, C2, I1, I2).
750 // 0=direct/full, 1=thermal relief, 2=no connection
751 uint8_t thStyle = aObj.start_angle & 0xFF;
752
753 if( thStyle == 0 )
754 pad->SetLocalZoneConnection( ZONE_CONNECTION::FULL );
755 else if( thStyle == 2 )
756 pad->SetLocalZoneConnection( ZONE_CONNECTION::NONE );
757 else
758 pad->SetLocalZoneConnection( ZONE_CONNECTION::THERMAL );
759 }
760 else
761 {
762 pad->SetAttribute( PAD_ATTRIB::SMD );
763
764 PCB_LAYER_ID padLayer = mapLayer( aObj.layer );
765
766 if( padLayer == B_Cu || padLayer == B_SilkS )
767 {
768 pad->SetLayerSet( LSET( { B_Cu, B_Paste, B_Mask } ) );
769 fp->SetLayer( B_Cu );
770 }
771 else
772 {
773 pad->SetLayerSet( PAD::SMDMask() );
774 }
775
776 int width = sprintToKicadCoord( aObj.outer * 2.0f );
777 int height = sprintToKicadCoord( aObj.inner * 2.0f );
778
779 if( height <= 0 )
780 height = width;
781
782 pad->SetSize( PADSTACK::ALL_LAYERS, VECTOR2I( width, height ) );
783
786 else
788 }
789
790 // Solder mask: soldermask==0 means no mask opening (pad is tented/covered)
791 if( aObj.soldermask == 0 )
792 {
793 pad->Padstack().FrontOuterLayers().has_solder_mask = false;
794 pad->Padstack().BackOuterLayers().has_solder_mask = false;
795 }
796
797 // Per-element ground plane clearance
798 if( aObj.clearance > 0 )
799 {
800 int clearance = sprintToKicadCoord( static_cast<float>( aObj.clearance ) );
801 pad->SetLocalClearance( std::optional<int>( clearance ) );
802 }
803
804 // Thermal spoke width
805 if( aObj.thermal_width > 0 )
806 {
807 int spokeWidth = sprintToKicadCoord( static_cast<float>( aObj.thermal_width ) );
808 pad->SetLocalThermalSpokeWidthOverride( std::optional<int>( spokeWidth ) );
809 }
810
811 // Set net name
812 if( !aObj.net_name.empty() )
813 {
814 wxString netName = wxString::FromUTF8( aObj.net_name );
815 NETINFO_ITEM* net = aBoard->FindNet( netName );
816
817 if( !net )
818 {
819 net = new NETINFO_ITEM( aBoard, netName );
820 aBoard->Add( net );
821 }
822
823 pad->SetNet( net );
824 }
825
826 pad->SetNumber( wxString::Format( wxS( "%d" ),
827 static_cast<int>( fp->Pads().size() + 1 ) ) );
828
829 fp->Add( pad );
830}
831
832
834 BOARD* aBoard, const SPRINT_LAYOUT::OBJECT& aObj,
835 std::vector<std::vector<VECTOR2I>>& aOutlineSegments )
836{
837 if( aObj.points.size() < 2 )
838 return;
839
840 PCB_LAYER_ID layer = mapLayer( aObj.layer );
841
842 if( layer == Edge_Cuts )
843 {
844 std::vector<VECTOR2I> segment;
845
846 for( const auto& pt : aObj.points )
847 segment.push_back( sprintToKicadPos( pt.x, pt.y ) );
848
849 aOutlineSegments.push_back( std::move( segment ) );
850 return;
851 }
852
853 int width = sprintToKicadCoord( static_cast<float>( aObj.line_width ) );
854
855 if( width <= 0 )
856 width = pcbIUScale.mmToIU( 0.25 );
857
858 for( size_t i = 0; i + 1 < aObj.points.size(); i++ )
859 {
860 PCB_SHAPE* shape = new PCB_SHAPE( aBoard );
861 shape->SetShape( SHAPE_T::SEGMENT );
862 shape->SetLayer( layer );
863 shape->SetWidth( width );
864 shape->SetStart( sprintToKicadPos( aObj.points[i].x, aObj.points[i].y ) );
865 shape->SetEnd( sprintToKicadPos( aObj.points[i + 1].x, aObj.points[i + 1].y ) );
866 aBoard->Add( shape );
867 }
868}
869
870
872 BOARD* aBoard, const SPRINT_LAYOUT::OBJECT& aObj,
873 std::vector<std::vector<VECTOR2I>>& aOutlineSegments )
874{
875 if( aObj.points.size() < 2 )
876 return;
877
878 PCB_LAYER_ID layer = mapLayer( aObj.layer );
879
880 if( layer == Edge_Cuts )
881 {
882 std::vector<VECTOR2I> segment;
883
884 for( const auto& pt : aObj.points )
885 segment.push_back( sprintToKicadPos( pt.x, pt.y ) );
886
887 aOutlineSegments.push_back( std::move( segment ) );
888 return;
889 }
890
891 bool isFilled = ( aObj.filled != 0 );
892 bool isCutout = ( aObj.keepout != 0 );
893
894 if( isCutout && LSET::AllCuMask().Contains( layer ) && aObj.points.size() >= 3 )
895 {
896 // Cutout area for ground plane exclusion -> rule area (keepout zone)
897 ZONE* zone = new ZONE( aBoard );
898 zone->SetLayer( layer );
899 zone->SetIsRuleArea( true );
900 zone->SetDoNotAllowZoneFills( true );
901 zone->SetDoNotAllowTracks( false );
902 zone->SetDoNotAllowVias( false );
903 zone->SetDoNotAllowPads( false );
904 zone->SetDoNotAllowFootprints( false );
905
906 SHAPE_POLY_SET outline;
907 outline.NewOutline();
908
909 for( const auto& pt : aObj.points )
910 {
911 VECTOR2I pos = sprintToKicadPos( pt.x, pt.y );
912 outline.Append( pos.x, pos.y );
913 }
914
915 zone->AddPolygon( outline.COutline( 0 ) );
916 aBoard->Add( zone );
917 }
918 else if( isFilled && LSET::AllCuMask().Contains( layer ) && aObj.points.size() >= 3 )
919 {
920 // Filled polygon on copper -> ZONE
921 ZONE* zone = new ZONE( aBoard );
922 zone->SetLayer( layer );
923 zone->SetIsRuleArea( false );
924 zone->SetDoNotAllowZoneFills( false );
925
926 SHAPE_POLY_SET outline;
927 outline.NewOutline();
928
929 for( const auto& pt : aObj.points )
930 {
931 VECTOR2I pos = sprintToKicadPos( pt.x, pt.y );
932 outline.Append( pos.x, pos.y );
933 }
934
935 zone->AddPolygon( outline.COutline( 0 ) );
936
937 if( !aObj.net_name.empty() )
938 {
939 wxString netName = wxString::FromUTF8( aObj.net_name );
940 NETINFO_ITEM* net = aBoard->FindNet( netName );
941
942 if( !net )
943 {
944 net = new NETINFO_ITEM( aBoard, netName );
945 aBoard->Add( net );
946 }
947
948 zone->SetNet( net );
949 }
950
951 aBoard->Add( zone );
952 }
953 else if( isFilled && aObj.points.size() >= 3 )
954 {
955 // Filled polygon on non-copper layer -> filled PCB_SHAPE
956 PCB_SHAPE* shape = new PCB_SHAPE( aBoard );
957 shape->SetShape( SHAPE_T::POLY );
958 shape->SetFilled( true );
959 shape->SetLayer( layer );
960 shape->SetWidth( 0 );
961
962 SHAPE_POLY_SET polySet;
963 polySet.NewOutline();
964
965 for( const auto& pt : aObj.points )
966 {
967 VECTOR2I pos = sprintToKicadPos( pt.x, pt.y );
968 polySet.Append( pos.x, pos.y );
969 }
970
971 shape->SetPolyShape( polySet );
972 aBoard->Add( shape );
973 }
974 else
975 {
976 // Unfilled polygon -> PCB_SHAPE polyline segments
977 int width = sprintToKicadCoord( static_cast<float>( aObj.line_width ) );
978
979 if( width <= 0 )
980 width = pcbIUScale.mmToIU( 0.25 );
981
982 for( size_t i = 0; i + 1 < aObj.points.size(); i++ )
983 {
984 PCB_SHAPE* shape = new PCB_SHAPE( aBoard );
985 shape->SetShape( SHAPE_T::SEGMENT );
986 shape->SetLayer( layer );
987 shape->SetWidth( width );
988 shape->SetStart( sprintToKicadPos( aObj.points[i].x, aObj.points[i].y ) );
989 shape->SetEnd( sprintToKicadPos( aObj.points[i + 1].x, aObj.points[i + 1].y ) );
990 aBoard->Add( shape );
991 }
992 }
993}
994
995
997 BOARD* aBoard, const SPRINT_LAYOUT::OBJECT& aObj,
998 std::vector<std::vector<VECTOR2I>>& aOutlineSegments )
999{
1000 PCB_LAYER_ID layer = mapLayer( aObj.layer );
1001 VECTOR2I center = sprintToKicadPos( aObj.x, aObj.y );
1002 float radius = ( aObj.outer + aObj.inner ) / 2.0f;
1003 int kiRadius = sprintToKicadCoord( radius );
1004 int width = sprintToKicadCoord( aObj.outer - aObj.inner );
1005
1006 if( width <= 0 )
1007 width = pcbIUScale.mmToIU( 0.25 );
1008
1009 int32_t startAngle = aObj.start_angle;
1010
1011 // line_width is uint32_t but stores end_angle (signed) for circle objects.
1012 // Two's complement reinterpretation is correct here.
1013 int32_t endAngle = static_cast<int32_t>( aObj.line_width );
1014
1015 bool isFullCircle = ( startAngle == 0 && endAngle == 0 )
1016 || ( endAngle - startAngle >= 360000 )
1017 || ( startAngle == endAngle );
1018
1019 if( layer == Edge_Cuts )
1020 {
1021 // Approximate arcs as line segments for outline reconstruction
1022 std::vector<VECTOR2I> segment;
1023
1024 if( isFullCircle )
1025 {
1026 for( int i = 0; i <= 24; i++ )
1027 {
1028 double angle = ( static_cast<double>( i ) / 24.0 ) * 2.0 * M_PI;
1029 int px = center.x + static_cast<int>( std::cos( angle ) * kiRadius );
1030 int py = center.y - static_cast<int>( std::sin( angle ) * kiRadius );
1031 segment.emplace_back( px, py );
1032 }
1033 }
1034 else
1035 {
1036 int32_t sa = startAngle;
1037 int32_t ea = endAngle;
1038
1039 if( ea <= sa )
1040 ea += 360000;
1041
1042 for( int32_t a = sa; a <= ea; a += 15000 )
1043 {
1044 double rad = ( static_cast<double>( a ) / 1000.0 ) * M_PI / 180.0;
1045 int px = center.x + static_cast<int>( std::cos( rad ) * kiRadius );
1046 int py = center.y - static_cast<int>( std::sin( rad ) * kiRadius );
1047 segment.emplace_back( px, py );
1048 }
1049
1050 double endRad = ( static_cast<double>( ea ) / 1000.0 ) * M_PI / 180.0;
1051 int epx = center.x + static_cast<int>( std::cos( endRad ) * kiRadius );
1052 int epy = center.y - static_cast<int>( std::sin( endRad ) * kiRadius );
1053 segment.emplace_back( epx, epy );
1054 }
1055
1056 aOutlineSegments.push_back( std::move( segment ) );
1057 return;
1058 }
1059
1060 PCB_SHAPE* shape = new PCB_SHAPE( aBoard );
1061 shape->SetLayer( layer );
1062 shape->SetWidth( width );
1063
1064 if( isFullCircle )
1065 {
1066 shape->SetShape( SHAPE_T::CIRCLE );
1067 shape->SetCenter( center );
1068 shape->SetEnd( VECTOR2I( center.x + kiRadius, center.y ) );
1069 }
1070 else
1071 {
1072 shape->SetShape( SHAPE_T::ARC );
1073 shape->SetCenter( center );
1074
1075 // Y-flip reverses angular direction, so negate start angle
1076 double startRad = ( static_cast<double>( startAngle ) / 1000.0 ) * M_PI / 180.0;
1077 int sx = center.x + static_cast<int>( std::cos( startRad ) * kiRadius );
1078 int sy = center.y - static_cast<int>( std::sin( startRad ) * kiRadius );
1079 shape->SetStart( VECTOR2I( sx, sy ) );
1080
1081 int32_t ea = endAngle;
1082
1083 if( ea <= startAngle )
1084 ea += 360000;
1085
1086 // Negate arc angle for Y-flip (reverses sweep direction)
1087 double arcAngle = -static_cast<double>( ea - startAngle ) / 1000.0;
1088 shape->SetArcAngleAndEnd( EDA_ANGLE( arcAngle, DEGREES_T ), true );
1089 }
1090
1091 aBoard->Add( shape );
1092}
1093
1094
1096 const SPRINT_LAYOUT::OBJECT& aObj )
1097{
1098 if( aObj.text.empty() )
1099 return;
1100
1101 // Skip component reference/value text that's already on the footprint
1102 if( aObj.component_id > 0 )
1103 return;
1104
1105 PCB_LAYER_ID layer = mapLayer( aObj.layer );
1106
1107 if( layer == Edge_Cuts )
1108 return;
1109
1110 PCB_TEXT* text = new PCB_TEXT( aBoard );
1111 text->SetLayer( layer );
1112 text->SetText( wxString::FromUTF8( aObj.text ) );
1113
1114 VECTOR2I pos = sprintToKicadPos( aObj.x, aObj.y );
1115 text->SetPosition( pos );
1116
1117 int height = sprintToKicadCoord( aObj.outer );
1118
1119 if( height <= 0 )
1120 height = pcbIUScale.mmToIU( 1.0 );
1121
1122 text->SetTextSize( VECTOR2I( height, height ) );
1123
1124 int thickness = sprintToKicadCoord( aObj.inner );
1125
1126 if( thickness <= 0 )
1127 thickness = std::max( 1, height / 8 );
1128
1129 text->SetTextThickness( thickness );
1130
1131 if( aObj.rotation != 0 )
1132 {
1133 double angleDeg = static_cast<double>( aObj.rotation ) / 1000.0;
1134 text->SetTextAngle( EDA_ANGLE( angleDeg, DEGREES_T ) );
1135 }
1136
1137 if( aObj.mirror != 0 )
1138 text->SetMirrored( true );
1139
1140 aBoard->Add( text );
1141}
1142
1143
1145 BOARD* aBoard, std::vector<std::vector<VECTOR2I>>& aOutlineSegments,
1146 const SPRINT_LAYOUT::BOARD_DATA& aBoardData )
1147{
1148 // Try to join outline segments into closed polygons
1149 // Similar to OpenBoardView's outline_order_segments algorithm
1150 static const int PROXIMITY_DELTA = 100; // 100 nm tolerance
1151
1152 auto closeEnough = []( const VECTOR2I& a, const VECTOR2I& b, int delta ) -> bool
1153 {
1154 return std::abs( a.x - b.x ) < delta && std::abs( a.y - b.y ) < delta;
1155 };
1156
1157 // Try to join segments end-to-end. After each successful join, restart
1158 // the inner scan because seg.back() has changed.
1159 for( size_t iterations = 0; iterations < aOutlineSegments.size(); iterations++ )
1160 {
1161 bool joined = false;
1162
1163 for( auto& seg : aOutlineSegments )
1164 {
1165 if( seg.size() < 2 )
1166 continue;
1167
1168 for( auto& other : aOutlineSegments )
1169 {
1170 if( &seg == &other || other.empty() )
1171 continue;
1172
1173 bool frontMatch = closeEnough( seg.back(), other.front(), PROXIMITY_DELTA );
1174 bool backMatch = !frontMatch
1175 && closeEnough( seg.back(), other.back(), PROXIMITY_DELTA );
1176
1177 if( backMatch )
1178 {
1179 std::reverse( other.begin(), other.end() );
1180 frontMatch = true;
1181 }
1182
1183 if( !frontMatch )
1184 continue;
1185
1186 if( seg.back() == other.front() )
1187 seg.insert( seg.end(), other.begin() + 1, other.end() );
1188 else
1189 seg.insert( seg.end(), other.begin(), other.end() );
1190
1191 other.clear();
1192 joined = true;
1193 break;
1194 }
1195 }
1196
1197 if( !joined )
1198 break;
1199 }
1200
1201 bool hasOutline = false;
1202
1203 for( const auto& seg : aOutlineSegments )
1204 {
1205 if( seg.size() < 2 )
1206 continue;
1207
1208 hasOutline = true;
1209
1210 for( size_t i = 0; i + 1 < seg.size(); i++ )
1211 {
1212 PCB_SHAPE* shape = new PCB_SHAPE( aBoard );
1213 shape->SetShape( SHAPE_T::SEGMENT );
1214 shape->SetLayer( Edge_Cuts );
1215 shape->SetWidth( pcbIUScale.mmToIU( 0.1 ) );
1216 shape->SetStart( seg[i] );
1217 shape->SetEnd( seg[i + 1] );
1218 aBoard->Add( shape );
1219 }
1220 }
1221
1222 // Fallback: create rectangular outline from board dimensions
1223 if( !hasOutline )
1224 {
1225 int w = sprintToKicadCoord( static_cast<float>( aBoardData.size_x ) );
1226 int h = sprintToKicadCoord( static_cast<float>( aBoardData.size_y ) );
1227
1228 if( w > 0 && h > 0 )
1229 {
1230 VECTOR2I corners[4] = {
1231 { 0, 0 },
1232 { w, 0 },
1233 { w, h },
1234 { 0, h }
1235 };
1236
1237 for( int i = 0; i < 4; i++ )
1238 {
1239 PCB_SHAPE* shape = new PCB_SHAPE( aBoard );
1240 shape->SetShape( SHAPE_T::SEGMENT );
1241 shape->SetLayer( Edge_Cuts );
1242 shape->SetWidth( pcbIUScale.mmToIU( 0.1 ) );
1243 shape->SetStart( corners[i] );
1244 shape->SetEnd( corners[( i + 1 ) % 4] );
1245 aBoard->Add( shape );
1246 }
1247 }
1248 }
1249}
int index
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:112
BOX2< VECTOR2I > BOX2I
Definition box2.h:922
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:990
void SetNet(NETINFO_ITEM *aNetInfo)
Set a NET_INFO object for the item.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:84
virtual void SetLayer(PCB_LAYER_ID aLayer)
Set the layer this item is on.
Definition board_item.h:316
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:323
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
Definition board.cpp:1237
NETINFO_ITEM * FindNet(int aNetcode) const
Search for a net with the given netcode.
Definition board.cpp:2581
const FOOTPRINTS & Footprints() const
Definition board.h:364
constexpr size_type GetWidth() const
Definition box2.h:214
constexpr const Vec GetCenter() const
Definition box2.h:230
constexpr size_type GetHeight() const
Definition box2.h:215
void SetCenter(const VECTOR2I &aCenter)
void SetPolyShape(const SHAPE_POLY_SET &aShape)
Definition eda_shape.h:356
virtual void SetFilled(bool aFlag)
Definition eda_shape.h:148
void SetStart(const VECTOR2I &aStart)
Definition eda_shape.h:190
void SetShape(SHAPE_T aShape)
Definition eda_shape.h:180
void SetEnd(const VECTOR2I &aEnd)
Definition eda_shape.h:232
void SetArcAngleAndEnd(const EDA_ANGLE &aAngle, bool aCheckNegativeAngle=false)
Set the end point from the angle center and start.
void SetWidth(int aWidth)
void SetPosition(const VECTOR2I &aPos) override
EDA_ITEM * Clone() const override
Invoke a function on all children.
void SetOrientationDegrees(double aOrientation)
Definition footprint.h:362
std::deque< PAD * > & Pads()
Definition footprint.h:326
void SetReference(const wxString &aReference)
Definition footprint.h:777
void SetValue(const wxString &aValue)
Definition footprint.h:798
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
void SetLibDescription(const wxString &aDesc)
Definition footprint.h:389
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
static LSET AllCuMask(int aCuLayerCount)
Return a mask holding the requested number of Cu PCB_LAYER_IDs.
Definition lset.cpp:599
Handle the data for a net.
Definition netinfo.h:50
static constexpr PCB_LAYER_ID ALL_LAYERS
! Temporary layer identifier to identify code that is not padstack-aware
Definition padstack.h:177
Definition pad.h:55
static LSET PTHMask()
layer set for a through hole pad
Definition pad.cpp:368
static LSET SMDMask()
layer set for a SMD pad on Front layer
Definition pad.cpp:375
void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
Represent a set of closed polygons.
int Append(int x, int y, int aOutline=-1, int aHole=-1, bool aAllowDuplication=false)
Appends a vertex at the end of the given outline/hole (default: the last outline)
int NewOutline()
Creates a new empty polygon in the set and returns its index.
const SHAPE_LINE_CHAIN & COutline(int aIndex) const
void addLineToBoard(BOARD *aBoard, const SPRINT_LAYOUT::OBJECT &aObj, std::vector< std::vector< VECTOR2I > > &aOutlineSegments)
SPRINT_LAYOUT::FILE_DATA m_fileData
PCB_LAYER_ID mapLayer(uint8_t aSprintLayer) const
void addPolyToBoard(BOARD *aBoard, const SPRINT_LAYOUT::OBJECT &aObj, std::vector< std::vector< VECTOR2I > > &aOutlineSegments)
std::string readFixedString(size_t aMaxLen)
bool Parse(const wxString &aFileName)
void buildOutline(BOARD *aBoard, std::vector< std::vector< VECTOR2I > > &aOutlineSegments, const SPRINT_LAYOUT::BOARD_DATA &aBoardData)
void addTextToBoard(BOARD *aBoard, const SPRINT_LAYOUT::OBJECT &aObj)
void parseObject(SPRINT_LAYOUT::OBJECT &aObject, bool aIsTextChild=false)
void addPadToBoard(BOARD *aBoard, const SPRINT_LAYOUT::OBJECT &aObj, std::map< uint16_t, FOOTPRINT * > &aComponentMap, std::map< wxString, std::unique_ptr< FOOTPRINT > > &aFootprintMap)
BOARD * CreateBoard(std::map< wxString, std::unique_ptr< FOOTPRINT > > &aFootprintMap, size_t aBoardIndex=0)
void parseBoardHeader(SPRINT_LAYOUT::BOARD_DATA &aBoard)
void addCircleToBoard(BOARD *aBoard, const SPRINT_LAYOUT::OBJECT &aObj, std::vector< std::vector< VECTOR2I > > &aOutlineSegments)
VECTOR2I sprintToKicadPos(float aX, float aY) const
int sprintToKicadCoord(float aValue) const
std::vector< uint8_t > m_buffer
Handle a list of polygons defining a copper zone.
Definition zone.h:74
void SetDoNotAllowPads(bool aEnable)
Definition zone.h:735
void SetLocalClearance(std::optional< int > aClearance)
Definition zone.h:187
void AddPolygon(std::vector< VECTOR2I > &aPolygon)
Add a polygon to the zone outline.
Definition zone.cpp:1173
void SetThermalReliefSpokeWidth(int aThermalReliefSpokeWidth)
Definition zone.h:238
virtual void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
Definition zone.cpp:550
void SetIsRuleArea(bool aEnable)
Definition zone.h:717
void SetDoNotAllowTracks(bool aEnable)
Definition zone.h:734
void SetDoNotAllowVias(bool aEnable)
Definition zone.h:733
void SetThermalReliefGap(int aThermalReliefGap)
Definition zone.h:227
void SetDoNotAllowFootprints(bool aEnable)
Definition zone.h:736
void SetDoNotAllowZoneFills(bool aEnable)
Definition zone.h:732
void SetAssignedPriority(unsigned aPriority)
Definition zone.h:121
void SetZoneName(const wxString &aName)
Definition zone.h:165
#define _(s)
@ DEGREES_T
Definition eda_angle.h:31
@ SEGMENT
Definition eda_shape.h:47
#define THROW_IO_ERROR(msg)
macro which captures the "call site" values of FILE_, __FUNCTION & LINE
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:60
@ Edge_Cuts
Definition layer_ids.h:112
@ B_Mask
Definition layer_ids.h:98
@ B_Cu
Definition layer_ids.h:65
@ B_Paste
Definition layer_ids.h:105
@ In2_Cu
Definition layer_ids.h:67
@ F_SilkS
Definition layer_ids.h:100
@ In1_Cu
Definition layer_ids.h:66
@ B_SilkS
Definition layer_ids.h:101
@ F_Cu
Definition layer_ids.h:64
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
@ NPTH
like PAD_PTH, but not plated mechanical use only, no connection allowed
Definition padstack.h:103
@ 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
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
VECTOR2I center
int radius
int clearance
int delta
#define M_PI
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:687
@ THERMAL
Use thermal relief for pads.
Definition zones.h:50
@ NONE
Pads are not covered.
Definition zones.h:49
@ FULL
pads are covered by copper
Definition zones.h:51