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