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;
127 static const std::filesystem::path stagedRoot =
128 std::filesystem::temp_directory_path()
129 / (
"kicad_pads_binary_cached_"
130 + std::to_string( std::chrono::steady_clock::now().time_since_epoch().count() ) );
131 const std::filesystem::path source( aFilename.ToStdString() );
132 const std::filesystem::path stagedDirectory =
133 stagedRoot / std::to_string( std::hash<std::string>{}( key ) );
134 const std::filesystem::path stagedPath = stagedDirectory / source.filename();
136 std::filesystem::create_directories( stagedDirectory );
137 std::filesystem::copy_file( source, stagedPath, std::filesystem::copy_options::overwrite_existing );
138 std::filesystem::permissions( stagedPath, std::filesystem::perms::owner_write,
139 std::filesystem::perm_options::add );
142 board.reset( plugin.
LoadBoard( wxString::FromUTF8( stagedPath.string() ),
nullptr ).release() );
147 board.reset( plugin.
LoadBoard( aFilename,
nullptr ).release() );
150 catch(
const std::exception& )
168 aBoard.
dir <<
" binary should be readable by PCB_IO_PADS_BINARY" );
170 std::shared_ptr<BOARD> board;
176 catch(
const std::exception& e )
178 BOOST_FAIL( aBoard.
dir <<
" binary threw exception during load: " << e.what() );
181 BOOST_REQUIRE_MESSAGE( board !=
nullptr, aBoard.
dir <<
" binary failed to load" );
186static std::shared_ptr<BOARD>
LoadBinaryPath(
const wxString& aFilename,
const std::string& aLabel )
191 aLabel <<
" binary should be readable by PCB_IO_PADS_BINARY" );
193 std::shared_ptr<BOARD> board;
199 catch(
const std::exception& e )
201 BOOST_FAIL( aLabel <<
" binary threw exception during load: " << e.what() );
205 BOOST_REQUIRE_MESSAGE( board !=
nullptr, aLabel <<
" binary failed to load" );
215 std::shared_ptr<BOARD> board;
221 catch(
const std::exception& e )
223 BOOST_FAIL( aBoard.
dir <<
" ASC threw exception during load: " << e.what() );
227 BOOST_REQUIRE_MESSAGE( board !=
nullptr, aBoard.
dir <<
" ASC failed to load" );
232static std::shared_ptr<BOARD>
LoadAscPath(
const wxString& aFilename,
const std::string& aLabel )
234 std::shared_ptr<BOARD> board;
240 catch(
const std::exception& e )
242 BOOST_FAIL( aLabel <<
" ASC threw exception during load: " << e.what() );
246 BOOST_REQUIRE_MESSAGE( board !=
nullptr, aLabel <<
" ASC failed to load" );
252 const std::string& aLabel )
257 auto viaAnchor = [](
const BOARD* aBoard ) -> std::optional<VECTOR2I>
259 std::optional<VECTOR2I>
anchor;
261 for(
PCB_TRACK* item : aBoard->Tracks() )
266 VECTOR2I position = item->GetPosition();
277 std::optional<VECTOR2I> binaryAnchor = viaAnchor( binary.get() );
278 std::optional<VECTOR2I> asciiAnchor = viaAnchor( ascii.get() );
280 if( binaryAnchor && asciiAnchor )
281 binary->Move( *asciiAnchor - *binaryAnchor );
283 using TRACK_GEOMETRY = std::tuple<int, int, int, int, int, int>;
285 auto trackGeometry = [](
const BOARD* aBoard )
287 std::set<TRACK_GEOMETRY> geometry;
289 for(
PCB_TRACK* track : aBoard->Tracks() )
297 if( std::tie(
end.x,
end.y ) < std::tie( start.
x, start.
y ) )
298 std::swap( start,
end );
300 geometry.emplace(
static_cast<int>( track->GetLayer() ), start.
x, start.
y,
end.x,
end.y,
307 const std::set<TRACK_GEOMETRY> binaryTracks = trackGeometry( binary.get() );
308 const std::set<TRACK_GEOMETRY> asciiTracks = trackGeometry( ascii.get() );
309 std::vector<TRACK_GEOMETRY> matched;
311 std::set_intersection( binaryTracks.begin(), binaryTracks.end(), asciiTracks.begin(), asciiTracks.end(),
312 std::back_inserter( matched ) );
314 return { matched.size(), asciiTracks.size() };
319 const std::string& aLabel )
324 auto viaAnchor = [](
const BOARD* aBoard ) -> std::optional<VECTOR2I>
326 std::optional<VECTOR2I>
anchor;
328 for(
PCB_TRACK* item : aBoard->Tracks() )
333 VECTOR2I position = item->GetPosition();
342 std::optional<VECTOR2I> binaryAnchor = viaAnchor( binary.get() );
343 std::optional<VECTOR2I> asciiAnchor = viaAnchor( ascii.get() );
345 if( binaryAnchor && asciiAnchor )
346 binary->Move( *asciiAnchor - *binaryAnchor );
348 using NETTED_TRACK = std::tuple<int, int, int, int, int, int, wxString>;
350 auto tracks = [](
const BOARD* aBoard )
352 std::set<NETTED_TRACK> rows;
354 for(
PCB_TRACK* track : aBoard->Tracks() )
362 if( std::tie(
end.x,
end.y ) < std::tie( start.
x, start.
y ) )
363 std::swap( start,
end );
365 rows.emplace(
static_cast<int>( track->GetLayer() ), start.
x, start.
y,
end.x,
end.y, track->GetWidth(),
366 track->GetNetname() );
372 const std::set<NETTED_TRACK> binaryTracks = tracks( binary.get() );
373 const std::set<NETTED_TRACK> asciiTracks = tracks( ascii.get() );
374 std::vector<NETTED_TRACK> matched;
376 std::set_intersection( binaryTracks.begin(), binaryTracks.end(), asciiTracks.begin(), asciiTracks.end(),
377 std::back_inserter( matched ) );
379 return { matched.size(), asciiTracks.size() };
383static std::pair<size_t, size_t>
ExactViaMatches(
const wxString& aBinaryPath,
const wxString& aAsciiPath,
384 const std::string& aLabel )
389 auto viaAnchor = [](
const BOARD* aBoard ) -> std::optional<VECTOR2I>
391 std::optional<VECTOR2I>
anchor;
393 for(
PCB_TRACK* item : aBoard->Tracks() )
398 VECTOR2I position = item->GetPosition();
407 std::optional<VECTOR2I> binaryAnchor = viaAnchor( binary.get() );
408 std::optional<VECTOR2I> asciiAnchor = viaAnchor( ascii.get() );
410 if( binaryAnchor && asciiAnchor )
411 binary->Move( *asciiAnchor - *binaryAnchor );
413 using VIA_GEOMETRY = std::tuple<wxString, int, int, int, int, int, int, int>;
415 auto vias = [](
const BOARD* aBoard )
417 std::set<VIA_GEOMETRY> rows;
419 for(
PCB_TRACK* item : aBoard->Tracks() )
426 rows.emplace(
via->GetNetname(),
via->GetPosition().x,
via->GetPosition().y,
427 via->GetWidth(
via->TopLayer() ),
via->GetDrillValue(),
static_cast<int>(
via->GetViaType() ),
428 static_cast<int>(
via->TopLayer() ),
static_cast<int>(
via->BottomLayer() ) );
434 const std::set<VIA_GEOMETRY> binaryVias = vias( binary.get() );
435 const std::set<VIA_GEOMETRY> asciiVias = vias( ascii.get() );
436 std::vector<VIA_GEOMETRY> matched;
438 std::set_intersection( binaryVias.begin(), binaryVias.end(), asciiVias.begin(), asciiVias.end(),
439 std::back_inserter( matched ) );
441 return { matched.size(), asciiVias.size() };
462static size_t CountVias(
const std::shared_ptr<BOARD>& aBoard )
466 for(
PCB_TRACK* item : aBoard->Tracks() )
480 for(
PCB_TRACK* item : aBoard->Tracks() )
494 if( fp->GetReference() == aReference )
504 std::set<wxString> names;
508 if( net && !net->GetNetname().IsEmpty() )
509 names.insert( net->GetNetname() );
516static wxString
JoinNetSet(
const std::set<wxString>& aNames )
520 for(
const wxString&
name : aNames )
522 if( !joined.IsEmpty() )
523 joined += wxString(
"," );
534 std::vector<std::string> rows;
538 for(
PAD*
pad : fp->Pads() )
541 std::ostringstream row;
543 row <<
"PAD " << std::quoted( fp->GetReference().ToStdString() ) <<
" "
544 << std::quoted(
pad->GetNumber().ToStdString() ) <<
" " <<
pad->GetPosition().x <<
" "
545 <<
pad->GetPosition().y <<
" " <<
pad->GetOrientation().AsTenthsOfADegree() <<
" "
546 <<
static_cast<int>(
pad->GetAttribute() ) <<
" " << drill.
x <<
" " << drill.
y <<
" "
547 <<
static_cast<int>(
pad->GetDrillShape() ) <<
" " << std::quoted(
pad->GetLayerSet().FmtHex() ) <<
" "
548 << std::quoted(
pad->GetNetname().ToStdString() );
555 row <<
" " <<
static_cast<int>( layer ) <<
":" <<
static_cast<int>(
pad->GetShape( layer ) ) <<
":"
556 << size.
x <<
":" << size.
y <<
":" << offset.
x <<
":" << offset.
y <<
":"
557 <<
pad->GetRoundRectCornerRadius( layer ) <<
":" <<
pad->GetChamferPositions( layer );
560 rows.push_back( row.str() );
566 std::ostringstream row;
570 row <<
"VIA " << std::quoted(
via->GetNetname().ToStdString() ) <<
" " <<
via->GetPosition().x <<
" "
571 <<
via->GetPosition().y <<
" " <<
via->GetWidth(
via->TopLayer() ) <<
" " <<
via->GetDrillValue() <<
" "
572 <<
static_cast<int>(
via->GetViaType() ) <<
" " <<
static_cast<int>(
via->TopLayer() ) <<
" "
573 <<
static_cast<int>(
via->BottomLayer() );
577 row <<
"ARC " << std::quoted( arc->GetNetname().ToStdString() ) <<
" "
578 <<
static_cast<int>( arc->GetLayer() ) <<
" " << arc->GetStart().x <<
" " << arc->GetStart().y <<
" "
579 << arc->GetMid().x <<
" " << arc->GetMid().y <<
" " << arc->GetEnd().x <<
" " << arc->GetEnd().y <<
" "
587 if( std::tie(
end.x,
end.y ) < std::tie( start.
x, start.
y ) )
588 std::swap( start,
end );
590 row <<
"TRACK " << std::quoted( track->GetNetname().ToStdString() ) <<
" "
591 <<
static_cast<int>( track->GetLayer() ) <<
" " << start.
x <<
" " << start.
y <<
" " <<
end.x <<
" "
592 <<
end.y <<
" " << track->GetWidth();
595 rows.push_back( row.str() );
598 std::sort( rows.begin(), rows.end() );
609 bool aDifferentRevision )
611 if( aDifferentRevision )
613 BOOST_WARN_MESSAGE( aBinaryCount == aAscCount,
614 aLabel <<
" binary=" << aBinaryCount <<
" asc=" << aAscCount <<
" (different revision)" );
618 if( aBinaryCount == aAscCount )
620 BOOST_CHECK_MESSAGE(
true, aLabel <<
" counts match: " << aBinaryCount );
624 size_t maxCount = std::max( aBinaryCount, aAscCount );
625 size_t diff = ( aBinaryCount > aAscCount ) ? aBinaryCount - aAscCount : aAscCount - aBinaryCount;
627 bool withinTolerance = ( diff <= 2 ) || ( diff * 100 / maxCount <= 5 );
629 BOOST_CHECK_MESSAGE( withinTolerance,
630 aLabel <<
" counts differ beyond tolerance: binary=" << aBinaryCount <<
" asc=" << aAscCount );
632 BOOST_WARN_MESSAGE( aBinaryCount == aAscCount,
633 aLabel <<
" exact count mismatch: binary=" << aBinaryCount <<
" asc=" << aAscCount );
643 BOOST_WARN_MESSAGE( aBinaryBoard->
Tracks().size() > 0, aBoard.
dir <<
" binary has no tracks" );
645 std::set<std::pair<int, int>> viaPositions;
646 bool hasDuplicate =
false;
655 auto key = std::make_pair(
via->GetPosition().x,
via->GetPosition().y );
657 if( viaPositions.count( key ) )
663 viaPositions.insert( key );
666 BOOST_CHECK_MESSAGE( !hasDuplicate, aBoard.
dir <<
" binary should have no duplicate through-hole vias" );
673 aBoard.
dir <<
" binary track on non-copper layer " << trk->GetLayer() );
679 for(
PAD*
pad : fp->Pads() )
683 aBoard.
dir <<
" " << fp->GetReference() <<
" pad has zero size" );
703 for(
const std::string& row : rows )
705 pads += row.rfind(
"PAD ", 0 ) == 0;
706 tracks += row.rfind(
"TRACK ", 0 ) == 0 || row.rfind(
"ARC ", 0 ) == 0;
707 vias += row.rfind(
"VIA ", 0 ) == 0;
710 BOOST_CHECK_GT( pads, 0u );
711 BOOST_CHECK_GT( tracks, 0u );
712 BOOST_CHECK_GT( vias, 0u );
720 std::vector<uint8_t> bytes;
731 auto writeRingU32 = [&](
size_t aLogicalOffset, uint32_t aValue )
733 for(
size_t byte = 0;
byte < 4; ++byte )
736 bytes.at( physical ) =
static_cast<uint8_t
>( aValue >> (
byte * 8 ) );
741 const size_t boundLoOffset = objects->
stride == 36 ? 12 : 24;
742 const size_t boundHiOffset = objects->
stride == 36 ? 16 : 28;
746 const size_t base = 32 +
static_cast<size_t>(
index ) * objects->
stride;
747 writeRingU32( base + boundLoOffset, 0x12345678 );
748 writeRingU32( base + boundHiOffset, 0x76543210 );
751 wxString tempBase = wxFileName::CreateTempFileName( wxS(
"kicad_pads_route_bounds_" ) );
752 wxRemoveFile( tempBase );
753 wxString tempPath = tempBase + wxS(
".pcb" );
756 wxFFile file( tempPath, wxS(
"wb" ) );
758 BOOST_REQUIRE_EQUAL( file.Write( bytes.data(), bytes.size() ), bytes.size() );
761 using ROUTE_ROW = std::tuple<std::string, int, double, std::vector<std::pair<double, double>>>;
764 std::vector<ROUTE_ROW> rows;
770 std::vector<std::pair<double, double>> points;
773 points.emplace_back( point.
x, point.
y );
775 rows.emplace_back( route.net_name, track.
layer, track.
width, std::move( points ) );
784 baseline.
Parse( source );
785 changedBounds.
Parse( tempPath );
786 wxRemoveFile( tempPath );
788 BOOST_CHECK( routeRows( changedBounds ) == routeRows( baseline ) );
801 BOOST_CHECK_MESSAGE( binaryPlugin.
CanReadBoard( binaryPath ),
802 board.dir <<
" binary should be recognized by PCB_IO_PADS_BINARY" );
804 BOOST_CHECK_MESSAGE( !ascPlugin.
CanReadBoard( binaryPath ),
805 board.dir <<
" binary should NOT be recognized by PCB_IO_PADS" );
818 BOOST_CHECK_MESSAGE( !binaryPlugin.
CanReadBoard( ascPath ),
819 board.dir <<
" ASCII should NOT be recognized by PCB_IO_PADS_BINARY" );
824#define BINARY_LOAD_TEST( name, idx ) \
825 BOOST_AUTO_TEST_CASE( BasicLoad_##name ) \
827 auto board = LoadBinary( PADS_BINARY_BOARDS[idx] ); \
829 BOOST_CHECK( board->Footprints().size() > 0 ); \
842#define FOOTPRINT_COUNT_TEST( name, idx ) \
843 BOOST_AUTO_TEST_CASE( FootprintCount_##name ) \
845 auto bin = LoadBinary( PADS_BINARY_BOARDS[idx] ); \
847 auto asc = LoadAsc( PADS_BINARY_BOARDS[idx] ); \
849 CheckCountWithTolerance( #name " footprints", bin->Footprints().size(), asc->Footprints().size(), \
850 PADS_BINARY_BOARDS[idx].differentRevision ); \
863#define NET_COUNT_TEST( name, idx ) \
864 BOOST_AUTO_TEST_CASE( NetCount_##name ) \
866 auto bin = LoadBinary( PADS_BINARY_BOARDS[idx] ); \
868 auto asc = LoadAsc( PADS_BINARY_BOARDS[idx] ); \
870 CheckCountWithTolerance( #name " nets", bin->GetNetCount(), asc->GetNetCount(), \
871 PADS_BINARY_BOARDS[idx].differentRevision ); \
884#define NET_NAMES_EXACT_TEST( name, idx ) \
885 BOOST_AUTO_TEST_CASE( NetNamesExact_##name ) \
887 auto bin = LoadBinary( PADS_BINARY_BOARDS[idx] ); \
889 auto binNames = BoardNetNames( bin.get() ); \
890 auto asc = LoadAsc( PADS_BINARY_BOARDS[idx] ); \
891 auto ascNames = BoardNetNames( asc.get() ); \
893 std::set<wxString> missing; \
894 std::set_difference( ascNames.begin(), ascNames.end(), binNames.begin(), binNames.end(), \
895 std::inserter( missing, missing.begin() ) ); \
897 std::set<wxString> extra; \
898 std::set_difference( binNames.begin(), binNames.end(), ascNames.begin(), ascNames.end(), \
899 std::inserter( extra, extra.begin() ) ); \
901 BOOST_CHECK_MESSAGE( missing.empty(), #name " missing binary nets: " << JoinNetSet( missing ) ); \
902 BOOST_CHECK_MESSAGE( extra.empty(), #name " extra binary nets: " << JoinNetSet( extra ) ); \
912#define ROUTED_TRACKS_TEST( name, idx ) \
913 BOOST_AUTO_TEST_CASE( RoutedTracks_##name ) \
915 auto bin = LoadBinary( PADS_BINARY_BOARDS[idx] ); \
917 BOOST_CHECK_GT( CountTraces( bin ), 0u ); \
951 std::set<std::tuple<int, int, int>> geoms;
955 for(
PAD*
pad : fp->Pads() )
958 geoms.emplace(
static_cast<int>(
pad->GetShape(
F_Cu ) ), sz.
x, sz.
y );
975 BOOST_CHECK_GE( binGeoms, 5u );
984 BOOST_CHECK_GE( binGeoms, ascGeoms / 2 );
1024 BOOST_REQUIRE_GE(
CountVias( asc ), 443u );
1025 BOOST_CHECK_GE(
CountVias( bin ), 443u );
1034 BOOST_CHECK_MESSAGE(
CountEdgeCutsShapes( board.get() ) > 0,
"LCORE_4 binary should have board outline shapes" );
1042 BOOST_CHECK_MESSAGE(
CountEdgeCutsShapes( board.get() ) > 0,
"LCORE_2 binary should have board outline shapes" );
1048 int indices[] = { 0, 1, 2, 3, 6, 7 };
1050 for(
int i : indices )
1073 bbox.
Merge( shape->GetBoundingBox() );
1111 BOOST_CHECK_MESSAGE( binArcs == ascArcs,
name <<
" edge-cut arc count: binary=" << binArcs <<
" asc=" << ascArcs );
1116 BOOST_REQUIRE_MESSAGE( binBox.
GetWidth() > 0 && ascBox.
GetWidth() > 0,
name <<
" edge-cut bounding box is empty" );
1122 double tolX = std::max( 400000.0, ascBox.
GetWidth() * 0.01 );
1123 double tolY = std::max( 400000.0, ascBox.
GetHeight() * 0.01 );
1159 if(
info.differentRevision )
1173 info.dir <<
" ships a binary outline but the ASC has none "
1174 "to validate against" );
1176 double tolX = std::max( 400000.0, ascBox.
GetWidth() * 0.02 );
1177 double tolY = std::max( 400000.0, ascBox.
GetHeight() * 0.02 );
1180 info.dir <<
" board outline width: binary=" << binBox.
GetWidth()
1181 <<
" asc=" << ascBox.
GetWidth() <<
" (wrong outline shipped)" );
1183 info.dir <<
" board outline height: binary=" << binBox.
GetHeight()
1184 <<
" asc=" << ascBox.
GetHeight() <<
" (wrong outline shipped)" );
1192 for(
FOOTPRINT* fp : bin->Footprints() )
1193 fpBox.
Merge( fp->GetPosition() );
1197 BOX2I grown = binBox;
1202 <<
" does not enclose the footprint cloud centered at " << fpBox.
GetCenter()
1203 <<
" (outline mispositioned)" );
1209#define STRUCTURAL_INTEGRITY_TEST( name, idx ) \
1210 BOOST_AUTO_TEST_CASE( StructuralIntegrity_##name ) \
1212 auto board = LoadBinary( PADS_BINARY_BOARDS[idx] ); \
1214 RunStructuralChecks( PADS_BINARY_BOARDS[idx], board.get() ); \
1217#define ZONE_COUNT_TEST( name, idx ) \
1218 BOOST_AUTO_TEST_CASE( ZoneCount_##name ) \
1220 auto bin = LoadBinary( PADS_BINARY_BOARDS[idx] ); \
1222 auto asc = LoadAsc( PADS_BINARY_BOARDS[idx] ); \
1224 size_t binZones = bin->Zones().size(); \
1225 size_t ascZones = asc->Zones().size(); \
1227 if( PADS_BINARY_BOARDS[idx].differentRevision ) \
1229 BOOST_WARN_MESSAGE( binZones > 0, #name " binary zone count: " << binZones << " (different revision)" ); \
1233 BOOST_WARN_MESSAGE( binZones == ascZones, \
1234 #name " zone count: binary=" << binZones << " asc=" << ascZones ); \
1260 BOOST_CHECK_EQUAL( std::count_if( bin->Drawings().begin(), bin->Drawings().end(),
1263 const PCB_SHAPE* shape = dynamic_cast<const PCB_SHAPE*>( aItem );
1264 return shape && shape->GetShape() == SHAPE_T::POLY && shape->IsSolidFill();
1279 BOOST_CHECK_EQUAL( std::count_if( bin->Drawings().begin(), bin->Drawings().end(),
1282 const PCB_SHAPE* shape = dynamic_cast<const PCB_SHAPE*>( aItem );
1283 return shape && shape->GetShape() == SHAPE_T::POLY && shape->IsSolidFill();
1291 struct EXPECTED_LEVEL
1298 const std::vector<EXPECTED_LEVEL>
expected = {
1299 { 1,
"DRW68014421", 4 }, { 1,
"DRW62739568", 26 }, { 3,
"DRW55434270", 1 },
1300 { 3,
"DRW28145516", 22 }, { 3,
"DRW81324514", 24 },
1303 std::map<size_t, std::shared_ptr<PADS_IO::BINARY_PARSER>> parsers;
1305 for(
const EXPECTED_LEVEL& item :
expected )
1307 auto& parser = parsers[item.board];
1311 parser = std::make_shared<PADS_IO::BINARY_PARSER>();
1314 parser->Parse( filename );
1317 auto shape = std::find_if( parser->GetCopperShapes().begin(), parser->GetCopperShapes().end(),
1320 return aShape.name == item.name;
1353 if(
dynamic_cast<PCB_TEXT*
>( item ) )
1367 if(
text &&
text->GetText() == aText )
1375#define FREE_TEXT_COUNT_TEST( name, idx ) \
1376 BOOST_AUTO_TEST_CASE( FreeTextCount_##name ) \
1378 auto bin = LoadBinary( PADS_BINARY_BOARDS[idx] ); \
1380 auto asc = LoadAsc( PADS_BINARY_BOARDS[idx] ); \
1382 size_t binTexts = CountFreeTexts( bin.get() ); \
1383 size_t ascTexts = CountFreeTexts( asc.get() ); \
1385 BOOST_WARN_MESSAGE( binTexts == ascTexts, \
1386 #name " free text count: binary=" << binTexts << " asc=" << ascTexts \
1387 << " (text extraction incomplete)" ); \
1406#define FREE_TEXT_EXACT_TEST( name, idx ) \
1407 BOOST_AUTO_TEST_CASE( FreeTextExact_##name ) \
1409 auto bin = LoadBinary( PADS_BINARY_BOARDS[idx] ); \
1411 auto asc = LoadAsc( PADS_BINARY_BOARDS[idx] ); \
1413 BOOST_CHECK_EQUAL( CountFreeTexts( bin.get() ), CountFreeTexts( asc.get() ) ); \
1426 BOOST_CHECK_MESSAGE( !
HasFreeText( bin.get(),
"CLU_DCDC5V" ),
1427 "MC4 binary cluster name CLU_DCDC5V should not import as free text" );
1428 BOOST_CHECK_MESSAGE( !
HasFreeText( bin.get(),
"CLU_DCDC3V3" ),
1429 "MC4 binary cluster name CLU_DCDC3V3 should not import as free text" );
1437 std::vector<uint8_t> bytes;
1450 std::copy_n( bytes.begin() + ringBase, 144, bytes.begin() + decoyBase );
1453 baseline.
Parse( source );
1455 wxString tempBase = wxFileName::CreateTempFileName( wxS(
"kicad_pads_text_decoy_" ) );
1456 wxRemoveFile( tempBase );
1457 wxString tempPath = tempBase + wxS(
".pcb" );
1460 wxFFile file( tempPath, wxS(
"wb" ) );
1462 BOOST_REQUIRE_EQUAL( file.Write( bytes.data(), bytes.size() ), bytes.size() );
1466 withDecoy.
Parse( tempPath );
1467 wxRemoveFile( tempPath );
1473#define PAD_COUNT_TEST( name, idx ) \
1474 BOOST_AUTO_TEST_CASE( PadCount_##name ) \
1476 auto bin = LoadBinary( PADS_BINARY_BOARDS[idx] ); \
1478 auto asc = LoadAsc( PADS_BINARY_BOARDS[idx] ); \
1480 size_t binPads = 0; \
1481 size_t ascPads = 0; \
1483 for( FOOTPRINT * fp : bin->Footprints() ) \
1484 binPads += fp->Pads().size(); \
1486 for( FOOTPRINT * fp : asc->Footprints() ) \
1487 ascPads += fp->Pads().size(); \
1489 BOOST_WARN_MESSAGE( binPads == ascPads, \
1490 #name " pad count: binary=" << binPads << " asc=" << ascPads \
1491 << " (part-to-decal linking incomplete)" ); \
1505#define PER_FOOTPRINT_CONTENT_TEST( name, idx, maxNameWrong, maxCountWrong ) \
1506 BOOST_AUTO_TEST_CASE( PerFootprintContent_##name ) \
1508 auto bin = LoadBinary( PADS_BINARY_BOARDS[idx] ); \
1510 auto asc = LoadAsc( PADS_BINARY_BOARDS[idx] ); \
1512 std::map<wxString, FOOTPRINT*> ascByRef; \
1514 for( FOOTPRINT * fp : asc->Footprints() ) \
1515 ascByRef[fp->GetReference()] = fp; \
1517 int resolved = 0, nameWrong = 0, countWrong = 0; \
1518 wxString wrongRefs; \
1520 for( FOOTPRINT * fp : bin->Footprints() ) \
1522 wxString binDecal = fp->GetFPID().GetLibItemName().wx_str(); \
1524 if( binDecal == fp->GetReference() ) \
1527 auto it = ascByRef.find( fp->GetReference() ); \
1529 if( it == ascByRef.end() ) \
1534 if( binDecal != it->second->GetFPID().GetLibItemName().wx_str() ) \
1537 wrongRefs += fp->GetReference() \
1538 + wxString::Format( ":name %s/%s %zu/%zu ", binDecal, \
1539 it->second->GetFPID().GetLibItemName().wx_str(), fp->Pads().size(), \
1540 it->second->Pads().size() ); \
1542 else if( fp->Pads().size() != it->second->Pads().size() ) \
1545 wrongRefs += fp->GetReference() \
1546 + wxString::Format( ":%zu/%zu ", fp->Pads().size(), it->second->Pads().size() ); \
1550 BOOST_TEST_MESSAGE( #name " per-footprint: resolved=" << resolved << " nameWrong=" << nameWrong \
1551 << " countWrong=" << countWrong \
1552 << " wrongRefs=" << wrongRefs ); \
1553 BOOST_CHECK_LE( nameWrong, maxNameWrong ); \
1554 BOOST_CHECK_LE( countWrong, maxCountWrong ); \
1569 BOOST_REQUIRE_MESSAGE( sm3 !=
nullptr,
"TMS1mmX19 binary should import section-boundary placement SM3" );
1592 for(
FOOTPRINT* fp : bin->Footprints() )
1594 binPads += fp->Pads().size();
1596 if( fp->GetFPID().GetLibItemName().wx_str() != fp->GetReference() )
1600 BOOST_TEST_MESSAGE(
"MAIS_FC v0x2021: pads=" << binPads <<
" resolved=" << resolved );
1610 std::map<wxString, std::pair<wxString, size_t>>
expected = {
1611 {
"J7", {
"54722-0201", 20 } }, {
"J1", {
"SOLDERLAND4", 11 } }, {
"J2", {
"CON3", 3 } },
1612 {
"J3", {
"CON3", 3 } }, {
"J4", {
"CON6", 6 } },
1617 for(
FOOTPRINT* fp : bin->Footprints() )
1619 auto it =
expected.find( fp->GetReference() );
1640 for(
FOOTPRINT* fp : bin->Footprints() )
1642 binPads += fp->Pads().size();
1644 if( fp->GetFPID().GetLibItemName().wx_str() != fp->GetReference() )
1648 BOOST_TEST_MESSAGE(
"Dexter_MotorCtrl v0x2021: pads=" << binPads <<
" resolved=" << resolved );
1654 BOOST_CHECK_GT( binPads, 800u );
1655 BOOST_CHECK_GT( resolved, 200 );
1660 std::map<wxString, std::pair<wxString, size_t>>
expected = {
1661 {
"D1", {
"DFN-6-9", 9 } },
1662 {
"L1", {
"IHLP-2525", 2 } },
1667 for(
FOOTPRINT* fp : bin->Footprints() )
1669 auto it =
expected.find( fp->GetReference() );
1707 const std::vector<Oracle> oracle = {
1708 {
"J1", 10800000, 0.0,
false }, {
"J2", 23663130, 180.0,
false }, {
"J3", 8785902, 0.0,
false },
1709 {
"J4", 21216083, 90.0,
false }, {
"J7", 9000000, 270.0,
true },
1712 std::map<wxString, FOOTPRINT*> binFps;
1714 for(
FOOTPRINT* fp : bin->Footprints() )
1715 binFps[fp->GetReference()] = fp;
1718 std::set<int> binYs;
1720 for(
const Oracle& o : oracle )
1722 BOOST_REQUIRE_MESSAGE( binFps.count( o.ref ),
"v0x2021 missing footprint " << o.ref );
1723 binYs.insert( binFps[o.ref]->GetPosition().y );
1726 BOOST_CHECK_MESSAGE( binYs.size() == oracle.size(),
"v0x2021 part Y collapsed to "
1727 << binYs.size() <<
" of " << oracle.size()
1728 <<
" distinct values (Y decode regression)" );
1731 for(
const Oracle& o : oracle )
1735 BOOST_CHECK_MESSAGE( fp->
IsFlipped() == o.flipped,
1736 "v0x2021 " << o.ref <<
" side " << fp->
IsFlipped() <<
" != " << o.flipped );
1742 double diff =
std::abs( deg - o.ori );
1744 BOOST_CHECK_MESSAGE( diff < 0.1 ||
std::abs( diff - 360.0 ) < 0.1,
1745 "v0x2021 " << o.ref <<
" orientation " << deg <<
" != " << o.ori );
1750 std::vector<Oracle> byAscY( oracle );
1751 std::sort( byAscY.begin(), byAscY.end(),
1752 [](
const Oracle& a,
const Oracle& b )
1754 return a.ascY < b.ascY;
1760 for(
size_t i = 1; i < byAscY.size(); ++i )
1762 int prev = binFps[byAscY[i - 1].ref]->GetPosition().y;
1763 int cur = binFps[byAscY[i].ref]->GetPosition().y;
1772 BOOST_CHECK_MESSAGE( incr || decr,
"v0x2021 placed-Y ordering does not match ASC oracle" );
1793 for(
FOOTPRINT* fp : bin->Footprints() )
1794 binPads += fp->Pads().size();
1796 for(
FOOTPRINT* fp : asc->Footprints() )
1797 ascPads += fp->Pads().size();
1810 for(
FOOTPRINT* fp : bin->Footprints() )
1811 binPads += fp->Pads().size();
1813 for(
FOOTPRINT* fp : asc->Footprints() )
1814 ascPads += fp->Pads().size();
1827 for(
FOOTPRINT* fp : bin->Footprints() )
1828 binPads += fp->Pads().size();
1830 for(
FOOTPRINT* fp : asc->Footprints() )
1831 ascPads += fp->Pads().size();
1852#define WIDTH_VARIETY_TEST( name, idx ) \
1853 BOOST_AUTO_TEST_CASE( TrackWidthVariety_##name ) \
1855 auto bin = LoadBinary( PADS_BINARY_BOARDS[idx] ); \
1857 auto asc = LoadAsc( PADS_BINARY_BOARDS[idx] ); \
1859 std::set<int> binWidths; \
1860 std::set<int> ascWidths; \
1862 for( PCB_TRACK * trk : bin->Tracks() ) \
1864 if( trk->Type() == PCB_TRACE_T ) \
1865 binWidths.insert( trk->GetWidth() ); \
1868 for( PCB_TRACK * trk : asc->Tracks() ) \
1870 if( trk->Type() == PCB_TRACE_T ) \
1871 ascWidths.insert( trk->GetWidth() ); \
1874 if( ascWidths.size() > 1 ) \
1876 BOOST_WARN_MESSAGE( binWidths.size() > 1, #name " binary should have multiple distinct track widths " \
1878 << binWidths.size() << ", ASCII has " \
1879 << ascWidths.size() << ")" ); \
1910 struct EXPECTED_OWNER
1912 std::string boardDir;
1913 std::string binaryFile;
1915 std::vector<std::pair<int, int>> firstVerts;
1918 const std::vector<EXPECTED_OWNER> cases = {
1919 {
"MC4_PLUS_CSHAPE",
1920 "MC4_PLUS_CSHAPE.pcb",
1922 { { 0, 0 }, { 0, 5486400 }, { 9982200, 5486400 }, { 10210800, 5257800 } } },
1923 {
"MC4_PLUS_CSHAPE",
1924 "MC4_PLUS_CSHAPE.pcb",
1926 { { 0, 0 }, { 0, 9784905 }, { -452970, 8882805 }, { -1905015, -2943150 } } },
1930 { { 0, -571500 }, { 0, 1333500 }, { 1714500, 1333500 }, { 1714500, 6096000 } } },
1934 { { 0, 0 }, { 0, -2095500 }, { 2857500, -2095500 }, { 3048000, -2286000 } } },
1938 { { 803078, -1462546 }, { -1259549, -2233786 }, { -295519, -4678038 }, { 1767106, -3906796 } } },
1941 std::map<std::string, std::shared_ptr<PADS_IO::BINARY_PARSER>> parsers;
1943 for(
const EXPECTED_OWNER& ec : cases )
1947 std::shared_ptr<PADS_IO::BINARY_PARSER>& parser = parsers[ec.boardDir];
1951 parser = std::make_shared<PADS_IO::BINARY_PARSER>();
1954 parser->Parse( filename );
1957 std::vector<VECTOR2I> loop;
1959 BOOST_REQUIRE_MESSAGE( parser->GetOwnerLoopForTest( ec.owner, loop ),
1960 ec.owner <<
" has no structural sec12 loop" );
1962 BOOST_REQUIRE_GE( loop.size(), ec.firstVerts.size() );
1964 for(
size_t i = 0; i < ec.firstVerts.size(); ++i )
1980 std::vector<uint8_t> field = {
'R', 0xE9,
's', 0 };
1984 BOOST_REQUIRE_EQUAL( raw.size(), 3u );
1985 BOOST_CHECK_EQUAL(
static_cast<unsigned>(
static_cast<uint8_t
>( raw[1] ) ), 0xE9u );
1991 wxString::FromUTF8(
"~{R\xC3\xA9s}" ) );
1999 std::vector<uint8_t> control = {
'A', 0x01,
'B', 0 };
2003 std::vector<uint8_t> padded = {
' ',
' ',
'N',
'E',
'T',
' ',
' ', 0 };
2015 const size_t sec14CountOffset = 10 + 14 * 16;
2021 std::vector<char> bytes( ( std::istreambuf_iterator<char>( in ) ), std::istreambuf_iterator<char>() );
2025 BOOST_REQUIRE_GT( bytes.size(), sec14CountOffset + 4 );
2027 bytes[sec14CountOffset + 0] =
static_cast<char>( 0xFF );
2028 bytes[sec14CountOffset + 1] =
static_cast<char>( 0xFF );
2029 bytes[sec14CountOffset + 2] =
static_cast<char>( 0xFF );
2030 bytes[sec14CountOffset + 3] =
static_cast<char>( 0x0F );
2032 std::filesystem::path mutated = std::filesystem::temp_directory_path()
2033 /
"kicad_pads_sec14_oversized_count.pcb";
2036 std::ofstream out( mutated, std::ios::binary );
2038 out.write( bytes.data(),
static_cast<std::streamsize
>( bytes.size() ) );
2043 BOOST_CHECK_EXCEPTION( parser.
Parse( wxString::FromUTF8( mutated.string() ) ),
IO_ERROR,
2046 return aError.What().Contains( wxT(
"decal-name table extent" ) );
2049 std::filesystem::remove( mutated );
2064 const std::vector<std::string> boardsWithOblong = {
"TMS1mmX19",
"MC4_PLUS_CSHAPE" };
2066 for(
const std::string& dir : boardsWithOblong )
2073 parser.
Parse( filename );
2077 BOOST_CHECK_MESSAGE( shapes.count(
"OF" ),
2078 dir <<
" decoded no OF padstack (shape-enum off-by-one regression)" );
2082 BOOST_CHECK_MESSAGE( shapes.count(
"R" ), dir <<
" decoded no round padstack" );
2083 BOOST_CHECK_MESSAGE( shapes.count(
"RF" ), dir <<
" decoded no RF padstack" );
2104 parser.
Parse( filename );
2106 const std::map<std::string, PADS_IO::PART_DECAL>& decals = parser.
GetPartDecals();
2111 std::vector<std::pair<double, double>> terms;
2114 const std::vector<Expected> cases = {
2115 {
"0402", { { -750000, 0 }, { 750000, 0 } } }, {
"0603", { { -945000, 0 }, { 945000, 0 } } },
2116 {
"0805", { { -1200000, 0 }, { 1200000, 0 } } }, {
"1206", { { -1800000, 0 }, { 1800000, 0 } } },
2117 {
"CC3", { { 0, -571500 }, { 0, 0 }, { 0, 571500 } } },
2120 for(
const Expected& e : cases )
2124 auto it = decals.find( e.name );
2125 BOOST_REQUIRE_MESSAGE( it != decals.end(),
"decal " << e.name <<
" missing" );
2127 const std::vector<PADS_IO::TERMINAL>& terms = it->second.terminals;
2128 BOOST_REQUIRE_EQUAL( terms.size(), e.terms.size() );
2130 for(
size_t t = 0; t < e.terms.size(); ++t )
2154 const PADS_BINARY_BOARD_INFO board{
"MC4_PLUS_CSHAPE",
"MC4_PLUS_CSHAPE.pcb",
"MC4_PLUS_CSHAPE.asc",
false };
2156 std::shared_ptr<BOARD> brd =
LoadBinary( board );
2159 std::map<wxString, std::set<wxString>> groupMembers;
2163 std::set<wxString>& refs = groupMembers[
group->GetName()];
2168 refs.insert(
static_cast<FOOTPRINT*
>( item )->GetReference() );
2172 BOOST_REQUIRE_MESSAGE( groupMembers.count(
"CLU_DCDC5V" ),
"cluster CLU_DCDC5V missing from PCB_GROUPs" );
2173 BOOST_REQUIRE_MESSAGE( groupMembers.count(
"CLU_DCDC3V3" ),
"cluster CLU_DCDC3V3 missing from PCB_GROUPs" );
2175 const std::set<wxString> clu5v = {
"C79",
"C80",
"C81",
"C82",
"C83",
"C84",
"C85",
"C86",
2176 "D4",
"L2",
"R84",
"R85",
"R86",
"R87",
"R89",
"U10" };
2177 const std::set<wxString> clu3v3 = {
"C72",
"C73",
"C74",
"C75",
"C76",
"C77",
"C78",
"D3",
2178 "L1",
"R77",
"R78",
"R79",
"R80",
"R81",
"R83",
"U9" };
2180 BOOST_CHECK( groupMembers[
"CLU_DCDC5V"] == clu5v );
2181 BOOST_CHECK( groupMembers[
"CLU_DCDC3V3"] == clu3v3 );
2189 std::vector<uint8_t> bytes;
2199 BOOST_REQUIRE_GT( connections->
count, 2u );
2200 BOOST_REQUIRE_GE(
text->physicalBytes, 68u );
2203 const size_t declaredRecord = connections->
physicalOffset - 36 + 2 * 68;
2204 BOOST_REQUIRE_LE( declaredRecord + 68, bytes.size() );
2206 std::copy_n( bytes.begin() + declaredRecord, 68, bytes.begin() +
text->physicalOffset );
2207 bytes[declaredRecord + 23] = 0x42;
2209 wxString tempBase = wxFileName::CreateTempFileName( wxS(
"kicad_pads_connection_decoy_" ) );
2210 wxRemoveFile( tempBase );
2211 wxString tempPath = tempBase + wxS(
".pcb" );
2214 wxFFile file( tempPath, wxS(
"wb" ) );
2216 BOOST_REQUIRE_EQUAL( file.Write( bytes.data(), bytes.size() ), bytes.size() );
2219 bool rejectedDeclaredRing =
false;
2224 parser.
Parse( tempPath );
2226 catch(
const std::exception& e )
2228 rejectedDeclaredRing = std::string( e.what() ).find(
"net-connection ring framing" ) != std::string::npos;
2231 wxRemoveFile( tempPath );
2232 BOOST_CHECK( rejectedDeclaredRing );
2240 std::vector<uint8_t> bytes;
2250 const size_t finalHead = connections->
physicalOffset - 36 +
static_cast<size_t>( connections->
count ) * 68;
2251 BOOST_REQUIRE_LE( finalHead + 36, bytes.size() );
2253 bytes[finalHead + 23] = 0x42;
2255 wxString tempBase = wxFileName::CreateTempFileName( wxS(
"kicad_pads_connection_final_" ) );
2256 wxRemoveFile( tempBase );
2257 wxString tempPath = tempBase + wxS(
".pcb" );
2260 wxFFile file( tempPath, wxS(
"wb" ) );
2262 BOOST_REQUIRE_EQUAL( file.Write( bytes.data(), bytes.size() ), bytes.size() );
2265 bool rejectedFinalHead =
false;
2270 parser.
Parse( tempPath );
2272 catch(
const std::exception& e )
2274 rejectedFinalHead = std::string( e.what() ).find(
"net-connection ring framing" ) != std::string::npos;
2277 wxRemoveFile( tempPath );
2278 BOOST_CHECK( rejectedFinalHead );
2286 std::vector<uint8_t> bytes;
2294 BOOST_REQUIRE_GT( connections->
count, 0u );
2296 const size_t firstRecord =
static_cast<size_t>( connections->
physicalOffset ) + 16;
2297 BOOST_REQUIRE_LE( firstRecord + 68, bytes.size() );
2298 bytes[firstRecord + 39] = 0x42;
2300 wxString tempBase = wxFileName::CreateTempFileName( wxS(
"kicad_pads_connection_legacy_" ) );
2301 wxRemoveFile( tempBase );
2302 wxString tempPath = tempBase + wxS(
".pcb" );
2305 wxFFile file( tempPath, wxS(
"wb" ) );
2307 BOOST_REQUIRE_EQUAL( file.Write( bytes.data(), bytes.size() ), bytes.size() );
2310 bool rejectedLegacyRing =
false;
2315 parser.
Parse( tempPath );
2317 catch(
const std::exception& e )
2319 rejectedLegacyRing = std::string( e.what() ).find(
"legacy net-connection ring framing" ) != std::string::npos;
2322 wxRemoveFile( tempPath );
2323 BOOST_CHECK( rejectedLegacyRing );
2336 std::vector<uint8_t> bytes;
2340 BOOST_REQUIRE_NO_THROW( sdb.
Load( std::move( bytes ) ) );
2363 BOOST_CHECK( sdb.
Section( -1 ) ==
nullptr );
2364 BOOST_CHECK( sdb.
Section( 10000 ) ==
nullptr );
2372 std::vector<uint8_t> bytes;
2376 BOOST_REQUIRE_NO_THROW( sdb.
Load( std::move( bytes ) ) );
2445 std::vector<uint8_t> bytes;
2449 baseline.
Load( bytes );
2465 auto removeState = [stateOffset]( std::vector<uint8_t>& aBytes )
2467 aBytes.erase( aBytes.begin() + stateOffset, aBytes.begin() + stateOffset + 4 );
2469 uint32_t containerOffset =
static_cast<uint32_t
>( aBytes[aBytes.size() - 4] )
2470 |
static_cast<uint32_t
>( aBytes[aBytes.size() - 3] ) << 8
2471 |
static_cast<uint32_t
>( aBytes[aBytes.size() - 2] ) << 16
2472 |
static_cast<uint32_t
>( aBytes[aBytes.size() - 1] ) << 24;
2473 containerOffset -= 4;
2474 aBytes[aBytes.size() - 4] =
static_cast<uint8_t
>( containerOffset );
2475 aBytes[aBytes.size() - 3] =
static_cast<uint8_t
>( containerOffset >> 8 );
2476 aBytes[aBytes.size() - 2] =
static_cast<uint8_t
>( containerOffset >> 16 );
2477 aBytes[aBytes.size() - 1] =
static_cast<uint8_t
>( containerOffset >> 24 );
2480 std::vector<uint8_t> zeroHandle = bytes;
2481 std::fill_n( zeroHandle.begin() + route->
physicalOffset + 4, 4, uint8_t{ 0 } );
2482 removeState( zeroHandle );
2485 BOOST_REQUIRE_NO_THROW( zeroHandleSlot.
Load( std::move( zeroHandle ) ) );
2490 std::vector<uint8_t> negativeHandle = bytes;
2492 removeState( negativeHandle );
2495 BOOST_REQUIRE_NO_THROW( negativeHandleSlot.
Load( std::move( negativeHandle ) ) );
2506 std::vector<uint8_t> bytes;
2510 baseline.
Load( bytes );
2516 BOOST_REQUIRE_GE(
text->physicalBytes, 64u );
2519 std::vector<uint8_t> decoy = bytes;
2520 std::copy_n( decoy.begin() + strings->
physicalOffset, 64, decoy.begin() +
text->physicalOffset );
2523 BOOST_REQUIRE_NO_THROW( withDecoy.
Load( std::move( decoy ) ) );
2527 std::vector<uint8_t> corrupt = bytes;
2528 const size_t extentOffset = 10 + 57 * 16 + 4;
2530 corrupt[extentOffset] =
static_cast<uint8_t
>( extent );
2531 corrupt[extentOffset + 1] =
static_cast<uint8_t
>( extent >> 8 );
2532 corrupt[extentOffset + 2] =
static_cast<uint8_t
>( extent >> 16 );
2533 corrupt[extentOffset + 3] =
static_cast<uint8_t
>( extent >> 24 );
2535 wxString tempBase = wxFileName::CreateTempFileName( wxS(
"kicad_pads_string_extent_corrupt_" ) );
2536 wxRemoveFile( tempBase );
2537 wxString tempPath = tempBase + wxS(
".pcb" );
2540 wxFFile file( tempPath, wxS(
"wb" ) );
2542 BOOST_REQUIRE_EQUAL( file.Write( corrupt.data(), corrupt.size() ), corrupt.size() );
2546 BOOST_CHECK_THROW( invalid.
Parse( tempPath ), std::exception );
2547 wxRemoveFile( tempPath );
2555 std::vector<uint8_t> bytes;
2559 baseline.
Load( bytes );
2577 std::vector<uint8_t> decoy = bytes;
2578 std::copy_n( decoy.begin() + owners->
physicalOffset, 112, decoy.begin() +
text->physicalOffset );
2581 BOOST_REQUIRE_NO_THROW( withDecoy.
Load( std::move( decoy ) ) );
2586 std::vector<uint8_t> corrupt = bytes;
2587 const size_t countOffset = 10 + 10 * 16;
2588 uint32_t count = owners->
count + 1;
2589 corrupt[countOffset] =
static_cast<uint8_t
>( count );
2590 corrupt[countOffset + 1] =
static_cast<uint8_t
>( count >> 8 );
2591 corrupt[countOffset + 2] =
static_cast<uint8_t
>( count >> 16 );
2592 corrupt[countOffset + 3] =
static_cast<uint8_t
>( count >> 24 );
2595 BOOST_CHECK_THROW( invalid.
Load( std::move( corrupt ) ), std::exception );
2603 std::vector<uint8_t> bytes;
2616 std::copy_n( bytes.begin() + owners->
physicalOffset, 88, bytes.begin() +
text->physicalOffset );
2618 wxString tempBase = wxFileName::CreateTempFileName( wxS(
"kicad_pads_pour_decoy_" ) );
2619 wxRemoveFile( tempBase );
2620 wxString tempPath = tempBase + wxS(
".pcb" );
2623 wxFFile file( tempPath, wxS(
"wb" ) );
2625 BOOST_REQUIRE_EQUAL( file.Write( bytes.data(), bytes.size() ), bytes.size() );
2629 wxRemoveFile( tempPath );
2647 std::vector<uint8_t> bytes;
2650 const size_t totalBytesOffset = 10 + 69 * 16 + 4;
2651 BOOST_REQUIRE( totalBytesOffset +
sizeof( uint32_t ) <= bytes.size() );
2653 uint32_t totalBytes =
static_cast<uint32_t
>( bytes[totalBytesOffset] )
2654 |
static_cast<uint32_t
>( bytes[totalBytesOffset + 1] ) << 8
2655 |
static_cast<uint32_t
>( bytes[totalBytesOffset + 2] ) << 16
2656 |
static_cast<uint32_t
>( bytes[totalBytesOffset + 3] ) << 24;
2657 BOOST_REQUIRE_GT( totalBytes, 0u );
2660 for(
size_t byte = 0;
byte <
sizeof( totalBytes ); ++byte )
2661 bytes[totalBytesOffset +
byte] =
static_cast<uint8_t
>( totalBytes >> (
byte * 8 ) );
2664 BOOST_CHECK_THROW( sdb.
Load( std::move( bytes ) ), std::exception );
2672 std::vector<uint8_t> bytes;
2675 const size_t countOffset = 10 + 62 * 16;
2676 BOOST_REQUIRE( countOffset +
sizeof( uint32_t ) <= bytes.size() );
2677 std::fill_n( bytes.begin() + countOffset,
sizeof( uint32_t ), 0 );
2679 wxString tempBase = wxFileName::CreateTempFileName( wxS(
"kicad_pads_route_count_" ) );
2680 wxRemoveFile( tempBase );
2681 wxString tempPath = tempBase + wxS(
".pcb" );
2684 wxFFile file( tempPath, wxS(
"wb" ) );
2686 BOOST_REQUIRE_EQUAL( file.Write( bytes.data(), bytes.size() ), bytes.size() );
2690 BOOST_CHECK_THROW( parser.
Parse( tempPath ), std::exception );
2691 wxRemoveFile( tempPath );
2701 std::vector<std::string> copperNames;
2704 const std::vector<CASE> cases = {
2705 {
"Dexter_MotorCtrl",
2706 "Dexter_MotorCtrl.pcb",
2707 {
"Top",
"Signal Routing 1",
"Power P3_3v",
"Signal Routing 2",
"Signal Routing 3",
"Power P33v",
2708 "Signal Routing 4",
"Bottom" } },
2709 {
"LCORE_2",
"LCORE_2.pcb", {
"Top",
"Bottom" } },
2711 "MC2_PLUS_REV1.pcb",
2712 {
"Top",
"Inner Layer 1 GND",
"Inner Layer 2",
"Inner Layer 3",
"Inner Layer 4 VCC",
"Bottom" } },
2715 for(
const CASE&
test : cases )
2720 std::vector<std::string>
actual;
2724 if( layer.is_copper )
2725 actual.push_back( layer.name );
2744 const std::vector<CASE> cases = {
2745 {
"Dexter_MotorCtrl",
"Dexter_MotorCtrl.pcb", 284,
"J1",
"D5" },
2746 {
"LCORE_2",
"LCORE_2.pcb", 30,
"C4",
"FL1" },
2747 {
"MC2_PLUS_REV1",
"MC2_PLUS_REV1.pcb", 402,
"C1",
"UC128" },
2750 for(
const CASE&
test : cases )
2755 const std::vector<PADS_IO::PART>& parts = parser.
GetParts();
2756 BOOST_REQUIRE_EQUAL( parts.size(),
test.count );
2765 static constexpr char GUID[] =
"{2FE18320-6448-11d1-A412-000000000000}";
2766 static constexpr size_t GUID_LENGTH =
sizeof( GUID ) - 1;
2768 std::vector<uint8_t> bytes( 128, 0 );
2769 const size_t footerStart = bytes.size() - GUID_LENGTH -
sizeof( uint32_t );
2770 const uint32_t containerItemsOffset = 24;
2772 std::copy_n(
reinterpret_cast<const uint8_t*
>( GUID ), GUID_LENGTH,
2773 bytes.begin() +
static_cast<std::ptrdiff_t
>( footerStart ) );
2775 for(
size_t i = 0; i <
sizeof( containerItemsOffset ); ++i )
2776 bytes[footerStart + GUID_LENGTH + i] =
static_cast<uint8_t
>( containerItemsOffset >> ( i * 8 ) );
2787 std::vector<uint8_t> tiny( 2, 0 );
2797 std::vector<uint8_t> bytes;
2799 BOOST_REQUIRE_GE( bytes.size(), 42u );
2801 const uint32_t invalidOffset =
static_cast<uint32_t
>( bytes.size() - 41 );
2803 for(
size_t byte = 0;
byte <
sizeof( invalidOffset ); ++byte )
2804 bytes[bytes.size() -
sizeof( invalidOffset ) +
byte] =
static_cast<uint8_t
>( invalidOffset >> (
byte * 8 ) );
2807 BOOST_CHECK_THROW( sdb.
Load( std::move( bytes ) ), std::exception );
2822 const std::vector<std::string> boards = {
2823 "Dexter_MotorCtrl/Dexter_MotorCtrl.pcb",
2824 "Ems4_Rev2/Ems4_Rev2.pcb",
2825 "LCORE_2/LCORE_2.pcb",
2826 "LCORE_4/LCORE_4.pcb",
2827 "MAIS_FC/MAIS_FC.pcb",
2828 "MC2_PLUS_REV1/MC2_PLUS_REV1.pcb",
2829 "TMS1mmX19/TMS1mmX19.pcb",
2832 for(
const std::string& board : boards )
2835 std::vector<uint8_t> bytes;
2839 auto u32At = [&bytes](
size_t aOffset )
2841 return static_cast<uint32_t
>( bytes[aOffset] ) | (
static_cast<uint32_t
>( bytes[aOffset + 1] ) << 8 )
2842 | (
static_cast<uint32_t
>( bytes[aOffset + 2] ) << 16 )
2843 | (
static_cast<uint32_t
>( bytes[aOffset + 3] ) << 24 );
2846 const uint32_t slots = u32At( 10 + 16 );
2847 const uint32_t dirBytes = u32At( 10 + 16 + 4 );
2848 const uint32_t viewStates = u32At( 10 + 2 * 16 );
2856 const uint32_t
expected = 10 + ( slots - 1 ) * 16 + viewStates * 48;
2859 BOOST_REQUIRE_NO_THROW( sdb.
Load( std::move( bytes ) ) );
2872 std::vector<uint8_t> bytes;
2876 baseline.
Load( bytes );
2880 BOOST_REQUIRE( scaleOffset +
sizeof(
float ) <= bytes.size() );
2881 std::fill_n( bytes.begin() + scaleOffset,
sizeof(
float ), uint8_t{ 0 } );
2883 const std::string marker =
"DFT_CONFIGURATION";
2884 auto markerIt = std::search( bytes.begin(), bytes.end(), marker.begin(), marker.end() );
2887 const char config[] =
"X\0"
2891 auto configIt = markerIt + marker.size() + 1;
2893 std::copy_n(
reinterpret_cast<const uint8_t*
>(
config ),
sizeof(
config ), configIt );
2895 wxString tempBase = wxFileName::CreateTempFileName( wxS(
"kicad_pads_origin_corrupt_" ) );
2896 wxRemoveFile( tempBase );
2897 wxString tempPath = tempBase + wxS(
".pcb" );
2900 wxFFile file( tempPath, wxS(
"wb" ) );
2902 BOOST_REQUIRE_EQUAL( file.Write( bytes.data(), bytes.size() ), bytes.size() );
2906 BOOST_CHECK_THROW( parser.
Parse( tempPath ), std::exception );
2907 wxRemoveFile( tempPath );
2913static std::string
FlattenPath(
const std::filesystem::path& aRoot,
const std::filesystem::path& aFile )
2915 std::filesystem::path relPath = std::filesystem::relative( aFile, aRoot );
2919 for(
const auto& part : relPath )
2924 std::string p = part.string();
2945 size_t nonThroughSeen = 0;
2949 if( board.differentRevision )
2954 std::pair<size_t, size_t> counts =
2961 for(
PCB_TRACK* item : ascii->Tracks() )
3019 auto thermalPadKeys = [](
const std::shared_ptr<BOARD>& aBoard )
3021 std::set<std::string> keys;
3023 for(
FOOTPRINT* fp : aBoard->Footprints() )
3025 for(
PAD*
pad : fp->Pads() )
3029 keys.insert( fp->GetReference().ToStdString() +
"." +
pad->GetNumber().ToStdString() );
3037 auto everyPourIsSolid = [](
const std::shared_ptr<BOARD>& aBoard )
3039 for(
ZONE* zone : aBoard->Zones() )
3041 if( !zone->GetIsRuleArea() && zone->GetNetCode() > 0 && zone->GetPadConnection() !=
ZONE_CONNECTION::FULL )
3054 std::shared_ptr<BOARD> binary =
LoadBinary( board );
3055 std::shared_ptr<BOARD> ascii =
LoadAsc( board );
3060 std::set<std::string> binKeys = thermalPadKeys( binary );
3061 std::set<std::string> ascKeys = thermalPadKeys( ascii );
3062 std::vector<std::string> extra;
3067 std::set_difference( binKeys.begin(), binKeys.end(), ascKeys.begin(), ascKeys.end(),
3068 std::back_inserter( extra ) );
3070 BOOST_CHECK_MESSAGE( extra.empty(),
"binary import invented a thermal override on "
3071 << extra.size() <<
" pad(s), first "
3072 << ( extra.empty() ? std::string(
"-" ) : extra.front() ) );
3074 BOOST_CHECK_MESSAGE( everyPourIsSolid( binary ),
3075 "binary netted pours must connect solid, leaving relief to the "
3076 "per-pad overrides" );
3077 BOOST_CHECK_MESSAGE( everyPourIsSolid( ascii ),
3078 "ASCII netted pours must connect solid, leaving relief to the "
3079 "per-pad overrides" );
3088 const wxString asciiPath = root +
"padstack-v2022.asc";
3089 const wxString v2022Path = root +
"padstack-v2022.pcb";
3090 const wxString v2027Path = root +
"padstack-v2027.pcb";
3095 ascii.
Parse( asciiPath );
3096 v2022.
Parse( v2022Path );
3097 v2027.
Parse( v2027Path );
3099 BOOST_REQUIRE_EQUAL( v2022.
GetVersion(), 0x2022 );
3100 BOOST_REQUIRE_EQUAL( v2027.
GetVersion(), 0x2027 );
3102 auto tuples = [](
const auto& aParser,
const std::set<std::string>& aDecalNames )
3104 std::vector<std::string>
result;
3106 for(
const auto& [decalName, decal] : aParser.GetPartDecals() )
3108 if( !aDecalNames.contains( decalName ) )
3111 for(
const auto& [
pin, stack] : decal.pad_stacks )
3115 std::ostringstream tuple;
3116 tuple << decalName <<
' ' <<
pin <<
' ' << layer.layer <<
' ' << layer.shape <<
' ' << std::fixed
3117 << std::setprecision( 0 ) << layer.sizeA <<
' ' << layer.sizeB <<
' '
3118 << layer.thermal_outer_diameter;
3119 result.push_back( tuple.str() );
3127 std::set<std::string> decalNames;
3130 decalNames.insert(
name );
3132 const std::vector<std::string> asciiTuples = tuples( ascii, decalNames );
3133 const std::vector<std::string> v2022Tuples = tuples( v2022, decalNames );
3134 const std::vector<std::string> v2027Tuples = tuples( v2027, decalNames );
3136 for(
const auto& [label, binaryTuples] : std::array<std::pair<
const char*,
const std::vector<std::string>*>, 2>{
3137 { {
"0x2022", &v2022Tuples }, {
"0x2027", &v2027Tuples } } } )
3141 BOOST_REQUIRE_EQUAL( binaryTuples->size(), asciiTuples.size() );
3143 asciiTuples.end() );
3147 const wxString thermalAsciiPath = root +
"padstack-thermal-v2027.asc";
3148 const wxString thermalBinaryPath = root +
"padstack-thermal-v2027.pcb";
3152 thermalAscii.
Parse( thermalAsciiPath );
3153 thermalBinary.
Parse( thermalBinaryPath );
3154 BOOST_REQUIRE_EQUAL( thermalBinary.
GetVersion(), 0x2027 );
3158 decalNames.insert(
name );
3160 const std::vector<std::string> thermalAsciiTuples = tuples( thermalAscii, decalNames );
3161 const std::vector<std::string> thermalBinaryTuples = tuples( thermalBinary, decalNames );
3163 BOOST_REQUIRE_EQUAL( thermalBinaryTuples.size(), thermalAsciiTuples.size() );
3165 thermalAsciiTuples.end() );
3177 size_t unplated = 0;
3179 auto check = [&](
const std::shared_ptr<BOARD>& aBoard,
const std::string& aLabel )
3181 for(
FOOTPRINT* footprint : aBoard->Footprints() )
3183 for(
PAD*
pad : footprint->Pads() )
3185 const std::string key =
3186 aLabel +
" " + footprint->GetReference().ToStdString() +
"." +
pad->GetNumber().ToStdString();
3191 BOOST_CHECK_MESSAGE(
pad->GetLayerSet().test(
F_Mask ) &&
pad->GetLayerSet().test(
B_Mask ),
3192 "plated hole imported tented: " << key );
3198 "unplated hole given inner copper: " << key );
3199 BOOST_CHECK_MESSAGE(
pad->GetNetCode() == 0,
"unplated hole joined a net: " << key );
3209 check(
LoadBinary( board ), board.dir +
" binary" );
3210 check(
LoadAsc( board ), board.dir +
" ASC" );
3214 BOOST_CHECK_GT( plated, 0u );
3215 BOOST_CHECK_GT( unplated, 0u );
3228 std::shared_ptr<BOARD> binary =
LoadBinaryPath( root +
"padstack-thermal-v2027.pcb",
"padstack-thermal-v2027" );
3229 std::shared_ptr<BOARD> ascii =
LoadAscPath( root +
"padstack-thermal-v2027.asc",
"padstack-thermal-v2027" );
3234 auto thermalPadKeys = [](
const std::shared_ptr<BOARD>& aBoard )
3236 std::set<std::string> keys;
3238 for(
FOOTPRINT* footprint : aBoard->Footprints() )
3240 for(
PAD*
pad : footprint->Pads() )
3243 keys.insert( footprint->GetReference().ToStdString() +
"." +
pad->GetNumber().ToStdString() );
3250 const std::set<std::string> binKeys = thermalPadKeys( binary );
3251 const std::set<std::string> ascKeys = thermalPadKeys( ascii );
3253 BOOST_REQUIRE_GT( ascKeys.size(), 0u );
3255 std::vector<std::string> missing;
3256 std::set_difference( ascKeys.begin(), ascKeys.end(), binKeys.begin(), binKeys.end(),
3257 std::back_inserter( missing ) );
3259 BOOST_CHECK_MESSAGE( missing.empty(),
"binary import lost the plane relief on "
3260 << missing.size() <<
" pad(s), first "
3261 << ( missing.empty() ? std::string(
"-" ) : missing.front() ) );
3274 dimension.
points = { { 0.0, 0.0 }, { 1000.0, 0.0 } };
3277 for(
bool basicUnits : {
false,
true } )
3312 std::shared_ptr<BOARD> board =
LoadAscPath(
path,
"synthetic_constraints_cp1252" );
3316 std::shared_ptr<NET_SETTINGS> netSettings = board->GetDesignSettings().m_NetSettings;
3320 for(
const auto& [
name, netclass] : netSettings->GetNetclasses() )
3321 BOOST_CHECK_MESSAGE( !
name.IsEmpty(),
"a net class imported under a blank name" );
3323 BOOST_CHECK_MESSAGE( netSettings->HasNetclass( wxString::FromUTF8(
"HAUTE_VITESSÉ" ) ),
3324 "the CP1252 net class name was lost" );
3325 BOOST_CHECK_MESSAGE( netSettings->HasNetclass( wxString::FromUTF8(
"DiffPair_PAIRE_DIFFÉ" ) ),
3326 "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 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
Checks if this PCB_IO can read the specified board file.
bool CanReadBoard(const wxString &aFileName) const override
Checks if this PCB_IO can read the specified board file.
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)
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