fix(engine): track multiple free rectangles in strip remnant filling
ComputeRemainderWithin only returned the larger of two possible free rectangles, permanently losing usable area on the other axis after each remainder item was placed. Replace the single shrinking box with a list of free rectangles using guillotine cuts so both sub-areas remain available for subsequent items. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -248,35 +248,48 @@ namespace OpenNest
|
||||
})
|
||||
.ToList();
|
||||
|
||||
// Fill remnant with remainder items, shrinking the available area after each.
|
||||
// Wrap progress so remnant fills include the strip parts already found.
|
||||
// Fill remnant with remainder items using free-rectangle tracking.
|
||||
// After each fill, the consumed box is split into two non-overlapping
|
||||
// sub-rectangles (guillotine cut) so no usable area is lost.
|
||||
if (remnantBox.Width > 0 && remnantBox.Length > 0)
|
||||
{
|
||||
var currentRemnant = remnantBox;
|
||||
var freeBoxes = new List<Box> { remnantBox };
|
||||
var remnantProgress = progress != null
|
||||
? new AccumulatingProgress(progress, allParts)
|
||||
: null;
|
||||
|
||||
foreach (var item in effectiveRemainder)
|
||||
{
|
||||
if (token.IsCancellationRequested)
|
||||
if (token.IsCancellationRequested || freeBoxes.Count == 0)
|
||||
break;
|
||||
|
||||
if (currentRemnant.Width <= 0 || currentRemnant.Length <= 0)
|
||||
break;
|
||||
var itemBbox = item.Drawing.Program.BoundingBox();
|
||||
var minItemDim = System.Math.Min(itemBbox.Width, itemBbox.Length);
|
||||
|
||||
var remnantInner = new DefaultNestEngine(Plate);
|
||||
var remnantParts = remnantInner.Fill(
|
||||
new NestItem { Drawing = item.Drawing, Quantity = item.Quantity },
|
||||
currentRemnant, remnantProgress, token);
|
||||
// Try free boxes from largest to smallest.
|
||||
freeBoxes.Sort((a, b) => b.Area().CompareTo(a.Area()));
|
||||
|
||||
if (remnantParts != null && remnantParts.Count > 0)
|
||||
for (var i = 0; i < freeBoxes.Count; i++)
|
||||
{
|
||||
allParts.AddRange(remnantParts);
|
||||
var box = freeBoxes[i];
|
||||
|
||||
// Shrink remnant to avoid overlap with next item.
|
||||
var usedBox = remnantParts.Cast<IBoundable>().GetBoundingBox();
|
||||
currentRemnant = ComputeRemainderWithin(currentRemnant, usedBox, spacing);
|
||||
if (System.Math.Min(box.Width, box.Length) < minItemDim)
|
||||
continue;
|
||||
|
||||
var remnantInner = new DefaultNestEngine(Plate);
|
||||
var remnantParts = remnantInner.Fill(
|
||||
new NestItem { Drawing = item.Drawing, Quantity = item.Quantity },
|
||||
box, remnantProgress, token);
|
||||
|
||||
if (remnantParts != null && remnantParts.Count > 0)
|
||||
{
|
||||
allParts.AddRange(remnantParts);
|
||||
freeBoxes.RemoveAt(i);
|
||||
|
||||
var usedBox = remnantParts.Cast<IBoundable>().GetBoundingBox();
|
||||
SplitFreeBox(box, usedBox, spacing, freeBoxes);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -291,7 +304,44 @@ namespace OpenNest
|
||||
return result;
|
||||
}
|
||||
|
||||
// ComputeRemainderWithin inherited from NestEngineBase
|
||||
private static void SplitFreeBox(Box parent, Box used, double spacing, List<Box> freeBoxes)
|
||||
{
|
||||
var hWidth = parent.Right - used.Right - spacing;
|
||||
var vHeight = parent.Top - used.Top - spacing;
|
||||
|
||||
if (hWidth > spacing && vHeight > spacing)
|
||||
{
|
||||
// Guillotine split: give the overlapping corner to the larger strip.
|
||||
var hFullArea = hWidth * parent.Length;
|
||||
var vFullArea = parent.Width * vHeight;
|
||||
|
||||
if (hFullArea >= vFullArea)
|
||||
{
|
||||
// hStrip gets full height; vStrip truncated to left of split line.
|
||||
freeBoxes.Add(new Box(used.Right + spacing, parent.Y, hWidth, parent.Length));
|
||||
var vWidth = used.Right + spacing - parent.X;
|
||||
if (vWidth > spacing)
|
||||
freeBoxes.Add(new Box(parent.X, used.Top + spacing, vWidth, vHeight));
|
||||
}
|
||||
else
|
||||
{
|
||||
// vStrip gets full width; hStrip truncated below split line.
|
||||
freeBoxes.Add(new Box(parent.X, used.Top + spacing, parent.Width, vHeight));
|
||||
var hHeight = used.Top + spacing - parent.Y;
|
||||
if (hHeight > spacing)
|
||||
freeBoxes.Add(new Box(used.Right + spacing, parent.Y, hWidth, hHeight));
|
||||
}
|
||||
}
|
||||
else if (hWidth > spacing)
|
||||
{
|
||||
freeBoxes.Add(new Box(used.Right + spacing, parent.Y, hWidth, parent.Length));
|
||||
}
|
||||
else if (vHeight > spacing)
|
||||
{
|
||||
freeBoxes.Add(new Box(parent.X, used.Top + spacing, parent.Width, vHeight));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wraps an IProgress to prepend previously placed parts to each report,
|
||||
/// so the UI shows the full picture (strip + remnant) during remnant fills.
|
||||
|
||||
Reference in New Issue
Block a user