KiCad PCB EDA Suite
Loading...
Searching...
No Matches
pads_binary_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 (C) 2026 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
24#include "pads_binary_parser.h"
25
26#include <algorithm>
27#include <cmath>
28#include <cstring>
29#include <fstream>
30#include <set>
31
32#include <ki_exception.h>
33#include <wx/log.h>
34
35namespace PADS_IO
36{
37
38// Expected footer GUID
39static const uint8_t FOOTER_GUID[] = "{2FE18320-6448-11d1-A412-000000000000}";
40
41// Pad shape codes from binary format
42static const std::map<uint8_t, std::string> PAD_SHAPE_NAMES = {
43 { 0x01, "RF" },
44 { 0x02, "R" },
45 { 0x03, "S" },
46 { 0x04, "OF" },
47};
48
49
51
52
54
55
56bool BINARY_PARSER::IsBinaryPadsFile( const wxString& aFileName )
57{
58 std::ifstream file( aFileName.fn_str(), std::ios::binary );
59
60 if( !file.is_open() )
61 return false;
62
63 uint8_t header[4];
64 file.read( reinterpret_cast<char*>( header ), 4 );
65
66 if( file.gcount() < 4 )
67 return false;
68
69 // Magic bytes: 0x00 0xFF
70 if( header[0] != 0x00 || header[1] != 0xFF )
71 return false;
72
73 uint16_t version = static_cast<uint16_t>( header[2] ) | ( static_cast<uint16_t>( header[3] ) << 8 );
74
75 return version == 0x2021 || version == 0x2025 || version == 0x2026 || version == 0x2027;
76}
77
78
79void BINARY_PARSER::Parse( const wxString& aFileName )
80{
81 std::ifstream file( aFileName.fn_str(), std::ios::binary | std::ios::ate );
82
83 if( !file.is_open() )
84 THROW_IO_ERROR( "Cannot open file" );
85
86 std::streamsize fileSize = file.tellg();
87 file.seekg( 0, std::ios::beg );
88
89 m_data.resize( static_cast<size_t>( fileSize ) );
90 file.read( reinterpret_cast<char*>( m_data.data() ), fileSize );
91
92 if( file.gcount() != fileSize )
93 THROW_IO_ERROR( "Failed to read entire file" );
94
112
113 // Filter out parts with empty ref des
114 m_parts.erase( std::remove_if( m_parts.begin(), m_parts.end(),
115 []( const PART& p ) { return p.name.empty(); } ),
116 m_parts.end() );
117}
118
119
121{
122 switch( m_version )
123 {
124 case 0x2021: return 73;
125 case 0x2025:
126 case 0x2026:
127 case 0x2027: return 74;
128 default: return 0;
129 }
130}
131
132
133uint8_t BINARY_PARSER::readU8( size_t aOffset ) const
134{
135 if( aOffset >= m_data.size() )
136 {
137 THROW_IO_ERROR( wxString::Format( "PADS binary read out of bounds at offset %zu (file size %zu)",
138 aOffset, m_data.size() ) );
139 }
140
141 return m_data[aOffset];
142}
143
144
145uint16_t BINARY_PARSER::readU16( size_t aOffset ) const
146{
147 if( aOffset + 2 > m_data.size() )
148 {
149 THROW_IO_ERROR( wxString::Format( "PADS binary read out of bounds at offset %zu (file size %zu)",
150 aOffset, m_data.size() ) );
151 }
152
153 return static_cast<uint16_t>( m_data[aOffset] )
154 | ( static_cast<uint16_t>( m_data[aOffset + 1] ) << 8 );
155}
156
157
158uint32_t BINARY_PARSER::readU32( size_t aOffset ) const
159{
160 if( aOffset + 4 > m_data.size() )
161 {
162 THROW_IO_ERROR( wxString::Format( "PADS binary read out of bounds at offset %zu (file size %zu)",
163 aOffset, m_data.size() ) );
164 }
165
166 return static_cast<uint32_t>( m_data[aOffset] )
167 | ( static_cast<uint32_t>( m_data[aOffset + 1] ) << 8 )
168 | ( static_cast<uint32_t>( m_data[aOffset + 2] ) << 16 )
169 | ( static_cast<uint32_t>( m_data[aOffset + 3] ) << 24 );
170}
171
172
173int32_t BINARY_PARSER::readI32( size_t aOffset ) const
174{
175 return static_cast<int32_t>( readU32( aOffset ) );
176}
177
178
179std::string BINARY_PARSER::readFixedString( size_t aOffset, size_t aMaxLen ) const
180{
181 if( aOffset >= m_data.size() )
182 return {};
183
184 size_t available = std::min( aMaxLen, m_data.size() - aOffset );
185 const uint8_t* start = &m_data[aOffset];
186 const uint8_t* end = start + available;
187
188 // Find null terminator
189 const uint8_t* null_pos = std::find( start, end, 0 );
190 size_t len = static_cast<size_t>( null_pos - start );
191
192 // Validate printable ASCII
193 for( size_t i = 0; i < len; ++i )
194 {
195 if( start[i] < 0x20 || start[i] >= 0x7F )
196 return {};
197 }
198
199 std::string result( reinterpret_cast<const char*>( start ), len );
200
201 // Trim trailing whitespace
202 while( !result.empty() && result.back() == ' ' )
203 result.pop_back();
204
205 return result;
206}
207
208
210{
211 if( m_data.size() < static_cast<size_t>( HEADER_SIZE + FOOTER_SIZE ) )
212 THROW_IO_ERROR( "File too small for PADS binary format" );
213
214 if( m_data[0] != 0x00 || m_data[1] != 0xFF )
215 THROW_IO_ERROR( "Invalid magic bytes" );
216
217 m_version = readU16( 2 );
218
219 if( m_version != 0x2021 && m_version != 0x2025 && m_version != 0x2026 && m_version != 0x2027 )
220 THROW_IO_ERROR( "Unsupported PADS binary version" );
221
223}
224
225
227{
228 size_t footerStart = m_data.size() - FOOTER_SIZE;
229
230 // Verify GUID at footer offset + 4
231 if( std::memcmp( &m_data[footerStart + 4], FOOTER_GUID, 38 ) != 0 )
232 THROW_IO_ERROR( "Invalid footer GUID" );
233
234 uint32_t sizeCheck = readU32( footerStart + 42 );
235 uint32_t expected = static_cast<uint32_t>( m_data.size() - FOOTER_SIZE );
236
237 if( sizeCheck != expected )
238 {
239 wxLogWarning( "PADS binary footer size mismatch: stored=%u, expected=%u",
240 sizeCheck, expected );
241 }
242}
243
244
246{
247 size_t dirStart = HEADER_SIZE;
248 size_t dirSize = static_cast<size_t>( m_numDirEntries ) * DIR_ENTRY_SIZE;
249
250 if( dirStart + dirSize > m_data.size() )
251 THROW_IO_ERROR( "File too small for section directory" );
252
253 uint32_t dataOffset = static_cast<uint32_t>( dirStart + dirSize );
254
255 m_dirEntries.clear();
256 m_dirEntries.reserve( m_numDirEntries );
257
258 for( int i = 0; i < m_numDirEntries; ++i )
259 {
260 size_t off = dirStart + static_cast<size_t>( i ) * DIR_ENTRY_SIZE;
261
262 DirEntry entry;
263 entry.index = i;
264 entry.count = readU32( off );
265 entry.totalBytes = readU32( off + 4 );
266 entry.dataOffset = 0;
267 entry.perItem = 0;
268
269 if( i > 0 )
270 {
271 entry.dataOffset = dataOffset;
272
273 if( entry.count > 0 && entry.totalBytes > 0 )
274 entry.perItem = entry.totalBytes / entry.count;
275
276 dataOffset += entry.totalBytes;
277 }
278
279 m_dirEntries.push_back( entry );
280 }
281}
282
283
285{
286 if( aIndex >= 0 && aIndex < static_cast<int>( m_dirEntries.size() ) )
287 return &m_dirEntries[aIndex];
288
289 return nullptr;
290}
291
292
293const uint8_t* BINARY_PARSER::sectionData( int aIndex ) const
294{
295 const DirEntry* entry = getSection( aIndex );
296
297 if( !entry || entry->totalBytes == 0 )
298 return nullptr;
299
300 if( entry->dataOffset + entry->totalBytes > m_data.size() )
301 return nullptr;
302
303 return &m_data[entry->dataOffset];
304}
305
306
307uint32_t BINARY_PARSER::sectionSize( int aIndex ) const
308{
309 const DirEntry* entry = getSection( aIndex );
310
311 if( !entry )
312 return 0;
313
314 return entry->totalBytes;
315}
316
317
318double BINARY_PARSER::toBasicCoordX( int32_t aRawValue ) const
319{
320 return static_cast<double>( aRawValue - ( m_originFound ? m_originX : 0 ) );
321}
322
323
324double BINARY_PARSER::toBasicCoordY( int32_t aRawValue ) const
325{
326 return static_cast<double>( aRawValue - ( m_originFound ? m_originY : 0 ) );
327}
328
329
330double BINARY_PARSER::toBasicAngle( int32_t aRawAngle ) const
331{
332 if( aRawAngle == 0 )
333 return 0.0;
334
335 return static_cast<double>( aRawAngle ) / static_cast<double>( ANGLE_SCALE );
336}
337
338
340{
341 const uint8_t* data = sectionData( 1 );
342 uint32_t size = sectionSize( 1 );
343
344 if( !data || size < 160 )
345 return;
346
347 // Board setup section contains u32 parameters at known offsets.
348 // Index 4 holds the maximum layer count.
349 uint32_t maxLayer = readU32( m_dirEntries[1].dataOffset + 4 * 4 );
350
351 if( maxLayer >= 1 && maxLayer <= 64 )
352 m_parameters.layer_count = static_cast<int>( maxLayer );
353 else
354 m_parameters.layer_count = 2;
355
356 // Section 1 stores the coordinate origin at offset +60/+64 as i32 LE pair.
357 // This is the same value as DFT_CONFIGURATION POLAR_GRID X/Y but is always present.
358 size_t secBase = m_dirEntries[1].dataOffset;
359
360 if( size >= 68 )
361 {
362 m_originX = readI32( secBase + 60 );
363 m_originY = readI32( secBase + 64 );
364 m_originFound = true;
365
366 m_parameters.origin.x = static_cast<double>( m_originX );
367 m_parameters.origin.y = static_cast<double>( m_originY );
368 }
369
370 // Binary coordinates are in BASIC units (1 BASIC = 1/38100 mil).
371 // Set MILS for the display unit; actual coordinate handling uses BASIC mode
372 // in the wrapper via SetBasicUnitsMode(true).
374}
375
376
378{
379 const uint8_t* data = sectionData( 57 );
380 uint32_t size = sectionSize( 57 );
381
382 if( !data || size == 0 )
383 return;
384
385 m_stringPoolBytes.assign( data, data + size );
386}
387
388
390{
391 const DirEntry* entry = getSection( 22 );
392
393 if( !entry || entry->count == 0 || entry->perItem == 0 )
394 return;
395
396 const uint8_t* data = sectionData( 22 );
397
398 if( !data )
399 return;
400
401 bool isOld = isOldFormat();
402 uint32_t recSize = entry->perItem;
403
404 // Field offsets differ between versions
405 int nameOff = 0, xOff = 0, yOff = 0, angleOff = 0;
406
407 if( isOld )
408 {
409 nameOff = 76;
410 xOff = 92;
411 yOff = -1;
412 angleOff = 4;
413 }
414 else
415 {
416 nameOff = 44;
417 xOff = 60;
418 yOff = 64;
419 angleOff = 68;
420 }
421
422 for( uint32_t i = 0; i < entry->count; ++i )
423 {
424 size_t off = static_cast<size_t>( i ) * recSize;
425
426 if( off + recSize > entry->totalBytes )
427 break;
428
429 size_t base = entry->dataOffset + off;
430 std::string refDes = readFixedString( base + nameOff, 16 );
431
432 if( refDes.empty() || !std::isalnum( static_cast<unsigned char>( refDes[0] ) ) )
433 continue;
434
435 int32_t x = readI32( base + xOff );
436
437 // v0x2021 Y coordinate encoding is not yet solved. Use 0 as placeholder.
438 int32_t y = ( yOff >= 0 ) ? readI32( base + yOff ) : 0;
439 int32_t angleRaw = readI32( base + angleOff );
440
441 PART part;
442 part.name = refDes;
443 part.location.x = toBasicCoordX( x );
444 part.location.y = toBasicCoordY( y );
445 part.rotation = toBasicAngle( angleRaw );
446 part.bottom_layer = false;
447 part.units = "M";
448
449 m_parts.push_back( part );
450 }
451}
452
453
455{
456 // Part records can be embedded in sections other than section 22.
457 // Scan sections 19 (design_rules) and 21 (board_outline) for FEFF-delimited part records.
458 bool isOld = isOldFormat();
459 int nameOff = 0, xOff = 0, yOff = 0, angleOff = 0, feffOff = 0;
460
461 if( isOld )
462 {
463 nameOff = 76;
464 xOff = 92;
465 yOff = -1;
466 angleOff = 4;
467 feffOff = 28;
468 }
469 else
470 {
471 nameOff = 44;
472 xOff = 60;
473 yOff = 64;
474 angleOff = 68;
475 feffOff = 92;
476 }
477
478 int recSize = feffOff + 2;
479
480 std::set<std::string> existingRefs;
481
482 for( const auto& p : m_parts )
483 existingRefs.insert( p.name );
484
485 for( int secIdx : { 19, 21 } )
486 {
487 const DirEntry* entry = getSection( secIdx );
488
489 if( !entry || entry->totalBytes == 0 )
490 continue;
491
492 const uint8_t* data = sectionData( secIdx );
493 uint32_t size = sectionSize( secIdx );
494
495 if( !data || size == 0 )
496 continue;
497
498 for( size_t pos = 0; pos + 1 < size; ++pos )
499 {
500 if( data[pos] != 0xFE || data[pos + 1] != 0xFF )
501 continue;
502
503 int recStart = static_cast<int>( pos ) - feffOff;
504
505 if( recStart < 0 || recStart + recSize > static_cast<int>( size ) )
506 continue;
507
508 size_t base = entry->dataOffset + recStart;
509 std::string refDes = readFixedString( base + nameOff, 16 );
510
511 if( refDes.empty() || !std::isalnum( static_cast<unsigned char>( refDes[0] ) ) )
512 continue;
513
514 if( existingRefs.count( refDes ) )
515 continue;
516
517 int32_t x = readI32( base + xOff );
518 int32_t y = ( yOff >= 0 ) ? readI32( base + yOff ) : 0;
519 int32_t angleRaw = readI32( base + angleOff );
520
521 PART part;
522 part.name = refDes;
523 part.location.x = toBasicCoordX( x );
524 part.location.y = toBasicCoordY( y );
525 part.rotation = toBasicAngle( angleRaw );
526 part.bottom_layer = false;
527 part.units = "M";
528
529 m_parts.push_back( part );
530 existingRefs.insert( refDes );
531 }
532 }
533}
534
535
537{
538 const DirEntry* entry = getSection( 4 );
539
540 if( !entry || entry->count == 0 || entry->perItem == 0 )
541 return;
542
543 const uint8_t* data = sectionData( 4 );
544
545 if( !data )
546 return;
547
548 bool isNew = !isOldFormat();
549 uint32_t recSize = entry->perItem;
550
551 // We store pad stacks indexed by their position in section 4.
552 // Part decals reference these by index.
553 for( uint32_t i = 0; i < entry->count; ++i )
554 {
555 size_t off = static_cast<size_t>( i ) * recSize;
556
557 if( off + recSize > entry->totalBytes )
558 break;
559
560 size_t base = entry->dataOffset + off;
561
562 int32_t padWidth = 0, drill = 0, finLength = 0, angleRaw = 0;
563 uint8_t marker = 0, shapeCode = 0;
564 uint16_t layerCount = 0;
565
566 if( isNew )
567 {
568 padWidth = readI32( base + 28 );
569 drill = readI32( base + 32 );
570 finLength = readI32( base + 36 );
571 angleRaw = readI32( base + 48 );
572 marker = readU8( base + 56 );
573 shapeCode = readU8( base + 57 );
574 layerCount = readU16( base + 58 );
575 }
576 else
577 {
578 padWidth = readI32( base + 24 );
579 drill = readI32( base + 28 );
580 finLength = readI32( base + 32 );
581 angleRaw = readI32( base + 40 );
582 marker = readU8( base + 48 );
583 shapeCode = readU8( base + 49 );
584 layerCount = readU16( base + 50 );
585 }
586
587 // Only process valid pad definitions (marker == 0xFE)
588 if( marker != 0xFE )
589 continue;
590
591 std::string shapeName = "R";
592 auto shapeIt = PAD_SHAPE_NAMES.find( shapeCode );
593
594 if( shapeIt != PAD_SHAPE_NAMES.end() )
595 shapeName = shapeIt->second;
596
597 double angle = toBasicAngle( angleRaw );
598
599 // Build a PAD_STACK_LAYER for the default layer (layer 0)
600 PAD_STACK_LAYER psl;
601 psl.layer = 0;
602 psl.shape = shapeName;
603 psl.sizeA = static_cast<double>( padWidth );
604 psl.sizeB = static_cast<double>( padWidth );
605 psl.drill = static_cast<double>( drill );
606 psl.plated = ( drill > 0 );
607 psl.rotation = angle;
608 psl.finger_offset = static_cast<double>( finLength );
609
610 m_padStackCache[static_cast<int>( i )].push_back( psl );
611 }
612}
613
614
616{
617 const DirEntry* entry = getSection( 10 );
618
619 if( !entry || entry->count == 0 || entry->perItem == 0 )
620 return;
621
622 const uint8_t* data = sectionData( 10 );
623
624 if( !data )
625 return;
626
627 bool isNew = !isOldFormat();
628 uint32_t recSize = entry->perItem;
629
630 for( uint32_t i = 0; i < entry->count; ++i )
631 {
632 size_t off = static_cast<size_t>( i ) * recSize;
633
634 if( off + recSize > entry->totalBytes )
635 break;
636
637 size_t base = entry->dataOffset + off;
638
639 std::string name;
640 std::string units = "I";
641
642 if( isNew )
643 {
644 name = readFixedString( base + 44, 32 );
645 uint8_t unitFlag = readU8( base + 76 );
646 units = ( unitFlag == 0x4D ) ? "M" : "I";
647 }
648 else
649 {
650 name = readFixedString( base + 28, 32 );
651 }
652
653 if( name.empty() )
654 continue;
655
656 PART_DECAL decal;
657 decal.name = name;
658 decal.units = units;
659
660 // TODO: Parse terminal positions from the binary format.
661 // For now, create a minimal decal that the converter can reference.
662
663 m_decals[name] = decal;
664 }
665}
666
667
669{
670 // Section 17 links part type names to decal indices via 224-byte records.
671 // name@+156, decal_idx@+112. Old format stores UI data here instead.
672 if( isOldFormat() )
673 return;
674
675 const DirEntry* entry = getSection( 17 );
676
677 if( !entry || entry->count == 0 || entry->perItem < 188 )
678 return;
679
680 const uint8_t* data = sectionData( 17 );
681
682 if( !data )
683 return;
684
685 // Build index of section 10 decal names for resolving decal indices
686 const DirEntry* decalEntry = getSection( 10 );
687 std::map<uint32_t, std::string> decalIndexToName;
688
689 if( decalEntry && decalEntry->count > 0 && decalEntry->perItem > 0 )
690 {
691 for( uint32_t i = 0; i < decalEntry->count; ++i )
692 {
693 size_t dOff = static_cast<size_t>( i ) * decalEntry->perItem;
694
695 if( dOff + decalEntry->perItem > decalEntry->totalBytes )
696 break;
697
698 size_t dBase = decalEntry->dataOffset + dOff;
699 std::string decalName = readFixedString( dBase + 44, 32 );
700
701 if( !decalName.empty() )
702 decalIndexToName[i] = decalName;
703 }
704 }
705
706 uint32_t recSize = entry->perItem;
707
708 // Build a map from footprint type name to decal name
709 std::map<std::string, std::string> fpTypeToDecal;
710
711 for( uint32_t i = 0; i < entry->count; ++i )
712 {
713 size_t off = static_cast<size_t>( i ) * recSize;
714
715 if( off + recSize > entry->totalBytes )
716 break;
717
718 size_t base = entry->dataOffset + off;
719 uint32_t decalIdx = readU32( base + 112 );
720 std::string fpTypeName = readFixedString( base + 156, 32 );
721
722 if( fpTypeName.empty() )
723 continue;
724
725 auto decalIt = decalIndexToName.find( decalIdx );
726
727 if( decalIt != decalIndexToName.end() )
728 fpTypeToDecal[fpTypeName] = decalIt->second;
729 }
730
731 // Store the mapping for use by part placement linking.
732 // The metadata region maps ref-des -> part-type-name, which we
733 // don't parse yet. For now, store as a member for future use.
734 m_fpTypeToDecal = fpTypeToDecal;
735}
736
737
739{
740 const DirEntry* entry = getSection( 12 );
741
742 if( !entry || entry->count == 0 )
743 return;
744
745 const uint8_t* data = sectionData( 12 );
746
747 if( !data )
748 return;
749
750 m_lineVertices.clear();
751 m_lineVertices.reserve( entry->count );
752
753 for( uint32_t i = 0; i < entry->count; ++i )
754 {
755 size_t off = static_cast<size_t>( i ) * 12;
756
757 if( off + 12 > entry->totalBytes )
758 break;
759
760 size_t base = entry->dataOffset + off;
761
762 LineVertex v;
763 v.x = readI32( base );
764 v.y = readI32( base + 4 );
765 v.extra = readU32( base + 8 );
766
767 m_lineVertices.push_back( v );
768 }
769}
770
771
773{
774 // Section 21 format varies by version:
775 // v0x2026: 16-byte records [u32 vertex_count, u32 unk1, u32 unk2, u32 sentinel=0xFFFFFFFF]
776 // v0x2025: Mixed ASCII/binary records with completely different layout
777 // v0x2027: Stores coordinates directly rather than vertex counts
778 // v0x2021: Old format with embedded outline data
779 // Only v0x2026 has a decoded record layout, so restrict parsing to that version.
780 if( m_version != 0x2026 )
781 return;
782
783 if( m_lineVertices.empty() )
784 return;
785
786 const DirEntry* entry = getSection( 21 );
787
788 if( !entry || entry->count == 0 )
789 return;
790
791 const uint8_t* data = sectionData( 21 );
792
793 if( !data )
794 return;
795
796 size_t vertexIdx = 0;
797
798 for( uint32_t i = 0; i < entry->count; ++i )
799 {
800 size_t off = static_cast<size_t>( i ) * 16;
801
802 if( off + 16 > entry->totalBytes )
803 break;
804
805 size_t base = entry->dataOffset + off;
806 uint32_t vertexCount = readU32( base );
807 uint32_t sentinel = readU32( base + 12 );
808
809 if( sentinel != 0xFFFFFFFF )
810 continue;
811
812 if( vertexCount == 0 || vertexCount > 10000
813 || vertexIdx + vertexCount > m_lineVertices.size() )
814 {
815 continue;
816 }
817
818 POLYLINE outline;
819 outline.layer = 1;
820 outline.width = 0.0;
821 outline.closed = true;
822
823 for( uint32_t v = 0; v < vertexCount; ++v )
824 {
825 const LineVertex& lv = m_lineVertices[vertexIdx + v];
826 outline.points.emplace_back( static_cast<double>( lv.x ),
827 static_cast<double>( lv.y ) );
828 }
829
830 vertexIdx += vertexCount;
831
832 if( outline.points.size() >= 3 )
833 m_boardOutlines.push_back( std::move( outline ) );
834 }
835}
836
837
838std::string BINARY_PARSER::extractNetName( const uint8_t* aData, size_t aOffset ) const
839{
840 if( !aData )
841 return {};
842
843 std::string name = readFixedString( aOffset, 48 );
844
845 if( name.empty() )
846 return {};
847
848 return name;
849}
850
851
852bool BINARY_PARSER::isValidNetName( const std::string& aName ) const
853{
854 if( aName.empty() )
855 return false;
856
857 char first = aName[0];
858
859 return std::isalpha( static_cast<unsigned char>( first ) )
860 || std::isdigit( static_cast<unsigned char>( first ) )
861 || first == '+' || first == '~' || first == '_' || first == '/';
862}
863
864
866{
867 std::set<std::string> existing;
868
869 if( !isOldFormat() )
870 {
871 // New format: section 23 has 424-byte records with net index at +112 and name at +116
872 const DirEntry* entry23 = getSection( 23 );
873
874 if( entry23 && entry23->count > 0 && entry23->perItem == 424 )
875 {
876 for( uint32_t i = 0; i < entry23->count; ++i )
877 {
878 size_t off = static_cast<size_t>( i ) * 424;
879
880 if( off + 424 > entry23->totalBytes )
881 break;
882
883 size_t base = entry23->dataOffset + off;
884 std::string name = readFixedString( base + 116, 48 );
885
886 if( !name.empty() && isValidNetName( name ) && !existing.count( name ) )
887 {
888 NET net;
889 net.name = name;
890 m_nets.push_back( net );
891 existing.insert( name );
892 }
893 }
894 }
895
896 // Section 22 fills in power/ground nets from 112-byte records
897 const DirEntry* entry22 = getSection( 22 );
898
899 if( entry22 && entry22->count > 0 && entry22->perItem == 112 )
900 {
901 for( uint32_t i = 0; i < entry22->count; ++i )
902 {
903 size_t off = static_cast<size_t>( i ) * 112;
904
905 if( off + 112 > entry22->totalBytes )
906 break;
907
908 size_t base = entry22->dataOffset + off;
909
910 for( int nameOff : { 28, 52, 76 } )
911 {
912 std::string name = readFixedString( base + nameOff, 24 );
913
914 if( !name.empty() && isValidNetName( name ) && !existing.count( name ) )
915 {
916 uint32_t netIdx = readU32( base + nameOff - 4 );
917
918 if( netIdx < 100000 || netIdx >= 0xFFFF0000 )
919 {
920 NET net;
921 net.name = name;
922 m_nets.push_back( net );
923 existing.insert( name );
924 break;
925 }
926 }
927 }
928 }
929 }
930 }
931 else
932 {
933 // Old format: section 23 has 144-byte records
934 const DirEntry* entry23 = getSection( 23 );
935
936 if( entry23 && entry23->count > 0 && entry23->perItem == 144 )
937 {
938 for( uint32_t i = 0; i < entry23->count; ++i )
939 {
940 size_t off = static_cast<size_t>( i ) * 144;
941
942 if( off + 144 > entry23->totalBytes )
943 break;
944
945 size_t base = entry23->dataOffset + off;
946 uint32_t netIdx = readU32( base + 8 );
947 std::string name = readFixedString( base + 12, 48 );
948
949 if( !name.empty() && isValidNetName( name ) && netIdx < 100000
950 && !existing.count( name ) )
951 {
952 NET net;
953 net.name = name;
954 m_nets.push_back( net );
955 existing.insert( name );
956 }
957 }
958 }
959
960 // Old format: section 22 has 96-byte records
961 const DirEntry* entry22 = getSection( 22 );
962
963 if( entry22 && entry22->count > 0 && entry22->perItem == 96 )
964 {
965 for( uint32_t i = 0; i < entry22->count; ++i )
966 {
967 size_t off = static_cast<size_t>( i ) * 96;
968
969 if( off + 96 > entry22->totalBytes )
970 break;
971
972 size_t base = entry22->dataOffset + off;
973
974 for( int nameOff : { 12, 60 } )
975 {
976 std::string name = readFixedString( base + nameOff, 48 );
977
978 if( !name.empty() && isValidNetName( name ) && !existing.count( name ) )
979 {
980 uint32_t netIdx = readU32( base + nameOff - 4 );
981
982 if( netIdx < 100000 )
983 {
984 NET net;
985 net.name = name;
986 m_nets.push_back( net );
987 existing.insert( name );
988 break;
989 }
990 }
991 }
992 }
993 }
994
995 // Old format: section 19 (design rules) has some nets stored after 0xFFFFFFFF markers
996 const DirEntry* entry19 = getSection( 19 );
997
998 if( entry19 && entry19->count > 0 )
999 {
1000 const uint8_t* sec19Data = sectionData( 19 );
1001
1002 if( sec19Data )
1003 {
1004 size_t sec19Size = entry19->totalBytes;
1005
1006 for( size_t pos = 0; pos + 4 < sec19Size; ++pos )
1007 {
1008 uint32_t val = static_cast<uint32_t>( sec19Data[pos] )
1009 | ( static_cast<uint32_t>( sec19Data[pos + 1] ) << 8 )
1010 | ( static_cast<uint32_t>( sec19Data[pos + 2] ) << 16 )
1011 | ( static_cast<uint32_t>( sec19Data[pos + 3] ) << 24 );
1012
1013 if( val == 0xFFFFFFFF )
1014 {
1015 for( size_t scan = pos + 4; scan + 2 < sec19Size && scan < pos + 40; ++scan )
1016 {
1017 if( sec19Data[scan] != 0
1018 && std::isalpha( static_cast<unsigned char>( sec19Data[scan] ) ) )
1019 {
1020 std::string name = readFixedString(
1021 entry19->dataOffset + scan, 48 );
1022
1023 if( !name.empty() && isValidNetName( name )
1024 && !existing.count( name ) )
1025 {
1026 NET net;
1027 net.name = name;
1028 m_nets.push_back( net );
1029 existing.insert( name );
1030 }
1031
1032 break;
1033 }
1034 }
1035
1036 pos += 3;
1037 }
1038 }
1039 }
1040 }
1041 }
1042}
1043
1044
1046{
1047 // Origin is already read from section 1 in parseBoardSetup().
1048 // Only fall back to the DFT_CONFIGURATION scan if that didn't work.
1049 if( m_originFound )
1050 return;
1051
1052 size_t lastDataEnd = HEADER_SIZE + static_cast<size_t>( m_numDirEntries ) * DIR_ENTRY_SIZE;
1053
1054 for( const auto& entry : m_dirEntries )
1055 {
1056 if( entry.index > 0 && entry.totalBytes > 0 )
1057 {
1058 size_t end = entry.dataOffset + entry.totalBytes;
1059
1060 if( end > lastDataEnd )
1061 lastDataEnd = end;
1062 }
1063 }
1064
1065 size_t footerStart = m_data.size() - FOOTER_SIZE;
1066
1067 if( lastDataEnd >= footerStart )
1068 return;
1069
1070 size_t dirEnd = HEADER_SIZE + static_cast<size_t>( m_numDirEntries ) * DIR_ENTRY_SIZE;
1071 parseDftConfig( dirEnd, footerStart );
1072}
1073
1074
1075void BINARY_PARSER::parseDftConfig( size_t aStart, size_t aEnd )
1076{
1077 // Search for "DFT_CONFIGURATION\0" marker
1078 static const char DFT_MARKER[] = "DFT_CONFIGURATION";
1079 size_t markerLen = std::strlen( DFT_MARKER );
1080
1081 for( size_t pos = aStart; pos + markerLen + 1 < aEnd; ++pos )
1082 {
1083 if( std::memcmp( &m_data[pos], DFT_MARKER, markerLen ) == 0
1084 && m_data[pos + markerLen] == 0 )
1085 {
1086 size_t configStart = pos + markerLen + 1;
1087
1088 // Skip PARENT markers and null bytes
1089 while( configStart < aEnd )
1090 {
1091 if( m_data[configStart] == 0 )
1092 {
1093 ++configStart;
1094 continue;
1095 }
1096
1097 if( configStart + 7 <= aEnd
1098 && std::memcmp( &m_data[configStart], "PARENT\0", 7 ) == 0 )
1099 {
1100 configStart += 7;
1101 continue;
1102 }
1103
1104 break;
1105 }
1106
1107 if( configStart >= aEnd )
1108 return;
1109
1110 // Detect format by checking for '.' padding in the first 16 bytes
1111 std::map<std::string, std::string> config;
1112 bool hasDot = false;
1113
1114 if( configStart + 16 <= aEnd )
1115 {
1116 for( size_t i = configStart; i < configStart + 16; ++i )
1117 {
1118 if( m_data[i] == '.' )
1119 {
1120 hasDot = true;
1121 break;
1122 }
1123 }
1124 }
1125
1126 if( hasDot )
1127 config = parseDftDotPadded( configStart, aEnd );
1128 else
1129 config = parseDftNullSeparated( configStart, aEnd );
1130
1131 auto xIt = config.find( "X" );
1132 auto yIt = config.find( "Y" );
1133
1134 if( xIt != config.end() && yIt != config.end() )
1135 {
1136 try
1137 {
1138 m_originX = static_cast<int32_t>( std::stod( xIt->second ) );
1139 m_originY = static_cast<int32_t>( std::stod( yIt->second ) );
1140 m_originFound = true;
1141
1142 m_parameters.origin.x = static_cast<double>( m_originX );
1143 m_parameters.origin.y = static_cast<double>( m_originY );
1144 }
1145 catch( ... )
1146 {
1147 wxLogTrace( "PADS", "Failed to parse DFT origin values" );
1148 }
1149 }
1150
1151 return;
1152 }
1153 }
1154}
1155
1156
1157std::map<std::string, std::string>
1158BINARY_PARSER::parseDftDotPadded( size_t aPos, size_t aEnd ) const
1159{
1160 std::map<std::string, std::string> config;
1161
1162 while( aPos + 16 <= aEnd )
1163 {
1164 // Keys are 16-byte fields padded with ASCII '.' (0x2E)
1165 bool validKey = true;
1166
1167 for( size_t i = aPos; i < aPos + 16; ++i )
1168 {
1169 uint8_t b = m_data[i];
1170
1171 if( !( ( b >= 0x20 && b <= 0x7E ) || b == 0x00 ) )
1172 {
1173 validKey = false;
1174 break;
1175 }
1176 }
1177
1178 if( !validKey )
1179 break;
1180
1181 // Extract key by stripping null bytes and dot padding
1182 std::string key;
1183
1184 for( size_t i = aPos; i < aPos + 16; ++i )
1185 {
1186 if( m_data[i] == 0 || m_data[i] == '.' )
1187 break;
1188
1189 key += static_cast<char>( m_data[i] );
1190 }
1191
1192 if( key.empty() )
1193 break;
1194
1195 aPos += 16;
1196
1197 // Skip optional null separator
1198 if( aPos < aEnd && m_data[aPos] == 0 )
1199 ++aPos;
1200
1201 // Read null-terminated value
1202 size_t valStart = aPos;
1203
1204 while( aPos < aEnd && m_data[aPos] != 0 )
1205 ++aPos;
1206
1207 if( aPos > valStart )
1208 {
1209 std::string value( reinterpret_cast<const char*>( &m_data[valStart] ),
1210 aPos - valStart );
1211 config[key] = value;
1212 }
1213
1214 if( aPos < aEnd )
1215 ++aPos;
1216
1217 // Skip PARENT markers
1218 if( aPos + 7 <= aEnd
1219 && std::memcmp( &m_data[aPos], "PARENT\0", 7 ) == 0 )
1220 {
1221 aPos += 7;
1222 }
1223 }
1224
1225 return config;
1226}
1227
1228
1229std::map<std::string, std::string>
1230BINARY_PARSER::parseDftNullSeparated( size_t aPos, size_t aEnd ) const
1231{
1232 std::map<std::string, std::string> config;
1233
1234 while( aPos < aEnd )
1235 {
1236 // Find null-terminated key
1237 size_t keyStart = aPos;
1238
1239 while( aPos < aEnd && m_data[aPos] != 0 )
1240 ++aPos;
1241
1242 if( aPos == keyStart )
1243 break;
1244
1245 // Validate key is printable ASCII
1246 bool validKey = true;
1247
1248 for( size_t i = keyStart; i < aPos; ++i )
1249 {
1250 if( m_data[i] < 0x20 || m_data[i] > 0x7E )
1251 {
1252 validKey = false;
1253 break;
1254 }
1255 }
1256
1257 if( !validKey )
1258 break;
1259
1260 std::string key( reinterpret_cast<const char*>( &m_data[keyStart] ), aPos - keyStart );
1261
1262 // Skip null terminator
1263 if( aPos < aEnd )
1264 ++aPos;
1265
1266 if( key == "PARENT" )
1267 continue;
1268
1269 // Read null-terminated value
1270 size_t valStart = aPos;
1271
1272 while( aPos < aEnd && m_data[aPos] != 0 )
1273 ++aPos;
1274
1275 if( aPos <= valStart )
1276 break;
1277
1278 std::string value( reinterpret_cast<const char*>( &m_data[valStart] ), aPos - valStart );
1279 config[key] = value;
1280
1281 if( aPos < aEnd )
1282 ++aPos;
1283 }
1284
1285 return config;
1286}
1287
1288
1289std::string BINARY_PARSER::resolveString( uint32_t aByteOffset ) const
1290{
1291 if( m_stringPoolBytes.empty() || aByteOffset >= m_stringPoolBytes.size() )
1292 return {};
1293
1294 const uint8_t* start = &m_stringPoolBytes[aByteOffset];
1295 const uint8_t* end = m_stringPoolBytes.data() + m_stringPoolBytes.size();
1296 const uint8_t* null_pos = std::find( start, end, 0 );
1297 size_t len = static_cast<size_t>( null_pos - start );
1298
1299 for( size_t i = 0; i < len; ++i )
1300 {
1301 if( start[i] < 0x20 || start[i] >= 0x7F )
1302 return {};
1303 }
1304
1305 return std::string( reinterpret_cast<const char*>( start ), len );
1306}
1307
1308
1310{
1311 // Section 8 contains text records (72 bytes each, all versions)
1312 // Field offsets (from binary-ASC cross-reference):
1313 // [0..3] u32 string pool byte offset (into section 57)
1314 // [28..31] i32 text height (confirmed via ASC height matching)
1315 // [32..35] i32 linewidth
1316 // [44..47] i32 X coordinate (confirmed via ASC coordinate matching)
1317 // [48..51] i32 Y coordinate
1318 // [52..55] i32 rotation angle (degrees * ANGLE_SCALE)
1319 // [56] u8 layer number
1320 // [68..69] u16 terminator (0xFEFF)
1321 const DirEntry* entry = getSection( 8 );
1322
1323 if( !entry || entry->count == 0 || entry->perItem < 72 )
1324 return;
1325
1326 const uint8_t* data = sectionData( 8 );
1327
1328 if( !data )
1329 return;
1330
1331 for( uint32_t i = 0; i < entry->count; ++i )
1332 {
1333 size_t off = static_cast<size_t>( i ) * 72;
1334
1335 if( off + 72 > entry->totalBytes )
1336 break;
1337
1338 size_t base = entry->dataOffset + off;
1339
1340 uint32_t strOffset = readU32( base );
1341 int32_t height = readI32( base + 28 );
1342 int32_t linewidth = readI32( base + 32 );
1343 int32_t x = readI32( base + 44 );
1344 int32_t y = readI32( base + 48 );
1345 int32_t angleRaw = readI32( base + 52 );
1346 uint8_t layer = readU8( base + 56 );
1347
1348 std::string content = resolveString( strOffset );
1349
1350 if( content.empty() )
1351 continue;
1352
1353 TEXT text;
1354 text.content = content;
1355 text.location.x = toBasicCoordX( x );
1356 text.location.y = toBasicCoordY( y );
1357 text.height = static_cast<double>( height );
1358 text.width = static_cast<double>( linewidth );
1359 text.layer = static_cast<int>( layer );
1360 text.rotation = toBasicAngle( angleRaw );
1361
1362 m_texts.push_back( text );
1363 }
1364}
1365
1366
1368{
1369 // Route data structure (new format only, v0x2025+):
1370 // Section 24 (68 bytes/record): connection records. Records with sentinel
1371 // 0xFE000000 at u32@20 are track segments. u32@8 and u32@12 are indices
1372 // into the section 60 vertex pool (start/end of each segment).
1373 // Section 59 (32 bytes/record): via/pin connection endpoints with XY at
1374 // the auto-detected marker offset (same layout as section 60 sub-records).
1375 // Section 60 (64 bytes/record): route vertex pool. Each record contains
1376 // two 32-byte sub-records with a 0x80 marker byte preceding XY data.
1377 // The marker position varies between files and is auto-detected.
1378 if( isOldFormat() )
1379 return;
1380
1381 m_routeSegments.clear();
1382 m_viaLocations.clear();
1383
1384 // Section 60 vertex pool (primary route vertices).
1385 // Each 64-byte record contains two 32-byte sub-records. Within each sub-record,
1386 // a 0x80 marker byte precedes the XY coordinate pair (two i32 values). The marker
1387 // position varies between files even of the same version, so we auto-detect it by
1388 // scanning the first few records for the most common 0x80 position.
1389 const DirEntry* entry60 = getSection( 60 );
1390
1391 if( !entry60 || entry60->count == 0 || entry60->perItem == 0 || !sectionData( 60 ) )
1392 return;
1393
1394 uint32_t n60 = entry60->count;
1395 uint32_t r60 = entry60->perItem;
1396
1397 // Auto-detect the 0x80 marker position by scanning the first 32 bytes of up to
1398 // 100 records and picking the position with the highest hit count.
1399 int markerOffset = -1;
1400 {
1401 uint32_t sampleCount = std::min( n60, static_cast<uint32_t>( 100 ) );
1402 int bestPos = -1;
1403 int bestCount = 0;
1404
1405 for( int candidate = 8; candidate < 28 && candidate + 8 < static_cast<int>( r60 ); ++candidate )
1406 {
1407 int hits = 0;
1408
1409 for( uint32_t s = 0; s < sampleCount; ++s )
1410 {
1411 size_t recOff = static_cast<size_t>( s ) * r60;
1412
1413 if( recOff + r60 > entry60->totalBytes )
1414 break;
1415
1416 if( readU8( entry60->dataOffset + recOff + candidate ) == 0x80 )
1417 hits++;
1418 }
1419
1420 if( hits > bestCount )
1421 {
1422 bestCount = hits;
1423 bestPos = candidate;
1424 }
1425 }
1426
1427 if( bestCount < static_cast<int>( sampleCount ) / 2 )
1428 return;
1429
1430 markerOffset = bestPos;
1431 }
1432
1433 int xyOffset = markerOffset + 1;
1434
1435 // Helper to read XY from a section 60 record
1436 auto readSec60XY = [&]( uint32_t aRecIdx, int32_t& aX, int32_t& aY ) -> bool
1437 {
1438 if( aRecIdx >= n60 )
1439 return false;
1440
1441 size_t off = static_cast<size_t>( aRecIdx ) * r60;
1442
1443 if( off + r60 > entry60->totalBytes )
1444 return false;
1445
1446 size_t base = entry60->dataOffset + off;
1447
1448 if( readU8( base + markerOffset ) != 0x80 )
1449 return false;
1450
1451 aX = readI32( base + xyOffset );
1452 aY = readI32( base + xyOffset + 4 );
1453 return true;
1454 };
1455
1456 // Section 24 connection records: build route segments by following the linking
1457 static constexpr uint32_t SEC24_SENTINEL = 0xFE000000;
1458 static constexpr int SEC24_REC_SIZE = 68;
1459
1460 const DirEntry* entry24 = getSection( 24 );
1461
1462 if( entry24 && entry24->count > 0 && entry24->perItem == SEC24_REC_SIZE && sectionData( 24 ) )
1463 {
1464 uint32_t n24 = entry24->count;
1465
1466 for( uint32_t i = 0; i < n24; ++i )
1467 {
1468 size_t off = static_cast<size_t>( i ) * SEC24_REC_SIZE;
1469
1470 if( off + SEC24_REC_SIZE > entry24->totalBytes )
1471 break;
1472
1473 size_t base = entry24->dataOffset + off;
1474 uint32_t sentinel = readU32( base + 20 );
1475
1476 if( sentinel != SEC24_SENTINEL )
1477 continue;
1478
1479 int32_t sec60Start = readI32( base + 8 );
1480 int32_t sec60End = readI32( base + 12 );
1481
1482 if( sec60Start < 0 || sec60End < 0 )
1483 continue;
1484
1485 int32_t x1 = 0, y1 = 0, x2 = 0, y2 = 0;
1486
1487 if( !readSec60XY( static_cast<uint32_t>( sec60Start ), x1, y1 ) )
1488 continue;
1489
1490 if( !readSec60XY( static_cast<uint32_t>( sec60End ), x2, y2 ) )
1491 continue;
1492
1493 // Width is at u32@24 in section 24 for v0x2025.
1494 // For other versions it's been observed as 0, so we leave it unset.
1495 int32_t width = readI32( base + 24 );
1496
1497 RouteSegment seg;
1498 seg.x1 = x1;
1499 seg.y1 = y1;
1500 seg.x2 = x2;
1501 seg.y2 = y2;
1502 seg.width = width;
1503 m_routeSegments.push_back( seg );
1504 }
1505 }
1506
1507 // Section 59: via/pin connection endpoints
1508 const DirEntry* entry59 = getSection( 59 );
1509
1510 if( entry59 && entry59->count > 0 && entry59->perItem > 0 && sectionData( 59 ) )
1511 {
1512 uint32_t recSize = entry59->perItem;
1513
1514 for( uint32_t i = 0; i < entry59->count; ++i )
1515 {
1516 size_t off = static_cast<size_t>( i ) * recSize;
1517
1518 if( off + recSize > entry59->totalBytes )
1519 break;
1520
1521 size_t base = entry59->dataOffset + off;
1522
1523 if( readU8( base + markerOffset ) != 0x80 )
1524 continue;
1525
1527 via.x = readI32( base + xyOffset );
1528 via.y = readI32( base + xyOffset + 4 );
1529 m_viaLocations.push_back( via );
1530 }
1531 }
1532
1533 // Build ROUTE objects from the extracted segments.
1534 // Without layer/net/width fully decoded, we emit a single anonymous route with
1535 // all segments. The loadTracksAndVias() converter assigns default layer/width.
1536 if( m_routeSegments.empty() && m_viaLocations.empty() )
1537 return;
1538
1539 ROUTE route;
1540 route.net_name = "";
1541
1542 for( const auto& seg : m_routeSegments )
1543 {
1544 TRACK track;
1545 track.layer = 0;
1546 track.width = static_cast<double>( seg.width );
1547 track.points.emplace_back( static_cast<double>( seg.x1 ), static_cast<double>( seg.y1 ) );
1548 track.points.emplace_back( static_cast<double>( seg.x2 ), static_cast<double>( seg.y2 ) );
1549 route.tracks.push_back( std::move( track ) );
1550 }
1551
1552 for( const auto& via : m_viaLocations )
1553 {
1554 VIA viaDef;
1555 viaDef.location.x = static_cast<double>( via.x );
1556 viaDef.location.y = static_cast<double>( via.y );
1557 route.vias.push_back( std::move( viaDef ) );
1558 }
1559
1560 m_routes.push_back( std::move( route ) );
1561}
1562
1563
1565{
1566 // Section 14 was originally labeled "copper_pours" but analysis revealed it
1567 // stores footprint pad position data (36-byte stride entries with XY pairs
1568 // relative to the footprint origin). Copper pour geometry is stored in the
1569 // metadata region, not in a numbered section.
1570 //
1571 // The pad position data supplements section 10 (partdecals) but is not yet
1572 // needed since the converter creates placeholder footprints without pads.
1573}
1574
1575
1576std::vector<LAYER_INFO> BINARY_PARSER::GetLayerInfos() const
1577{
1578 std::vector<LAYER_INFO> infos;
1579 int layerCount = m_parameters.layer_count;
1580
1581 for( int i = 1; i <= layerCount; ++i )
1582 {
1584 info.number = i;
1585 info.name = "Layer " + std::to_string( i );
1586 info.is_copper = true;
1587 info.required = true;
1588
1590
1591 infos.push_back( info );
1592 }
1593
1594 return infos;
1595}
1596
1597} // namespace PADS_IO
const char * name
std::map< std::string, std::string > parseDftNullSeparated(size_t aPos, size_t aEnd) const
uint8_t readU8(size_t aOffset) const
double toBasicCoordY(int32_t aRawValue) const
std::vector< uint8_t > m_stringPoolBytes
std::vector< NET > m_nets
std::string readFixedString(size_t aOffset, size_t aMaxLen) const
bool isValidNetName(const std::string &aName) const
static constexpr int FOOTER_SIZE
std::vector< PART > m_parts
uint32_t readU32(size_t aOffset) const
std::map< std::string, std::string > parseDftDotPadded(size_t aPos, size_t aEnd) const
std::map< int, std::vector< PAD_STACK_LAYER > > m_padStackCache
const DirEntry * getSection(int aIndex) const
static constexpr int32_t ANGLE_SCALE
static bool IsBinaryPadsFile(const wxString &aFileName)
Check if a file appears to be a PADS binary PCB file.
std::map< std::string, PART_DECAL > m_decals
static constexpr int HEADER_SIZE
std::vector< uint8_t > m_data
std::vector< POLYLINE > m_boardOutlines
std::string resolveString(uint32_t aByteOffset) const
uint16_t readU16(size_t aOffset) const
uint32_t sectionSize(int aIndex) const
void parseDftConfig(size_t aStart, size_t aEnd)
std::string extractNetName(const uint8_t *aData, size_t aOffset) const
void Parse(const wxString &aFileName)
double toBasicCoordX(int32_t aRawValue) const
const uint8_t * sectionData(int aIndex) const
static constexpr int DIR_ENTRY_SIZE
std::vector< RouteSegment > m_routeSegments
std::map< std::string, std::string > m_fpTypeToDecal
std::vector< ViaLocation > m_viaLocations
std::vector< DirEntry > m_dirEntries
std::vector< ROUTE > m_routes
std::vector< LineVertex > m_lineVertices
std::vector< TEXT > m_texts
int32_t readI32(size_t aOffset) const
double toBasicAngle(int32_t aRawAngle) const
std::vector< LAYER_INFO > GetLayerInfos() const
#define THROW_IO_ERROR(msg)
macro which captures the "call site" values of FILE_, __FUNCTION & LINE
static const uint8_t FOOTER_GUID[]
static const std::map< uint8_t, std::string > PAD_SHAPE_NAMES
@ ROUTING
Copper routing layer.
std::string name
double drill
Drill hole diameter (0 for SMD)
std::string shape
Shape code: R, S, A, O, OF, RF, RT, ST, RA, SA, RC, OC.
bool plated
True if drill is plated (PTH vs NPTH)
double rotation
Pad rotation angle in degrees.
double finger_offset
Finger pad offset along orientation axis.
double sizeB
Secondary size (height for rectangles/ovals)
double sizeA
Primary size (diameter or width)
std::string name
std::string units
A polyline that may contain arc segments.
bool closed
True if polyline forms a closed shape.
std::vector< ARC_POINT > points
Polyline vertices, may include arcs.
std::vector< VIA > vias
std::vector< TRACK > tracks
std::string net_name
std::vector< ARC_POINT > points
Track points, may include arc segments.
VECTOR3I expected(15, 30, 45)
VECTOR2I end
wxString result
Test unit parsing edge cases and error handling.