39static const uint8_t
FOOTER_GUID[] =
"{2FE18320-6448-11d1-A412-000000000000}";
58 std::ifstream file( aFileName.fn_str(), std::ios::binary );
64 file.read(
reinterpret_cast<char*
>( header ), 4 );
66 if( file.gcount() < 4 )
70 if( header[0] != 0x00 || header[1] != 0xFF )
73 uint16_t version =
static_cast<uint16_t
>( header[2] ) | (
static_cast<uint16_t
>( header[3] ) << 8 );
75 return version == 0x2021 || version == 0x2025 || version == 0x2026 || version == 0x2027;
81 std::ifstream file( aFileName.fn_str(), std::ios::binary | std::ios::ate );
86 std::streamsize fileSize = file.tellg();
87 file.seekg( 0, std::ios::beg );
89 m_data.resize(
static_cast<size_t>( fileSize ) );
90 file.read(
reinterpret_cast<char*
>(
m_data.data() ), fileSize );
92 if( file.gcount() != fileSize )
115 [](
const PART& p ) { return p.name.empty(); } ),
124 case 0x2021:
return 73;
127 case 0x2027:
return 74;
135 if( aOffset >=
m_data.size() )
137 THROW_IO_ERROR( wxString::Format(
"PADS binary read out of bounds at offset %zu (file size %zu)",
138 aOffset,
m_data.size() ) );
147 if( aOffset + 2 >
m_data.size() )
149 THROW_IO_ERROR( wxString::Format(
"PADS binary read out of bounds at offset %zu (file size %zu)",
150 aOffset,
m_data.size() ) );
153 return static_cast<uint16_t
>(
m_data[aOffset] )
154 | (
static_cast<uint16_t
>(
m_data[aOffset + 1] ) << 8 );
160 if( aOffset + 4 >
m_data.size() )
162 THROW_IO_ERROR( wxString::Format(
"PADS binary read out of bounds at offset %zu (file size %zu)",
163 aOffset,
m_data.size() ) );
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 );
175 return static_cast<int32_t
>(
readU32( aOffset ) );
181 if( aOffset >=
m_data.size() )
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;
189 const uint8_t* null_pos = std::find( start,
end, 0 );
190 size_t len =
static_cast<size_t>( null_pos - start );
193 for(
size_t i = 0; i < len; ++i )
195 if( start[i] < 0x20 || start[i] >= 0x7F )
199 std::string
result(
reinterpret_cast<const char*
>( start ), len );
234 uint32_t sizeCheck =
readU32( footerStart + 42 );
239 wxLogWarning(
"PADS binary footer size mismatch: stored=%u, expected=%u",
250 if( dirStart + dirSize >
m_data.size() )
253 uint32_t dataOffset =
static_cast<uint32_t
>( dirStart + dirSize );
260 size_t off = dirStart +
static_cast<size_t>( i ) *
DIR_ENTRY_SIZE;
265 entry.totalBytes =
readU32( off + 4 );
266 entry.dataOffset = 0;
271 entry.dataOffset = dataOffset;
273 if( entry.count > 0 && entry.totalBytes > 0 )
274 entry.perItem = entry.totalBytes / entry.count;
276 dataOffset += entry.totalBytes;
286 if( aIndex >= 0 && aIndex <
static_cast<int>(
m_dirEntries.size() ) )
335 return static_cast<double>( aRawAngle ) /
static_cast<double>(
ANGLE_SCALE );
344 if( !data || size < 160 )
351 if( maxLayer >= 1 && maxLayer <= 64 )
352 m_parameters.layer_count =
static_cast<int>( maxLayer );
382 if( !data || size == 0 )
393 if( !entry || entry->
count == 0 || entry->
perItem == 0 )
402 uint32_t recSize = entry->
perItem;
405 int nameOff = 0, xOff = 0, yOff = 0, angleOff = 0;
422 for( uint32_t i = 0; i < entry->
count; ++i )
424 size_t off =
static_cast<size_t>( i ) * recSize;
432 if( refDes.empty() || !std::isalnum(
static_cast<unsigned char>( refDes[0] ) ) )
435 int32_t x =
readI32( base + xOff );
438 int32_t y = ( yOff >= 0 ) ?
readI32( base + yOff ) : 0;
439 int32_t angleRaw =
readI32( base + angleOff );
459 int nameOff = 0, xOff = 0, yOff = 0, angleOff = 0, feffOff = 0;
478 int recSize = feffOff + 2;
480 std::set<std::string> existingRefs;
483 existingRefs.insert( p.name );
485 for(
int secIdx : { 19, 21 } )
495 if( !data || size == 0 )
498 for(
size_t pos = 0; pos + 1 < size; ++pos )
500 if( data[pos] != 0xFE || data[pos + 1] != 0xFF )
503 int recStart =
static_cast<int>( pos ) - feffOff;
505 if( recStart < 0 || recStart + recSize >
static_cast<int>( size ) )
511 if( refDes.empty() || !std::isalnum(
static_cast<unsigned char>( refDes[0] ) ) )
514 if( existingRefs.count( refDes ) )
517 int32_t x =
readI32( base + xOff );
518 int32_t y = ( yOff >= 0 ) ?
readI32( base + yOff ) : 0;
519 int32_t angleRaw =
readI32( base + angleOff );
530 existingRefs.insert( refDes );
540 if( !entry || entry->
count == 0 || entry->
perItem == 0 )
549 uint32_t recSize = entry->
perItem;
553 for( uint32_t i = 0; i < entry->
count; ++i )
555 size_t off =
static_cast<size_t>( i ) * recSize;
562 int32_t padWidth = 0, drill = 0, finLength = 0, angleRaw = 0;
563 uint8_t marker = 0, shapeCode = 0;
564 uint16_t layerCount = 0;
568 padWidth =
readI32( base + 28 );
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 );
578 padWidth =
readI32( base + 24 );
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 );
591 std::string shapeName =
"R";
595 shapeName = shapeIt->second;
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 );
619 if( !entry || entry->
count == 0 || entry->
perItem == 0 )
628 uint32_t recSize = entry->
perItem;
630 for( uint32_t i = 0; i < entry->
count; ++i )
632 size_t off =
static_cast<size_t>( i ) * recSize;
640 std::string units =
"I";
645 uint8_t unitFlag =
readU8( base + 76 );
646 units = ( unitFlag == 0x4D ) ?
"M" :
"I";
677 if( !entry || entry->
count == 0 || entry->
perItem < 188 )
687 std::map<uint32_t, std::string> decalIndexToName;
689 if( decalEntry && decalEntry->
count > 0 && decalEntry->
perItem > 0 )
691 for( uint32_t i = 0; i < decalEntry->
count; ++i )
693 size_t dOff =
static_cast<size_t>( i ) * decalEntry->
perItem;
701 if( !decalName.empty() )
702 decalIndexToName[i] = decalName;
706 uint32_t recSize = entry->
perItem;
709 std::map<std::string, std::string> fpTypeToDecal;
711 for( uint32_t i = 0; i < entry->
count; ++i )
713 size_t off =
static_cast<size_t>( i ) * recSize;
719 uint32_t decalIdx =
readU32( base + 112 );
722 if( fpTypeName.empty() )
725 auto decalIt = decalIndexToName.find( decalIdx );
727 if( decalIt != decalIndexToName.end() )
728 fpTypeToDecal[fpTypeName] = decalIt->second;
742 if( !entry || entry->
count == 0 )
753 for( uint32_t i = 0; i < entry->
count; ++i )
755 size_t off =
static_cast<size_t>( i ) * 12;
788 if( !entry || entry->
count == 0 )
796 size_t vertexIdx = 0;
798 for( uint32_t i = 0; i < entry->
count; ++i )
800 size_t off =
static_cast<size_t>( i ) * 16;
806 uint32_t vertexCount =
readU32( base );
807 uint32_t sentinel =
readU32( base + 12 );
809 if( sentinel != 0xFFFFFFFF )
812 if( vertexCount == 0 || vertexCount > 10000
823 for( uint32_t v = 0; v < vertexCount; ++v )
826 outline.
points.emplace_back(
static_cast<double>( lv.
x ),
827 static_cast<double>( lv.
y ) );
830 vertexIdx += vertexCount;
832 if( outline.
points.size() >= 3 )
857 char first = aName[0];
859 return std::isalpha(
static_cast<unsigned char>( first ) )
860 || std::isdigit(
static_cast<unsigned char>( first ) )
861 || first ==
'+' || first ==
'~' || first ==
'_' || first ==
'/';
867 std::set<std::string> existing;
874 if( entry23 && entry23->
count > 0 && entry23->
perItem == 424 )
876 for( uint32_t i = 0; i < entry23->
count; ++i )
878 size_t off =
static_cast<size_t>( i ) * 424;
891 existing.insert(
name );
899 if( entry22 && entry22->
count > 0 && entry22->
perItem == 112 )
901 for( uint32_t i = 0; i < entry22->
count; ++i )
903 size_t off =
static_cast<size_t>( i ) * 112;
910 for(
int nameOff : { 28, 52, 76 } )
916 uint32_t netIdx =
readU32( base + nameOff - 4 );
918 if( netIdx < 100000 || netIdx >= 0xFFFF0000 )
923 existing.insert(
name );
936 if( entry23 && entry23->
count > 0 && entry23->
perItem == 144 )
938 for( uint32_t i = 0; i < entry23->
count; ++i )
940 size_t off =
static_cast<size_t>( i ) * 144;
946 uint32_t netIdx =
readU32( base + 8 );
950 && !existing.count(
name ) )
955 existing.insert(
name );
963 if( entry22 && entry22->
count > 0 && entry22->
perItem == 96 )
965 for( uint32_t i = 0; i < entry22->
count; ++i )
967 size_t off =
static_cast<size_t>( i ) * 96;
974 for(
int nameOff : { 12, 60 } )
980 uint32_t netIdx =
readU32( base + nameOff - 4 );
982 if( netIdx < 100000 )
987 existing.insert(
name );
998 if( entry19 && entry19->
count > 0 )
1006 for(
size_t pos = 0; pos + 4 < sec19Size; ++pos )
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 );
1013 if( val == 0xFFFFFFFF )
1015 for(
size_t scan = pos + 4; scan + 2 < sec19Size && scan < pos + 40; ++scan )
1017 if( sec19Data[scan] != 0
1018 && std::isalpha(
static_cast<unsigned char>( sec19Data[scan] ) ) )
1024 && !existing.count(
name ) )
1029 existing.insert(
name );
1056 if( entry.index > 0 && entry.totalBytes > 0 )
1058 size_t end = entry.dataOffset + entry.totalBytes;
1060 if(
end > lastDataEnd )
1067 if( lastDataEnd >= footerStart )
1078 static const char DFT_MARKER[] =
"DFT_CONFIGURATION";
1079 size_t markerLen = std::strlen( DFT_MARKER );
1081 for(
size_t pos = aStart; pos + markerLen + 1 < aEnd; ++pos )
1083 if( std::memcmp( &
m_data[pos], DFT_MARKER, markerLen ) == 0
1084 &&
m_data[pos + markerLen] == 0 )
1086 size_t configStart = pos + markerLen + 1;
1089 while( configStart < aEnd )
1091 if(
m_data[configStart] == 0 )
1097 if( configStart + 7 <= aEnd
1098 && std::memcmp( &
m_data[configStart],
"PARENT\0", 7 ) == 0 )
1107 if( configStart >= aEnd )
1111 std::map<std::string, std::string>
config;
1112 bool hasDot =
false;
1114 if( configStart + 16 <= aEnd )
1116 for(
size_t i = configStart; i < configStart + 16; ++i )
1131 auto xIt =
config.find(
"X" );
1132 auto yIt =
config.find(
"Y" );
1138 m_originX =
static_cast<int32_t
>( std::stod( xIt->second ) );
1139 m_originY =
static_cast<int32_t
>( std::stod( yIt->second ) );
1147 wxLogTrace(
"PADS",
"Failed to parse DFT origin values" );
1157std::map<std::string, std::string>
1160 std::map<std::string, std::string>
config;
1162 while( aPos + 16 <= aEnd )
1165 bool validKey =
true;
1167 for(
size_t i = aPos; i < aPos + 16; ++i )
1171 if( !( ( b >= 0x20 && b <= 0x7E ) || b == 0x00 ) )
1184 for(
size_t i = aPos; i < aPos + 16; ++i )
1189 key +=
static_cast<char>(
m_data[i] );
1198 if( aPos < aEnd &&
m_data[aPos] == 0 )
1202 size_t valStart = aPos;
1204 while( aPos < aEnd &&
m_data[aPos] != 0 )
1207 if( aPos > valStart )
1209 std::string value(
reinterpret_cast<const char*
>( &
m_data[valStart] ),
1218 if( aPos + 7 <= aEnd
1219 && std::memcmp( &
m_data[aPos],
"PARENT\0", 7 ) == 0 )
1229std::map<std::string, std::string>
1232 std::map<std::string, std::string>
config;
1234 while( aPos < aEnd )
1237 size_t keyStart = aPos;
1239 while( aPos < aEnd &&
m_data[aPos] != 0 )
1242 if( aPos == keyStart )
1246 bool validKey =
true;
1248 for(
size_t i = keyStart; i < aPos; ++i )
1260 std::string key(
reinterpret_cast<const char*
>( &
m_data[keyStart] ), aPos - keyStart );
1266 if( key ==
"PARENT" )
1270 size_t valStart = aPos;
1272 while( aPos < aEnd &&
m_data[aPos] != 0 )
1275 if( aPos <= valStart )
1278 std::string value(
reinterpret_cast<const char*
>( &
m_data[valStart] ), aPos - valStart );
1296 const uint8_t* null_pos = std::find( start,
end, 0 );
1297 size_t len =
static_cast<size_t>( null_pos - start );
1299 for(
size_t i = 0; i < len; ++i )
1301 if( start[i] < 0x20 || start[i] >= 0x7F )
1305 return std::string(
reinterpret_cast<const char*
>( start ), len );
1323 if( !entry || entry->
count == 0 || entry->
perItem < 72 )
1331 for( uint32_t i = 0; i < entry->
count; ++i )
1333 size_t off =
static_cast<size_t>( i ) * 72;
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 );
1350 if( content.empty() )
1354 text.content = content;
1357 text.height =
static_cast<double>( height );
1358 text.width =
static_cast<double>( linewidth );
1359 text.layer =
static_cast<int>( layer );
1394 uint32_t n60 = entry60->
count;
1395 uint32_t r60 = entry60->
perItem;
1399 int markerOffset = -1;
1401 uint32_t sampleCount = std::min( n60,
static_cast<uint32_t
>( 100 ) );
1405 for(
int candidate = 8; candidate < 28 && candidate + 8 < static_cast<int>( r60 ); ++candidate )
1409 for( uint32_t s = 0; s < sampleCount; ++s )
1411 size_t recOff =
static_cast<size_t>( s ) * r60;
1420 if( hits > bestCount )
1423 bestPos = candidate;
1427 if( bestCount <
static_cast<int>( sampleCount ) / 2 )
1430 markerOffset = bestPos;
1433 int xyOffset = markerOffset + 1;
1436 auto readSec60XY = [&]( uint32_t aRecIdx, int32_t& aX, int32_t& aY ) ->
bool
1438 if( aRecIdx >= n60 )
1441 size_t off =
static_cast<size_t>( aRecIdx ) * r60;
1448 if(
readU8( base + markerOffset ) != 0x80 )
1451 aX =
readI32( base + xyOffset );
1452 aY =
readI32( base + xyOffset + 4 );
1457 static constexpr uint32_t SEC24_SENTINEL = 0xFE000000;
1458 static constexpr int SEC24_REC_SIZE = 68;
1464 uint32_t n24 = entry24->
count;
1466 for( uint32_t i = 0; i < n24; ++i )
1468 size_t off =
static_cast<size_t>( i ) * SEC24_REC_SIZE;
1470 if( off + SEC24_REC_SIZE > entry24->
totalBytes )
1474 uint32_t sentinel =
readU32( base + 20 );
1476 if( sentinel != SEC24_SENTINEL )
1479 int32_t sec60Start =
readI32( base + 8 );
1480 int32_t sec60End =
readI32( base + 12 );
1482 if( sec60Start < 0 || sec60End < 0 )
1485 int32_t x1 = 0, y1 = 0, x2 = 0, y2 = 0;
1487 if( !readSec60XY(
static_cast<uint32_t
>( sec60Start ), x1, y1 ) )
1490 if( !readSec60XY(
static_cast<uint32_t
>( sec60End ), x2, y2 ) )
1495 int32_t width =
readI32( base + 24 );
1512 uint32_t recSize = entry59->
perItem;
1514 for( uint32_t i = 0; i < entry59->
count; ++i )
1516 size_t off =
static_cast<size_t>( i ) * recSize;
1523 if(
readU8( base + markerOffset ) != 0x80 )
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 ) );
1557 route.
vias.push_back( std::move( viaDef ) );
1560 m_routes.push_back( std::move( route ) );
1578 std::vector<LAYER_INFO> infos;
1581 for(
int i = 1; i <= layerCount; ++i )
1585 info.name =
"Layer " + std::to_string( i );
1586 info.is_copper =
true;
1587 info.required =
true;
1591 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()
#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.