Files
OpenNest/OpenNest.Engine/Fill/RemnantFiller.cs
AJ Isaacs e969260f3d refactor(engine): introduce PairFillResult and remove FillRemainingStrip
PairFiller now returns PairFillResult (Parts + BestFits) instead of
using a mutable BestFits property. Extracted EvaluateCandidates,
TryReduceWorkArea, and BuildTilingAngles for clarity. Simplified the
candidate loop by leveraging FillScore comparison semantics.

Removed FillRemainingStrip and all its helpers (FindPlacedEdge,
BuildRemainingStrip, BuildRotationSet, FindBestFill, TryFewerRows,
RemainderPatterns) from FillLinear — these were a major bottleneck in
strip nesting, running expensive fills on undersized remnant strips.
ShrinkFiller + RemnantFiller already handle space optimization, making
the remainder strip fill redundant.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 15:53:23 -04:00

143 lines
4.5 KiB
C#

using OpenNest.Geometry;
using System;
using System.Collections.Generic;
using System.Threading;
namespace OpenNest.Engine.Fill
{
/// <summary>
/// Iteratively fills remnant boxes with items using a RemnantFinder.
/// After each fill, re-discovers free rectangles and tries again
/// until no more items can be placed.
/// </summary>
public class RemnantFiller
{
private readonly RemnantFinder finder;
private readonly double spacing;
public RemnantFiller(Box workArea, double spacing)
{
this.spacing = spacing;
finder = new RemnantFinder(workArea);
}
public void AddObstacles(IEnumerable<Part> parts)
{
foreach (var part in parts)
finder.AddObstacle(part.BoundingBox.Offset(spacing));
}
public List<Part> FillItems(
List<NestItem> items,
Func<NestItem, Box, List<Part>> fillFunc,
CancellationToken token = default,
IProgress<NestProgress> progress = null)
{
if (items == null || items.Count == 0)
return new List<Part>();
var allParts = new List<Part>();
// Track quantities locally — do not mutate the input NestItem objects.
var localQty = BuildLocalQuantities(items);
while (!token.IsCancellationRequested)
{
var minDim = FindMinItemDimension(items, localQty);
if (minDim == double.MaxValue)
break;
var freeBoxes = finder.FindRemnants(minDim);
if (freeBoxes.Count == 0)
break;
if (!TryFillOneItem(items, freeBoxes, localQty, fillFunc, allParts, token))
break;
}
return allParts;
}
private static Dictionary<string, int> BuildLocalQuantities(List<NestItem> items)
{
var localQty = new Dictionary<string, int>(items.Count);
foreach (var item in items)
localQty[item.Drawing.Name] = item.Quantity;
return localQty;
}
private static double FindMinItemDimension(List<NestItem> items, Dictionary<string, int> localQty)
{
var minDim = double.MaxValue;
foreach (var item in items)
{
if (localQty[item.Drawing.Name] <= 0)
continue;
var bb = item.Drawing.Program.BoundingBox();
var dim = System.Math.Min(bb.Width, bb.Length);
if (dim < minDim)
minDim = dim;
}
return minDim;
}
private bool TryFillOneItem(
List<NestItem> items,
List<Box> freeBoxes,
Dictionary<string, int> localQty,
Func<NestItem, Box, List<Part>> fillFunc,
List<Part> allParts,
CancellationToken token)
{
foreach (var item in items)
{
if (token.IsCancellationRequested)
return false;
var qty = localQty[item.Drawing.Name];
if (qty <= 0)
continue;
var placed = TryFillInRemnants(item, qty, freeBoxes, fillFunc);
if (placed == null)
continue;
allParts.AddRange(placed);
localQty[item.Drawing.Name] = System.Math.Max(0, qty - placed.Count);
foreach (var p in placed)
finder.AddObstacle(p.BoundingBox.Offset(spacing));
return true;
}
return false;
}
private static List<Part> TryFillInRemnants(
NestItem item,
int qty,
List<Box> freeBoxes,
Func<NestItem, Box, List<Part>> fillFunc)
{
var itemBbox = item.Drawing.Program.BoundingBox();
foreach (var box in freeBoxes)
{
var fitsNormal = box.Width >= itemBbox.Width && box.Length >= itemBbox.Length;
var fitsRotated = box.Width >= itemBbox.Length && box.Length >= itemBbox.Width;
if (!fitsNormal && !fitsRotated)
continue;
var fillItem = new NestItem { Drawing = item.Drawing, Quantity = qty };
var parts = fillFunc(fillItem, box);
if (parts != null && parts.Count > 0)
return parts;
}
return null;
}
}
}