fix(bestfit): never reject pair candidates by utilization

Thin-framed, hollow, or concave parts (e.g. SULLYS-035's frame) have
inherently low part-to-bbox utilization yet nest tightly, so the 30%
MinUtilization floor wrongly dropped every candidate for them. Pair
quality is judged by the rotated pair bounding-box area the results
are already sorted on; utilization now only ever serves as the
high-aspect exception (UtilizationOverride), never as a rejection.

Adds a hollow-frame helper plus regression tests that kept pairs
exist with low utilization and results stay sorted by pair area.
This commit is contained in:
aj
2026-09-26 14:14:44 -04:00
parent 77b729e58c
commit 094c4c196b
3 changed files with 53 additions and 11 deletions
@@ -42,6 +42,34 @@ public class NfpBestFitIntegrationTests
Assert.True(bestUtilization > 0.5);
}
[Fact]
public void FindBestFits_KeepsLowUtilizationFrame_CutoutsDoNotRejectPair()
{
// A thin-walled hollow frame nests tightly despite tiny part/bbox utilization.
// Utilization must never reject a pair; ranking is by pair bounding-box area.
var finder = new BestFitFinder(48, 96);
var drawing = TestHelpers.MakeFrameDrawing(w: 20, h: 6, t: 0.5);
var results = finder.FindBestFits(drawing);
Assert.NotEmpty(results.Where(r => r.Keep));
Assert.All(results.Where(r => r.Keep), r => Assert.Equal("Valid", r.Reason));
var best = results.Where(r => r.Keep).First();
Assert.True(best.Utilization < 0.3);
}
[Fact]
public void FindBestFits_ResultsAreSortedByPairBoundingArea()
{
var finder = new BestFitFinder(48, 96);
var results = finder.FindBestFits(TestHelpers.MakeFrameDrawing());
Assert.True(results.Count > 1);
Assert.True(
results.Zip(results.Skip(1), (a, b) => a.RotatedArea <= b.RotatedArea).All(x => x)
);
}
[Fact]
public void FindBestFits_NoOverlaps_InKeptResults()
{