35static const uint8_t
FOOTER_GUID[] =
"{2FE18320-6448-11d1-A412-000000000000}";
54 std::ifstream file( aFileName.fn_str(), std::ios::binary );
60 file.read(
reinterpret_cast<char*
>(
header ), 4 );
62 if( file.gcount() < 4 )
69 uint16_t version =
static_cast<uint16_t
>(
header[2] ) | (
static_cast<uint16_t
>(
header[3] ) << 8 );
71 return version == 0x2021 || version == 0x2025 || version == 0x2026 || version == 0x2027;
77 std::ifstream file( aFileName.fn_str(), std::ios::binary | std::ios::ate );
82 std::streamsize fileSize = file.tellg();
83 file.seekg( 0, std::ios::beg );
85 m_data.resize(
static_cast<size_t>( fileSize ) );
86 file.read(
reinterpret_cast<char*
>(
m_data.data() ), fileSize );
88 if( file.gcount() != fileSize )
111 [](
const PART& p ) { return p.name.empty(); } ),
120 case 0x2021:
return 73;
123 case 0x2027:
return 74;
131 if( aOffset >=
m_data.size() )
133 THROW_IO_ERROR( wxString::Format(
"PADS binary read out of bounds at offset %zu (file size %zu)",
134 aOffset,
m_data.size() ) );
143 if( aOffset + 2 >
m_data.size() )
145 THROW_IO_ERROR( wxString::Format(
"PADS binary read out of bounds at offset %zu (file size %zu)",
146 aOffset,
m_data.size() ) );
149 return static_cast<uint16_t
>(
m_data[aOffset] )
150 | (
static_cast<uint16_t
>(
m_data[aOffset + 1] ) << 8 );
156 if( aOffset + 4 >
m_data.size() )
158 THROW_IO_ERROR( wxString::Format(
"PADS binary read out of bounds at offset %zu (file size %zu)",
159 aOffset,
m_data.size() ) );
162 return static_cast<uint32_t
>(
m_data[aOffset] )
163 | (
static_cast<uint32_t
>(
m_data[aOffset + 1] ) << 8 )
164 | (
static_cast<uint32_t
>(
m_data[aOffset + 2] ) << 16 )
165 | (
static_cast<uint32_t
>(
m_data[aOffset + 3] ) << 24 );
171 return static_cast<int32_t
>(
readU32( aOffset ) );
177 if( aOffset >=
m_data.size() )
180 size_t available = std::min( aMaxLen,
m_data.size() - aOffset );
181 const uint8_t* start = &
m_data[aOffset];
182 const uint8_t*
end = start + available;
185 const uint8_t* null_pos = std::find( start,
end, 0 );
186 size_t len =
static_cast<size_t>( null_pos - start );
189 for(
size_t i = 0; i < len; ++i )
191 if( start[i] < 0x20 || start[i] >= 0x7F )
195 std::string
result(
reinterpret_cast<const char*
>( start ), len );
230 uint32_t sizeCheck =
readU32( footerStart + 42 );
235 wxLogWarning(
"PADS binary footer size mismatch: stored=%u, expected=%u",
246 if( dirStart + dirSize >
m_data.size() )
249 uint32_t dataOffset =
static_cast<uint32_t
>( dirStart + dirSize );
256 size_t off = dirStart +
static_cast<size_t>( i ) *
DIR_ENTRY_SIZE;
261 entry.totalBytes =
readU32( off + 4 );
262 entry.dataOffset = 0;
267 entry.dataOffset = dataOffset;
269 if( entry.count > 0 && entry.totalBytes > 0 )
270 entry.perItem = entry.totalBytes / entry.count;
272 dataOffset += entry.totalBytes;
282 if( aIndex >= 0 && aIndex <
static_cast<int>(
m_dirEntries.size() ) )
331 return static_cast<double>( aRawAngle ) /
static_cast<double>(
ANGLE_SCALE );
340 if( !data || size < 160 )
347 if( maxLayer >= 1 && maxLayer <= 64 )
348 m_parameters.layer_count =
static_cast<int>( maxLayer );
378 if( !data || size == 0 )
389 if( !entry || entry->
count == 0 || entry->
perItem == 0 )
398 uint32_t recSize = entry->
perItem;
401 int nameOff = 0, xOff = 0, yOff = 0, angleOff = 0;
418 for( uint32_t i = 0; i < entry->
count; ++i )
420 size_t off =
static_cast<size_t>( i ) * recSize;
428 if( refDes.empty() || !std::isalnum(
static_cast<unsigned char>( refDes[0] ) ) )
431 int32_t x =
readI32( base + xOff );
434 int32_t y = ( yOff >= 0 ) ?
readI32( base + yOff ) : 0;
435 int32_t angleRaw =
readI32( base + angleOff );
455 int nameOff = 0, xOff = 0, yOff = 0, angleOff = 0, feffOff = 0;
474 int recSize = feffOff + 2;
476 std::set<std::string> existingRefs;
479 existingRefs.insert( p.name );
481 for(
int secIdx : { 19, 21 } )
491 if( !data || size == 0 )
494 for(
size_t pos = 0; pos + 1 < size; ++pos )
496 if( data[pos] != 0xFE || data[pos + 1] != 0xFF )
499 int recStart =
static_cast<int>( pos ) - feffOff;
501 if( recStart < 0 || recStart + recSize >
static_cast<int>( size ) )
507 if( refDes.empty() || !std::isalnum(
static_cast<unsigned char>( refDes[0] ) ) )
510 if( existingRefs.count( refDes ) )
513 int32_t x =
readI32( base + xOff );
514 int32_t y = ( yOff >= 0 ) ?
readI32( base + yOff ) : 0;
515 int32_t angleRaw =
readI32( base + angleOff );
526 existingRefs.insert( refDes );
536 if( !entry || entry->
count == 0 || entry->
perItem == 0 )
545 uint32_t recSize = entry->
perItem;
549 for( uint32_t i = 0; i < entry->
count; ++i )
551 size_t off =
static_cast<size_t>( i ) * recSize;
558 int32_t padWidth = 0, drill = 0, finLength = 0, angleRaw = 0;
559 uint8_t marker = 0, shapeCode = 0;
560 uint16_t layerCount = 0;
564 padWidth =
readI32( base + 28 );
566 finLength =
readI32( base + 36 );
567 angleRaw =
readI32( base + 48 );
568 marker =
readU8( base + 56 );
569 shapeCode =
readU8( base + 57 );
570 layerCount =
readU16( base + 58 );
574 padWidth =
readI32( base + 24 );
576 finLength =
readI32( base + 32 );
577 angleRaw =
readI32( base + 40 );
578 marker =
readU8( base + 48 );
579 shapeCode =
readU8( base + 49 );
580 layerCount =
readU16( base + 50 );
587 std::string shapeName =
"R";
591 shapeName = shapeIt->second;
598 psl.
shape = shapeName;
599 psl.
sizeA =
static_cast<double>( padWidth );
600 psl.
sizeB =
static_cast<double>( padWidth );
601 psl.
drill =
static_cast<double>( drill );
602 psl.
plated = ( drill > 0 );
615 if( !entry || entry->
count == 0 || entry->
perItem == 0 )
624 uint32_t recSize = entry->
perItem;
626 for( uint32_t i = 0; i < entry->
count; ++i )
628 size_t off =
static_cast<size_t>( i ) * recSize;
636 std::string units =
"I";
641 uint8_t unitFlag =
readU8( base + 76 );
642 units = ( unitFlag == 0x4D ) ?
"M" :
"I";
673 if( !entry || entry->
count == 0 || entry->
perItem < 188 )
683 std::map<uint32_t, std::string> decalIndexToName;
685 if( decalEntry && decalEntry->
count > 0 && decalEntry->
perItem > 0 )
687 for( uint32_t i = 0; i < decalEntry->
count; ++i )
689 size_t dOff =
static_cast<size_t>( i ) * decalEntry->
perItem;
697 if( !decalName.empty() )
698 decalIndexToName[i] = decalName;
702 uint32_t recSize = entry->
perItem;
705 std::map<std::string, std::string> fpTypeToDecal;
707 for( uint32_t i = 0; i < entry->
count; ++i )
709 size_t off =
static_cast<size_t>( i ) * recSize;
715 uint32_t decalIdx =
readU32( base + 112 );
718 if( fpTypeName.empty() )
721 auto decalIt = decalIndexToName.find( decalIdx );
723 if( decalIt != decalIndexToName.end() )
724 fpTypeToDecal[fpTypeName] = decalIt->second;
738 if( !entry || entry->
count == 0 )
749 for( uint32_t i = 0; i < entry->
count; ++i )
751 size_t off =
static_cast<size_t>( i ) * 12;
784 if( !entry || entry->
count == 0 )
792 size_t vertexIdx = 0;
794 for( uint32_t i = 0; i < entry->
count; ++i )
796 size_t off =
static_cast<size_t>( i ) * 16;
802 uint32_t vertexCount =
readU32( base );
803 uint32_t sentinel =
readU32( base + 12 );
805 if( sentinel != 0xFFFFFFFF )
808 if( vertexCount == 0 || vertexCount > 10000
819 for( uint32_t v = 0; v < vertexCount; ++v )
822 outline.
points.emplace_back(
static_cast<double>( lv.
x ),
823 static_cast<double>( lv.
y ) );
826 vertexIdx += vertexCount;
828 if( outline.
points.size() >= 3 )
853 char first = aName[0];
855 return std::isalpha(
static_cast<unsigned char>( first ) )
856 || std::isdigit(
static_cast<unsigned char>( first ) )
857 || first ==
'+' || first ==
'~' || first ==
'_' || first ==
'/';
863 std::set<std::string> existing;
870 if( entry23 && entry23->
count > 0 && entry23->
perItem == 424 )
872 for( uint32_t i = 0; i < entry23->
count; ++i )
874 size_t off =
static_cast<size_t>( i ) * 424;
887 existing.insert(
name );
895 if( entry22 && entry22->
count > 0 && entry22->
perItem == 112 )
897 for( uint32_t i = 0; i < entry22->
count; ++i )
899 size_t off =
static_cast<size_t>( i ) * 112;
906 for(
int nameOff : { 28, 52, 76 } )
912 uint32_t netIdx =
readU32( base + nameOff - 4 );
914 if( netIdx < 100000 || netIdx >= 0xFFFF0000 )
919 existing.insert(
name );
932 if( entry23 && entry23->
count > 0 && entry23->
perItem == 144 )
934 for( uint32_t i = 0; i < entry23->
count; ++i )
936 size_t off =
static_cast<size_t>( i ) * 144;
942 uint32_t netIdx =
readU32( base + 8 );
946 && !existing.count(
name ) )
951 existing.insert(
name );
959 if( entry22 && entry22->
count > 0 && entry22->
perItem == 96 )
961 for( uint32_t i = 0; i < entry22->
count; ++i )
963 size_t off =
static_cast<size_t>( i ) * 96;
970 for(
int nameOff : { 12, 60 } )
976 uint32_t netIdx =
readU32( base + nameOff - 4 );
978 if( netIdx < 100000 )
983 existing.insert(
name );
994 if( entry19 && entry19->
count > 0 )
1002 for(
size_t pos = 0; pos + 4 < sec19Size; ++pos )
1004 uint32_t val =
static_cast<uint32_t
>( sec19Data[pos] )
1005 | (
static_cast<uint32_t
>( sec19Data[pos + 1] ) << 8 )
1006 | (
static_cast<uint32_t
>( sec19Data[pos + 2] ) << 16 )
1007 | (
static_cast<uint32_t
>( sec19Data[pos + 3] ) << 24 );
1009 if( val == 0xFFFFFFFF )
1011 for(
size_t scan = pos + 4; scan + 2 < sec19Size && scan < pos + 40; ++scan )
1013 if( sec19Data[scan] != 0
1014 && std::isalpha(
static_cast<unsigned char>( sec19Data[scan] ) ) )
1020 && !existing.count(
name ) )
1025 existing.insert(
name );
1052 if( entry.index > 0 && entry.totalBytes > 0 )
1054 size_t end = entry.dataOffset + entry.totalBytes;
1056 if(
end > lastDataEnd )
1063 if( lastDataEnd >= footerStart )
1074 static const char DFT_MARKER[] =
"DFT_CONFIGURATION";
1075 size_t markerLen = std::strlen( DFT_MARKER );
1077 for(
size_t pos = aStart; pos + markerLen + 1 < aEnd; ++pos )
1079 if( std::memcmp( &
m_data[pos], DFT_MARKER, markerLen ) == 0
1080 &&
m_data[pos + markerLen] == 0 )
1082 size_t configStart = pos + markerLen + 1;
1085 while( configStart < aEnd )
1087 if(
m_data[configStart] == 0 )
1093 if( configStart + 7 <= aEnd
1094 && std::memcmp( &
m_data[configStart],
"PARENT\0", 7 ) == 0 )
1103 if( configStart >= aEnd )
1107 std::map<std::string, std::string>
config;
1108 bool hasDot =
false;
1110 if( configStart + 16 <= aEnd )
1112 for(
size_t i = configStart; i < configStart + 16; ++i )
1127 auto xIt =
config.find(
"X" );
1128 auto yIt =
config.find(
"Y" );
1134 m_originX =
static_cast<int32_t
>( std::stod( xIt->second ) );
1135 m_originY =
static_cast<int32_t
>( std::stod( yIt->second ) );
1143 wxLogTrace(
"PADS",
"Failed to parse DFT origin values" );
1153std::map<std::string, std::string>
1156 std::map<std::string, std::string>
config;
1158 while( aPos + 16 <= aEnd )
1161 bool validKey =
true;
1163 for(
size_t i = aPos; i < aPos + 16; ++i )
1167 if( !( ( b >= 0x20 && b <= 0x7E ) || b == 0x00 ) )
1180 for(
size_t i = aPos; i < aPos + 16; ++i )
1185 key +=
static_cast<char>(
m_data[i] );
1194 if( aPos < aEnd &&
m_data[aPos] == 0 )
1198 size_t valStart = aPos;
1200 while( aPos < aEnd &&
m_data[aPos] != 0 )
1203 if( aPos > valStart )
1205 std::string value(
reinterpret_cast<const char*
>( &
m_data[valStart] ),
1214 if( aPos + 7 <= aEnd
1215 && std::memcmp( &
m_data[aPos],
"PARENT\0", 7 ) == 0 )
1225std::map<std::string, std::string>
1228 std::map<std::string, std::string>
config;
1230 while( aPos < aEnd )
1233 size_t keyStart = aPos;
1235 while( aPos < aEnd &&
m_data[aPos] != 0 )
1238 if( aPos == keyStart )
1242 bool validKey =
true;
1244 for(
size_t i = keyStart; i < aPos; ++i )
1256 std::string key(
reinterpret_cast<const char*
>( &
m_data[keyStart] ), aPos - keyStart );
1262 if( key ==
"PARENT" )
1266 size_t valStart = aPos;
1268 while( aPos < aEnd &&
m_data[aPos] != 0 )
1271 if( aPos <= valStart )
1274 std::string value(
reinterpret_cast<const char*
>( &
m_data[valStart] ), aPos - valStart );
1292 const uint8_t* null_pos = std::find( start,
end, 0 );
1293 size_t len =
static_cast<size_t>( null_pos - start );
1295 for(
size_t i = 0; i < len; ++i )
1297 if( start[i] < 0x20 || start[i] >= 0x7F )
1301 return std::string(
reinterpret_cast<const char*
>( start ), len );
1319 if( !entry || entry->
count == 0 || entry->
perItem < 72 )
1327 for( uint32_t i = 0; i < entry->
count; ++i )
1329 size_t off =
static_cast<size_t>( i ) * 72;
1336 uint32_t strOffset =
readU32( base );
1337 int32_t height =
readI32( base + 28 );
1338 int32_t linewidth =
readI32( base + 32 );
1339 int32_t x =
readI32( base + 44 );
1340 int32_t y =
readI32( base + 48 );
1341 int32_t angleRaw =
readI32( base + 52 );
1342 uint8_t layer =
readU8( base + 56 );
1346 if( content.empty() )
1350 text.content = content;
1353 text.height =
static_cast<double>( height );
1354 text.width =
static_cast<double>( linewidth );
1355 text.layer =
static_cast<int>( layer );
1390 uint32_t n60 = entry60->
count;
1391 uint32_t r60 = entry60->
perItem;
1395 int markerOffset = -1;
1397 uint32_t sampleCount = std::min( n60,
static_cast<uint32_t
>( 100 ) );
1401 for(
int candidate = 8; candidate < 28 && candidate + 8 < static_cast<int>( r60 ); ++candidate )
1405 for( uint32_t s = 0; s < sampleCount; ++s )
1407 size_t recOff =
static_cast<size_t>( s ) * r60;
1416 if( hits > bestCount )
1419 bestPos = candidate;
1423 if( bestCount <
static_cast<int>( sampleCount ) / 2 )
1426 markerOffset = bestPos;
1429 int xyOffset = markerOffset + 1;
1432 auto readSec60XY = [&]( uint32_t aRecIdx, int32_t& aX, int32_t& aY ) ->
bool
1434 if( aRecIdx >= n60 )
1437 size_t off =
static_cast<size_t>( aRecIdx ) * r60;
1444 if(
readU8( base + markerOffset ) != 0x80 )
1447 aX =
readI32( base + xyOffset );
1448 aY =
readI32( base + xyOffset + 4 );
1453 static constexpr uint32_t SEC24_SENTINEL = 0xFE000000;
1454 static constexpr int SEC24_REC_SIZE = 68;
1460 uint32_t n24 = entry24->
count;
1462 for( uint32_t i = 0; i < n24; ++i )
1464 size_t off =
static_cast<size_t>( i ) * SEC24_REC_SIZE;
1466 if( off + SEC24_REC_SIZE > entry24->
totalBytes )
1470 uint32_t sentinel =
readU32( base + 20 );
1472 if( sentinel != SEC24_SENTINEL )
1475 int32_t sec60Start =
readI32( base + 8 );
1476 int32_t sec60End =
readI32( base + 12 );
1478 if( sec60Start < 0 || sec60End < 0 )
1481 int32_t x1 = 0, y1 = 0, x2 = 0, y2 = 0;
1483 if( !readSec60XY(
static_cast<uint32_t
>( sec60Start ), x1, y1 ) )
1486 if( !readSec60XY(
static_cast<uint32_t
>( sec60End ), x2, y2 ) )
1491 int32_t width =
readI32( base + 24 );
1508 uint32_t recSize = entry59->
perItem;
1510 for( uint32_t i = 0; i < entry59->
count; ++i )
1512 size_t off =
static_cast<size_t>( i ) * recSize;
1519 if(
readU8( base + markerOffset ) != 0x80 )
1542 track.
width =
static_cast<double>( seg.width );
1543 track.
points.emplace_back(
static_cast<double>( seg.x1 ),
static_cast<double>( seg.y1 ) );
1544 track.
points.emplace_back(
static_cast<double>( seg.x2 ),
static_cast<double>( seg.y2 ) );
1545 route.
tracks.push_back( std::move( track ) );
1553 route.
vias.push_back( std::move( viaDef ) );
1556 m_routes.push_back( std::move( route ) );
1574 std::vector<LAYER_INFO> infos;
1577 for(
int i = 1; i <= layerCount; ++i )
1581 info.name =
"Layer " + std::to_string( i );
1582 info.is_copper =
true;
1583 info.required =
true;
1587 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)
std::vector< std::string > header
table push_back({ "Source", "Layer", "Vertices", "Strategy", "Build(us)", "ns/query", "Mquery/s", "Inside" })
wxString result
Test unit parsing edge cases and error handling.