67#include <wx/filename.h>
81 {
"TMS1mmX19",
"TMS1mmX19.pcb",
"TMS1mmX19.asc",
false },
82 {
"MC4_PLUS_CSHAPE",
"MC4_PLUS_CSHAPE.pcb",
"MC4_PLUS_CSHAPE.asc",
false },
83 {
"MC2_PLUS_REV1",
"MC2_PLUS_REV1.pcb",
"MC2_PLUS_REV1.asc",
true },
84 {
"Ems4_Rev2",
"Ems4_Rev2.pcb",
"Ems4_Rev2.asc",
false },
85 {
"LCORE_4",
"LCORE_4.pcb",
"LCORE_4.asc",
false },
86 {
"LCORE_2",
"LCORE_2.pcb",
"LCORE_2.asc",
false },
87 {
"Dexter_MotorCtrl",
"Dexter_MotorCtrl.pcb",
"Dexter_MotorCtrl.asc",
true },
88 {
"MAIS_FC",
"MAIS_FC.pcb",
"MAIS_FC.asc",
true },
111static std::shared_ptr<BOARD>
CachedLoad(
const wxString& aFilename,
bool aBinary )
113 static std::map<std::string, std::shared_ptr<BOARD>> cache;
115 std::string key = ( aBinary ?
"B:" :
"A:" ) + aFilename.ToStdString();
116 auto it = cache.find( key );
118 if( it != cache.end() )
121 std::shared_ptr<BOARD> board;
128 const std::filesystem::path
source( aFilename.ToStdString() );
129 const std::filesystem::path stagedDirectory =
130 stagedRoot.
Path() / std::to_string( std::hash<std::string>{}( key ) );
131 const std::filesystem::path stagedPath = stagedDirectory /
source.filename();
133 std::filesystem::create_directories( stagedDirectory );
134 std::filesystem::copy_file(
source, stagedPath, std::filesystem::copy_options::overwrite_existing );
135 std::filesystem::permissions( stagedPath, std::filesystem::perms::owner_write,
136 std::filesystem::perm_options::add );
139 board.reset( plugin.
LoadBoard( wxString::FromUTF8( stagedPath.string() ),
nullptr ).release() );
144 board.reset( plugin.
LoadBoard( aFilename,
nullptr ).release() );
147 catch(
const std::exception& )
165 aBoard.
dir <<
" binary should be readable by PCB_IO_PADS_BINARY" );
167 std::shared_ptr<BOARD> board;
173 catch(
const std::exception& e )
175 BOOST_FAIL( aBoard.
dir <<
" binary threw exception during load: " << e.what() );
178 BOOST_REQUIRE_MESSAGE( board !=
nullptr, aBoard.
dir <<
" binary failed to load" );
183static std::shared_ptr<BOARD>
LoadBinaryPath(
const wxString& aFilename,
const std::string& aLabel )
188 aLabel <<
" binary should be readable by PCB_IO_PADS_BINARY" );
190 std::shared_ptr<BOARD> board;
196 catch(
const std::exception& e )
198 BOOST_FAIL( aLabel <<
" binary threw exception during load: " << e.what() );
202 BOOST_REQUIRE_MESSAGE( board !=
nullptr, aLabel <<
" binary failed to load" );
212 std::shared_ptr<BOARD> board;
218 catch(
const std::exception& e )
220 BOOST_FAIL( aBoard.
dir <<
" ASC threw exception during load: " << e.what() );
224 BOOST_REQUIRE_MESSAGE( board !=
nullptr, aBoard.
dir <<
" ASC failed to load" );
229static std::shared_ptr<BOARD>
LoadAscPath(
const wxString& aFilename,
const std::string& aLabel )
231 std::shared_ptr<BOARD> board;
237 catch(
const std::exception& e )
239 BOOST_FAIL( aLabel <<
" ASC threw exception during load: " << e.what() );
243 BOOST_REQUIRE_MESSAGE( board !=
nullptr, aLabel <<
" ASC failed to load" );
249 const std::string& aLabel )
254 auto viaAnchor = [](
const BOARD* aBoard ) -> std::optional<VECTOR2I>
256 std::optional<VECTOR2I>
anchor;
258 for(
PCB_TRACK* item : aBoard->Tracks() )
263 VECTOR2I position = item->GetPosition();
274 std::optional<VECTOR2I> binaryAnchor = viaAnchor( binary.get() );
275 std::optional<VECTOR2I> asciiAnchor = viaAnchor( ascii.get() );
277 if( binaryAnchor && asciiAnchor )
278 binary->Move( *asciiAnchor - *binaryAnchor );
280 using TRACK_GEOMETRY = std::tuple<int, int, int, int, int, int>;
282 auto trackGeometry = [](
const BOARD* aBoard )
284 std::set<TRACK_GEOMETRY> geometry;
286 for(
PCB_TRACK* track : aBoard->Tracks() )
294 if( std::tie(
end.x,
end.y ) < std::tie( start.
x, start.
y ) )
295 std::swap( start,
end );
297 geometry.emplace(
static_cast<int>( track->GetLayer() ), start.
x, start.
y,
end.x,
end.y,
304 const std::set<TRACK_GEOMETRY> binaryTracks = trackGeometry( binary.get() );
305 const std::set<TRACK_GEOMETRY> asciiTracks = trackGeometry( ascii.get() );
306 std::vector<TRACK_GEOMETRY> matched;
308 std::set_intersection( binaryTracks.begin(), binaryTracks.end(), asciiTracks.begin(), asciiTracks.end(),
309 std::back_inserter( matched ) );
311 return { matched.size(), asciiTracks.size() };
316 const std::string& aLabel )
321 auto viaAnchor = [](
const BOARD* aBoard ) -> std::optional<VECTOR2I>
323 std::optional<VECTOR2I>
anchor;
325 for(
PCB_TRACK* item : aBoard->Tracks() )
330 VECTOR2I position = item->GetPosition();
339 std::optional<VECTOR2I> binaryAnchor = viaAnchor( binary.get() );
340 std::optional<VECTOR2I> asciiAnchor = viaAnchor( ascii.get() );
342 if( binaryAnchor && asciiAnchor )
343 binary->Move( *asciiAnchor - *binaryAnchor );
345 using NETTED_TRACK = std::tuple<int, int, int, int, int, int, wxString>;
347 auto tracks = [](
const BOARD* aBoard )
349 std::set<NETTED_TRACK> rows;
351 for(
PCB_TRACK* track : aBoard->Tracks() )
359 if( std::tie(
end.x,
end.y ) < std::tie( start.
x, start.
y ) )
360 std::swap( start,
end );
362 rows.emplace(
static_cast<int>( track->GetLayer() ), start.
x, start.
y,
end.x,
end.y, track->GetWidth(),
363 track->GetNetname() );
369 const std::set<NETTED_TRACK> binaryTracks = tracks( binary.get() );
370 const std::set<NETTED_TRACK> asciiTracks = tracks( ascii.get() );
371 std::vector<NETTED_TRACK> matched;
373 std::set_intersection( binaryTracks.begin(), binaryTracks.end(), asciiTracks.begin(), asciiTracks.end(),
374 std::back_inserter( matched ) );
376 return { matched.size(), asciiTracks.size() };
380static std::pair<size_t, size_t>
ExactViaMatches(
const wxString& aBinaryPath,
const wxString& aAsciiPath,
381 const std::string& aLabel )
386 auto viaAnchor = [](
const BOARD* aBoard ) -> std::optional<VECTOR2I>
388 std::optional<VECTOR2I>
anchor;
390 for(
PCB_TRACK* item : aBoard->Tracks() )
395 VECTOR2I position = item->GetPosition();
404 std::optional<VECTOR2I> binaryAnchor = viaAnchor( binary.get() );
405 std::optional<VECTOR2I> asciiAnchor = viaAnchor( ascii.get() );
407 if( binaryAnchor && asciiAnchor )
408 binary->Move( *asciiAnchor - *binaryAnchor );
410 using VIA_GEOMETRY = std::tuple<wxString, int, int, int, int, int, int, int>;
412 auto vias = [](
const BOARD* aBoard )
414 std::set<VIA_GEOMETRY> rows;
416 for(
PCB_TRACK* item : aBoard->Tracks() )
423 rows.emplace(
via->GetNetname(),
via->GetPosition().x,
via->GetPosition().y,
424 via->GetWidth(
via->TopLayer() ),
via->GetDrillValue(),
static_cast<int>(
via->GetViaType() ),
425 static_cast<int>(
via->TopLayer() ),
static_cast<int>(
via->BottomLayer() ) );
431 const std::set<VIA_GEOMETRY> binaryVias = vias( binary.get() );
432 const std::set<VIA_GEOMETRY> asciiVias = vias( ascii.get() );
433 std::vector<VIA_GEOMETRY> matched;
435 std::set_intersection( binaryVias.begin(), binaryVias.end(), asciiVias.begin(), asciiVias.end(),
436 std::back_inserter( matched ) );
438 return { matched.size(), asciiVias.size() };
459static size_t CountVias(
const std::shared_ptr<BOARD>& aBoard )
463 for(
PCB_TRACK* item : aBoard->Tracks() )
477 for(
PCB_TRACK* item : aBoard->Tracks() )
491 if( fp->GetReference() == aReference )
501 std::set<wxString> names;
505 if( net && !net->GetNetname().IsEmpty() )
506 names.insert( net->GetNetname() );
513static wxString
JoinNetSet(
const std::set<wxString>& aNames )
517 for(
const wxString&
name : aNames )
519 if( !joined.IsEmpty() )
520 joined += wxString(
"," );
531 std::vector<std::string> rows;
535 for(
PAD*
pad : fp->Pads() )
538 std::ostringstream row;
540 row <<
"PAD " << std::quoted( fp->GetReference().ToStdString() ) <<
" "
541 << std::quoted(
pad->GetNumber().ToStdString() ) <<
" " <<
pad->GetPosition().x <<
" "
542 <<
pad->GetPosition().y <<
" " <<
pad->GetOrientation().AsTenthsOfADegree() <<
" "
543 <<
static_cast<int>(
pad->GetAttribute() ) <<
" " << drill.
x <<
" " << drill.
y <<
" "
544 <<
static_cast<int>(
pad->GetDrillShape() ) <<
" " << std::quoted(
pad->GetLayerSet().FmtHex() ) <<
" "
545 << std::quoted(
pad->GetNetname().ToStdString() );
552 row <<
" " <<
static_cast<int>( layer ) <<
":" <<
static_cast<int>(
pad->GetShape( layer ) ) <<
":"
553 << size.
x <<
":" << size.
y <<
":" << offset.
x <<
":" << offset.
y <<
":"
554 <<
pad->GetRoundRectCornerRadius( layer ) <<
":" <<
pad->GetChamferPositions( layer );
557 rows.push_back( row.str() );
563 std::ostringstream row;
567 row <<
"VIA " << std::quoted(
via->GetNetname().ToStdString() ) <<
" " <<
via->GetPosition().x <<
" "
568 <<
via->GetPosition().y <<
" " <<
via->GetWidth(
via->TopLayer() ) <<
" " <<
via->GetDrillValue() <<
" "
569 <<
static_cast<int>(
via->GetViaType() ) <<
" " <<
static_cast<int>(
via->TopLayer() ) <<
" "
570 <<
static_cast<int>(
via->BottomLayer() );
574 row <<
"ARC " << std::quoted( arc->GetNetname().ToStdString() ) <<
" "
575 <<
static_cast<int>( arc->GetLayer() ) <<
" " << arc->GetStart().x <<
" " << arc->GetStart().y <<
" "
576 << arc->GetMid().x <<
" " << arc->GetMid().y <<
" " << arc->GetEnd().x <<
" " << arc->GetEnd().y <<
" "
584 if( std::tie(
end.x,
end.y ) < std::tie( start.
x, start.
y ) )
585 std::swap( start,
end );
587 row <<
"TRACK " << std::quoted( track->GetNetname().ToStdString() ) <<
" "
588 <<
static_cast<int>( track->GetLayer() ) <<
" " << start.
x <<
" " << start.
y <<
" " <<
end.x <<
" "
589 <<
end.y <<
" " << track->GetWidth();
592 rows.push_back( row.str() );
595 std::sort( rows.begin(), rows.end() );
606 bool aDifferentRevision )
608 if( aDifferentRevision )
610 BOOST_WARN_MESSAGE( aBinaryCount == aAscCount,
611 aLabel <<
" binary=" << aBinaryCount <<
" asc=" << aAscCount <<
" (different revision)" );
615 if( aBinaryCount == aAscCount )
617 BOOST_CHECK_MESSAGE(
true, aLabel <<
" counts match: " << aBinaryCount );
621 size_t maxCount = std::max( aBinaryCount, aAscCount );
622 size_t diff = ( aBinaryCount > aAscCount ) ? aBinaryCount - aAscCount : aAscCount - aBinaryCount;
624 bool withinTolerance = ( diff <= 2 ) || ( diff * 100 / maxCount <= 5 );
626 BOOST_CHECK_MESSAGE( withinTolerance,
627 aLabel <<
" counts differ beyond tolerance: binary=" << aBinaryCount <<
" asc=" << aAscCount );
629 BOOST_WARN_MESSAGE( aBinaryCount == aAscCount,
630 aLabel <<
" exact count mismatch: binary=" << aBinaryCount <<
" asc=" << aAscCount );
640 BOOST_WARN_MESSAGE( aBinaryBoard->
Tracks().size() > 0, aBoard.
dir <<
" binary has no tracks" );
642 std::set<std::pair<int, int>> viaPositions;
643 bool hasDuplicate =
false;
652 auto key = std::make_pair(
via->GetPosition().x,
via->GetPosition().y );
654 if( viaPositions.count( key ) )
660 viaPositions.insert( key );
663 BOOST_CHECK_MESSAGE( !hasDuplicate, aBoard.
dir <<
" binary should have no duplicate through-hole vias" );
670 aBoard.
dir <<
" binary track on non-copper layer " << trk->GetLayer() );
676 for(
PAD*
pad : fp->Pads() )
680 aBoard.
dir <<
" " << fp->GetReference() <<
" pad has zero size" );
700 for(
const std::string& row : rows )
702 pads += row.rfind(
"PAD ", 0 ) == 0;
703 tracks += row.rfind(
"TRACK ", 0 ) == 0 || row.rfind(
"ARC ", 0 ) == 0;
704 vias += row.rfind(
"VIA ", 0 ) == 0;
707 BOOST_CHECK_GT( pads, 0u );
708 BOOST_CHECK_GT( tracks, 0u );
709 BOOST_CHECK_GT( vias, 0u );
717 std::vector<uint8_t>
bytes;
728 auto writeRingU32 = [&](
size_t aLogicalOffset, uint32_t aValue )
730 for(
size_t byte = 0;
byte < 4; ++byte )
733 bytes.at( physical ) =
static_cast<uint8_t
>( aValue >> (
byte * 8 ) );
738 const size_t boundLoOffset = objects->
stride == 36 ? 12 : 24;
739 const size_t boundHiOffset = objects->
stride == 36 ? 16 : 28;
743 const size_t base = 32 +
static_cast<size_t>(
index ) * objects->
stride;
744 writeRingU32( base + boundLoOffset, 0x12345678 );
745 writeRingU32( base + boundHiOffset, 0x76543210 );
748 wxString tempBase = wxFileName::CreateTempFileName( wxS(
"kicad_pads_route_bounds_" ) );
749 wxRemoveFile( tempBase );
750 wxString tempPath = tempBase + wxS(
".pcb" );
753 wxFFile file( tempPath, wxS(
"wb" ) );
755 BOOST_REQUIRE_EQUAL( file.Write(
bytes.data(),
bytes.size() ),
bytes.size() );
758 using ROUTE_ROW = std::tuple<std::string, int, double, std::vector<std::pair<double, double>>>;
761 std::vector<ROUTE_ROW> rows;
767 std::vector<std::pair<double, double>> points;
770 points.emplace_back( point.
x, point.
y );
772 rows.emplace_back( route.net_name, track.
layer, track.
width, std::move( points ) );
782 changedBounds.
Parse( tempPath );
783 wxRemoveFile( tempPath );
785 BOOST_CHECK( routeRows( changedBounds ) == routeRows( baseline ) );
798 BOOST_CHECK_MESSAGE( binaryPlugin.
CanReadBoard( binaryPath ),
799 board.dir <<
" binary should be recognized by PCB_IO_PADS_BINARY" );
801 BOOST_CHECK_MESSAGE( !ascPlugin.
CanReadBoard( binaryPath ),
802 board.dir <<
" binary should NOT be recognized by PCB_IO_PADS" );
815 BOOST_CHECK_MESSAGE( !binaryPlugin.
CanReadBoard( ascPath ),
816 board.dir <<
" ASCII should NOT be recognized by PCB_IO_PADS_BINARY" );
821#define BINARY_LOAD_TEST( name, idx ) \
822 BOOST_AUTO_TEST_CASE( BasicLoad_##name ) \
824 auto board = LoadBinary( PADS_BINARY_BOARDS[idx] ); \
826 BOOST_CHECK( board->Footprints().size() > 0 ); \
839#define FOOTPRINT_COUNT_TEST( name, idx ) \
840 BOOST_AUTO_TEST_CASE( FootprintCount_##name ) \
842 auto bin = LoadBinary( PADS_BINARY_BOARDS[idx] ); \
844 auto asc = LoadAsc( PADS_BINARY_BOARDS[idx] ); \
846 CheckCountWithTolerance( #name " footprints", bin->Footprints().size(), asc->Footprints().size(), \
847 PADS_BINARY_BOARDS[idx].differentRevision ); \
860#define NET_COUNT_TEST( name, idx ) \
861 BOOST_AUTO_TEST_CASE( NetCount_##name ) \
863 auto bin = LoadBinary( PADS_BINARY_BOARDS[idx] ); \
865 auto asc = LoadAsc( PADS_BINARY_BOARDS[idx] ); \
867 CheckCountWithTolerance( #name " nets", bin->GetNetCount(), asc->GetNetCount(), \
868 PADS_BINARY_BOARDS[idx].differentRevision ); \
881#define NET_NAMES_EXACT_TEST( name, idx ) \
882 BOOST_AUTO_TEST_CASE( NetNamesExact_##name ) \
884 auto bin = LoadBinary( PADS_BINARY_BOARDS[idx] ); \
886 auto binNames = BoardNetNames( bin.get() ); \
887 auto asc = LoadAsc( PADS_BINARY_BOARDS[idx] ); \
888 auto ascNames = BoardNetNames( asc.get() ); \
890 std::set<wxString> missing; \
891 std::set_difference( ascNames.begin(), ascNames.end(), binNames.begin(), binNames.end(), \
892 std::inserter( missing, missing.begin() ) ); \
894 std::set<wxString> extra; \
895 std::set_difference( binNames.begin(), binNames.end(), ascNames.begin(), ascNames.end(), \
896 std::inserter( extra, extra.begin() ) ); \
898 BOOST_CHECK_MESSAGE( missing.empty(), #name " missing binary nets: " << JoinNetSet( missing ) ); \
899 BOOST_CHECK_MESSAGE( extra.empty(), #name " extra binary nets: " << JoinNetSet( extra ) ); \
909#define ROUTED_TRACKS_TEST( name, idx ) \
910 BOOST_AUTO_TEST_CASE( RoutedTracks_##name ) \
912 auto bin = LoadBinary( PADS_BINARY_BOARDS[idx] ); \
914 BOOST_CHECK_GT( CountTraces( bin ), 0u ); \
948 std::set<std::tuple<int, int, int>> geoms;
952 for(
PAD*
pad : fp->Pads() )
955 geoms.emplace(
static_cast<int>(
pad->GetShape(
F_Cu ) ), sz.
x, sz.
y );
972 BOOST_CHECK_GE( binGeoms, 5u );
981 BOOST_CHECK_GE( binGeoms, ascGeoms / 2 );
1021 BOOST_REQUIRE_GE(
CountVias( asc ), 443u );
1022 BOOST_CHECK_GE(
CountVias( bin ), 443u );
1031 BOOST_CHECK_MESSAGE(
CountEdgeCutsShapes( board.get() ) > 0,
"LCORE_4 binary should have board outline shapes" );
1039 BOOST_CHECK_MESSAGE(
CountEdgeCutsShapes( board.get() ) > 0,
"LCORE_2 binary should have board outline shapes" );
1045 int indices[] = { 0, 1, 2, 3, 6, 7 };
1047 for(
int i : indices )
1070 bbox.
Merge( shape->GetBoundingBox() );
1108 BOOST_CHECK_MESSAGE( binArcs == ascArcs,
name <<
" edge-cut arc count: binary=" << binArcs <<
" asc=" << ascArcs );
1113 BOOST_REQUIRE_MESSAGE( binBox.
GetWidth() > 0 && ascBox.
GetWidth() > 0,
name <<
" edge-cut bounding box is empty" );
1119 double tolX = std::max( 400000.0, ascBox.
GetWidth() * 0.01 );
1120 double tolY = std::max( 400000.0, ascBox.
GetHeight() * 0.01 );
1156 if(
info.differentRevision )
1170 info.dir <<
" ships a binary outline but the ASC has none "
1171 "to validate against" );
1173 double tolX = std::max( 400000.0, ascBox.
GetWidth() * 0.02 );
1174 double tolY = std::max( 400000.0, ascBox.
GetHeight() * 0.02 );
1177 info.dir <<
" board outline width: binary=" << binBox.
GetWidth()
1178 <<
" asc=" << ascBox.
GetWidth() <<
" (wrong outline shipped)" );
1180 info.dir <<
" board outline height: binary=" << binBox.
GetHeight()
1181 <<
" asc=" << ascBox.
GetHeight() <<
" (wrong outline shipped)" );
1189 for(
FOOTPRINT* fp : bin->Footprints() )
1190 fpBox.
Merge( fp->GetPosition() );
1194 BOX2I grown = binBox;
1199 <<
" does not enclose the footprint cloud centered at " << fpBox.
GetCenter()
1200 <<
" (outline mispositioned)" );
1206#define STRUCTURAL_INTEGRITY_TEST( name, idx ) \
1207 BOOST_AUTO_TEST_CASE( StructuralIntegrity_##name ) \
1209 auto board = LoadBinary( PADS_BINARY_BOARDS[idx] ); \
1211 RunStructuralChecks( PADS_BINARY_BOARDS[idx], board.get() ); \
1214#define ZONE_COUNT_TEST( name, idx ) \
1215 BOOST_AUTO_TEST_CASE( ZoneCount_##name ) \
1217 auto bin = LoadBinary( PADS_BINARY_BOARDS[idx] ); \
1219 auto asc = LoadAsc( PADS_BINARY_BOARDS[idx] ); \
1221 size_t binZones = bin->Zones().size(); \
1222 size_t ascZones = asc->Zones().size(); \
1224 if( PADS_BINARY_BOARDS[idx].differentRevision ) \
1226 BOOST_WARN_MESSAGE( binZones > 0, #name " binary zone count: " << binZones << " (different revision)" ); \
1230 BOOST_WARN_MESSAGE( binZones == ascZones, \
1231 #name " zone count: binary=" << binZones << " asc=" << ascZones ); \
1257 BOOST_CHECK_EQUAL( std::count_if( bin->Drawings().begin(), bin->Drawings().end(),
1260 const PCB_SHAPE* shape = dynamic_cast<const PCB_SHAPE*>( aItem );
1261 return shape && shape->GetShape() == SHAPE_T::POLY && shape->IsSolidFill();
1276 BOOST_CHECK_EQUAL( std::count_if( bin->Drawings().begin(), bin->Drawings().end(),
1279 const PCB_SHAPE* shape = dynamic_cast<const PCB_SHAPE*>( aItem );
1280 return shape && shape->GetShape() == SHAPE_T::POLY && shape->IsSolidFill();
1288 struct EXPECTED_LEVEL
1295 const std::vector<EXPECTED_LEVEL>
expected = {
1296 { 1,
"DRW68014421", 4 }, { 1,
"DRW62739568", 26 }, { 3,
"DRW55434270", 1 },
1297 { 3,
"DRW28145516", 22 }, { 3,
"DRW81324514", 24 },
1300 std::map<size_t, std::shared_ptr<PADS_IO::BINARY_PARSER>> parsers;
1302 for(
const EXPECTED_LEVEL& item :
expected )
1304 auto& parser = parsers[item.board];
1308 parser = std::make_shared<PADS_IO::BINARY_PARSER>();
1311 parser->Parse( filename );
1314 auto shape = std::find_if( parser->GetCopperShapes().begin(), parser->GetCopperShapes().end(),
1317 return aShape.name == item.name;
1350 if(
dynamic_cast<PCB_TEXT*
>( item ) )
1364 if(
text &&
text->GetText() == aText )
1372#define FREE_TEXT_COUNT_TEST( name, idx ) \
1373 BOOST_AUTO_TEST_CASE( FreeTextCount_##name ) \
1375 auto bin = LoadBinary( PADS_BINARY_BOARDS[idx] ); \
1377 auto asc = LoadAsc( PADS_BINARY_BOARDS[idx] ); \
1379 size_t binTexts = CountFreeTexts( bin.get() ); \
1380 size_t ascTexts = CountFreeTexts( asc.get() ); \
1382 BOOST_WARN_MESSAGE( binTexts == ascTexts, \
1383 #name " free text count: binary=" << binTexts << " asc=" << ascTexts \
1384 << " (text extraction incomplete)" ); \
1403#define FREE_TEXT_EXACT_TEST( name, idx ) \
1404 BOOST_AUTO_TEST_CASE( FreeTextExact_##name ) \
1406 auto bin = LoadBinary( PADS_BINARY_BOARDS[idx] ); \
1408 auto asc = LoadAsc( PADS_BINARY_BOARDS[idx] ); \
1410 BOOST_CHECK_EQUAL( CountFreeTexts( bin.get() ), CountFreeTexts( asc.get() ) ); \
1423 BOOST_CHECK_MESSAGE( !
HasFreeText( bin.get(),
"CLU_DCDC5V" ),
1424 "MC4 binary cluster name CLU_DCDC5V should not import as free text" );
1425 BOOST_CHECK_MESSAGE( !
HasFreeText( bin.get(),
"CLU_DCDC3V3" ),
1426 "MC4 binary cluster name CLU_DCDC3V3 should not import as free text" );
1434 std::vector<uint8_t>
bytes;
1447 std::copy_n(
bytes.begin() + ringBase, 144,
bytes.begin() + decoyBase );
1452 wxString tempBase = wxFileName::CreateTempFileName( wxS(
"kicad_pads_text_decoy_" ) );
1453 wxRemoveFile( tempBase );
1454 wxString tempPath = tempBase + wxS(
".pcb" );
1457 wxFFile file( tempPath, wxS(
"wb" ) );
1459 BOOST_REQUIRE_EQUAL( file.Write(
bytes.data(),
bytes.size() ),
bytes.size() );
1463 withDecoy.
Parse( tempPath );
1464 wxRemoveFile( tempPath );
1470#define PAD_COUNT_TEST( name, idx ) \
1471 BOOST_AUTO_TEST_CASE( PadCount_##name ) \
1473 auto bin = LoadBinary( PADS_BINARY_BOARDS[idx] ); \
1475 auto asc = LoadAsc( PADS_BINARY_BOARDS[idx] ); \
1477 size_t binPads = 0; \
1478 size_t ascPads = 0; \
1480 for( FOOTPRINT * fp : bin->Footprints() ) \
1481 binPads += fp->Pads().size(); \
1483 for( FOOTPRINT * fp : asc->Footprints() ) \
1484 ascPads += fp->Pads().size(); \
1486 BOOST_WARN_MESSAGE( binPads == ascPads, \
1487 #name " pad count: binary=" << binPads << " asc=" << ascPads \
1488 << " (part-to-decal linking incomplete)" ); \
1502#define PER_FOOTPRINT_CONTENT_TEST( name, idx, maxNameWrong, maxCountWrong ) \
1503 BOOST_AUTO_TEST_CASE( PerFootprintContent_##name ) \
1505 auto bin = LoadBinary( PADS_BINARY_BOARDS[idx] ); \
1507 auto asc = LoadAsc( PADS_BINARY_BOARDS[idx] ); \
1509 std::map<wxString, FOOTPRINT*> ascByRef; \
1511 for( FOOTPRINT * fp : asc->Footprints() ) \
1512 ascByRef[fp->GetReference()] = fp; \
1514 int resolved = 0, nameWrong = 0, countWrong = 0; \
1515 wxString wrongRefs; \
1517 for( FOOTPRINT * fp : bin->Footprints() ) \
1519 wxString binDecal = fp->GetFPID().GetLibItemName().wx_str(); \
1521 if( binDecal == fp->GetReference() ) \
1524 auto it = ascByRef.find( fp->GetReference() ); \
1526 if( it == ascByRef.end() ) \
1531 if( binDecal != it->second->GetFPID().GetLibItemName().wx_str() ) \
1534 wrongRefs += fp->GetReference() \
1535 + wxString::Format( ":name %s/%s %zu/%zu ", binDecal, \
1536 it->second->GetFPID().GetLibItemName().wx_str(), fp->Pads().size(), \
1537 it->second->Pads().size() ); \
1539 else if( fp->Pads().size() != it->second->Pads().size() ) \
1542 wrongRefs += fp->GetReference() \
1543 + wxString::Format( ":%zu/%zu ", fp->Pads().size(), it->second->Pads().size() ); \
1547 BOOST_TEST_MESSAGE( #name " per-footprint: resolved=" << resolved << " nameWrong=" << nameWrong \
1548 << " countWrong=" << countWrong \
1549 << " wrongRefs=" << wrongRefs ); \
1550 BOOST_CHECK_LE( nameWrong, maxNameWrong ); \
1551 BOOST_CHECK_LE( countWrong, maxCountWrong ); \
1566 BOOST_REQUIRE_MESSAGE( sm3 !=
nullptr,
"TMS1mmX19 binary should import section-boundary placement SM3" );
1589 for(
FOOTPRINT* fp : bin->Footprints() )
1591 binPads += fp->Pads().size();
1593 if( fp->GetFPID().GetLibItemName().wx_str() != fp->GetReference() )
1597 BOOST_TEST_MESSAGE(
"MAIS_FC v0x2021: pads=" << binPads <<
" resolved=" << resolved );
1607 std::map<wxString, std::pair<wxString, size_t>>
expected = {
1608 {
"J7", {
"54722-0201", 20 } }, {
"J1", {
"SOLDERLAND4", 11 } }, {
"J2", {
"CON3", 3 } },
1609 {
"J3", {
"CON3", 3 } }, {
"J4", {
"CON6", 6 } },
1614 for(
FOOTPRINT* fp : bin->Footprints() )
1616 auto it =
expected.find( fp->GetReference() );
1637 for(
FOOTPRINT* fp : bin->Footprints() )
1639 binPads += fp->Pads().size();
1641 if( fp->GetFPID().GetLibItemName().wx_str() != fp->GetReference() )
1645 BOOST_TEST_MESSAGE(
"Dexter_MotorCtrl v0x2021: pads=" << binPads <<
" resolved=" << resolved );
1651 BOOST_CHECK_GT( binPads, 800u );
1652 BOOST_CHECK_GT( resolved, 200 );
1657 std::map<wxString, std::pair<wxString, size_t>>
expected = {
1658 {
"D1", {
"DFN-6-9", 9 } },
1659 {
"L1", {
"IHLP-2525", 2 } },
1664 for(
FOOTPRINT* fp : bin->Footprints() )
1666 auto it =
expected.find( fp->GetReference() );
1704 const std::vector<Oracle> oracle = {
1705 {
"J1", 10800000, 0.0,
false }, {
"J2", 23663130, 180.0,
false }, {
"J3", 8785902, 0.0,
false },
1706 {
"J4", 21216083, 90.0,
false }, {
"J7", 9000000, 270.0,
true },
1709 std::map<wxString, FOOTPRINT*> binFps;
1711 for(
FOOTPRINT* fp : bin->Footprints() )
1712 binFps[fp->GetReference()] = fp;
1715 std::set<int> binYs;
1717 for(
const Oracle& o : oracle )
1719 BOOST_REQUIRE_MESSAGE( binFps.count( o.ref ),
"v0x2021 missing footprint " << o.ref );
1720 binYs.insert( binFps[o.ref]->GetPosition().y );
1723 BOOST_CHECK_MESSAGE( binYs.size() == oracle.size(),
"v0x2021 part Y collapsed to "
1724 << binYs.size() <<
" of " << oracle.size()
1725 <<
" distinct values (Y decode regression)" );
1728 for(
const Oracle& o : oracle )
1732 BOOST_CHECK_MESSAGE( fp->
IsFlipped() == o.flipped,
1733 "v0x2021 " << o.ref <<
" side " << fp->
IsFlipped() <<
" != " << o.flipped );
1739 double diff =
std::abs( deg - o.ori );
1741 BOOST_CHECK_MESSAGE( diff < 0.1 ||
std::abs( diff - 360.0 ) < 0.1,
1742 "v0x2021 " << o.ref <<
" orientation " << deg <<
" != " << o.ori );
1747 std::vector<Oracle> byAscY( oracle );
1748 std::sort( byAscY.begin(), byAscY.end(),
1749 [](
const Oracle& a,
const Oracle& b )
1751 return a.ascY < b.ascY;
1757 for(
size_t i = 1; i < byAscY.size(); ++i )
1759 int prev = binFps[byAscY[i - 1].ref]->GetPosition().y;
1760 int cur = binFps[byAscY[i].ref]->GetPosition().y;
1769 BOOST_CHECK_MESSAGE( incr || decr,
"v0x2021 placed-Y ordering does not match ASC oracle" );
1790 for(
FOOTPRINT* fp : bin->Footprints() )
1791 binPads += fp->Pads().size();
1793 for(
FOOTPRINT* fp : asc->Footprints() )
1794 ascPads += fp->Pads().size();
1807 for(
FOOTPRINT* fp : bin->Footprints() )
1808 binPads += fp->Pads().size();
1810 for(
FOOTPRINT* fp : asc->Footprints() )
1811 ascPads += fp->Pads().size();
1824 for(
FOOTPRINT* fp : bin->Footprints() )
1825 binPads += fp->Pads().size();
1827 for(
FOOTPRINT* fp : asc->Footprints() )
1828 ascPads += fp->Pads().size();
1849#define WIDTH_VARIETY_TEST( name, idx ) \
1850 BOOST_AUTO_TEST_CASE( TrackWidthVariety_##name ) \
1852 auto bin = LoadBinary( PADS_BINARY_BOARDS[idx] ); \
1854 auto asc = LoadAsc( PADS_BINARY_BOARDS[idx] ); \
1856 std::set<int> binWidths; \
1857 std::set<int> ascWidths; \
1859 for( PCB_TRACK * trk : bin->Tracks() ) \
1861 if( trk->Type() == PCB_TRACE_T ) \
1862 binWidths.insert( trk->GetWidth() ); \
1865 for( PCB_TRACK * trk : asc->Tracks() ) \
1867 if( trk->Type() == PCB_TRACE_T ) \
1868 ascWidths.insert( trk->GetWidth() ); \
1871 if( ascWidths.size() > 1 ) \
1873 BOOST_WARN_MESSAGE( binWidths.size() > 1, #name " binary should have multiple distinct track widths " \
1875 << binWidths.size() << ", ASCII has " \
1876 << ascWidths.size() << ")" ); \
1907 struct EXPECTED_OWNER
1909 std::string boardDir;
1910 std::string binaryFile;
1912 std::vector<std::pair<int, int>> firstVerts;
1915 const std::vector<EXPECTED_OWNER> cases = {
1916 {
"MC4_PLUS_CSHAPE",
1917 "MC4_PLUS_CSHAPE.pcb",
1919 { { 0, 0 }, { 0, 5486400 }, { 9982200, 5486400 }, { 10210800, 5257800 } } },
1920 {
"MC4_PLUS_CSHAPE",
1921 "MC4_PLUS_CSHAPE.pcb",
1923 { { 0, 0 }, { 0, 9784905 }, { -452970, 8882805 }, { -1905015, -2943150 } } },
1927 { { 0, -571500 }, { 0, 1333500 }, { 1714500, 1333500 }, { 1714500, 6096000 } } },
1931 { { 0, 0 }, { 0, -2095500 }, { 2857500, -2095500 }, { 3048000, -2286000 } } },
1935 { { 803078, -1462546 }, { -1259549, -2233786 }, { -295519, -4678038 }, { 1767106, -3906796 } } },
1938 std::map<std::string, std::shared_ptr<PADS_IO::BINARY_PARSER>> parsers;
1940 for(
const EXPECTED_OWNER& ec : cases )
1944 std::shared_ptr<PADS_IO::BINARY_PARSER>& parser = parsers[ec.boardDir];
1948 parser = std::make_shared<PADS_IO::BINARY_PARSER>();
1951 parser->Parse( filename );
1954 std::vector<VECTOR2I> loop;
1956 BOOST_REQUIRE_MESSAGE( parser->GetOwnerLoopForTest( ec.owner, loop ),
1957 ec.owner <<
" has no structural sec12 loop" );
1959 BOOST_REQUIRE_GE( loop.size(), ec.firstVerts.size() );
1961 for(
size_t i = 0; i < ec.firstVerts.size(); ++i )
1977 std::vector<uint8_t> field = {
'R', 0xE9,
's', 0 };
1981 BOOST_REQUIRE_EQUAL( raw.size(), 3u );
1982 BOOST_CHECK_EQUAL(
static_cast<unsigned>(
static_cast<uint8_t
>( raw[1] ) ), 0xE9u );
1988 wxString::FromUTF8(
"~{R\xC3\xA9s}" ) );
1996 std::vector<uint8_t> control = {
'A', 0x01,
'B', 0 };
2000 std::vector<uint8_t> padded = {
' ',
' ',
'N',
'E',
'T',
' ',
' ', 0 };
2012 const size_t sec14CountOffset = 10 + 14 * 16;
2018 std::vector<char>
bytes( ( std::istreambuf_iterator<char>( in ) ), std::istreambuf_iterator<char>() );
2022 BOOST_REQUIRE_GT(
bytes.size(), sec14CountOffset + 4 );
2024 bytes[sec14CountOffset + 0] =
static_cast<char>( 0xFF );
2025 bytes[sec14CountOffset + 1] =
static_cast<char>( 0xFF );
2026 bytes[sec14CountOffset + 2] =
static_cast<char>( 0xFF );
2027 bytes[sec14CountOffset + 3] =
static_cast<char>( 0x0F );
2029 std::filesystem::path mutated = std::filesystem::temp_directory_path()
2030 /
"kicad_pads_sec14_oversized_count.pcb";
2033 std::ofstream out( mutated, std::ios::binary );
2035 out.write(
bytes.data(),
static_cast<std::streamsize
>(
bytes.size() ) );
2040 BOOST_CHECK_EXCEPTION( parser.
Parse( wxString::FromUTF8( mutated.string() ) ),
IO_ERROR,
2043 return aError.What().Contains( wxT(
"decal-name table extent" ) );
2046 std::filesystem::remove( mutated );
2061 const std::vector<std::string> boardsWithOblong = {
"TMS1mmX19",
"MC4_PLUS_CSHAPE" };
2063 for(
const std::string& dir : boardsWithOblong )
2070 parser.
Parse( filename );
2074 BOOST_CHECK_MESSAGE( shapes.count(
"OF" ),
2075 dir <<
" decoded no OF padstack (shape-enum off-by-one regression)" );
2079 BOOST_CHECK_MESSAGE( shapes.count(
"R" ), dir <<
" decoded no round padstack" );
2080 BOOST_CHECK_MESSAGE( shapes.count(
"RF" ), dir <<
" decoded no RF padstack" );
2101 parser.
Parse( filename );
2103 const std::map<std::string, PADS_IO::PART_DECAL>& decals = parser.
GetPartDecals();
2108 std::vector<std::pair<double, double>> terms;
2111 const std::vector<Expected> cases = {
2112 {
"0402", { { -750000, 0 }, { 750000, 0 } } }, {
"0603", { { -945000, 0 }, { 945000, 0 } } },
2113 {
"0805", { { -1200000, 0 }, { 1200000, 0 } } }, {
"1206", { { -1800000, 0 }, { 1800000, 0 } } },
2114 {
"CC3", { { 0, -571500 }, { 0, 0 }, { 0, 571500 } } },
2117 for(
const Expected& e : cases )
2121 auto it = decals.find( e.name );
2122 BOOST_REQUIRE_MESSAGE( it != decals.end(),
"decal " << e.name <<
" missing" );
2124 const std::vector<PADS_IO::TERMINAL>& terms = it->second.terminals;
2125 BOOST_REQUIRE_EQUAL( terms.size(), e.terms.size() );
2127 for(
size_t t = 0; t < e.terms.size(); ++t )
2151 const PADS_BINARY_BOARD_INFO board{
"MC4_PLUS_CSHAPE",
"MC4_PLUS_CSHAPE.pcb",
"MC4_PLUS_CSHAPE.asc",
false };
2153 std::shared_ptr<BOARD> brd =
LoadBinary( board );
2156 std::map<wxString, std::set<wxString>> groupMembers;
2160 std::set<wxString>& refs = groupMembers[
group->GetName()];
2165 refs.insert(
static_cast<FOOTPRINT*
>( item )->GetReference() );
2169 BOOST_REQUIRE_MESSAGE( groupMembers.count(
"CLU_DCDC5V" ),
"cluster CLU_DCDC5V missing from PCB_GROUPs" );
2170 BOOST_REQUIRE_MESSAGE( groupMembers.count(
"CLU_DCDC3V3" ),
"cluster CLU_DCDC3V3 missing from PCB_GROUPs" );
2172 const std::set<wxString> clu5v = {
"C79",
"C80",
"C81",
"C82",
"C83",
"C84",
"C85",
"C86",
2173 "D4",
"L2",
"R84",
"R85",
"R86",
"R87",
"R89",
"U10" };
2174 const std::set<wxString> clu3v3 = {
"C72",
"C73",
"C74",
"C75",
"C76",
"C77",
"C78",
"D3",
2175 "L1",
"R77",
"R78",
"R79",
"R80",
"R81",
"R83",
"U9" };
2177 BOOST_CHECK( groupMembers[
"CLU_DCDC5V"] == clu5v );
2178 BOOST_CHECK( groupMembers[
"CLU_DCDC3V3"] == clu3v3 );
2186 std::vector<uint8_t>
bytes;
2196 BOOST_REQUIRE_GT( connections->
count, 2u );
2197 BOOST_REQUIRE_GE(
text->physicalBytes, 68u );
2200 const size_t declaredRecord = connections->
physicalOffset - 36 + 2 * 68;
2201 BOOST_REQUIRE_LE( declaredRecord + 68,
bytes.size() );
2203 std::copy_n(
bytes.begin() + declaredRecord, 68,
bytes.begin() +
text->physicalOffset );
2204 bytes[declaredRecord + 23] = 0x42;
2206 wxString tempBase = wxFileName::CreateTempFileName( wxS(
"kicad_pads_connection_decoy_" ) );
2207 wxRemoveFile( tempBase );
2208 wxString tempPath = tempBase + wxS(
".pcb" );
2211 wxFFile file( tempPath, wxS(
"wb" ) );
2213 BOOST_REQUIRE_EQUAL( file.Write(
bytes.data(),
bytes.size() ),
bytes.size() );
2216 bool rejectedDeclaredRing =
false;
2221 parser.
Parse( tempPath );
2223 catch(
const std::exception& e )
2225 rejectedDeclaredRing = std::string( e.what() ).find(
"net-connection ring framing" ) != std::string::npos;
2228 wxRemoveFile( tempPath );
2229 BOOST_CHECK( rejectedDeclaredRing );
2237 std::vector<uint8_t>
bytes;
2247 const size_t finalHead = connections->
physicalOffset - 36 +
static_cast<size_t>( connections->
count ) * 68;
2248 BOOST_REQUIRE_LE( finalHead + 36,
bytes.size() );
2250 bytes[finalHead + 23] = 0x42;
2252 wxString tempBase = wxFileName::CreateTempFileName( wxS(
"kicad_pads_connection_final_" ) );
2253 wxRemoveFile( tempBase );
2254 wxString tempPath = tempBase + wxS(
".pcb" );
2257 wxFFile file( tempPath, wxS(
"wb" ) );
2259 BOOST_REQUIRE_EQUAL( file.Write(
bytes.data(),
bytes.size() ),
bytes.size() );
2262 bool rejectedFinalHead =
false;
2267 parser.
Parse( tempPath );
2269 catch(
const std::exception& e )
2271 rejectedFinalHead = std::string( e.what() ).find(
"net-connection ring framing" ) != std::string::npos;
2274 wxRemoveFile( tempPath );
2275 BOOST_CHECK( rejectedFinalHead );
2283 std::vector<uint8_t>
bytes;
2291 BOOST_REQUIRE_GT( connections->
count, 0u );
2293 const size_t firstRecord =
static_cast<size_t>( connections->
physicalOffset ) + 16;
2294 BOOST_REQUIRE_LE( firstRecord + 68,
bytes.size() );
2295 bytes[firstRecord + 39] = 0x42;
2297 wxString tempBase = wxFileName::CreateTempFileName( wxS(
"kicad_pads_connection_legacy_" ) );
2298 wxRemoveFile( tempBase );
2299 wxString tempPath = tempBase + wxS(
".pcb" );
2302 wxFFile file( tempPath, wxS(
"wb" ) );
2304 BOOST_REQUIRE_EQUAL( file.Write(
bytes.data(),
bytes.size() ),
bytes.size() );
2307 bool rejectedLegacyRing =
false;
2312 parser.
Parse( tempPath );
2314 catch(
const std::exception& e )
2316 rejectedLegacyRing = std::string( e.what() ).find(
"legacy net-connection ring framing" ) != std::string::npos;
2319 wxRemoveFile( tempPath );
2320 BOOST_CHECK( rejectedLegacyRing );
2333 std::vector<uint8_t>
bytes;
2337 BOOST_REQUIRE_NO_THROW( sdb.
Load( std::move(
bytes ) ) );
2360 BOOST_CHECK( sdb.
Section( -1 ) ==
nullptr );
2361 BOOST_CHECK( sdb.
Section( 10000 ) ==
nullptr );
2369 std::vector<uint8_t>
bytes;
2373 BOOST_REQUIRE_NO_THROW( sdb.
Load( std::move(
bytes ) ) );
2442 std::vector<uint8_t>
bytes;
2462 auto removeState = [stateOffset]( std::vector<uint8_t>& aBytes )
2464 aBytes.erase( aBytes.begin() + stateOffset, aBytes.begin() + stateOffset + 4 );
2466 uint32_t containerOffset =
static_cast<uint32_t
>( aBytes[aBytes.size() - 4] )
2467 |
static_cast<uint32_t
>( aBytes[aBytes.size() - 3] ) << 8
2468 |
static_cast<uint32_t
>( aBytes[aBytes.size() - 2] ) << 16
2469 |
static_cast<uint32_t
>( aBytes[aBytes.size() - 1] ) << 24;
2470 containerOffset -= 4;
2471 aBytes[aBytes.size() - 4] =
static_cast<uint8_t
>( containerOffset );
2472 aBytes[aBytes.size() - 3] =
static_cast<uint8_t
>( containerOffset >> 8 );
2473 aBytes[aBytes.size() - 2] =
static_cast<uint8_t
>( containerOffset >> 16 );
2474 aBytes[aBytes.size() - 1] =
static_cast<uint8_t
>( containerOffset >> 24 );
2477 std::vector<uint8_t> zeroHandle =
bytes;
2478 std::fill_n( zeroHandle.begin() + route->
physicalOffset + 4, 4, uint8_t{ 0 } );
2479 removeState( zeroHandle );
2482 BOOST_REQUIRE_NO_THROW( zeroHandleSlot.
Load( std::move( zeroHandle ) ) );
2487 std::vector<uint8_t> negativeHandle =
bytes;
2489 removeState( negativeHandle );
2492 BOOST_REQUIRE_NO_THROW( negativeHandleSlot.
Load( std::move( negativeHandle ) ) );
2503 std::vector<uint8_t>
bytes;
2513 BOOST_REQUIRE_GE(
text->physicalBytes, 64u );
2516 std::vector<uint8_t> decoy =
bytes;
2517 std::copy_n( decoy.begin() + strings->
physicalOffset, 64, decoy.begin() +
text->physicalOffset );
2520 BOOST_REQUIRE_NO_THROW( withDecoy.
Load( std::move( decoy ) ) );
2524 std::vector<uint8_t> corrupt =
bytes;
2525 const size_t extentOffset = 10 + 57 * 16 + 4;
2527 corrupt[extentOffset] =
static_cast<uint8_t
>( extent );
2528 corrupt[extentOffset + 1] =
static_cast<uint8_t
>( extent >> 8 );
2529 corrupt[extentOffset + 2] =
static_cast<uint8_t
>( extent >> 16 );
2530 corrupt[extentOffset + 3] =
static_cast<uint8_t
>( extent >> 24 );
2532 wxString tempBase = wxFileName::CreateTempFileName( wxS(
"kicad_pads_string_extent_corrupt_" ) );
2533 wxRemoveFile( tempBase );
2534 wxString tempPath = tempBase + wxS(
".pcb" );
2537 wxFFile file( tempPath, wxS(
"wb" ) );
2539 BOOST_REQUIRE_EQUAL( file.Write( corrupt.data(), corrupt.size() ), corrupt.size() );
2543 BOOST_CHECK_THROW( invalid.
Parse( tempPath ), std::exception );
2544 wxRemoveFile( tempPath );
2552 std::vector<uint8_t>
bytes;
2574 std::vector<uint8_t> decoy =
bytes;
2575 std::copy_n( decoy.begin() + owners->
physicalOffset, 112, decoy.begin() +
text->physicalOffset );
2578 BOOST_REQUIRE_NO_THROW( withDecoy.
Load( std::move( decoy ) ) );
2583 std::vector<uint8_t> corrupt =
bytes;
2584 const size_t countOffset = 10 + 10 * 16;
2585 uint32_t count = owners->
count + 1;
2586 corrupt[countOffset] =
static_cast<uint8_t
>( count );
2587 corrupt[countOffset + 1] =
static_cast<uint8_t
>( count >> 8 );
2588 corrupt[countOffset + 2] =
static_cast<uint8_t
>( count >> 16 );
2589 corrupt[countOffset + 3] =
static_cast<uint8_t
>( count >> 24 );
2592 BOOST_CHECK_THROW( invalid.
Load( std::move( corrupt ) ), std::exception );
2600 std::vector<uint8_t>
bytes;
2615 wxString tempBase = wxFileName::CreateTempFileName( wxS(
"kicad_pads_pour_decoy_" ) );
2616 wxRemoveFile( tempBase );
2617 wxString tempPath = tempBase + wxS(
".pcb" );
2620 wxFFile file( tempPath, wxS(
"wb" ) );
2622 BOOST_REQUIRE_EQUAL( file.Write(
bytes.data(),
bytes.size() ),
bytes.size() );
2626 wxRemoveFile( tempPath );
2644 std::vector<uint8_t>
bytes;
2647 const size_t totalBytesOffset = 10 + 69 * 16 + 4;
2650 uint32_t totalBytes =
static_cast<uint32_t
>(
bytes[totalBytesOffset] )
2651 |
static_cast<uint32_t
>(
bytes[totalBytesOffset + 1] ) << 8
2652 |
static_cast<uint32_t
>(
bytes[totalBytesOffset + 2] ) << 16
2653 |
static_cast<uint32_t
>(
bytes[totalBytesOffset + 3] ) << 24;
2654 BOOST_REQUIRE_GT( totalBytes, 0u );
2657 for(
size_t byte = 0;
byte <
sizeof( totalBytes ); ++byte )
2658 bytes[totalBytesOffset +
byte] =
static_cast<uint8_t
>( totalBytes >> (
byte * 8 ) );
2661 BOOST_CHECK_THROW( sdb.
Load( std::move(
bytes ) ), std::exception );
2669 std::vector<uint8_t>
bytes;
2672 const size_t countOffset = 10 + 62 * 16;
2674 std::fill_n(
bytes.begin() + countOffset,
sizeof( uint32_t ), 0 );
2676 wxString tempBase = wxFileName::CreateTempFileName( wxS(
"kicad_pads_route_count_" ) );
2677 wxRemoveFile( tempBase );
2678 wxString tempPath = tempBase + wxS(
".pcb" );
2681 wxFFile file( tempPath, wxS(
"wb" ) );
2683 BOOST_REQUIRE_EQUAL( file.Write(
bytes.data(),
bytes.size() ),
bytes.size() );
2687 BOOST_CHECK_THROW( parser.
Parse( tempPath ), std::exception );
2688 wxRemoveFile( tempPath );
2698 std::vector<std::string> copperNames;
2701 const std::vector<CASE> cases = {
2702 {
"Dexter_MotorCtrl",
2703 "Dexter_MotorCtrl.pcb",
2704 {
"Top",
"Signal Routing 1",
"Power P3_3v",
"Signal Routing 2",
"Signal Routing 3",
"Power P33v",
2705 "Signal Routing 4",
"Bottom" } },
2706 {
"LCORE_2",
"LCORE_2.pcb", {
"Top",
"Bottom" } },
2708 "MC2_PLUS_REV1.pcb",
2709 {
"Top",
"Inner Layer 1 GND",
"Inner Layer 2",
"Inner Layer 3",
"Inner Layer 4 VCC",
"Bottom" } },
2712 for(
const CASE&
test : cases )
2717 std::vector<std::string>
actual;
2721 if( layer.is_copper )
2722 actual.push_back( layer.name );
2741 const std::vector<CASE> cases = {
2742 {
"Dexter_MotorCtrl",
"Dexter_MotorCtrl.pcb", 284,
"J1",
"D5" },
2743 {
"LCORE_2",
"LCORE_2.pcb", 30,
"C4",
"FL1" },
2744 {
"MC2_PLUS_REV1",
"MC2_PLUS_REV1.pcb", 402,
"C1",
"UC128" },
2747 for(
const CASE&
test : cases )
2752 const std::vector<PADS_IO::PART>& parts = parser.
GetParts();
2753 BOOST_REQUIRE_EQUAL( parts.size(),
test.count );
2762 static constexpr char GUID[] =
"{2FE18320-6448-11d1-A412-000000000000}";
2763 static constexpr size_t GUID_LENGTH =
sizeof( GUID ) - 1;
2765 std::vector<uint8_t>
bytes( 128, 0 );
2766 const size_t footerStart =
bytes.size() - GUID_LENGTH -
sizeof( uint32_t );
2767 const uint32_t containerItemsOffset = 24;
2769 std::copy_n(
reinterpret_cast<const uint8_t*
>( GUID ), GUID_LENGTH,
2770 bytes.begin() +
static_cast<std::ptrdiff_t
>( footerStart ) );
2772 for(
size_t i = 0; i <
sizeof( containerItemsOffset ); ++i )
2773 bytes[footerStart + GUID_LENGTH + i] =
static_cast<uint8_t
>( containerItemsOffset >> ( i * 8 ) );
2784 std::vector<uint8_t> tiny( 2, 0 );
2794 std::vector<uint8_t>
bytes;
2796 BOOST_REQUIRE_GE(
bytes.size(), 42u );
2798 const uint32_t invalidOffset =
static_cast<uint32_t
>(
bytes.size() - 41 );
2800 for(
size_t byte = 0;
byte <
sizeof( invalidOffset ); ++byte )
2801 bytes[
bytes.size() -
sizeof( invalidOffset ) +
byte] =
static_cast<uint8_t
>( invalidOffset >> (
byte * 8 ) );
2804 BOOST_CHECK_THROW( sdb.
Load( std::move(
bytes ) ), std::exception );
2819 const std::vector<std::string> boards = {
2820 "Dexter_MotorCtrl/Dexter_MotorCtrl.pcb",
2821 "Ems4_Rev2/Ems4_Rev2.pcb",
2822 "LCORE_2/LCORE_2.pcb",
2823 "LCORE_4/LCORE_4.pcb",
2824 "MAIS_FC/MAIS_FC.pcb",
2825 "MC2_PLUS_REV1/MC2_PLUS_REV1.pcb",
2826 "TMS1mmX19/TMS1mmX19.pcb",
2829 for(
const std::string& board : boards )
2832 std::vector<uint8_t>
bytes;
2836 auto u32At = [&
bytes](
size_t aOffset )
2838 return static_cast<uint32_t
>(
bytes[aOffset] ) | (
static_cast<uint32_t
>(
bytes[aOffset + 1] ) << 8 )
2839 | (
static_cast<uint32_t
>(
bytes[aOffset + 2] ) << 16 )
2840 | (
static_cast<uint32_t
>(
bytes[aOffset + 3] ) << 24 );
2843 const uint32_t slots = u32At( 10 + 16 );
2844 const uint32_t dirBytes = u32At( 10 + 16 + 4 );
2845 const uint32_t viewStates = u32At( 10 + 2 * 16 );
2853 const uint32_t
expected = 10 + ( slots - 1 ) * 16 + viewStates * 48;
2856 BOOST_REQUIRE_NO_THROW( sdb.
Load( std::move(
bytes ) ) );
2869 std::vector<uint8_t>
bytes;
2878 std::fill_n(
bytes.begin() + scaleOffset,
sizeof(
float ), uint8_t{ 0 } );
2880 const std::string marker =
"DFT_CONFIGURATION";
2881 auto markerIt = std::search(
bytes.begin(),
bytes.end(), marker.begin(), marker.end() );
2884 const char config[] =
"X\0"
2888 auto configIt = markerIt + marker.size() + 1;
2890 std::copy_n(
reinterpret_cast<const uint8_t*
>(
config ),
sizeof(
config ), configIt );
2892 wxString tempBase = wxFileName::CreateTempFileName( wxS(
"kicad_pads_origin_corrupt_" ) );
2893 wxRemoveFile( tempBase );
2894 wxString tempPath = tempBase + wxS(
".pcb" );
2897 wxFFile file( tempPath, wxS(
"wb" ) );
2899 BOOST_REQUIRE_EQUAL( file.Write(
bytes.data(),
bytes.size() ),
bytes.size() );
2903 BOOST_CHECK_THROW( parser.
Parse( tempPath ), std::exception );
2904 wxRemoveFile( tempPath );
2910static std::string
FlattenPath(
const std::filesystem::path& aRoot,
const std::filesystem::path& aFile )
2912 std::filesystem::path relPath = std::filesystem::relative( aFile, aRoot );
2916 for(
const auto& part : relPath )
2921 std::string p = part.string();
2942 size_t nonThroughSeen = 0;
2946 if( board.differentRevision )
2951 std::pair<size_t, size_t> counts =
2958 for(
PCB_TRACK* item : ascii->Tracks() )
3016 auto thermalPadKeys = [](
const std::shared_ptr<BOARD>& aBoard )
3018 std::set<std::string> keys;
3020 for(
FOOTPRINT* fp : aBoard->Footprints() )
3022 for(
PAD*
pad : fp->Pads() )
3026 keys.insert( fp->GetReference().ToStdString() +
"." +
pad->GetNumber().ToStdString() );
3034 auto everyPourIsSolid = [](
const std::shared_ptr<BOARD>& aBoard )
3036 for(
ZONE* zone : aBoard->Zones() )
3038 if( !zone->GetIsRuleArea() && zone->GetNetCode() > 0 && zone->GetPadConnection() !=
ZONE_CONNECTION::FULL )
3051 std::shared_ptr<BOARD> binary =
LoadBinary( board );
3052 std::shared_ptr<BOARD> ascii =
LoadAsc( board );
3057 std::set<std::string> binKeys = thermalPadKeys( binary );
3058 std::set<std::string> ascKeys = thermalPadKeys( ascii );
3059 std::vector<std::string> extra;
3064 std::set_difference( binKeys.begin(), binKeys.end(), ascKeys.begin(), ascKeys.end(),
3065 std::back_inserter( extra ) );
3067 BOOST_CHECK_MESSAGE( extra.empty(),
"binary import invented a thermal override on "
3068 << extra.size() <<
" pad(s), first "
3069 << ( extra.empty() ? std::string(
"-" ) : extra.front() ) );
3071 BOOST_CHECK_MESSAGE( everyPourIsSolid( binary ),
3072 "binary netted pours must connect solid, leaving relief to the "
3073 "per-pad overrides" );
3074 BOOST_CHECK_MESSAGE( everyPourIsSolid( ascii ),
3075 "ASCII netted pours must connect solid, leaving relief to the "
3076 "per-pad overrides" );
3085 const wxString asciiPath = root +
"padstack-v2022.asc";
3086 const wxString v2022Path = root +
"padstack-v2022.pcb";
3087 const wxString v2027Path = root +
"padstack-v2027.pcb";
3092 ascii.
Parse( asciiPath );
3093 v2022.
Parse( v2022Path );
3094 v2027.
Parse( v2027Path );
3096 BOOST_REQUIRE_EQUAL( v2022.
GetVersion(), 0x2022 );
3097 BOOST_REQUIRE_EQUAL( v2027.
GetVersion(), 0x2027 );
3099 auto tuples = [](
const auto& aParser,
const std::set<std::string>& aDecalNames )
3101 std::vector<std::string>
result;
3103 for(
const auto& [decalName, decal] : aParser.GetPartDecals() )
3105 if( !aDecalNames.contains( decalName ) )
3108 for(
const auto& [
pin, stack] : decal.pad_stacks )
3112 std::ostringstream tuple;
3113 tuple << decalName <<
' ' <<
pin <<
' ' << layer.layer <<
' ' << layer.shape <<
' ' << std::fixed
3114 << std::setprecision( 0 ) << layer.sizeA <<
' ' << layer.sizeB <<
' '
3115 << layer.thermal_outer_diameter;
3116 result.push_back( tuple.str() );
3124 std::set<std::string> decalNames;
3127 decalNames.insert(
name );
3129 const std::vector<std::string> asciiTuples = tuples( ascii, decalNames );
3130 const std::vector<std::string> v2022Tuples = tuples( v2022, decalNames );
3131 const std::vector<std::string> v2027Tuples = tuples( v2027, decalNames );
3133 for(
const auto& [label, binaryTuples] : std::array<std::pair<
const char*,
const std::vector<std::string>*>, 2>{
3134 { {
"0x2022", &v2022Tuples }, {
"0x2027", &v2027Tuples } } } )
3138 BOOST_REQUIRE_EQUAL( binaryTuples->size(), asciiTuples.size() );
3140 asciiTuples.end() );
3144 const wxString thermalAsciiPath = root +
"padstack-thermal-v2027.asc";
3145 const wxString thermalBinaryPath = root +
"padstack-thermal-v2027.pcb";
3149 thermalAscii.
Parse( thermalAsciiPath );
3150 thermalBinary.
Parse( thermalBinaryPath );
3151 BOOST_REQUIRE_EQUAL( thermalBinary.
GetVersion(), 0x2027 );
3155 decalNames.insert(
name );
3157 const std::vector<std::string> thermalAsciiTuples = tuples( thermalAscii, decalNames );
3158 const std::vector<std::string> thermalBinaryTuples = tuples( thermalBinary, decalNames );
3160 BOOST_REQUIRE_EQUAL( thermalBinaryTuples.size(), thermalAsciiTuples.size() );
3162 thermalAsciiTuples.end() );
3174 size_t unplated = 0;
3176 auto check = [&](
const std::shared_ptr<BOARD>& aBoard,
const std::string& aLabel )
3178 for(
FOOTPRINT* footprint : aBoard->Footprints() )
3180 for(
PAD*
pad : footprint->Pads() )
3182 const std::string key =
3183 aLabel +
" " + footprint->GetReference().ToStdString() +
"." +
pad->GetNumber().ToStdString();
3188 BOOST_CHECK_MESSAGE(
pad->GetLayerSet().test(
F_Mask ) &&
pad->GetLayerSet().test(
B_Mask ),
3189 "plated hole imported tented: " << key );
3195 "unplated hole given inner copper: " << key );
3196 BOOST_CHECK_MESSAGE(
pad->GetNetCode() == 0,
"unplated hole joined a net: " << key );
3206 check(
LoadBinary( board ), board.dir +
" binary" );
3207 check(
LoadAsc( board ), board.dir +
" ASC" );
3211 BOOST_CHECK_GT( plated, 0u );
3212 BOOST_CHECK_GT( unplated, 0u );
3225 std::shared_ptr<BOARD> binary =
LoadBinaryPath( root +
"padstack-thermal-v2027.pcb",
"padstack-thermal-v2027" );
3226 std::shared_ptr<BOARD> ascii =
LoadAscPath( root +
"padstack-thermal-v2027.asc",
"padstack-thermal-v2027" );
3231 auto thermalPadKeys = [](
const std::shared_ptr<BOARD>& aBoard )
3233 std::set<std::string> keys;
3235 for(
FOOTPRINT* footprint : aBoard->Footprints() )
3237 for(
PAD*
pad : footprint->Pads() )
3240 keys.insert( footprint->GetReference().ToStdString() +
"." +
pad->GetNumber().ToStdString() );
3247 const std::set<std::string> binKeys = thermalPadKeys( binary );
3248 const std::set<std::string> ascKeys = thermalPadKeys( ascii );
3250 BOOST_REQUIRE_GT( ascKeys.size(), 0u );
3252 std::vector<std::string> missing;
3253 std::set_difference( ascKeys.begin(), ascKeys.end(), binKeys.begin(), binKeys.end(),
3254 std::back_inserter( missing ) );
3256 BOOST_CHECK_MESSAGE( missing.empty(),
"binary import lost the plane relief on "
3257 << missing.size() <<
" pad(s), first "
3258 << ( missing.empty() ? std::string(
"-" ) : missing.front() ) );
3271 dimension.
points = { { 0.0, 0.0 }, { 1000.0, 0.0 } };
3274 for(
bool basicUnits : {
false,
true } )
3309 std::shared_ptr<BOARD> board =
LoadAscPath(
path,
"synthetic_constraints_cp1252" );
3313 std::shared_ptr<NET_SETTINGS> netSettings = board->GetDesignSettings().m_NetSettings;
3317 for(
const auto& [
name, netclass] : netSettings->GetNetclasses() )
3318 BOOST_CHECK_MESSAGE( !
name.IsEmpty(),
"a net class imported under a blank name" );
3320 BOOST_CHECK_MESSAGE( netSettings->HasNetclass( wxString::FromUTF8(
"HAUTE_VITESSÉ" ) ),
3321 "the CP1252 net class name was lost" );
3322 BOOST_CHECK_MESSAGE( netSettings->HasNetclass( wxString::FromUTF8(
"DiffPair_PAIRE_DIFFÉ" ) ),
3323 "the CP1252 differential pair name was lost" );
constexpr EDA_IU_SCALE pcbIUScale
General utilities for PCB file IO for QA programs.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Information pertinent to a Pcbnew printed circuit board.
const NETINFO_LIST & GetNetInfo() const
const FOOTPRINTS & Footprints() const
const TRACKS & Tracks() const
void SetCopperLayerCount(int aCount)
const DRAWINGS & Drawings() const
constexpr BOX2< Vec > & Inflate(coord_type dx, coord_type dy)
Inflates the rectangle horizontally by dx and vertically by dy.
constexpr size_type GetWidth() const
constexpr BOX2< Vec > & Merge(const BOX2< Vec > &aRect)
Modify the position and size of the rectangle in order to contain aRect.
constexpr const Vec GetCenter() const
constexpr size_type GetHeight() const
constexpr bool Contains(const Vec &aPoint) const
A base class for most all the KiCad significant classes used in schematics and boards.
Hold an error message and may be used when throwing exceptions containing meaningful error messages.
const std::filesystem::path & Path() const
Get the path to the temporary directory as a std::filesystem::path.
const UTF8 & GetLibItemName() const
static const LSET & InternalCuMask()
Return a complete set of internal copper layers which is all Cu layers except F_Cu and B_Cu.
Handle the data for a net.
static constexpr PCB_LAYER_ID ALL_LAYERS
! The layer identifier to use for the single defintion on normal padstacks
Reader for the PADS PowerPCB binary .pcb format.
const std::map< std::string, PART_DECAL > & GetPartDecals() const
uint16_t GetVersion() const
std::set< std::string > GetPadStackShapesForTest() const
const std::vector< TEXT > & GetTexts() const
void Parse(const wxString &aFileName)
const std::vector< PART > & GetParts() const
std::vector< LAYER_INFO > GetLayerInfos() const
The parsed container of a PADS .pcb file: header, the section directory, and the coordinate system.
const SDB_COORDS & Coords() const
const SDB_SECTION * Section(int aIndex) const
size_t SectionCount() const
void Load(std::vector< uint8_t > aBytes)
Validate the header and footer, parse the section directory, and derive the coordinate origin.
static bool IsSupportedVersion(uint16_t aVersion)
const std::map< std::string, PART_DECAL > & GetPartDecals() const
void Parse(const wxString &aFileName)
int32_t DesignY(int32_t aRaw) const
uint32_t HeaderBase() const
Absolute file offset of the PCB board-setup parameter block read by PADS_SDB (the origin sits at Head...
int32_t DesignX(int32_t aRaw) const
Builds KiCad board objects from the PADS_IO model structs.
PADS_UNIT_CONVERTER & UnitConverter()
void LoadDimensions(const std::vector< PADS_IO::DIMENSION > &aDimensions)
void SetScaleFactor(double aScaleFactor)
Set the nanometers-per-file-unit factor used by the coordinate transform.
static constexpr double MILS_TO_NM
static constexpr double BASIC_TO_NM
void SetBasicUnitsMode(bool aEnabled)
Enable or disable BASIC units mode.
int GetLineThickness() const
A set of BOARD_ITEMs (i.e., without duplicates).
PCB I/O plugin for importing the native PADS Layout binary .pcb format, distinct from the ASCII forma...
bool CanReadBoard(const wxString &aFileName) const override
bool CanReadBoard(const wxString &aFileName) const override
std::unique_ptr< BOARD > LoadBoard(const wxString &aFileName, const std::map< std::string, UTF8 > *aProperties=nullptr, PROJECT *aProject=nullptr)
Load information from some input file format that this PCB_IO implementation knows about into new BOA...
PCB_LAYER_ID BottomLayer() const
void SetLayerPair(PCB_LAYER_ID aTopLayer, PCB_LAYER_ID aBottomLayer)
For a via m_layer contains the top layer, the other layer is in m_bottomLayer/.
void SetViaType(VIATYPE aViaType)
PCB_LAYER_ID TopLayer() const
Handle a list of polygons defining a copper zone.
static bool empty(const wxTextEntryBase *aCtrl)
static std::string ToStdString(const wxString &aStr)
bool IsCopperLayer(int aLayerId)
Test whether a layer is a copper layer.
PCB_LAYER_ID
A quick note on layer IDs:
std::string GetPcbnewTestDataDir()
Utility which returns a path to the data directory where the test board files are stored.
wxString ConvertText(const std::string &aText)
Decode text from a PADS file, which uses an 8-bit codepage rather than UTF-8.
wxString ConvertInvertedNetName(const std::string &aNetName)
Convert a PADS net name to KiCad notation.
bool ReadFileToBuffer(const wxString &aFileName, std::vector< uint8_t > &aOut)
Read an entire file into aOut.
void ValidateSdbFooter(const std::vector< uint8_t > &aData, size_t aFooterStart, const char *aGuid, size_t aGuidLen)
As CheckSdbFooter, throwing a bare IO_ERROR on failure.
std::string readFixedString(const std::vector< uint8_t > &aData, size_t aOffset, size_t aMaxLen)
As readFixedStringRaw, then trim trailing spaces.
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
@ NPTH
like PAD_PTH, but not plated mechanical use only, no connection allowed
@ PTH
Plated through hole pad.
Class to handle a set of BOARD_ITEMs.
A polyline point that may instead be an arc segment.
Standalone copper area from the LINES section (type=COPPER), not part of a pour.
double crossbar_pos
Y for horizontal, X for vertical.
std::vector< POINT > points
Measurement endpoints.
The PADS PowerPCB .pcb file is a serialized snapshot of PADS' in-memory SDB (System DataBase) object ...
uint32_t physicalLiveCount
std::vector< ARC_POINT > points
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
static std::vector< uint8_t > bytes(const std::string &aText)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_CHECK_EQUAL_COLLECTIONS(mixed.begin(), mixed.end(), expMixed.begin(), expMixed.end())
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
VECTOR3I expected(15, 30, 45)
static int CountEdgeCutsShapes(const BOARD *aBoard)
static size_t CountVias(const std::shared_ptr< BOARD > &aBoard)
#define BINARY_LOAD_TEST(name, idx)
#define NET_NAMES_EXACT_TEST(name, idx)
static std::shared_ptr< BOARD > LoadAsc(const PADS_BINARY_BOARD_INFO &aBoard)
#define FOOTPRINT_COUNT_TEST(name, idx)
#define STRUCTURAL_INTEGRITY_TEST(name, idx)
static std::string FlattenPath(const std::filesystem::path &aRoot, const std::filesystem::path &aFile)
static bool HasFreeText(const BOARD *aBoard, const wxString &aText)
static wxString JoinNetSet(const std::set< wxString > &aNames)
#define ROUTED_TRACKS_TEST(name, idx)
static int CountEdgeCutsArcs(const BOARD *aBoard)
static std::shared_ptr< BOARD > LoadAscPath(const wxString &aFilename, const std::string &aLabel)
BOOST_AUTO_TEST_CASE(CanonicalGeometryRowsCoverPadsTracksAndVias)
static wxString GetAscPath(const PADS_BINARY_BOARD_INFO &aBoard)
static std::shared_ptr< BOARD > LoadBinary(const PADS_BINARY_BOARD_INFO &aBoard)
static std::pair< size_t, size_t > ExactNettedTrackMatches(const wxString &aBinaryPath, const wxString &aAsciiPath, const std::string &aLabel)
static std::shared_ptr< BOARD > CachedLoad(const wxString &aFilename, bool aBinary)
Load a binary .pcb file.
static size_t DistinctPadGeometries(BOARD *aBoard)
#define FREE_TEXT_EXACT_TEST(name, idx)
Free-text extraction is solved for these boards (sec5/sec8 text-header stream with metadata lagging g...
static std::vector< std::string > CanonicalGeometryRows(const BOARD *aBoard)
#define FREE_TEXT_COUNT_TEST(name, idx)
#define NET_COUNT_TEST(name, idx)
#define PAD_COUNT_TEST(name, idx)
#define PER_FOOTPRINT_CONTENT_TEST(name, idx, maxNameWrong, maxCountWrong)
Per-footprint content correctness over the RESOLVED set.
static FOOTPRINT * FindFootprintByReference(const BOARD *aBoard, const wxString &aReference)
static std::shared_ptr< BOARD > LoadBinaryPath(const wxString &aFilename, const std::string &aLabel)
static std::pair< size_t, size_t > ExactViaMatches(const wxString &aBinaryPath, const wxString &aAsciiPath, const std::string &aLabel)
static std::pair< size_t, size_t > ExactLayeredTrackMatches(const wxString &aBinaryPath, const wxString &aAsciiPath, const std::string &aLabel)
static void CheckArcOutlineMatchesAsc(int aIdx)
static std::set< wxString > BoardNetNames(const BOARD *aBoard)
static size_t CountTraces(const std::shared_ptr< BOARD > &aBoard)
static void CheckCountWithTolerance(const std::string &aLabel, size_t aBinaryCount, size_t aAscCount, bool aDifferentRevision)
Compare counts with tolerance for binary/ASC differences.
static const PADS_BINARY_BOARD_INFO PADS_BINARY_BOARDS[]
static wxString GetBinaryPath(const PADS_BINARY_BOARD_INFO &aBoard)
static size_t CountFreeTexts(const BOARD *aBoard)
static BOX2I EdgeCutsBBox(const BOARD *aBoard)
#define ZONE_COUNT_TEST(name, idx)
static void RunStructuralChecks(const PADS_BINARY_BOARD_INFO &aBoard, const BOARD *aBinaryBoard)
Structural integrity checks.
#define WIDTH_VARIETY_TEST(name, idx)
Verify that the binary import produces multiple distinct track widths when the ASCII reference file h...
BOOST_TEST_CONTEXT("Test Clearance")
BOOST_TEST_MESSAGE("Polyline has "<< chain.PointCount()<< " points")
wxString result
Test unit parsing edge cases and error handling.
BOOST_CHECK_EQUAL(result, "25.4")
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
@ PCB_FOOTPRINT_T
class FOOTPRINT, a footprint
@ PCB_ARC_T
class PCB_ARC, an arc track segment on a copper layer
@ PCB_TRACE_T
class PCB_TRACK, a track segment (segment on a copper layer)
VECTOR2< int32_t > VECTOR2I
@ THERMAL
Use thermal relief for pads.
@ FULL
pads are covered by copper