26#include <nlohmann/json.hpp>
39namespace fs = std::filesystem;
50 SHAPE_POLY_SET polySet;
57 std::vector<ZONE_ENTRY> zones;
67 int triangleCount = 0;
69 double originalArea = 0.0;
70 double triangulatedArea = 0.0;
71 double areaCoverage = 0.0;
72 double meanTriArea = 0.0;
73 double stddevTriArea = 0.0;
74 int spikeyTriangles = 0;
75 double spikeyRatio = 0.0;
79 double minAnglePctl1 = 0.0;
80 double minAnglePctl5 = 0.0;
81 double minAnglePctl50 = 0.0;
82 int trisBelow5Deg = 0;
83 int trisBelow10Deg = 0;
84 int trisBelow15Deg = 0;
85 double meanRadiusRatio = 0.0;
89 std::vector<double> minAngles;
97 int triangleCount = 0;
98 double areaCoverage = 0.0;
99 double spikeyRatio = 0.0;
100 double stddevTriArea = 0.0;
101 int spikeyTriangles = 0;
102 double originalArea = 0.0;
103 double minAnglePctl1 = 0.0;
104 double minAnglePctl5 = 0.0;
105 int trisBelow10Deg = 0;
112 std::vector<BASELINE_ZONE> zones;
118 std::map<std::string, BASELINE_BOARD> boards;
119 int totalTriangles = 0;
120 int totalSpikeyTri = 0;
121 double spikeyRatio = 0.0;
137struct ZONE_COMPARISON
144 int baseTriangles = 0;
145 int curTriangles = 0;
146 double baseSpikeyRatio = 0.0;
147 double curSpikeyRatio = 0.0;
148 double baseStddev = 0.0;
149 double curStddev = 0.0;
150 double baseCoverage = 0.0;
151 double curCoverage = 0.0;
152 double baseMinAngleP5 = 0.0;
153 double curMinAngleP5 = 0.0;
154 int baseBelow10Deg = 0;
155 int curBelow10Deg = 0;
156 int64_t baseTimeUs = 0;
157 int64_t curTimeUs = 0;
159 double spikeyDeltaPp()
const {
return ( curSpikeyRatio - baseSpikeyRatio ) * 100.0; }
162 double minAngleP5DeltaDeg()
const {
return curMinAngleP5 - baseMinAngleP5; }
164 double triangleDeltaPct()
const
166 if( baseTriangles == 0 )
167 return curTriangles == 0 ? 0.0 : 100.0;
169 return ( curTriangles - baseTriangles ) /
static_cast<double>( baseTriangles ) * 100.0;
172 double stddevDeltaPct()
const
174 if( baseStddev == 0.0 )
175 return curStddev == 0.0 ? 0.0 : 100.0;
177 return ( curStddev - baseStddev ) / baseStddev * 100.0;
180 double timeDeltaPct()
const
182 if( baseTimeUs == 0 )
183 return curTimeUs == 0 ? 0.0 : 100.0;
185 return static_cast<double>( curTimeUs - baseTimeUs ) /
static_cast<double>( baseTimeUs )
191BASELINE_DATA LoadBaseline(
const fs::path& aJsonPath )
193 BASELINE_DATA baseline;
195 if( !fs::exists( aJsonPath ) )
198 std::ifstream file( aJsonPath );
200 if( !file.is_open() )
207 j = nlohmann::json::parse( file );
209 catch(
const nlohmann::json::exception& )
214 if( j.contains(
"metadata" ) )
216 baseline.boardCount = j[
"metadata"].value(
"board_count", 0 );
217 baseline.zoneCount = j[
"metadata"].value(
"zone_count", 0 );
220 if( j.contains(
"global" ) )
222 baseline.totalTriangles = j[
"global"].value(
"total_triangles", 0 );
223 baseline.totalSpikeyTri = j[
"global"].value(
"total_spikey_triangles", 0 );
224 baseline.spikeyRatio = j[
"global"].value(
"spikey_ratio", 0.0 );
227 if( j.contains(
"boards" ) )
229 for(
const auto& boardJson : j[
"boards"] )
231 std::string source = boardJson.value(
"source",
"" );
232 BASELINE_BOARD board;
234 if( boardJson.contains(
"zones" ) )
236 for(
const auto& zoneJson : boardJson[
"zones"] )
239 zone.layer = zoneJson.value(
"layer",
"" );
240 zone.net = zoneJson.value(
"net",
"" );
241 zone.triangleCount = zoneJson.value(
"triangle_count", 0 );
242 zone.areaCoverage = zoneJson.value(
"area_coverage", 0.0 );
243 zone.spikeyRatio = zoneJson.value(
"spikey_ratio", 0.0 );
244 zone.stddevTriArea = zoneJson.value(
"stddev_triangle_area_nm2", 0.0 );
245 zone.spikeyTriangles = zoneJson.value(
"spikey_triangles", 0 );
246 zone.originalArea = zoneJson.value(
"original_area_nm2", 0.0 );
247 zone.minAnglePctl1 = zoneJson.value(
"min_angle_p1_deg", 0.0 );
248 zone.minAnglePctl5 = zoneJson.value(
"min_angle_p5_deg", 0.0 );
249 zone.trisBelow10Deg = zoneJson.value(
"tris_below_10deg", 0 );
250 zone.timeUs = zoneJson.value(
"time_us", (int64_t) 0 );
251 board.zones.push_back( zone );
255 baseline.boards[source] = std::move( board );
259 baseline.valid =
true;
264bool ParsePolyFile(
const fs::path& aPath, BOARD_ENTRY& aBoard )
266 std::ifstream file( aPath );
268 if( !file.is_open() )
271 std::string content( ( std::istreambuf_iterator<char>( file ) ),
272 std::istreambuf_iterator<char>() );
274 size_t srcStart = content.find(
"(source \"" );
276 if( srcStart != std::string::npos )
279 size_t srcEnd = content.find(
"\")", srcStart );
281 if( srcEnd != std::string::npos )
282 aBoard.source = content.substr( srcStart, srcEnd - srcStart );
287 while( ( zonePos = content.find(
"(zone (layer \"", zonePos ) ) != std::string::npos )
291 size_t layerStart = zonePos + 14;
292 size_t layerEnd = content.find(
"\")", layerStart );
293 entry.layer = content.substr( layerStart, layerEnd - layerStart );
295 size_t netStart = content.find(
"(net \"", layerEnd );
297 if( netStart != std::string::npos )
300 size_t netEnd = content.find(
"\")", netStart );
301 entry.net = content.substr( netStart, netEnd - netStart );
304 size_t ocStart = content.find(
"(outline_count ", layerEnd );
306 if( ocStart != std::string::npos )
309 entry.outlineCount = std::stoi( content.substr( ocStart ) );
312 size_t vcStart = content.find(
"(vertex_count ", layerEnd );
314 if( vcStart != std::string::npos )
317 entry.vertexCount = std::stoi( content.substr( vcStart ) );
320 size_t polysetStart = content.find(
"polyset ", zonePos );
322 if( polysetStart != std::string::npos )
324 std::string remainder = content.substr( polysetStart );
325 std::stringstream ss( remainder );
327 if( entry.polySet.
Parse( ss ) )
328 aBoard.zones.push_back( std::move( entry ) );
331 zonePos = layerEnd + 1;
334 return !aBoard.zones.empty();
339constexpr int TIMING_ITERATIONS = 5;
349 double s = ( ab + bc + ca ) / 2.0;
351 if( s <= 0.0 || ab <= 0.0 || bc <= 0.0 || ca <= 0.0 )
354 double area = std::sqrt( std::max( 0.0, s * ( s - ab ) * ( s - bc ) * ( s - ca ) ) );
356 return 8.0 * area * area / ( s * ab * bc * ca );
361double Percentile(
const std::vector<double>& aSorted,
double aPct )
363 if( aSorted.empty() )
366 if( aSorted.size() == 1 )
367 return aSorted.front();
369 double rank = aPct / 100.0 *
static_cast<double>( aSorted.size() - 1 );
370 size_t lo =
static_cast<size_t>( std::floor( rank ) );
371 size_t hi =
static_cast<size_t>( std::ceil( rank ) );
372 double frac = rank -
static_cast<double>( lo );
374 return aSorted[lo] + frac * ( aSorted[hi] - aSorted[lo] );
378ZONE_STATS ComputeZoneStats( ZONE_ENTRY& aZone )
381 stats.layer = aZone.layer;
382 stats.net = aZone.net;
383 stats.outlineCount = aZone.outlineCount;
384 stats.vertexCount = aZone.vertexCount;
385 stats.originalArea = aZone.polySet.
Area();
390 std::vector<int64_t> timings;
392 for(
int iter = 0; iter < TIMING_ITERATIONS; iter++ )
397 cold.CacheTriangulation();
399 timings.push_back(
static_cast<int64_t
>( timer.
msecs() * 1000.0 ) );
402 std::sort( timings.begin(), timings.end() );
403 stats.timeUs = timings[timings.size() / 2];
408 std::vector<double> triAreas;
414 for(
const auto& tri : triPoly->Triangles() )
415 triAreas.push_back( tri.Area() );
418 stats.triangleCount =
static_cast<int>( triAreas.size() );
419 stats.triangulatedArea = std::accumulate( triAreas.begin(), triAreas.end(), 0.0 );
421 if( stats.originalArea > 0.0 )
422 stats.areaCoverage = stats.triangulatedArea / stats.originalArea;
424 if( !triAreas.empty() )
426 stats.meanTriArea = stats.triangulatedArea /
static_cast<double>( triAreas.size() );
428 double sumSqDiff = 0.0;
430 for(
double a : triAreas )
432 double diff = a - stats.meanTriArea;
433 sumSqDiff += diff * diff;
436 stats.stddevTriArea = std::sqrt( sumSqDiff /
static_cast<double>( triAreas.size() ) );
439 double radiusRatioSum = 0.0;
445 for(
const auto& tri : triPoly->Triangles() )
452 stats.spikeyTriangles++;
455 stats.minAngles.push_back( minAngle );
458 stats.trisBelow5Deg++;
460 if( minAngle < 10.0 )
461 stats.trisBelow10Deg++;
463 if( minAngle < 15.0 )
464 stats.trisBelow15Deg++;
466 radiusRatioSum += TriangleRadiusRatio( pa, pb, pc );
470 if( stats.triangleCount > 0 )
472 stats.spikeyRatio =
static_cast<double>( stats.spikeyTriangles ) / stats.triangleCount;
473 stats.meanRadiusRatio = radiusRatioSum / stats.triangleCount;
475 std::vector<double> sorted = stats.minAngles;
476 std::sort( sorted.begin(), sorted.end() );
477 stats.minAnglePctl1 = Percentile( sorted, 1.0 );
478 stats.minAnglePctl5 = Percentile( sorted, 5.0 );
479 stats.minAnglePctl50 = Percentile( sorted, 50.0 );
486nlohmann::json ZoneStatsToJson(
const ZONE_STATS& aStats )
489 j[
"layer"] = aStats.layer;
490 j[
"net"] = aStats.net;
491 j[
"outline_count"] = aStats.outlineCount;
492 j[
"vertex_count"] = aStats.vertexCount;
493 j[
"triangle_count"] = aStats.triangleCount;
494 j[
"time_us"] = aStats.timeUs;
495 j[
"original_area_nm2"] = aStats.originalArea;
496 j[
"triangulated_area_nm2"] = aStats.triangulatedArea;
497 j[
"area_coverage"] = aStats.areaCoverage;
498 j[
"mean_triangle_area_nm2"] = aStats.meanTriArea;
499 j[
"stddev_triangle_area_nm2"] = aStats.stddevTriArea;
500 j[
"spikey_triangles"] = aStats.spikeyTriangles;
501 j[
"spikey_ratio"] = aStats.spikeyRatio;
502 j[
"min_angle_p1_deg"] = aStats.minAnglePctl1;
503 j[
"min_angle_p5_deg"] = aStats.minAnglePctl5;
504 j[
"min_angle_p50_deg"] = aStats.minAnglePctl50;
505 j[
"tris_below_5deg"] = aStats.trisBelow5Deg;
506 j[
"tris_below_10deg"] = aStats.trisBelow10Deg;
507 j[
"tris_below_15deg"] = aStats.trisBelow15Deg;
508 j[
"mean_radius_ratio"] = aStats.meanRadiusRatio;
513ZONE_COMPARISON CompareZone(
const std::string& aSource,
const ZONE_STATS& aCurrent,
514 const BASELINE_ZONE* aBaseline )
517 cmp.source = aSource;
518 cmp.layer = aCurrent.layer;
519 cmp.net = aCurrent.net;
520 cmp.curTriangles = aCurrent.triangleCount;
521 cmp.curSpikeyRatio = aCurrent.spikeyRatio;
522 cmp.curStddev = aCurrent.stddevTriArea;
523 cmp.curCoverage = aCurrent.areaCoverage;
524 cmp.curMinAngleP5 = aCurrent.minAnglePctl5;
525 cmp.curBelow10Deg = aCurrent.trisBelow10Deg;
526 cmp.curTimeUs = aCurrent.timeUs;
530 cmp.type = CHANGE_TYPE::UNCHANGED;
534 cmp.baseTriangles = aBaseline->triangleCount;
535 cmp.baseSpikeyRatio = aBaseline->spikeyRatio;
536 cmp.baseStddev = aBaseline->stddevTriArea;
537 cmp.baseCoverage = aBaseline->areaCoverage;
538 cmp.baseMinAngleP5 = aBaseline->minAnglePctl5;
539 cmp.baseBelow10Deg = aBaseline->trisBelow10Deg;
540 cmp.baseTimeUs = aBaseline->timeUs;
542 bool coverageBroke = aCurrent.originalArea > 0.0
543 && ( aCurrent.areaCoverage < 0.99 || aCurrent.areaCoverage > 1.01 );
544 bool newFailure = aCurrent.triangleCount == 0 && aBaseline->triangleCount > 0
545 && aCurrent.originalArea > 0.0;
547 if( coverageBroke || newFailure )
549 cmp.type = CHANGE_TYPE::BREAKING;
566 double regressScore = ( cmp.baseMinAngleP5 - cmp.curMinAngleP5 )
567 + ( cmp.curBelow10Deg - cmp.baseBelow10Deg )
568 / std::max( 1.0,
static_cast<double>( cmp.baseTriangles ) )
574 constexpr int64_t SPEED_FLOOR_US = 100;
575 bool speedMeasurable = std::max( cmp.baseTimeUs, cmp.curTimeUs ) >= SPEED_FLOOR_US;
577 std::vector<AXIS> axes = {
578 { speedMeasurable ? cmp.timeDeltaPct() : 0.0, 15.0,
true },
579 { regressScore, 1.0,
true },
580 { cmp.triangleDeltaPct(), 5.0,
true },
583 cmp.type = CHANGE_TYPE::UNCHANGED;
585 for(
const AXIS& axis : axes )
587 double signedWorse = axis.lowerBetter ? axis.delta : -axis.delta;
589 if( signedWorse > axis.threshold )
591 cmp.type = CHANGE_TYPE::REGRESSION;
595 if( signedWorse < -axis.threshold )
597 cmp.type = CHANGE_TYPE::IMPROVEMENT;
606std::string FormatSign(
double aValue,
const std::string& aSuffix )
608 std::ostringstream ss;
609 ss << std::fixed << std::setprecision( 1 );
614 ss << aValue << aSuffix;
619std::string FormatZoneDetail(
const ZONE_COMPARISON& aCmp )
621 std::ostringstream ss;
622 ss <<
" " << aCmp.source <<
" " << aCmp.layer <<
" \"" << aCmp.net <<
"\"" <<
"\n";
623 ss << std::fixed << std::setprecision( 1 );
624 ss <<
" time: " << FormatSign( aCmp.timeDeltaPct(),
"%" );
625 ss <<
" minAngleP5: " << aCmp.baseMinAngleP5 <<
" -> " << aCmp.curMinAngleP5
626 <<
" deg (" << FormatSign( aCmp.minAngleP5DeltaDeg(),
"deg" ) <<
")";
627 ss <<
" <10deg: " << aCmp.baseBelow10Deg <<
" -> " << aCmp.curBelow10Deg;
628 ss <<
"\n spikey: " << ( aCmp.baseSpikeyRatio * 100.0 ) <<
"% -> "
629 << ( aCmp.curSpikeyRatio * 100.0 ) <<
"% (" << FormatSign( aCmp.spikeyDeltaPp(),
"pp" )
631 ss <<
" triangles: " << aCmp.baseTriangles <<
" -> " << aCmp.curTriangles
632 <<
" (" << FormatSign( aCmp.triangleDeltaPct(),
"%" ) <<
")";
634 if( aCmp.baseStddev > 0.0 || aCmp.curStddev > 0.0 )
636 ss <<
" stddev: " << FormatSign( aCmp.stddevDeltaPct(),
"%" );
643void OutputComparisonReport(
const BASELINE_DATA& aBaseline,
644 const std::vector<ZONE_COMPARISON>& aComparisons,
645 int aTotalTriangles,
int aTotalSpikeyTri,
int aTotalZones )
647 std::vector<ZONE_COMPARISON> breaking;
648 std::vector<ZONE_COMPARISON> regressions;
649 std::vector<ZONE_COMPARISON> improvements;
652 for(
const auto& cmp : aComparisons )
656 case CHANGE_TYPE::BREAKING: breaking.push_back( cmp );
break;
657 case CHANGE_TYPE::REGRESSION: regressions.push_back( cmp );
break;
658 case CHANGE_TYPE::IMPROVEMENT: improvements.push_back( cmp );
break;
659 case CHANGE_TYPE::UNCHANGED: unchanged++;
break;
663 std::sort( improvements.begin(), improvements.end(),
664 [](
const ZONE_COMPARISON& a,
const ZONE_COMPARISON& b )
666 return a.spikeyDeltaPp() < b.spikeyDeltaPp();
669 std::sort( regressions.begin(), regressions.end(),
670 [](
const ZONE_COMPARISON& a,
const ZONE_COMPARISON& b )
672 return a.spikeyDeltaPp() > b.spikeyDeltaPp();
675 std::ostringstream report;
676 report << std::fixed << std::setprecision( 1 );
678 report <<
"\n=== Triangulation Comparison vs Baseline ===\n\n";
680 report <<
"Baseline: " << aBaseline.boardCount <<
" boards, "
681 << aBaseline.zoneCount <<
" zones\n";
682 report <<
"Current: " << aTotalZones <<
" zones\n\n";
684 double baseSpikey = aBaseline.spikeyRatio * 100.0;
685 double curSpikey = aTotalTriangles > 0
686 ?
static_cast<double>( aTotalSpikeyTri ) / aTotalTriangles * 100.0
689 report <<
"Global:\n";
690 report <<
" Triangles: " << aBaseline.totalTriangles <<
" -> " << aTotalTriangles
691 <<
" (" << FormatSign(
692 aTotalTriangles - aBaseline.totalTriangles == 0
694 : ( aTotalTriangles - aBaseline.totalTriangles )
695 /
static_cast<double>( aBaseline.totalTriangles )
699 report <<
" Spikey: " << baseSpikey <<
"% -> " << curSpikey <<
"% ("
700 << FormatSign( curSpikey - baseSpikey,
"pp" ) <<
")\n";
701 report <<
" Spikey ct: " << aBaseline.totalSpikeyTri <<
" -> " << aTotalSpikeyTri
704 report <<
"BREAKING: " << breaking.size() <<
" zones\n";
706 for(
const auto& cmp : breaking )
707 report << FormatZoneDetail( cmp ) <<
"\n";
709 if( breaking.empty() )
710 report <<
" (none)\n";
712 report <<
"\nREGRESSIONS: " << regressions.size() <<
" zones"
713 <<
" (lexicographic: speed >+5%, else regularity worse, else triangles >+5%)\n";
717 for(
const auto& cmp : regressions )
721 report <<
" ... and " << ( regressions.size() - 20 ) <<
" more\n";
725 report << FormatZoneDetail( cmp ) <<
"\n";
729 if( regressions.empty() )
730 report <<
" (none)\n";
732 report <<
"\nIMPROVEMENTS: " << improvements.size() <<
" zones\n";
736 for(
const auto& cmp : improvements )
740 report <<
" ... and " << ( improvements.size() - 20 ) <<
" more\n";
744 report << FormatZoneDetail( cmp ) <<
"\n";
748 if( improvements.empty() )
749 report <<
" (none)\n";
751 report <<
"\nSummary: " << improvements.size() <<
" improved, "
752 << regressions.size() <<
" regressed, "
753 << breaking.size() <<
" breaking, "
754 << unchanged <<
" unchanged\n";
759 std::to_string( breaking.size() )
760 +
" zone(s) have breaking triangulation changes" );
764std::string GetTriangulationDataDir()
777 std::string dataDir = GetTriangulationDataDir();
779 if( !fs::exists( dataDir ) || fs::is_empty( dataDir ) )
781 BOOST_TEST_MESSAGE(
"No triangulation data in " << dataDir <<
", skipping benchmark" );
785 fs::path
jsonPath = fs::path( dataDir ) /
"triangulation_status.json";
786 BASELINE_DATA baseline = LoadBaseline(
jsonPath );
791 << baseline.zoneCount <<
" zones, "
792 << baseline.totalTriangles <<
" triangles" );
801 for(
const auto& entry : fs::directory_iterator( dataDir ) )
803 if( entry.path().extension() ==
".kicad_polys" )
813 if(
const char* variant = std::getenv(
"KICAD_TRI_VARIANT" ) )
819 int totalBelow10 = 0;
824 std::vector<double> globalMinAngles;
826 std::vector<ZONE_COMPARISON> comparisons;
832 if( !ParsePolyFile( polyFile, board ) )
838 int boardTriangles = 0;
840 double boardTimeUs = 0.0;
842 const BASELINE_BOARD* baseBoard =
nullptr;
843 auto it = baseline.boards.find( board.source );
845 if( it != baseline.boards.end() )
846 baseBoard = &it->second;
848 for(
size_t zi = 0; zi < board.zones.size(); zi++ )
850 ZONE_STATS stats = ComputeZoneStats( board.zones[zi] );
853 stats.triangleCount > 0 || stats.originalArea == 0.0,
854 board.source +
" " + stats.layer +
" " + stats.net
855 +
" produced 0 triangles with non-zero area" );
857 if( stats.originalArea > 0.0 )
860 stats.areaCoverage > 0.999 && stats.areaCoverage < 1.001,
861 board.source +
" " + stats.layer +
" " + stats.net
862 +
" area coverage: " + std::to_string( stats.areaCoverage ) );
867 const BASELINE_ZONE* baseZone =
nullptr;
869 if( baseBoard && zi < baseBoard->zones.size() )
870 baseZone = &baseBoard->zones[zi];
872 comparisons.push_back( CompareZone( board.source, stats, baseZone ) );
875 boardTriangles += stats.triangleCount;
876 boardSpikey += stats.spikeyTriangles;
877 boardTimeUs +=
static_cast<double>( stats.timeUs );
878 totalBelow10 += stats.trisBelow10Deg;
879 globalMinAngles.insert( globalMinAngles.end(), stats.minAngles.begin(),
880 stats.minAngles.end() );
896 std::sort( globalMinAngles.begin(), globalMinAngles.end() );
898 <<
" p5: " << Percentile( globalMinAngles, 5.0 )
899 <<
" p50: " << Percentile( globalMinAngles, 50.0 )
900 <<
" (<10deg: " << totalBelow10 <<
" = "
912 std::string dataDir = GetTriangulationDataDir();
914 if( !fs::exists( dataDir ) || fs::is_empty( dataDir ) )
922 for(
const auto& entry : fs::directory_iterator( dataDir ) )
924 if( entry.path().extension() ==
".kicad_polys" )
942 if( !ParsePolyFile( polyFile, board ) )
945 nlohmann::json boardJson;
946 boardJson[
"source"] = board.source;
947 nlohmann::json zonesJson = nlohmann::json::array();
949 int boardTriangles = 0;
951 double boardTimeUs = 0.0;
953 for( ZONE_ENTRY& zone : board.zones )
955 ZONE_STATS stats = ComputeZoneStats( zone );
956 zonesJson.push_back( ZoneStatsToJson( stats ) );
958 boardTriangles += stats.triangleCount;
959 boardSpikey += stats.spikeyTriangles;
960 boardTimeUs +=
static_cast<double>( stats.timeUs );
965 boardJson[
"zones"] = zonesJson;
967 nlohmann::json boardTotals;
968 boardTotals[
"triangle_count"] = boardTriangles;
969 boardTotals[
"time_us"] =
static_cast<int64_t
>( boardTimeUs );
970 boardTotals[
"spikey_ratio"] = boardTriangles > 0
971 ?
static_cast<double>( boardSpikey ) / boardTriangles
973 boardJson[
"board_totals"] = boardTotals;
999 fs::path
jsonPath = fs::path( dataDir ) /
"triangulation_status.json";
A small class to help profiling.
void Stop()
Save the time when this function was called, and set the counter stane to stop.
double msecs(bool aSinceLast=false)
Represent a set of closed polygons.
double Area()
Return the area of this poly set.
bool Parse(std::stringstream &aStream) override
virtual void CacheTriangulation(bool aSimplify=false, const TASK_SUBMITTER &aSubmitter={})
Build a polygon triangulation, needed to draw a polygon on OpenGL and in some other calculations.
const TRIANGULATED_POLYGON * TriangulatedPolygon(int aIndex) const
unsigned int TriangulatedPolyCount() const
Return the number of triangulated polygons.
double Distance(const VECTOR2< extended_type > &aVector) const
Compute the distance between two vectors.
CHANGE_TYPE
Types of changes.
Exact orientation and in-circle predicates over integer coordinates.
double TriangleMinAngleDeg(const VECTOR2I &a, const VECTOR2I &b, const VECTOR2I &c)
The smallest interior angle of a triangle, in degrees; near zero for a sliver.
bool IsSliverTriangle(const VECTOR2I &a, const VECTOR2I &b, const VECTOR2I &c)
A triangle is a sliver when its longest edge exceeds ten times its shortest.
std::string GetTestDataRootDir()
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_AUTO_TEST_SUITE_END()
std::ofstream jsonFile("pip_benchmark_results.json")
BOOST_CHECK_MESSAGE(totalMismatches==0, std::to_string(totalMismatches)+" board(s) with strategy disagreements")
std::vector< fs::path > polyFiles
BOOST_TEST_MESSAGE("\n=== Real-World Polygon PIP Benchmark ===\n"<< formatTable(table))
nlohmann::json metadataJson
nlohmann::json boardsJson
nlohmann::json globalJson
BOOST_AUTO_TEST_CASE(BenchmarkAllExtractedPolygons)
std::ofstream jsonFile(jsonPath)
nlohmann::json jsonOutput
VECTOR2< int32_t > VECTOR2I