60using LAYER_SPAN = std::pair<int, int>;
63using SPAN_DRILL_TABLE = std::map<LAYER_SPAN, std::map<int, int>>;
67int toMicrons(
int aNanometres )
69 return static_cast<int>( std::llround( aNanometres / 1000.0 ) );
78std::pair<LAYER_SPAN, std::map<int, int>> parseDrillFile(
const std::filesystem::path& aPath )
80 static const std::regex spanRe( R
"(-(\d+)-(\d+)\.drl$)" );
81 static const std::regex toolDefRe( R
"(^T(\d+)C([0-9.]+))" );
82 static const std::regex toolSelRe( R
"(^T(\d+)\s*$)" );
83 static const std::regex coordRe( R
"(^X.*Y)" );
84 static const std::regex repeatRe( R
"(^R(\d+))" );
86 LAYER_SPAN span{ 0, 0 };
88 std::string name = aPath.filename().string();
90 if( std::regex_search(
name, m, spanRe ) )
91 span = { std::stoi( m[1] ), std::stoi( m[2] ) };
93 std::map<int, int> toolDiaUm;
94 std::map<int, int> diaToCount;
97 std::ifstream file( aPath );
100 while( std::getline( file, line ) )
103 while( !line.empty() && ( line.back() ==
'\r' || line.back() ==
'\n' ) )
106 if( std::regex_search( line, m, toolDefRe ) )
108 int tool = std::stoi( m[1] );
109 double mm = std::stod( m[2] );
110 toolDiaUm[tool] =
static_cast<int>( std::llround( mm * 1000.0 ) );
112 else if( std::regex_search( line, m, toolSelRe ) )
114 activeTool = std::stoi( m[1] );
116 else if( std::regex_search( line, m, coordRe ) )
118 if( activeTool >= 0 && toolDiaUm.count( activeTool ) )
119 diaToCount[toolDiaUm[activeTool]] += 1;
121 else if( std::regex_search( line, m, repeatRe ) )
123 if( activeTool >= 0 && toolDiaUm.count( activeTool ) )
124 diaToCount[toolDiaUm[activeTool]] += std::stoi( m[1] );
128 return { span, diaToCount };
133SPAN_DRILL_TABLE loadDrillGroundTruth(
const std::string& aBoardDir )
135 SPAN_DRILL_TABLE
table;
137 for(
const auto& entry : std::filesystem::directory_iterator( aBoardDir ) )
139 if( entry.path().extension() !=
".drl" )
142 auto [span, counts] = parseDrillFile( entry.path() );
144 for(
const auto& [dia, count] : counts )
145 table[span][dia] += count;
153std::map<PCB_LAYER_ID, int> buildCopperOrdinal(
const BOARD& aBoard )
155 std::map<PCB_LAYER_ID, int> ordinal;
159 ordinal[layer] = idx++;
166LAYER_SPAN viaLayerSpan(
const PCB_VIA& aVia,
const std::map<PCB_LAYER_ID, int>& aOrdinal,
int aTotal )
170 auto it = aOrdinal.find( aLayer );
171 return it != aOrdinal.end() ? it->second : aDefault;
178 std::swap(
top, bot );
185SPAN_DRILL_TABLE collectBoardDrillTable(
const BOARD& aBoard )
187 SPAN_DRILL_TABLE
table;
188 std::map<PCB_LAYER_ID, int> ordinal = buildCopperOrdinal( aBoard );
197 int drill = toMicrons(
via->GetDrillValue() );
202 table[viaLayerSpan( *
via, ordinal, total )][drill] += 1;
207 for(
PAD*
pad : fp->Pads() )
217 int drill = toMicrons(
pad->GetDrillSizeX() );
223 table[{ 1, total }][drill] += 1;
232std::string describeTable(
const SPAN_DRILL_TABLE& aTable )
236 for(
const auto& [span, sizes] : aTable )
238 for(
const auto& [dia, count] : sizes )
240 out +=
" span " + std::to_string( span.first ) +
"-" + std::to_string( span.second )
241 +
" drill " + std::to_string( dia ) +
"um x " + std::to_string( count ) +
"\n";
249struct DRILL_SPAN_FIXTURE
254 m_groundTruth = loadDrillGroundTruth( m_boardDir );
256 m_boardDir +
"20250806_UNOQ.brd" );
259 std::string m_boardDir;
260 SPAN_DRILL_TABLE m_groundTruth;
261 BOARD* m_board =
nullptr;
267BOOST_FIXTURE_TEST_SUITE( AllegroDrillSpans, DRILL_SPAN_FIXTURE )
277 BOOST_REQUIRE_MESSAGE( m_board !=
nullptr,
"20250806_UNOQ.brd failed to import" );
280 BOOST_REQUIRE_MESSAGE( m_groundTruth.size() >= 6,
281 "Expected at least 6 drill spans, parsed " << m_groundTruth.size() );
283 BOOST_TEST_MESSAGE(
"Ground-truth drill table:\n" << describeTable( m_groundTruth ) );
297 std::map<VIATYPE, int> typeCounts;
298 std::set<LAYER_SPAN> viaSpans;
299 std::map<PCB_LAYER_ID, int> ordinal = buildCopperOrdinal( *m_board );
300 const int total = m_board->GetCopperLayerCount();
302 for(
PCB_TRACK* track : m_board->Tracks() )
308 typeCounts[
via->GetViaType()]++;
309 viaSpans.insert( viaLayerSpan( *
via, ordinal, total ) );
317 std::string spanList;
319 for(
const LAYER_SPAN& s : viaSpans )
320 spanList +=
" " + std::to_string( s.first ) +
"-" + std::to_string( s.second );
330 const std::set<LAYER_SPAN> expectedViaSpans = { { 1, 2 }, { 1, 8 }, { 2, 3 },
331 { 3, 6 }, { 6, 7 }, { 7, 8 } };
333 BOOST_CHECK_MESSAGE( viaSpans == expectedViaSpans,
"Unexpected via span set:" << spanList );
345 SPAN_DRILL_TABLE boardTable = collectBoardDrillTable( *m_board );
350 auto keySet = [](
const SPAN_DRILL_TABLE& aTable )
352 std::set<std::tuple<int, int, int>> keys;
354 for(
const auto& [span, sizes] : aTable )
355 for(
const auto& [dia, count] : sizes )
356 keys.insert( { span.first, span.second, dia } );
361 std::set<std::tuple<int, int, int>> truthKeys = keySet( m_groundTruth );
362 std::set<std::tuple<int, int, int>> boardKeys = keySet( boardTable );
364 std::vector<std::tuple<int, int, int>> missing;
365 std::vector<std::tuple<int, int, int>> extra;
367 std::set_difference( truthKeys.begin(), truthKeys.end(), boardKeys.begin(), boardKeys.end(),
368 std::back_inserter( missing ) );
369 std::set_difference( boardKeys.begin(), boardKeys.end(), truthKeys.begin(), truthKeys.end(),
370 std::back_inserter( extra ) );
372 for(
const auto& [t, b, d] : missing )
373 BOOST_TEST_MESSAGE(
" MISSING from board: span " << t <<
"-" << b <<
" drill " << d <<
"um" );
375 for(
const auto& [t, b, d] : extra )
376 BOOST_TEST_MESSAGE(
" EXTRA in board: span " << t <<
"-" << b <<
" drill " << d <<
"um" );
379 missing.size() <<
" (span, drill) combinations from the .drl files are "
380 "absent from the imported board" );
382 extra.size() <<
" (span, drill) combinations in the imported board do not "
383 "exist in any .drl file" );
388 for(
const auto& [span, sizes] : m_groundTruth )
390 for(
const auto& [dia, truthCount] : sizes )
394 if(
auto sIt = boardTable.find( span ); sIt != boardTable.end() )
396 if(
auto dIt = sIt->second.find( dia ); dIt != sIt->second.end() )
397 boardCount = dIt->second;
400 const int tolerance = std::max( 5, truthCount / 100 );
403 "span " << span.first <<
"-" << span.second <<
" drill " << dia
404 <<
"um: board has " << boardCount <<
" holes, .drl has "
405 << truthCount <<
" (tolerance " << tolerance <<
")" );
Information pertinent to a Pcbnew printed circuit board.
int GetCopperLayerCount() const
const FOOTPRINTS & Footprints() const
const TRACKS & Tracks() const
const LSET & GetEnabledLayers() const
A proxy function that calls the corresponding function in m_BoardSettings.
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...
LSEQ CuStack() const
Return a sequence of copper layers in starting from the front/top and extending to the back/bottom.
PCB_LAYER_ID BottomLayer() const
PCB_LAYER_ID TopLayer() const
PCB_LAYER_ID
A quick note on layer IDs:
std::string AllegroBoardDataDir(const std::string &aBoardName)
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.
BOOST_AUTO_TEST_CASE(GroundTruthAndBoardLoad)
The fixture and its ground truth must be well-formed before the cross-validation tests have any meani...
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
KIBIS top(path, &reporter)
BOOST_CHECK_MESSAGE(totalMismatches==0, std::to_string(totalMismatches)+" board(s) with strategy disagreements")
BOOST_TEST_MESSAGE("\n=== Real-World Polygon PIP Benchmark ===\n"<< formatTable(table))
std::vector< std::vector< std::string > > table
BOOST_CHECK_EQUAL(result, "25.4")
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)