36static const uint8_t
FOOTER_GUID[] =
"{2FE18320-6448-11d1-A412-000000000000}";
55 std::ifstream file( aFileName.fn_str(), std::ios::binary );
61 file.read(
reinterpret_cast<char*
>( header ), 4 );
63 if( file.gcount() < 4 )
67 if( header[0] != 0x00 || header[1] != 0xFF )
70 uint16_t version =
static_cast<uint16_t
>( header[2] ) | (
static_cast<uint16_t
>( header[3] ) << 8 );
72 return version == 0x2021 || version == 0x2025 || version == 0x2026 || version == 0x2027;
78 std::ifstream file( aFileName.fn_str(), std::ios::binary | std::ios::ate );
83 std::streamsize fileSize = file.tellg();
84 file.seekg( 0, std::ios::beg );
86 m_data.resize(
static_cast<size_t>( fileSize ) );
87 file.read(
reinterpret_cast<char*
>(
m_data.data() ), fileSize );
89 if( file.gcount() != fileSize )
112 [](
const PART& p ) { return p.name.empty(); } ),
121 case 0x2021:
return 73;
124 case 0x2027:
return 74;
132 if( aOffset >=
m_data.size() )
134 THROW_IO_ERROR( wxString::Format(
"PADS binary read out of bounds at offset %zu (file size %zu)",
135 aOffset,
m_data.size() ) );
144 if( aOffset + 2 >
m_data.size() )
146 THROW_IO_ERROR( wxString::Format(
"PADS binary read out of bounds at offset %zu (file size %zu)",
147 aOffset,
m_data.size() ) );
150 return static_cast<uint16_t
>(
m_data[aOffset] )
151 | (
static_cast<uint16_t
>(
m_data[aOffset + 1] ) << 8 );
157 if( aOffset + 4 >
m_data.size() )
159 THROW_IO_ERROR( wxString::Format(
"PADS binary read out of bounds at offset %zu (file size %zu)",
160 aOffset,
m_data.size() ) );
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 );
172 return static_cast<int32_t
>(
readU32( aOffset ) );
178 if( aOffset >=
m_data.size() )
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;
186 const uint8_t* null_pos = std::find( start,
end, 0 );
187 size_t len =
static_cast<size_t>( null_pos - start );
190 for(
size_t i = 0; i < len; ++i )
192 if( start[i] < 0x20 || start[i] >= 0x7F )
196 std::string
result(
reinterpret_cast<const char*
>( start ), len );
231 uint32_t sizeCheck =
readU32( footerStart + 42 );
236 wxLogTrace(
tracePadsIo,
"PADS binary footer size mismatch: stored=%u, expected=%u",
247 if( dirStart + dirSize >
m_data.size() )
250 uint32_t dataOffset =
static_cast<uint32_t
>( dirStart + dirSize );
257 size_t off = dirStart +
static_cast<size_t>( i ) *
DIR_ENTRY_SIZE;
262 entry.totalBytes =
readU32( off + 4 );
263 entry.dataOffset = 0;
268 entry.dataOffset = dataOffset;
270 if( entry.count > 0 && entry.totalBytes > 0 )
271 entry.perItem = entry.totalBytes / entry.count;
273 dataOffset += entry.totalBytes;
283 if( aIndex >= 0 && aIndex <
static_cast<int>(
m_dirEntries.size() ) )
332 return static_cast<double>( aRawAngle ) /
static_cast<double>(
ANGLE_SCALE );
341 if( !data || size < 160 )
348 if( maxLayer >= 1 && maxLayer <= 64 )
349 m_parameters.layer_count =
static_cast<int>( maxLayer );
379 if( !data || size == 0 )
390 if( !entry || entry->
count == 0 || entry->
perItem == 0 )
399 uint32_t recSize = entry->
perItem;
402 int nameOff = 0, xOff = 0, yOff = 0, angleOff = 0;
419 for( uint32_t i = 0; i < entry->
count; ++i )
421 size_t off =
static_cast<size_t>( i ) * recSize;
429 if( refDes.empty() || !std::isalnum(
static_cast<unsigned char>( refDes[0] ) ) )
432 int32_t x =
readI32( base + xOff );
435 int32_t y = ( yOff >= 0 ) ?
readI32( base + yOff ) : 0;
436 int32_t angleRaw =
readI32( base + angleOff );
456 int nameOff = 0, xOff = 0, yOff = 0, angleOff = 0, feffOff = 0;
475 int recSize = feffOff + 2;
477 std::set<std::string> existingRefs;
480 existingRefs.insert( p.name );
482 for(
int secIdx : { 19, 21 } )
492 if( !data || size == 0 )
495 for(
size_t pos = 0; pos + 1 < size; ++pos )
497 if( data[pos] != 0xFE || data[pos + 1] != 0xFF )
500 int recStart =
static_cast<int>( pos ) - feffOff;
502 if( recStart < 0 || recStart + recSize >
static_cast<int>( size ) )
508 if( refDes.empty() || !std::isalnum(
static_cast<unsigned char>( refDes[0] ) ) )
511 if( existingRefs.count( refDes ) )
514 int32_t x =
readI32( base + xOff );
515 int32_t y = ( yOff >= 0 ) ?
readI32( base + yOff ) : 0;
516 int32_t angleRaw =
readI32( base + angleOff );
527 existingRefs.insert( refDes );
537 if( !entry || entry->
count == 0 || entry->
perItem == 0 )
546 uint32_t recSize = entry->
perItem;
550 for( uint32_t i = 0; i < entry->
count; ++i )
552 size_t off =
static_cast<size_t>( i ) * recSize;
559 int32_t padWidth = 0, drill = 0, finLength = 0, angleRaw = 0;
560 uint8_t marker = 0, shapeCode = 0;
561 uint16_t layerCount = 0;
565 padWidth =
readI32( base + 28 );
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 );
575 padWidth =
readI32( base + 24 );
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 );
588 std::string shapeName =
"R";
592 shapeName = shapeIt->second;
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 );
616 if( !entry || entry->
count == 0 || entry->
perItem == 0 )
625 uint32_t recSize = entry->
perItem;
627 for( uint32_t i = 0; i < entry->
count; ++i )
629 size_t off =
static_cast<size_t>( i ) * recSize;
637 std::string units =
"I";
642 uint8_t unitFlag =
readU8( base + 76 );
643 units = ( unitFlag == 0x4D ) ?
"M" :
"I";
674 if( !entry || entry->
count == 0 || entry->
perItem < 188 )
684 std::map<uint32_t, std::string> decalIndexToName;
686 if( decalEntry && decalEntry->
count > 0 && decalEntry->
perItem > 0 )
688 for( uint32_t i = 0; i < decalEntry->
count; ++i )
690 size_t dOff =
static_cast<size_t>( i ) * decalEntry->
perItem;
698 if( !decalName.empty() )
699 decalIndexToName[i] = decalName;
703 uint32_t recSize = entry->
perItem;
706 std::map<std::string, std::string> fpTypeToDecal;
708 for( uint32_t i = 0; i < entry->
count; ++i )
710 size_t off =
static_cast<size_t>( i ) * recSize;
716 uint32_t decalIdx =
readU32( base + 112 );
719 if( fpTypeName.empty() )
722 auto decalIt = decalIndexToName.find( decalIdx );
724 if( decalIt != decalIndexToName.end() )
725 fpTypeToDecal[fpTypeName] = decalIt->second;
739 if( !entry || entry->
count == 0 )
750 for( uint32_t i = 0; i < entry->
count; ++i )
752 size_t off =
static_cast<size_t>( i ) * 12;
785 if( !entry || entry->
count == 0 )
793 size_t vertexIdx = 0;
795 for( uint32_t i = 0; i < entry->
count; ++i )
797 size_t off =
static_cast<size_t>( i ) * 16;
803 uint32_t vertexCount =
readU32( base );
804 uint32_t sentinel =
readU32( base + 12 );
806 if( sentinel != 0xFFFFFFFF )
809 if( vertexCount == 0 || vertexCount > 10000
820 for( uint32_t v = 0; v < vertexCount; ++v )
823 outline.
points.emplace_back(
static_cast<double>( lv.
x ),
824 static_cast<double>( lv.
y ) );
827 vertexIdx += vertexCount;
829 if( outline.
points.size() >= 3 )
854 char first = aName[0];
856 return std::isalpha(
static_cast<unsigned char>( first ) )
857 || std::isdigit(
static_cast<unsigned char>( first ) )
858 || first ==
'+' || first ==
'~' || first ==
'_' || first ==
'/';
864 std::set<std::string> existing;
871 if( entry23 && entry23->
count > 0 && entry23->
perItem == 424 )
873 for( uint32_t i = 0; i < entry23->
count; ++i )
875 size_t off =
static_cast<size_t>( i ) * 424;
888 existing.insert(
name );
896 if( entry22 && entry22->
count > 0 && entry22->
perItem == 112 )
898 for( uint32_t i = 0; i < entry22->
count; ++i )
900 size_t off =
static_cast<size_t>( i ) * 112;
907 for(
int nameOff : { 28, 52, 76 } )
913 uint32_t netIdx =
readU32( base + nameOff - 4 );
915 if( netIdx < 100000 || netIdx >= 0xFFFF0000 )
920 existing.insert(
name );
933 if( entry23 && entry23->
count > 0 && entry23->
perItem == 144 )
935 for( uint32_t i = 0; i < entry23->
count; ++i )
937 size_t off =
static_cast<size_t>( i ) * 144;
943 uint32_t netIdx =
readU32( base + 8 );
947 && !existing.count(
name ) )
952 existing.insert(
name );
960 if( entry22 && entry22->
count > 0 && entry22->
perItem == 96 )
962 for( uint32_t i = 0; i < entry22->
count; ++i )
964 size_t off =
static_cast<size_t>( i ) * 96;
971 for(
int nameOff : { 12, 60 } )
977 uint32_t netIdx =
readU32( base + nameOff - 4 );
979 if( netIdx < 100000 )
984 existing.insert(
name );
995 if( entry19 && entry19->
count > 0 )
1003 for(
size_t pos = 0; pos + 4 < sec19Size; ++pos )
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 );
1010 if( val == 0xFFFFFFFF )
1012 for(
size_t scan = pos + 4; scan + 2 < sec19Size && scan < pos + 40; ++scan )
1014 if( sec19Data[scan] != 0
1015 && std::isalpha(
static_cast<unsigned char>( sec19Data[scan] ) ) )
1021 && !existing.count(
name ) )
1026 existing.insert(
name );
1053 if( entry.index > 0 && entry.totalBytes > 0 )
1055 size_t end = entry.dataOffset + entry.totalBytes;
1057 if(
end > lastDataEnd )
1064 if( lastDataEnd >= footerStart )
1075 static const char DFT_MARKER[] =
"DFT_CONFIGURATION";
1076 size_t markerLen = std::strlen( DFT_MARKER );
1078 for(
size_t pos = aStart; pos + markerLen + 1 < aEnd; ++pos )
1080 if( std::memcmp( &
m_data[pos], DFT_MARKER, markerLen ) == 0
1081 &&
m_data[pos + markerLen] == 0 )
1083 size_t configStart = pos + markerLen + 1;
1086 while( configStart < aEnd )
1088 if(
m_data[configStart] == 0 )
1094 if( configStart + 7 <= aEnd
1095 && std::memcmp( &
m_data[configStart],
"PARENT\0", 7 ) == 0 )
1104 if( configStart >= aEnd )
1108 std::map<std::string, std::string>
config;
1109 bool hasDot =
false;
1111 if( configStart + 16 <= aEnd )
1113 for(
size_t i = configStart; i < configStart + 16; ++i )
1128 auto xIt =
config.find(
"X" );
1129 auto yIt =
config.find(
"Y" );
1135 m_originX =
static_cast<int32_t
>( std::stod( xIt->second ) );
1136 m_originY =
static_cast<int32_t
>( std::stod( yIt->second ) );
1144 wxLogTrace(
"PADS",
"Failed to parse DFT origin values" );
1154std::map<std::string, std::string>
1157 std::map<std::string, std::string>
config;
1159 while( aPos + 16 <= aEnd )
1162 bool validKey =
true;
1164 for(
size_t i = aPos; i < aPos + 16; ++i )
1168 if( !( ( b >= 0x20 && b <= 0x7E ) || b == 0x00 ) )
1181 for(
size_t i = aPos; i < aPos + 16; ++i )
1186 key +=
static_cast<char>(
m_data[i] );
1195 if( aPos < aEnd &&
m_data[aPos] == 0 )
1199 size_t valStart = aPos;
1201 while( aPos < aEnd &&
m_data[aPos] != 0 )
1204 if( aPos > valStart )
1206 std::string value(
reinterpret_cast<const char*
>( &
m_data[valStart] ),
1215 if( aPos + 7 <= aEnd
1216 && std::memcmp( &
m_data[aPos],
"PARENT\0", 7 ) == 0 )
1226std::map<std::string, std::string>
1229 std::map<std::string, std::string>
config;
1231 while( aPos < aEnd )
1234 size_t keyStart = aPos;
1236 while( aPos < aEnd &&
m_data[aPos] != 0 )
1239 if( aPos == keyStart )
1243 bool validKey =
true;
1245 for(
size_t i = keyStart; i < aPos; ++i )
1257 std::string key(
reinterpret_cast<const char*
>( &
m_data[keyStart] ), aPos - keyStart );
1263 if( key ==
"PARENT" )
1267 size_t valStart = aPos;
1269 while( aPos < aEnd &&
m_data[aPos] != 0 )
1272 if( aPos <= valStart )
1275 std::string value(
reinterpret_cast<const char*
>( &
m_data[valStart] ), aPos - valStart );
1293 const uint8_t* null_pos = std::find( start,
end, 0 );
1294 size_t len =
static_cast<size_t>( null_pos - start );
1296 for(
size_t i = 0; i < len; ++i )
1298 if( start[i] < 0x20 || start[i] >= 0x7F )
1302 return std::string(
reinterpret_cast<const char*
>( start ), len );
1320 if( !entry || entry->
count == 0 || entry->
perItem < 72 )
1328 for( uint32_t i = 0; i < entry->
count; ++i )
1330 size_t off =
static_cast<size_t>( i ) * 72;
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 );
1347 if( content.empty() )
1351 text.content = content;
1354 text.height =
static_cast<double>( height );
1355 text.width =
static_cast<double>( linewidth );
1356 text.layer =
static_cast<int>( layer );
1391 uint32_t n60 = entry60->
count;
1392 uint32_t r60 = entry60->
perItem;
1396 int markerOffset = -1;
1398 uint32_t sampleCount = std::min( n60,
static_cast<uint32_t
>( 100 ) );
1402 for(
int candidate = 8; candidate < 28 && candidate + 8 < static_cast<int>( r60 ); ++candidate )
1406 for( uint32_t s = 0; s < sampleCount; ++s )
1408 size_t recOff =
static_cast<size_t>( s ) * r60;
1417 if( hits > bestCount )
1420 bestPos = candidate;
1424 if( bestCount <
static_cast<int>( sampleCount ) / 2 )
1427 markerOffset = bestPos;
1430 int xyOffset = markerOffset + 1;
1433 auto readSec60XY = [&]( uint32_t aRecIdx, int32_t& aX, int32_t& aY ) ->
bool
1435 if( aRecIdx >= n60 )
1438 size_t off =
static_cast<size_t>( aRecIdx ) * r60;
1445 if(
readU8( base + markerOffset ) != 0x80 )
1448 aX =
readI32( base + xyOffset );
1449 aY =
readI32( base + xyOffset + 4 );
1454 static constexpr uint32_t SEC24_SENTINEL = 0xFE000000;
1455 static constexpr int SEC24_REC_SIZE = 68;
1461 uint32_t n24 = entry24->
count;
1463 for( uint32_t i = 0; i < n24; ++i )
1465 size_t off =
static_cast<size_t>( i ) * SEC24_REC_SIZE;
1467 if( off + SEC24_REC_SIZE > entry24->
totalBytes )
1471 uint32_t sentinel =
readU32( base + 20 );
1473 if( sentinel != SEC24_SENTINEL )
1476 int32_t sec60Start =
readI32( base + 8 );
1477 int32_t sec60End =
readI32( base + 12 );
1479 if( sec60Start < 0 || sec60End < 0 )
1482 int32_t x1 = 0, y1 = 0, x2 = 0, y2 = 0;
1484 if( !readSec60XY(
static_cast<uint32_t
>( sec60Start ), x1, y1 ) )
1487 if( !readSec60XY(
static_cast<uint32_t
>( sec60End ), x2, y2 ) )
1492 int32_t width =
readI32( base + 24 );
1509 uint32_t recSize = entry59->
perItem;
1511 for( uint32_t i = 0; i < entry59->
count; ++i )
1513 size_t off =
static_cast<size_t>( i ) * recSize;
1520 if(
readU8( base + markerOffset ) != 0x80 )
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 ) );
1554 route.
vias.push_back( std::move( viaDef ) );
1557 m_routes.push_back( std::move( route ) );
1575 std::vector<LAYER_INFO> infos;
1578 for(
int i = 1; i <= layerCount; ++i )
1582 info.name =
"Layer " + std::to_string( i );
1583 info.is_copper =
true;
1584 info.required =
true;
1588 infos.push_back(
info );
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
void parseSection19Parts()
std::string resolveString(uint32_t aByteOffset) const
uint16_t readU16(size_t aOffset) const
uint32_t sectionSize(int aIndex) const
void parseRouteVertices()
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
void parsePartPlacements()
std::vector< ROUTE > m_routes
std::vector< LineVertex > m_lineVertices
void parseFootprintDefs()
std::vector< TEXT > m_texts
int32_t readI32(size_t aOffset) const
int dirEntryCount() const
double toBasicAngle(int32_t aRawAngle) const
std::vector< LAYER_INFO > GetLayerInfos() const
void parseMetadataRegion()
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.
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)
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< TRACK > tracks
std::vector< ARC_POINT > points
Track points, may include arc segments.
VECTOR3I expected(15, 30, 45)
wxString result
Test unit parsing edge cases and error handling.
wxLogTrace helper definitions.