feat: implement StripeFiller.Fill with pair iteration, stripe tiling, and remnant fill

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-21 07:44:59 -04:00
parent 2ae1d513cf
commit 0597a11a23
2 changed files with 247 additions and 2 deletions

View File

@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using OpenNest.Engine.BestFit;
using OpenNest.Engine.Strategies;
using OpenNest.Geometry;
using OpenNest.Math;
@@ -25,8 +26,131 @@ public class StripeFiller
public List<Part> Fill()
{
// Placeholder — implemented in Task 3
return new List<Part>();
var bestFits = GetPairCandidates();
if (bestFits.Count == 0)
return new List<Part>();
var workArea = _context.WorkArea;
var spacing = _context.Plate.PartSpacing;
var drawing = _context.Item.Drawing;
var perpAxis = _primaryAxis == NestDirection.Horizontal
? NestDirection.Vertical
: NestDirection.Horizontal;
var sheetSpan = GetDimension(workArea, _primaryAxis);
var strategyName = _primaryAxis == NestDirection.Horizontal ? "Row" : "Column";
List<Part> bestParts = null;
var bestScore = default(FillScore);
for (var i = 0; i < bestFits.Count; i++)
{
_context.Token.ThrowIfCancellationRequested();
var candidate = bestFits[i];
var pairParts = candidate.BuildParts(drawing);
var (angle, waste, count) = ConvergeStripeAngle(
pairParts, sheetSpan, spacing, _primaryAxis, _context.Token);
if (count <= 0)
continue;
var rotatedPattern = FillHelpers.BuildRotatedPattern(pairParts, angle);
var perpDim = GetDimension(rotatedPattern.BoundingBox, perpAxis);
var stripeBox = MakeStripeBox(workArea, perpDim, _primaryAxis);
var stripeEngine = new FillLinear(stripeBox, spacing);
var stripeParts = stripeEngine.Fill(rotatedPattern, _primaryAxis);
if (stripeParts == null || stripeParts.Count == 0)
continue;
var stripePattern = new Pattern();
stripePattern.Parts.AddRange(stripeParts);
stripePattern.UpdateBounds();
var gridEngine = new FillLinear(workArea, spacing);
var gridParts = gridEngine.Fill(stripePattern, perpAxis);
if (gridParts == null || gridParts.Count == 0)
continue;
var allParts = new List<Part>(gridParts);
var remnantParts = FillRemnant(gridParts, drawing, angle, workArea, spacing);
if (remnantParts != null)
allParts.AddRange(remnantParts);
var score = FillScore.Compute(allParts, workArea);
if (bestParts == null || score > bestScore)
{
bestParts = allParts;
bestScore = score;
}
NestEngineBase.ReportProgress(_context.Progress, NestPhase.Custom,
_context.PlateNumber, bestParts, workArea,
$"{strategyName}: {i + 1}/{bestFits.Count} pairs, best = {bestScore.Count} parts");
}
return bestParts ?? new List<Part>();
}
private List<BestFitResult> GetPairCandidates()
{
List<BestFitResult> bestFits;
if (_context.SharedState.TryGetValue("BestFits", out var cached))
bestFits = (List<BestFitResult>)cached;
else
bestFits = BestFitCache.GetOrCompute(
_context.Item.Drawing,
_context.Plate.Size.Length,
_context.Plate.Size.Width,
_context.Plate.PartSpacing);
return bestFits
.Where(r => r.Keep)
.Take(MaxPairCandidates)
.ToList();
}
private static Box MakeStripeBox(Box workArea, double perpDim, NestDirection primaryAxis)
{
return primaryAxis == NestDirection.Horizontal
? new Box(workArea.X, workArea.Y, workArea.Width, perpDim)
: new Box(workArea.X, workArea.Y, perpDim, workArea.Length);
}
private List<Part> FillRemnant(
List<Part> gridParts, Drawing drawing, double angle,
Box workArea, double spacing)
{
var gridBox = gridParts.GetBoundingBox();
var minDim = System.Math.Min(
drawing.Program.BoundingBox().Width,
drawing.Program.BoundingBox().Length);
Box remnantBox;
if (_primaryAxis == NestDirection.Horizontal)
{
var remnantY = gridBox.Top + spacing;
var remnantLength = workArea.Top - remnantY;
if (remnantLength < minDim)
return null;
remnantBox = new Box(workArea.X, remnantY, workArea.Width, remnantLength);
}
else
{
var remnantX = gridBox.Right + spacing;
var remnantWidth = workArea.Right - remnantX;
if (remnantWidth < minDim)
return null;
remnantBox = new Box(remnantX, workArea.Y, remnantWidth, workArea.Length);
}
var engine = new FillLinear(remnantBox, spacing);
var parts = engine.Fill(drawing, angle, _primaryAxis);
return parts != null && parts.Count > 0 ? parts : null;
}
public static double FindAngleForTargetSpan(