refactor: replace ShrinkFiller shrink loop with TrimToCount

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-20 00:06:58 -04:00
parent 85278bbb75
commit 6d66636e3d
2 changed files with 8 additions and 62 deletions

View File

@@ -16,9 +16,8 @@ namespace OpenNest.Engine.Fill
} }
/// <summary> /// <summary>
/// Fills a box then iteratively shrinks one axis by the spacing amount /// Fills a box and trims excess parts by removing those farthest from
/// until the part count drops. Returns the tightest box that still fits /// the origin along the shrink axis.
/// the target number of parts.
/// </summary> /// </summary>
public static class ShrinkFiller public static class ShrinkFiller
{ {
@@ -28,22 +27,17 @@ namespace OpenNest.Engine.Fill
double spacing, double spacing,
ShrinkAxis axis, ShrinkAxis axis,
CancellationToken token = default, CancellationToken token = default,
int maxIterations = 20,
int targetCount = 0, int targetCount = 0,
IProgress<NestProgress> progress = null, IProgress<NestProgress> progress = null,
int plateNumber = 0, int plateNumber = 0,
List<Part> placedParts = null) List<Part> placedParts = null)
{ {
// If a target count is specified, estimate a smaller starting box
// to avoid an expensive full-area fill.
var startBox = box; var startBox = box;
if (targetCount > 0) if (targetCount > 0)
startBox = EstimateStartBox(item, box, spacing, axis, targetCount); startBox = EstimateStartBox(item, box, spacing, axis, targetCount);
var parts = fillFunc(item, startBox); var parts = fillFunc(item, startBox);
// If estimate was too aggressive and we got fewer than target,
// fall back to the full box.
if (targetCount > 0 && startBox != box if (targetCount > 0 && startBox != box
&& (parts == null || parts.Count < targetCount)) && (parts == null || parts.Count < targetCount))
{ {
@@ -53,47 +47,18 @@ namespace OpenNest.Engine.Fill
if (parts == null || parts.Count == 0) if (parts == null || parts.Count == 0)
return new ShrinkResult { Parts = parts ?? new List<Part>(), Dimension = 0 }; return new ShrinkResult { Parts = parts ?? new List<Part>(), Dimension = 0 };
// Shrink target: if a target count was given and we got at least that many,
// shrink to fit targetCount (not the full count). This produces a tighter box.
// If we got fewer than target, shrink to maintain what we have.
var shrinkTarget = targetCount > 0 var shrinkTarget = targetCount > 0
? System.Math.Min(targetCount, parts.Count) ? System.Math.Min(targetCount, parts.Count)
: parts.Count; : parts.Count;
var bestParts = parts; if (parts.Count > shrinkTarget)
var bestDim = MeasureDimension(parts, box, axis); parts = TrimToCount(parts, shrinkTarget, axis);
ReportShrinkProgress(progress, plateNumber, placedParts, bestParts, box, axis, bestDim); var dim = MeasureDimension(parts, box, axis);
for (var i = 0; i < maxIterations; i++) ReportShrinkProgress(progress, plateNumber, placedParts, parts, box, axis, dim);
{
if (token.IsCancellationRequested)
break;
var trialDim = bestDim - spacing; return new ShrinkResult { Parts = parts, Dimension = dim };
if (trialDim <= 0)
break;
var trialBox = axis == ShrinkAxis.Width
? new Box(box.X, box.Y, trialDim, box.Length)
: new Box(box.X, box.Y, box.Width, trialDim);
// Report the trial box before the fill so the UI updates the
// work area border immediately rather than after the fill completes.
ReportShrinkProgress(progress, plateNumber, placedParts, bestParts, trialBox, axis, trialDim);
var trialParts = fillFunc(item, trialBox);
if (trialParts == null || trialParts.Count < shrinkTarget)
break;
bestParts = trialParts;
bestDim = MeasureDimension(trialParts, box, axis);
ReportShrinkProgress(progress, plateNumber, placedParts, bestParts, trialBox, axis, bestDim);
}
return new ShrinkResult { Parts = bestParts, Dimension = bestDim };
} }
private static void ReportShrinkProgress( private static void ReportShrinkProgress(

View File

@@ -17,7 +17,7 @@ public class ShrinkFillerTests
} }
[Fact] [Fact]
public void Shrink_ReducesDimension_UntilCountDrops() public void Shrink_FillsAndReturnsDimension()
{ {
var drawing = MakeSquareDrawing(10); var drawing = MakeSquareDrawing(10);
var item = new NestItem { Drawing = drawing }; var item = new NestItem { Drawing = drawing };
@@ -59,25 +59,6 @@ public class ShrinkFillerTests
Assert.True(result.Dimension <= 100); Assert.True(result.Dimension <= 100);
} }
[Fact]
public void Shrink_RespectsMaxIterations()
{
var callCount = 0;
Func<NestItem, Box, List<Part>> fillFunc = (ni, b) =>
{
callCount++;
return new List<Part> { TestHelpers.MakePartAt(0, 0, 5) };
};
var item = new NestItem { Drawing = MakeSquareDrawing(5) };
var box = new Box(0, 0, 100, 100);
ShrinkFiller.Shrink(fillFunc, item, box, 1.0, ShrinkAxis.Height, maxIterations: 3);
// 1 initial + up to 3 shrink iterations = max 4 calls
Assert.True(callCount <= 4);
}
[Fact] [Fact]
public void Shrink_RespectsCancellation() public void Shrink_RespectsCancellation()
{ {