62 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
80 std::unique_ptr<BOARD> board = LoadAllegroBoard(
"TRS80_POWER/TRS80_POWER.brd" );
84 int emptyRefDesCount = 0;
85 int validRefDesCount = 0;
87 for(
FOOTPRINT* fp : board->Footprints() )
89 wxString refdes = fp->GetReference();
91 if( refdes.IsEmpty() )
97 BOOST_TEST_MESSAGE(
"Valid RefDes: " << validRefDesCount <<
", Empty: " << emptyRefDesCount );
100 BOOST_CHECK_GT( validRefDesCount, emptyRefDesCount );
109 std::unique_ptr<BOARD> board = LoadAllegroBoard(
"TRS80_POWER/TRS80_POWER.brd" );
113 int validPadCount = 0;
114 int zeroPadCount = 0;
115 int hugePadCount = 0;
117 for(
FOOTPRINT* fp : board->Footprints() )
119 for(
PAD*
pad : fp->Pads() )
123 if( size.
x == 0 || size.
y == 0 )
127 <<
pad->GetNumber() );
129 else if( size.
x > 50000000 || size.
y > 50000000 )
133 <<
": " << size.
x / 1000000.0 <<
"mm x "
134 << size.
y / 1000000.0 <<
"mm" );
144 <<
", Huge: " << hugePadCount );
156 std::unique_ptr<BOARD> board = LoadAllegroBoard(
"TRS80_POWER/TRS80_POWER.brd" );
160 int validViaCount = 0;
161 int suspiciousViaCount = 0;
164 const int HARDCODED_SIZE = 1000000;
166 for(
PCB_TRACK* track : board->Tracks() )
171 int width =
via->GetWidth(
F_Cu );
173 if( width == HARDCODED_SIZE )
175 suspiciousViaCount++;
177 else if( width > 0 && width < 10000000 )
184 BOOST_TEST_MESSAGE(
"Valid vias: " << validViaCount <<
", Hardcoded-size vias: " << suspiciousViaCount );
187 if( suspiciousViaCount > 0 )
189 BOOST_WARN_MESSAGE(
false,
"Found " << suspiciousViaCount <<
" vias with hardcoded 1mm size" );
199 std::unique_ptr<BOARD> board = LoadAllegroBoard(
"TRS80_POWER/TRS80_POWER.brd" );
203 int validTrackCount = 0;
204 int zeroTrackCount = 0;
206 for(
PCB_TRACK* track : board->Tracks() )
210 int width = track->GetWidth();
214 else if( width > 0 && width < 10000000 )
219 BOOST_TEST_MESSAGE(
"Valid tracks: " << validTrackCount <<
", Zero-width: " << zeroTrackCount );
230 std::unique_ptr<BOARD> board = LoadAllegroBoard(
"TRS80_POWER/TRS80_POWER.brd" );
234 int numberedPads = 0;
235 int unnumberedPads = 0;
237 for(
FOOTPRINT* fp : board->Footprints() )
239 for(
PAD*
pad : fp->Pads() )
241 if(
pad->GetNumber().IsEmpty() )
248 BOOST_TEST_MESSAGE(
"Numbered pads: " << numberedPads <<
", Unnumbered: " << unnumberedPads );
252 if( unnumberedPads > 0 )
254 BOOST_WARN_MESSAGE(
false,
"Found " << unnumberedPads <<
" pads without numbers" );
276 std::vector<SEG> outlineSegs;
300 outlineSegs.push_back( seg );
308 for(
size_t i = 0; i < polyPoints.size() - 1; i++ )
311 VECTOR2I end = polyPoints[( i + 1 ) % polyPoints.size()];
312 outlineSegs.emplace_back( start,
end );
322 false,
"Unexpected shape type in board outline: " <<
static_cast<int>( shape->
GetShape() ) );
327 if( !outlineSegs.empty() )
331 int connectedCount = 0;
333 for(
const SEG& seg : outlineSegs )
335 for(
const SEG& other : outlineSegs )
341 if( seg.A == other.A || seg.A == other.B )
345 if( seg.B == other.A || seg.B == other.B )
353 BOOST_CHECK_GE( connectedCount, outlineSegs.size() * 2 );
363 std::unique_ptr<BOARD> board = LoadAllegroBoard(
"TRS80_POWER/TRS80_POWER.brd" );
373 BOOST_CHECK_GE( outlineSegmentCount, 1 );
384 std::unique_ptr<BOARD> board = LoadAllegroBoard(
"TRS80_POWER/TRS80_POWER.brd" );
390 bool hasBbox =
false;
398 boardBbox = item->GetBoundingBox();
403 boardBbox.
Merge( item->GetBoundingBox() );
408 BOOST_REQUIRE_MESSAGE( hasBbox,
"Board should have an outline" );
414 BOX2I testBbox = boardBbox;
417 for(
FOOTPRINT* fp : board->Footprints() )
419 for(
PAD*
pad : fp->Pads() )
423 if( testBbox.
Contains( padCenter ) )
431 <<
pad->GetNumber() <<
" at ("
432 << padCenter.
x / 1000000.0 <<
", "
433 << padCenter.
y / 1000000.0 <<
") mm" );
438 BOOST_TEST_MESSAGE(
"Pads inside outline: " << padsInside <<
", outside: " << padsOutside );
442 if( padsOutside > 0 )
444 BOOST_WARN_MESSAGE(
false,
"Found " << padsOutside <<
" pads outside board outline" );
448 BOOST_CHECK_GT( padsInside, padsOutside );
458 BOOST_CHECK_EXCEPTION(
459 LoadAllegroBoard(
"v13_header/v13_header.brd" ),
IO_ERROR,
462 wxString msg = e.
What();
464 return msg.Contains( wxS(
"predates Allegro 16.0" ) )
465 && msg.Contains( wxS(
"Allegro PCB Design" ) );
477 std::unique_ptr<BOARD> board = LoadAllegroBoard(
"rects/rects.brd" );
483 ZONE* zone = board->Zones().front();
489 int copperPolyCount = 0;
490 int copperPolyWithNet = 0;
518 std::unique_ptr<BOARD> board = LoadAllegroBoard(
"copper_text/copper_text.brd" );
521 int copperTextCount = 0;
522 bool foundTestingText =
false;
534 if(
text->GetText() == wxS(
"TESTING" ) )
536 foundTestingText =
true;
583 std::vector<std::string>
boards;
589 for(
const auto& boardDir : std::filesystem::directory_iterator( dataPath ) )
591 if( !boardDir.is_directory() )
594 for(
const auto& entry : std::filesystem::directory_iterator( boardDir ) )
596 if( entry.is_regular_file() && entry.path().extension() ==
".brd" && entry.file_size() > 0 )
598 std::string
name = entry.path().filename().string();
601 if(
name !=
"v13_header.brd" )
603 boards.push_back( boardDir.path().string() +
"/" +
name );
609 catch(
const std::filesystem::filesystem_error& e )
633 BOARD* board = GetCachedBoard( dataPath );
636 const std::vector<wxString> expectedLayers = { wxS(
"TOP" ), wxS(
"LYR2_GND" ),
637 wxS(
"LYR5_PWR" ), wxS(
"BOTTOM" ) };
639 for(
const wxString& layerName : expectedLayers )
646 "Layer " << layerName <<
" should exist" );
648 const ZONE* largest =
nullptr;
649 double largestArea = 0;
653 if( zone->GetIsRuleArea() )
656 if( zone->GetNetCode() == 0 )
659 if( !zone->GetLayerSet().Contains( layerId ) )
662 BOX2I bbox = zone->GetBoundingBox();
663 double area =
static_cast<double>( bbox.
GetWidth() )
664 *
static_cast<double>( bbox.
GetHeight() );
666 if( area > largestArea )
673 BOOST_REQUIRE_MESSAGE( largest !=
nullptr,
674 "Should find a netted copper zone on " << layerName );
686 std::vector<std::string>
boards = GetAllBoardFiles();
688 for(
const std::string& boardPath :
boards )
690 std::string boardName = std::filesystem::path( boardPath ).filename().string();
691 BOARD* board = GetCachedBoard( boardPath );
698 int negativePadCount = 0;
702 for(
PAD*
pad : fp->Pads() )
706 if( size.
x < 0 || size.
y < 0 )
710 <<
" pad " <<
pad->GetNumber() <<
": " << size.
x <<
" x "
728 std::vector<std::string>
boards = GetAllBoardFiles();
730 for(
const std::string& boardPath :
boards )
732 std::string boardName = std::filesystem::path( boardPath ).filename().string();
733 BOARD* board = GetCachedBoard( boardPath );
740 int invalidViaCount = 0;
747 int drill =
via->GetDrill();
748 int width =
via->GetWidth(
F_Cu );
754 <<
via->GetPosition().x / 1000000.0 <<
", "
755 <<
via->GetPosition().y / 1000000.0
756 <<
") has drill " << drill / 1000000.0
757 <<
"mm > width " << width / 1000000.0 <<
"mm" );
774 std::vector<std::string>
boards = GetAllBoardFiles();
776 for(
const std::string& boardPath :
boards )
778 std::string boardName = std::filesystem::path( boardPath ).filename().string();
779 BOARD* board = GetCachedBoard( boardPath );
786 int misclassifiedSmdCount = 0;
787 int correctSmdCount = 0;
788 int correctThCount = 0;
792 for(
PAD*
pad : fp->Pads() )
794 bool hasDrill =
pad->GetDrillSizeX() > 0 &&
pad->GetDrillSizeY() > 0;
799 misclassifiedSmdCount++;
801 <<
"." <<
pad->GetNumber()
802 <<
" has no drill but is marked as PTH (should be SMD)" );
816 <<
", Correct TH=" << correctThCount
817 <<
", Misclassified=" << misclassifiedSmdCount );
831 std::vector<std::string>
boards = GetAllBoardFiles();
833 for(
const std::string& boardPath :
boards )
835 std::string boardName = std::filesystem::path( boardPath ).filename().string();
836 BOARD* board = GetCachedBoard( boardPath );
843 int quadPackageCount = 0;
844 int packagesWithRotatedPads = 0;
845 int packagesWithUnrotatedPads = 0;
849 wxString refdes = fp->GetReference().Upper();
852 if( !refdes.StartsWith(
"U" ) )
856 if( fp->Pads().size() < 16 )
863 for(
PAD*
pad : fp->Pads() )
874 padBounds.
Merge( pos );
882 if( width == 0 || height == 0 )
885 double aspectRatio =
static_cast<double>( std::max( width, height ) ) /
886 static_cast<double>( std::min( width, height ) );
888 if( aspectRatio > 2.0 )
894 std::set<int> uniqueAngles;
896 for(
PAD*
pad : fp->Pads() )
900 int degrees =
static_cast<int>( angle.
AsDegrees() + 0.5 ) % 360;
901 uniqueAngles.insert( degrees );
906 if( uniqueAngles.size() >= 2 )
908 packagesWithRotatedPads++;
910 <<
" has " << uniqueAngles.size() <<
" unique pad orientations" );
914 packagesWithUnrotatedPads++;
916 <<
" has only " << uniqueAngles.size()
917 <<
" unique pad orientation (may be missing rotation)" );
921 if( quadPackageCount > 0 )
924 <<
" potential quad packages, "
925 << packagesWithRotatedPads <<
" with rotated pads, "
926 << packagesWithUnrotatedPads <<
" without" );
930 if( packagesWithRotatedPads == 0 && quadPackageCount > 0 )
932 BOOST_WARN_MESSAGE(
false, boardName <<
" has no packages with rotated pads" );
949 BOARD* board = GetCachedBoard( dataPath );
950 BOOST_REQUIRE_MESSAGE( board !=
nullptr,
"BeagleBone_Black_RevC.brd should load successfully" );
957 if( fp->GetReference() ==
"C78" )
964 BOOST_REQUIRE_MESSAGE( c78 !=
nullptr,
"Footprint C78 should exist in BeagleBone Black" );
981 if( fp->GetLayer() ==
F_Cu )
983 else if( fp->GetLayer() ==
B_Cu )
987 BOOST_TEST_MESSAGE(
"Footprints on top: " << topCount <<
", on bottom: " << bottomCount );
990 BOOST_CHECK_GT( topCount, 0 );
991 BOOST_CHECK_GT( bottomCount, 0 );
1001 std::vector<std::string>
boards = GetAllBoardFiles();
1003 for(
const std::string& boardPath :
boards )
1005 std::string boardName = std::filesystem::path( boardPath ).filename().string();
1006 BOARD* board = GetCachedBoard( boardPath );
1014 int disconnectedArcs = 0;
1017 std::map<int, std::vector<VECTOR2I>> netEndpoints;
1021 int netCode = track->GetNetCode();
1025 netEndpoints[netCode].push_back( track->GetStart() );
1026 netEndpoints[netCode].push_back( track->GetEnd() );
1030 netEndpoints[netCode].push_back( track->GetPosition() );
1037 for(
PAD*
pad : fp->Pads() )
1039 int netCode =
pad->GetNetCode();
1042 netEndpoints[netCode].push_back(
pad->GetPosition() );
1060 bool startConnected =
false;
1061 bool endConnected =
false;
1062 const int tolerance = 1000;
1064 for(
const VECTOR2I& pt : netEndpoints[netCode] )
1066 if( ( pt - arcStart ).EuclideanNorm() < tolerance )
1067 startConnected =
true;
1069 if( ( pt - arcEnd ).EuclideanNorm() < tolerance )
1070 endConnected =
true;
1075 if( !startConnected && !endConnected && netEndpoints[netCode].size() > 2 )
1079 << arcStart.
x / 1000000.0 <<
", "
1080 << arcStart.
y / 1000000.0 <<
") to ("
1081 << arcEnd.
x / 1000000.0 <<
", "
1082 << arcEnd.
y / 1000000.0
1083 <<
") appears disconnected from net " << netCode );
1090 << disconnectedArcs <<
" disconnected" );
1097 BOOST_CHECK_LE( disconnectedArcs, arcCount / 5 );
1149 std::vector<wxString> fields;
1152 for(
size_t i = 0; i < aLine.size(); ++i )
1154 if( aLine[i] ==
'!' )
1156 fields.push_back( current );
1161 current += aLine[i];
1165 if( !current.empty() )
1166 fields.push_back( current );
1178 wxString tag = aTag.BeforeFirst(
' ' );
1180 return static_cast<int>( val );
1186 std::ifstream file( aPath );
1188 if( !file.is_open() )
1199 SECTION currentSection = SECTION::UNKNOWN;
1205 while( std::getline( file, line ) )
1207 if( line.empty() || line[0] ==
'J' )
1210 if( line[0] ==
'A' )
1212 if( line.find(
"NET_NAME_SORT!NODE_SORT!NET_NAME!REFDES!" ) != std::string::npos )
1213 currentSection = SECTION::NET_NODES;
1214 else if( line.find(
"SYM_TYPE!SYM_NAME!REFDES!SYM_MIRROR!" ) != std::string::npos )
1215 currentSection = SECTION::SYM_PLACEMENT;
1216 else if( line.find(
"CLASS!SUBCLASS!RECORD_TAG!GRAPHIC_DATA_NAME!" ) != std::string::npos )
1217 currentSection = SECTION::GRAPHICS;
1219 currentSection = SECTION::UNKNOWN;
1224 if( line[0] !=
'S' )
1227 auto fields =
SplitAlgLine( wxString::FromUTF8( line ) );
1229 switch( currentSection )
1231 case SECTION::NET_NODES:
1234 if( fields.size() >= 5 )
1236 wxString netName = fields[3];
1237 wxString refdes = fields[4];
1239 if( !netName.empty() )
1243 if( !refdes.empty() )
1250 case SECTION::SYM_PLACEMENT:
1253 if( fields.size() >= 4 )
1255 wxString symType = fields[1];
1256 wxString symName = fields[2];
1257 wxString refdes = fields[3];
1259 if( symType == wxT(
"PACKAGE" ) && !refdes.empty() )
1261 data.
refDes.insert( refdes );
1268 case SECTION::GRAPHICS:
1274 if( fields.size() < 16 || fields[1] != wxT(
"BOUNDARY" ) )
1277 wxString closureType = fields[15];
1279 if( closureType != wxT(
"SHAPE" ) )
1282 wxString layer = fields[2];
1290 if( fields.size() > 23 )
1291 netName = fields[23];
1293 auto key = std::make_pair( layer, recordId );
1294 auto& zone = zoneMap[key];
1296 zone.recordId = recordId;
1298 if( !netName.empty() )
1299 zone.netName = netName;
1301 double x1 = 0, y1 = 0, x2 = 0, y2 = 0;
1303 if( fields.size() > 9 )
1305 fields[6].ToDouble( &x1 );
1306 fields[7].ToDouble( &y1 );
1307 fields[8].ToDouble( &x2 );
1308 fields[9].ToDouble( &y2 );
1309 zone.AddPoint( x1, y1 );
1310 zone.AddPoint( x2, y2 );
1320 for(
auto& [key, zone] : zoneMap )
1342 std::vector<BRD_ALG_PAIR> boardsWithAlg;
1344 for(
const auto& boardDir : std::filesystem::directory_iterator( dataPath ) )
1346 if( !boardDir.is_directory() )
1349 std::filesystem::path boardPath;
1350 std::filesystem::path algPath;
1352 for(
const auto& entry : std::filesystem::directory_iterator( boardDir ) )
1354 if( !entry.is_regular_file() )
1357 if( entry.path().extension() ==
".brd" )
1358 boardPath = entry.path();
1359 else if( entry.path().extension() ==
".alg" )
1360 algPath = entry.path();
1362 if( !boardPath.empty() && !algPath.empty() )
1364 boardsWithAlg.push_back( { boardPath.string(), algPath.string() } );
1370 return boardsWithAlg;
1381 BOOST_REQUIRE_GT( boardsWithAlg.size(), 0u );
1383 for(
const auto& [brdFile, algFile] : boardsWithAlg )
1388 BOOST_REQUIRE_GT( algData.
netNames.size(), 0u );
1390 BOARD* board = GetCachedBoard( brdFile );
1393 std::set<wxString> boardNets;
1397 if( net->GetNetCode() > 0 )
1398 boardNets.insert( net->GetNetname() );
1401 int missingNets = 0;
1403 for(
const wxString& algNet : algData.
netNames )
1405 if( boardNets.find( algNet ) == boardNets.end() )
1409 if( missingNets <= 10 )
1415 << boardNets.size() <<
", missing " << missingNets );
1429 BOOST_REQUIRE_GT( boardsWithAlg.size(), 0u );
1431 for(
const auto& [brdFile, algFile] : boardsWithAlg )
1436 BOOST_REQUIRE_GT( algData.
refDes.size(), 0u );
1438 BOARD* board = GetCachedBoard( brdFile );
1441 std::set<wxString> boardRefDes;
1444 boardRefDes.insert( fp->GetReference() );
1446 int missingRefDes = 0;
1447 int extraRefDes = 0;
1449 for(
const wxString& algRef : algData.
refDes )
1451 if( boardRefDes.find( algRef ) == boardRefDes.end() )
1455 if( missingRefDes <= 10 )
1460 for(
const wxString& boardRef : boardRefDes )
1462 if( algData.
refDes.find( boardRef ) == algData.
refDes.end() )
1466 if( extraRefDes <= 10 )
1472 <<
" components, board has " << boardRefDes.size()
1473 <<
", missing " << missingRefDes
1474 <<
", extra " << extraRefDes );
1486 std::vector<std::string>
boards = GetAllBoardFiles();
1488 for(
const std::string& boardPath :
boards )
1490 std::string boardName = std::filesystem::path( boardPath ).filename().string();
1491 BOARD* board = GetCachedBoard( boardPath );
1498 int zeroWidthCount = 0;
1507 if( track->GetWidth() <= 0 )
1511 if( zeroWidthCount <= 5 )
1514 << track->GetStart().x / 1000000.0 <<
", "
1515 << track->GetStart().y / 1000000.0 <<
")" );
1555 double minX = std::numeric_limits<double>::max();
1556 double minY = std::numeric_limits<double>::max();
1557 double maxX = std::numeric_limits<double>::lowest();
1558 double maxY = std::numeric_limits<double>::lowest();
1571 std::ifstream file( aPath );
1573 if( !file.is_open() )
1578 while( std::getline( file, line ) )
1580 if( line.empty() || line[0] !=
'S' )
1585 if( fields.size() < 10 )
1588 bool isDesignOutline = ( fields[1] == wxT(
"BOARD GEOMETRY" )
1589 && fields[2] == wxT(
"DESIGN_OUTLINE" ) );
1591 bool isOutline = ( fields[1] == wxT(
"BOARD GEOMETRY" )
1592 && fields[2] == wxT(
"OUTLINE" ) );
1594 if( !isDesignOutline && !isOutline )
1597 wxString shapeType = fields[4];
1600 if( shapeType == wxT(
"LINE" ) && fields.size() >= 10 )
1603 fields[6].ToCDouble( &seg.
x1 );
1604 fields[7].ToCDouble( &seg.
y1 );
1605 fields[8].ToCDouble( &seg.
x2 );
1606 fields[9].ToCDouble( &seg.
y2 );
1611 else if( shapeType == wxT(
"ARC" ) && fields.size() >= 15 )
1614 fields[6].ToCDouble( &seg.
x1 );
1615 fields[7].ToCDouble( &seg.
y1 );
1616 fields[8].ToCDouble( &seg.
x2 );
1617 fields[9].ToCDouble( &seg.
y2 );
1618 fields[10].ToCDouble( &seg.
centerX );
1619 fields[11].ToCDouble( &seg.
centerY );
1620 fields[12].ToCDouble( &seg.
radius );
1621 seg.
clockwise = ( fields[14] == wxT(
"CLOCKWISE" ) );
1626 else if( shapeType == wxT(
"RECTANGLE" ) && fields.size() >= 10 )
1629 fields[6].ToCDouble( &seg.
x1 );
1630 fields[7].ToCDouble( &seg.
y1 );
1631 fields[8].ToCDouble( &seg.
x2 );
1632 fields[9].ToCDouble( &seg.
y2 );
1642 if( isDesignOutline )
1686 BOOST_REQUIRE_GT( testBoards.size(), 0u );
1688 for(
const auto& [brdFile, algFile] : testBoards )
1700 BOARD* board = GetCachedBoard( brdFile );
1703 int edgeCutsCount = 0;
1714 <<
" -> expected Edge_Cuts segments: " << expectedCount );
1732 BOOST_REQUIRE_GT( testBoards.size(), 0u );
1735 const double milToNm = 25400.0;
1738 const int toleranceNm =
static_cast<int>( 2.0 * milToNm );
1740 for(
const auto& [brdFile, algFile] : testBoards )
1752 BOARD* board = GetCachedBoard( brdFile );
1756 bool hasBbox =
false;
1764 boardBbox = item->GetBoundingBox();
1769 boardBbox.
Merge( item->GetBoundingBox() );
1774 BOOST_REQUIRE_MESSAGE( hasBbox,
"Board should have Edge_Cuts outline" );
1777 int algMinXnm =
static_cast<int>( algOutlines.
minX * milToNm );
1778 int algMinYnm =
static_cast<int>( algOutlines.
minY * milToNm );
1779 int algMaxXnm =
static_cast<int>( algOutlines.
maxX * milToNm );
1780 int algMaxYnm =
static_cast<int>( algOutlines.
maxY * milToNm );
1781 int algWidthNm = algMaxXnm - algMinXnm;
1782 int algHeightNm = algMaxYnm - algMinYnm;
1784 int boardWidth = boardBbox.
GetWidth();
1785 int boardHeight = boardBbox.
GetHeight();
1788 << algOutlines.
minX <<
"," << algOutlines.
minY <<
" to "
1789 << algOutlines.
maxX <<
"," << algOutlines.
maxY
1790 <<
" = " << ( algOutlines.
maxX - algOutlines.
minX ) <<
" x "
1791 << ( algOutlines.
maxY - algOutlines.
minY ) );
1793 << boardBbox.
GetLeft() <<
"," << boardBbox.
GetTop() <<
" to "
1795 <<
" = " << boardWidth <<
" x " << boardHeight );
1799 BOOST_CHECK_CLOSE(
static_cast<double>( boardWidth ),
1800 static_cast<double>( algWidthNm ), 3.0 );
1801 BOOST_CHECK_CLOSE(
static_cast<double>( boardHeight ),
1802 static_cast<double>( algHeightNm ), 3.0 );
1819 BOOST_REQUIRE_GT( testBoards.size(), 0u );
1821 const double milToNm = 25400.0;
1822 const int toleranceNm =
static_cast<int>( 2.0 * milToNm );
1824 for(
const auto& [brdFile, algFile] : testBoards )
1834 bool allLines =
true;
1852 BOARD* board = GetCachedBoard( brdFile );
1856 struct ENDPOINT_PAIR
1862 std::vector<ENDPOINT_PAIR> boardSegments;
1872 boardSegments.push_back( { shape->
GetStart(), shape->
GetEnd() } );
1876 std::vector<ENDPOINT_PAIR> algSegments;
1882 VECTOR2I start(
static_cast<int>( seg.x1 * milToNm ),
1883 static_cast<int>( seg.y1 * milToNm ) );
1884 VECTOR2I end(
static_cast<int>( seg.x2 * milToNm ),
1885 static_cast<int>( seg.y2 * milToNm ) );
1886 algSegments.push_back( { start,
end } );
1890 int x1 =
static_cast<int>( seg.x1 * milToNm );
1891 int y1 =
static_cast<int>( seg.y1 * milToNm );
1892 int x2 =
static_cast<int>( seg.x2 * milToNm );
1893 int y2 =
static_cast<int>( seg.y2 * milToNm );
1895 algSegments.push_back( { { x1, y1 }, { x2, y1 } } );
1896 algSegments.push_back( { { x2, y1 }, { x2, y2 } } );
1897 algSegments.push_back( { { x2, y2 }, { x1, y2 } } );
1898 algSegments.push_back( { { x1, y2 }, { x1, y1 } } );
1904 if( boardSegments.size() != algSegments.size() )
1910 int matchedCount = 0;
1912 std::vector<bool> used( boardSegments.size(),
false );
1914 for(
size_t ai = 0; ai < algSegments.size(); ++ai )
1916 const auto& algSeg = algSegments[ai];
1918 int64_t bestDist = std::numeric_limits<int64_t>::max();
1920 for(
size_t bi = 0; bi < boardSegments.size(); ++bi )
1925 const auto& bSeg = boardSegments[bi];
1928 auto dist = [&](
const VECTOR2I& aAlgPt,
const VECTOR2I& aBoardPt ) -> int64_t
1930 int64_t dx =
std::abs(
static_cast<int64_t
>( aAlgPt.
x )
1931 -
static_cast<int64_t
>( aBoardPt.x ) );
1932 int64_t dy =
std::abs(
static_cast<int64_t
>( aAlgPt.
y )
1933 -
static_cast<int64_t
>( aBoardPt.y ) );
1937 int64_t d1 = dist( algSeg.start, bSeg.start ) + dist( algSeg.end, bSeg.end );
1938 int64_t d2 = dist( algSeg.start, bSeg.end ) + dist( algSeg.end, bSeg.start );
1939 int64_t d = std::min( d1, d2 );
1944 bestIdx =
static_cast<int>( bi );
1948 if( bestIdx >= 0 && bestDist < 2LL * toleranceNm )
1950 used[bestIdx] =
true;
1956 << algSeg.start.x / 1000000.0 <<
", "
1957 << algSeg.start.y / 1000000.0 <<
") -> ("
1958 << algSeg.end.x / 1000000.0 <<
", "
1959 << algSeg.end.y / 1000000.0 <<
") mm"
1960 <<
" bestDist=" << bestDist );
1965 <<
" outline segments" );
1979 std::vector<std::string>
boards = GetAllBoardFiles();
1981 for(
const std::string& boardPath :
boards )
1983 std::string boardName = std::filesystem::path( boardPath ).filename().string();
1984 BOARD* board = GetCachedBoard( boardPath );
1992 int smdWithDrill = 0;
1996 for(
PAD*
pad : fp->Pads() )
1999 bool hasDrill =
pad->GetDrillSizeX() > 0;
2005 if( pthNoDrill <= 5 )
2008 << fp->GetReference() <<
"."
2009 <<
pad->GetNumber() );
2017 if( smdWithDrill <= 5 )
2020 << fp->GetReference() <<
"."
2021 <<
pad->GetNumber() );
2041 BOOST_REQUIRE_GT( boardsWithAlg.size(), 0u );
2043 for(
const auto& [brdFile, algFile] : boardsWithAlg )
2049 BOARD* board = GetCachedBoard( brdFile );
2052 size_t boardCopperZoneLayers = 0;
2054 for(
const ZONE* zone : board->
Zones() )
2056 if( !zone->GetIsRuleArea() )
2057 boardCopperZoneLayers += ( zone->GetLayerSet() &
LSET::AllCuMask() ).count();
2063 <<
" zone polygons, board has " << boardCopperZoneLayers
2064 <<
" copper zone-layers" );
2066 BOOST_CHECK_EQUAL(
static_cast<size_t>( boardCopperZoneLayers ), algZoneCount );
2079 BOOST_REQUIRE_GT( boardsWithAlg.size(), 0u );
2081 for(
const auto& [brdFile, algFile] : boardsWithAlg )
2087 BOARD* board = GetCachedBoard( brdFile );
2090 std::map<wxString, int> algLayerCounts;
2093 algLayerCounts[zone.
layer]++;
2095 std::map<wxString, int> boardLayerCounts;
2097 for(
const ZONE* zone : board->
Zones() )
2099 if( zone->GetIsRuleArea() )
2111 for(
const auto& [layer, count] : algLayerCounts )
2113 auto it = boardLayerCounts.find( layer );
2114 int boardCount = ( it != boardLayerCounts.end() ) ? it->second : 0;
2132 BOOST_REQUIRE_GT( boardsWithAlg.size(), 0u );
2134 for(
const auto& [brdFile, algFile] : boardsWithAlg )
2140 BOARD* board = GetCachedBoard( brdFile );
2144 const double milsToNm = 25400.0;
2146 std::map<wxString, std::vector<double>> algAreas;
2150 double w = ( zone.
maxX - zone.
minX ) * milsToNm;
2151 double h = ( zone.
maxY - zone.
minY ) * milsToNm;
2152 algAreas[zone.
layer].push_back( w * h );
2155 std::map<wxString, std::vector<double>> boardAreas;
2157 for(
const ZONE* zone : board->
Zones() )
2159 if( zone->GetIsRuleArea() )
2162 BOX2I bbox = zone->GetBoundingBox();
2163 double area =
static_cast<double>( bbox.
GetWidth() )
2164 *
static_cast<double>( bbox.
GetHeight() );
2169 boardAreas[board->
GetLayerName( layer )].push_back( area );
2176 for(
auto& [layer, algList] : algAreas )
2178 std::sort( algList.begin(), algList.end() );
2179 auto it = boardAreas.find( layer );
2181 if( it == boardAreas.end() || it->second.size() != algList.size() )
2184 std::sort( it->second.begin(), it->second.end() );
2186 for(
size_t i = 0; i < algList.size(); ++i )
2188 double ref = std::max( algList[i], 1.0 );
2189 double err =
std::abs( it->second[i] - algList[i] ) / ref;
2199 if( mismatched <= 5 )
2202 <<
": alg area " << algList[i] / ( milsToNm * milsToNm )
2203 <<
" sq mils vs board area "
2204 << it->second[i] / ( milsToNm * milsToNm )
2212 << mismatched <<
" mismatched" );
2214 int total = matched + mismatched;
2218 BOOST_CHECK_GT( matched, total * 8 / 10 );
2235 BOARD* board = GetCachedBoard( dataPath );
2241 const std::set<wxString> targetRefs = { wxS(
"J1" ), wxS(
"P5" ), wxS(
"U5" ),
2242 wxS(
"U13" ), wxS(
"C78" ) };
2244 int testedCount = 0;
2245 int failedCount = 0;
2249 if( targetRefs.find( fp->GetReference() ) == targetRefs.end() )
2255 bool hasFab =
false;
2257 for(
BOARD_ITEM* item : fp->GraphicalItems() )
2259 if( item->GetLayer() == fabLayer )
2263 fabBbox = item->GetBoundingBox();
2268 fabBbox.
Merge( item->GetBoundingBox() );
2273 if( !hasFab || fp->Pads().empty() )
2278 BOX2I testBbox = fabBbox;
2285 for(
PAD*
pad : fp->Pads() )
2289 if( !testBbox.
Contains( padCenter ) )
2293 <<
" at (" << padCenter.
x / 1e6 <<
", "
2294 << padCenter.
y / 1e6 <<
") mm is outside F.Fab bbox" );
2301 BOOST_CHECK_GE( testedCount, 4 );
2315 BOARD* board = GetCachedBoard( dataPath );
2320 wxString ref = fp->GetReference();
2322 if( ref != wxS(
"P6" ) && ref != wxS(
"P10" ) )
2327 for(
PAD*
pad : fp->Pads() )
2331 if( !
pad->GetNumber().ToLong( &padNum ) )
2335 if( padNum < 1 || padNum > 19 )
2339 BOX2I bbox =
pad->GetBoundingBox();
2344 if( bboxW == bboxH )
2350 ref <<
" pad " <<
pad->GetNumber()
2351 <<
" should be visually wider than tall: "
2352 << bboxW / 1e6 <<
" x " << bboxH / 1e6 <<
" mm" );
2371 BOARD* board = GetCachedBoard( dataPath );
2374 int oblongCount = 0;
2378 for(
PAD*
pad : fp->Pads() )
2382 if( drillSize.
x <= 0 || drillSize.
y <= 0 )
2385 if( drillSize.
x == drillSize.
y )
2395 <<
" slot: " << drillSize.
x / 1e6 <<
" x "
2396 << drillSize.
y / 1e6 <<
" mm"
2397 <<
" attr=" <<
static_cast<int>(
pad->GetAttribute() ) );
2416 BOARD* board = GetCachedBoard( dataPath );
2424 if( fp->GetReference() == wxT(
"J1" ) )
2431 BOOST_REQUIRE_MESSAGE( j1 !=
nullptr,
"Footprint J1 must exist in BeagleBone_Black_RevC" );
2435 BOOST_CHECK_CLOSE( orientation.
AsDegrees(), 90.0, 0.1 );
2451 BOARD* rawBoard =
nullptr;
2455 rawBoard = plugin.
LoadBoard( dataPath,
nullptr,
nullptr,
nullptr );
2461 catch(
const std::exception& e )
2466 reporter.PrintAllMessages(
"UIImportPath_NullBoard" );
2468 BOOST_REQUIRE_MESSAGE( rawBoard !=
nullptr,
"LoadBoard with nullptr aAppendToMe must return a valid board" );
2470 std::unique_ptr<BOARD> board( rawBoard );
2472 BOOST_CHECK_GT( board->GetNetCount(), 0 );
2473 BOOST_CHECK_GT( board->Footprints().size(), 0 );
2474 BOOST_CHECK_GT( board->Tracks().size(), 0 );
2491 std::vector<std::string>
boards = GetAllBoardFiles();
2493 for(
const std::string& boardPath :
boards )
2495 std::string boardName = std::filesystem::path( boardPath ).filename().string();
2496 BOARD* board = GetCachedBoard( boardPath );
2503 int inconsistentCount = 0;
2507 const bool onBottom = fp->IsFlipped();
2509 for(
PAD*
pad : fp->Pads() )
2514 LSET layers =
pad->GetLayerSet();
2518 if( onBottom && hasTopCopper && !hasBotCopper )
2520 inconsistentCount++;
2522 <<
pad->GetNumber() <<
" is on bottom footprint but SMD "
2523 <<
"pad has F.Cu without B.Cu" );
2525 else if( !onBottom && hasBotCopper && !hasTopCopper )
2527 inconsistentCount++;
2529 <<
pad->GetNumber() <<
" is on top footprint but SMD "
2530 <<
"pad has B.Cu without F.Cu" );
2553 std::vector<std::string>
boards = {
2557 for(
const std::string& boardPath :
boards )
2559 std::string boardName = std::filesystem::path( boardPath ).filename().string();
2560 BOARD* board = GetCachedBoard( boardPath );
2566 int inconsistentCount = 0;
2567 int checkedFootprints = 0;
2572 bool hasSmd =
false;
2575 for(
PAD*
pad : fp->Pads() )
2583 if( !hasSmd || hasTH )
2586 checkedFootprints++;
2588 const bool onBottom = fp->IsFlipped();
2590 for(
BOARD_ITEM* item : fp->GraphicalItems() )
2597 bool wrongSide =
false;
2606 inconsistentCount++;
2608 boardName <<
": " << fp->GetReference() <<
" is "
2609 << ( onBottom ?
"bottom" :
"top" ) <<
"-side SMD but has "
2610 << item->GetClass() <<
" on "
2617 <<
" SMD-only footprints for tech layer consistency" );
2634 BOARD* board = GetCachedBoard( dataPath );
2644 std::vector<SLOT_CHECK> checks = {
2645 { wxS(
"P6" ), wxS(
"20" ) },
2646 { wxS(
"P6" ), wxS(
"21" ) },
2647 { wxS(
"P3" ), wxS(
"6" ) },
2648 { wxS(
"P1" ), wxS(
"1" ) },
2649 { wxS(
"P1" ), wxS(
"2" ) },
2650 { wxS(
"P1" ), wxS(
"3" ) },
2653 for(
const auto& check : checks )
2661 if( candidate->GetReference() == check.fpRef )
2668 BOOST_REQUIRE_MESSAGE( fp !=
nullptr,
"Footprint " << check.fpRef <<
" should exist" );
2672 for(
PAD* candidate : fp->
Pads() )
2674 if( candidate->GetNumber() == check.padNum )
2681 BOOST_REQUIRE_MESSAGE(
pad !=
nullptr,
2682 "Pad " << check.padNum <<
" should exist on " << check.fpRef );
2688 <<
": pad=" << padSize.
x <<
"x" << padSize.
y
2689 <<
" drill=" << drillSize.
x <<
"x" << drillSize.
y );
2692 if( drillSize.
x != drillSize.
y )
2694 bool padIsTaller = ( padSize.
y > padSize.
x );
2695 bool drillIsTaller = ( drillSize.
y > drillSize.
x );
2698 "Drill slot should match pad orientation" );
2713 BOARD* board = GetCachedBoard( dataPath );
2716 int filledZoneCount = 0;
2717 int totalCopperZones = 0;
2719 for(
const ZONE* zone : board->
Zones() )
2721 if( zone->GetIsRuleArea() || zone->GetNetCode() == 0 )
2726 if( zone->IsFilled() )
2731 <<
" layers=" << zone->GetLayerSet().count() );
2736 <<
", filled: " << filledZoneCount );
2738 BOOST_CHECK_GT( totalCopperZones, 0 );
2739 BOOST_CHECK_GT( filledZoneCount, 0 );
2751 BOARD* board = GetCachedBoard( dataPath );
2754 int teardropZones = 0;
2756 for(
const ZONE* zone : board->
Zones() )
2758 if( zone->IsTeardropArea() )
2762 BOOST_CHECK_GT( teardropZones, 1000 );
2766 int padsWithTeardrops = 0;
2771 for(
const PAD*
pad : fp->Pads() )
2775 if(
pad->GetTeardropsEnabled() )
2776 padsWithTeardrops++;
2780 BOOST_CHECK_GT( padsWithTeardrops, 0 );
2781 BOOST_TEST_MESSAGE(
"Pads with teardrops enabled: " << padsWithTeardrops <<
" / " << totalPads );
2783 int viasWithTeardrops = 0;
2793 if(
static_cast<const PCB_VIA*
>( track )->GetTeardropsEnabled() )
2794 viasWithTeardrops++;
2797 BOOST_CHECK_GT( viasWithTeardrops, 0 );
2798 BOOST_TEST_MESSAGE(
"Vias with teardrops enabled: " << viasWithTeardrops <<
" / " << totalVias );
2810 BOARD* board = GetCachedBoard( dataPath );
2813 int teardropZones = 0;
2815 for(
const ZONE* zone : board->
Zones() )
2817 if( zone->IsTeardropArea() )
2823 int padsWithTeardrops = 0;
2827 for(
const PAD*
pad : fp->Pads() )
2829 if(
pad->GetTeardropsEnabled() )
2830 padsWithTeardrops++;
2851 BOARD* rawBoard = plugin.
LoadBoard( dataPath,
nullptr,
nullptr,
nullptr );
2855 std::unique_ptr<BOARD> board( rawBoard );
2858 "m_LegacyNetclassesLoaded must be true after Allegro import" );
2860 "m_LegacyDesignSettingsLoaded must be true after Allegro import" );
2870 std::vector<std::string>
boards = GetAllBoardFiles();
2872 for(
const std::string& boardPath :
boards )
2874 std::string boardName = std::filesystem::path( boardPath ).filename().string();
2875 BOARD* board = GetCachedBoard( boardPath );
2883 const auto& netclasses = netSettings->GetNetclasses();
2887 for(
const auto& [
name, nc] : netclasses )
2890 << nc->GetTrackWidth() <<
" clearance="
2891 << nc->GetClearance() );
2895 if( !
name.StartsWith( wxS(
"DP_" ) )
2896 && !
name.StartsWith( wxS(
"MG_" ) )
2897 && !
name.StartsWith( wxS(
"W" ) ) )
2900 name <<
" should have a track width" );
2902 name <<
" track width should be positive" );
General utilities for PCB file IO for QA programs.
wxString GetNetname() const
std::shared_ptr< NET_SETTINGS > m_NetSettings
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 ZONES & Zones() const
PCB_LAYER_ID GetLayerID(const wxString &aLayerName) const
Return the ID of a layer.
const FOOTPRINTS & Footprints() const
const TRACKS & Tracks() const
const wxString GetLayerName(PCB_LAYER_ID aLayer) const
Return the name of a aLayer.
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
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 size_type GetHeight() const
constexpr coord_type GetLeft() const
constexpr bool Contains(const Vec &aPoint) const
constexpr coord_type GetRight() const
constexpr coord_type GetTop() const
constexpr coord_type GetBottom() const
std::vector< VECTOR2I > GetPolyPoints() const
Duplicate the polygon outlines into a flat list of VECTOR2I points.
const VECTOR2I & GetEnd() const
Return the ending point of the graphic.
const VECTOR2I & GetStart() const
Return the starting point of the graphic.
virtual void SetReporter(REPORTER *aReporter)
Set an optional reporter for warnings/errors.
Hold an error message and may be used when throwing exceptions containing meaningful error messages.
virtual const wxString What() const
A composite of Problem() and Where()
virtual const char * what() const override
std::exception interface, returned as UTF-8
static ALLEGRO_CACHED_LOADER & GetInstance()
Get the singleton instance of the Allegro board cache loader.
BOARD * GetCachedBoard(const std::string &aFilePath)
Get a cached board for the given file path, or load it if not already cached, without forcing a reloa...
Custom REPORTER that captures all messages for later analysis in the unit test framework.
LSET is a set of PCB_LAYER_IDs.
static LSET AllCuMask(int aCuLayerCount)
Return a mask holding the requested number of Cu PCB_LAYER_IDs.
bool Contains(PCB_LAYER_ID aLayer) const
See if the layer set contains a PCB layer.
Handle the data for a net.
BOARD * LoadBoard(const wxString &aFileName, BOARD *aAppendToMe, const std::map< std::string, UTF8 > *aProperties, PROJECT *aProject) override
Load information from some input file format that this PCB_IO implementation knows about into either ...
PCB_LAYER_ID GetLayer() const override
Return the primary layer this item is on.
const VECTOR2I & GetStart() const
const VECTOR2I & GetEnd() const
Handle a list of polygons defining a copper zone.
PCB_LAYER_ID GetFirstLayer() const
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
bool IsFrontLayer(PCB_LAYER_ID aLayerId)
Layer classification: check if it's a front layer.
bool IsBackLayer(PCB_LAYER_ID aLayerId)
Layer classification: check if it's a back layer.
bool IsCopperLayer(int aLayerId)
Test whether a layer is a copper layer.
PCB_LAYER_ID
A quick note on layer IDs:
std::array< SEG, 4 > BoxToSegs(const BOX2I &aBox)
Decompose a BOX2 into four segments.
std::string AllegroBoardDataDir(const std::string &aBoardName)
void PrintBoardStats(const BOARD *aBoard, const std::string &aBoardName)
Print detailed board statistics for debugging using test-framework logging.
std::string AllegroBoardFile(const std::string &aFileName)
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
PAD_ATTRIB
The set of pad shapes, used with PAD::{Set,Get}Attribute().
@ NPTH
like PAD_PTH, but not plated mechanical use only, no connection allowed
@ SMD
Smd pad, appears on the solder paste layer (default)
@ PTH
Plated through hole pad.
Utility functions for working with shapes.
Parse board outline geometry from a .alg ASCII reference file.
void updateBounds(double aX, double aY)
static ALG_OUTLINE_DATA ParseAlgOutlines(const std::string &aPath)
std::vector< OUTLINE_SEGMENT > designOutlineSegments
std::vector< OUTLINE_SEGMENT > outlineSegments
int expectedEdgeCutsSegments() const
Expected number of Edge_Cuts segments when translating to KiCad.
std::set< wxString > netNames
static ALG_REFERENCE_DATA ParseAlgFile(const std::string &aPath)
std::map< wxString, wxString > refDesToSymName
std::set< wxString > refDes
static std::vector< wxString > SplitAlgLine(const wxString &aLine)
std::vector< ALG_ZONE_POLYGON > zonePolygons
static int ParseRecordId(const wxString &aTag)
Extract the integer record ID from a RECORD_TAG field like "36 1 0".
std::map< wxString, std::set< wxString > > netToRefDes
Parse a FabMaster .alg file and extract reference data for cross-validation.
void AddPoint(double aX, double aY)
Data for parameterized all-boards test.
Fixture for comprehensive board import tests with error capturing.
PCB_IO_ALLEGRO m_allegroPlugin
BOARD * GetCachedBoard(const std::string &aFilePath)
Get a cached board, loading it on first access.
static std::vector< std::string > GetAllBoardFiles()
Get list of all .brd files in the Allegro test data directory.
ALLEGRO_COMPREHENSIVE_FIXTURE()
std::unique_ptr< BOARD > LoadAllegroBoard(const std::string &aFileName)
PCB_IO_ALLEGRO m_allegroPlugin
BOOST_AUTO_TEST_CASE(FootprintRefDes)
Test that footprints have valid reference designators.
static void AssertOutlineValid(const BOARD &aBoard)
static std::vector< BRD_ALG_PAIR > getBoardsWithAlg()
Get a list of all board files in the test data that have a corresponding .alg reference file.
static unsigned CountOutlineElements(const BOARD &board)
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
IbisParser parser & reporter
BOOST_CHECK_MESSAGE(totalMismatches==0, std::to_string(totalMismatches)+" board(s) with strategy disagreements")
std::vector< BOARD_BEST > boards
BOOST_TEST_MESSAGE("\n=== Real-World Polygon PIP Benchmark ===\n"<< formatTable(table))
BOOST_TEST_CONTEXT("Test Clearance")
BOOST_CHECK_EQUAL(result, "25.4")
@ PCB_SHAPE_T
class PCB_SHAPE, a segment not on copper layers
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
@ PCB_TEXT_T
class PCB_TEXT, text on a layer
@ 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