56using LAYER_SPAN = std::pair<int, int>;
59using SPAN_DRILL_TABLE = std::map<LAYER_SPAN, std::map<int, int>>;
63int toMicrons(
int aNanometres )
65 return static_cast<int>( std::llround( aNanometres / 1000.0 ) );
74std::pair<LAYER_SPAN, std::map<int, int>> parseDrillFile(
const std::filesystem::path& aPath )
76 static const std::regex spanRe( R
"(-(\d+)-(\d+)\.drl$)" );
77 static const std::regex toolDefRe( R
"(^T(\d+)C([0-9.]+))" );
78 static const std::regex toolSelRe( R
"(^T(\d+)\s*$)" );
79 static const std::regex coordRe( R
"(^X.*Y)" );
80 static const std::regex repeatRe( R
"(^R(\d+))" );
82 LAYER_SPAN span{ 0, 0 };
84 std::string name = aPath.filename().string();
86 if( std::regex_search(
name, m, spanRe ) )
87 span = { std::stoi( m[1] ), std::stoi( m[2] ) };
89 std::map<int, int> toolDiaUm;
90 std::map<int, int> diaToCount;
93 std::ifstream file( aPath );
96 while( std::getline( file, line ) )
99 while( !line.empty() && ( line.back() ==
'\r' || line.back() ==
'\n' ) )
102 if( std::regex_search( line, m, toolDefRe ) )
104 int tool = std::stoi( m[1] );
105 double mm = std::stod( m[2] );
106 toolDiaUm[tool] =
static_cast<int>( std::llround( mm * 1000.0 ) );
108 else if( std::regex_search( line, m, toolSelRe ) )
110 activeTool = std::stoi( m[1] );
112 else if( std::regex_search( line, m, coordRe ) )
114 if( activeTool >= 0 && toolDiaUm.count( activeTool ) )
115 diaToCount[toolDiaUm[activeTool]] += 1;
117 else if( std::regex_search( line, m, repeatRe ) )
119 if( activeTool >= 0 && toolDiaUm.count( activeTool ) )
120 diaToCount[toolDiaUm[activeTool]] += std::stoi( m[1] );
124 return { span, diaToCount };
129SPAN_DRILL_TABLE loadDrillGroundTruth(
const std::string& aBoardDir )
131 SPAN_DRILL_TABLE
table;
133 for(
const auto& entry : std::filesystem::directory_iterator( aBoardDir ) )
135 if( entry.path().extension() !=
".drl" )
138 auto [span, counts] = parseDrillFile( entry.path() );
140 for(
const auto& [dia, count] : counts )
141 table[span][dia] += count;
149std::map<PCB_LAYER_ID, int> buildCopperOrdinal(
const BOARD& aBoard )
151 std::map<PCB_LAYER_ID, int> ordinal;
155 ordinal[layer] = idx++;
162LAYER_SPAN viaLayerSpan(
const PCB_VIA& aVia,
const std::map<PCB_LAYER_ID, int>& aOrdinal,
int aTotal )
166 auto it = aOrdinal.find( aLayer );
167 return it != aOrdinal.end() ? it->second : aDefault;
174 std::swap(
top, bot );
181SPAN_DRILL_TABLE collectBoardDrillTable(
const BOARD& aBoard )
183 SPAN_DRILL_TABLE
table;
184 std::map<PCB_LAYER_ID, int> ordinal = buildCopperOrdinal( aBoard );
193 int drill = toMicrons(
via->GetDrillValue() );
198 table[viaLayerSpan( *
via, ordinal, total )][drill] += 1;
203 for(
PAD*
pad : fp->Pads() )
213 int drill = toMicrons(
pad->GetDrillSizeX() );
219 table[{ 1, total }][drill] += 1;
228std::string describeTable(
const SPAN_DRILL_TABLE& aTable )
232 for(
const auto& [span, sizes] : aTable )
234 for(
const auto& [dia, count] : sizes )
236 out +=
" span " + std::to_string( span.first ) +
"-" + std::to_string( span.second )
237 +
" drill " + std::to_string( dia ) +
"um x " + std::to_string( count ) +
"\n";
245struct DRILL_SPAN_FIXTURE
250 m_groundTruth = loadDrillGroundTruth( m_boardDir );
252 m_boardDir +
"20250806_UNOQ.brd" );
255 std::string m_boardDir;
256 SPAN_DRILL_TABLE m_groundTruth;
257 BOARD* m_board =
nullptr;
263BOOST_FIXTURE_TEST_SUITE( AllegroDrillSpans, DRILL_SPAN_FIXTURE )
273 BOOST_REQUIRE_MESSAGE( m_board !=
nullptr,
"20250806_UNOQ.brd failed to import" );
276 BOOST_REQUIRE_MESSAGE( m_groundTruth.size() >= 6,
277 "Expected at least 6 drill spans, parsed " << m_groundTruth.size() );
279 BOOST_TEST_MESSAGE(
"Ground-truth drill table:\n" << describeTable( m_groundTruth ) );
293 std::map<VIATYPE, int> typeCounts;
294 std::set<LAYER_SPAN> viaSpans;
295 std::map<PCB_LAYER_ID, int> ordinal = buildCopperOrdinal( *m_board );
296 const int total = m_board->GetCopperLayerCount();
298 for(
PCB_TRACK* track : m_board->Tracks() )
304 typeCounts[
via->GetViaType()]++;
305 viaSpans.insert( viaLayerSpan( *
via, ordinal, total ) );
313 std::string spanList;
315 for(
const LAYER_SPAN& s : viaSpans )
316 spanList +=
" " + std::to_string( s.first ) +
"-" + std::to_string( s.second );
326 const std::set<LAYER_SPAN> expectedViaSpans = { { 1, 2 }, { 1, 8 }, { 2, 3 },
327 { 3, 6 }, { 6, 7 }, { 7, 8 } };
329 BOOST_CHECK_MESSAGE( viaSpans == expectedViaSpans,
"Unexpected via span set:" << spanList );
341 SPAN_DRILL_TABLE boardTable = collectBoardDrillTable( *m_board );
346 auto keySet = [](
const SPAN_DRILL_TABLE& aTable )
348 std::set<std::tuple<int, int, int>> keys;
350 for(
const auto& [span, sizes] : aTable )
351 for(
const auto& [dia, count] : sizes )
352 keys.insert( { span.first, span.second, dia } );
357 std::set<std::tuple<int, int, int>> truthKeys = keySet( m_groundTruth );
358 std::set<std::tuple<int, int, int>> boardKeys = keySet( boardTable );
360 std::vector<std::tuple<int, int, int>> missing;
361 std::vector<std::tuple<int, int, int>> extra;
363 std::set_difference( truthKeys.begin(), truthKeys.end(), boardKeys.begin(), boardKeys.end(),
364 std::back_inserter( missing ) );
365 std::set_difference( boardKeys.begin(), boardKeys.end(), truthKeys.begin(), truthKeys.end(),
366 std::back_inserter( extra ) );
368 for(
const auto& [t, b, d] : missing )
369 BOOST_TEST_MESSAGE(
" MISSING from board: span " << t <<
"-" << b <<
" drill " << d <<
"um" );
371 for(
const auto& [t, b, d] : extra )
372 BOOST_TEST_MESSAGE(
" EXTRA in board: span " << t <<
"-" << b <<
" drill " << d <<
"um" );
375 missing.size() <<
" (span, drill) combinations from the .drl files are "
376 "absent from the imported board" );
378 extra.size() <<
" (span, drill) combinations in the imported board do not "
379 "exist in any .drl file" );
384 for(
const auto& [span, sizes] : m_groundTruth )
386 for(
const auto& [dia, truthCount] : sizes )
390 if(
auto sIt = boardTable.find( span ); sIt != boardTable.end() )
392 if(
auto dIt = sIt->second.find( dia ); dIt != sIt->second.end() )
393 boardCount = dIt->second;
396 const int tolerance = std::max( 5, truthCount / 100 );
399 "span " << span.first <<
"-" << span.second <<
" drill " << dia
400 <<
"um: board has " << boardCount <<
" holes, .drl has "
401 << 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)