Files
OpenNest/OpenNest.Engine/Jobs/Placement/Fillers/PlateFillerBase.cs
T
ajandClaude Opus 5.5 9f2e3d5291 feat(engine): compare clamp-hit dropping with filling past the clamp strip
Dropping the parts that land on a clamp suits small parts, which fill the
gaps between clamps. Large parts often do better if the fill area stops
short of the clamps. The rows still fit, and no part is lost on a clamp.

When every clamp in the fill area lies on one edge, interactive Fill now
also fills the area with that edge moved just past the clamp keep-outs,
as a larger edge spacing would. It keeps whichever layout the strategy's
comparer ranks better, and a tie keeps the dropped-parts layout. Clamps
on more than one edge skip the comparison. The second fill reuses the
cached best-fit pairs. While it runs, a preview counts as the new best
only when it beats the first layout, so the preview never gets worse.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 12:49:48 -04:00

122 lines
3.2 KiB
C#

using System;
using System.Collections.Generic;
using System.Threading;
using OpenNest.Engine;
using OpenNest.Engine.Fill;
using OpenNest.Engine.Strategies;
using OpenNest.Geometry;
namespace OpenNest.Engine.Jobs.Placement.Fillers;
internal abstract class PlateFillerBase
{
private IFillComparer comparer;
protected PlateFillerBase(Plate plate)
{
Plate = plate;
}
public Plate Plate { get; }
public int PlateNumber { get; set; }
public NestDirection NestDirection { get; set; }
public NestPhase WinnerPhase { get; protected set; }
public List<PhaseResult> PhaseResults { get; } = new();
public List<AngleResult> AngleResults { get; } = new();
protected IFillComparer Comparer => comparer ??= CreateComparer();
protected virtual IFillComparer CreateComparer() => new DefaultFillComparer();
public virtual NestDirection? PreferredDirection => null;
public virtual ShrinkAxis TrimAxis => ShrinkAxis.Width;
public virtual List<double> BuildAngles(
NestItem item,
ClassificationResult classification,
Box workArea
)
{
return new List<double>
{
classification.PrimaryAngle,
classification.PrimaryAngle + OpenNest.Math.Angle.HalfPI,
};
}
protected virtual void RecordProductiveAngles(List<AngleResult> angleResults) { }
protected FillPolicy BuildPolicy() => new(Comparer, PreferredDirection);
protected string BuildProgressSummary() => BuildProgressSummary(PhaseResults);
private static string BuildProgressSummary(IReadOnlyList<PhaseResult> phaseResults)
{
if (phaseResults.Count == 0)
return null;
var parts = new List<string>(phaseResults.Count);
foreach (var result in phaseResults)
parts.Add($"{result.Phase.ShortName()}: {result.PartCount}");
return string.Join(" | ", parts);
}
internal bool IsBetterFill(List<Part> candidate, List<Part> current, Box workArea) =>
Comparer.IsBetter(candidate, current, workArea);
public virtual List<Part> Fill(
NestItem item,
Box workArea,
IProgress<NestProgress> progress,
CancellationToken token
)
{
return new List<Part>();
}
public virtual List<Part> Fill(
List<Part> groupParts,
Box workArea,
IProgress<NestProgress> progress,
CancellationToken token
)
{
return new List<Part>();
}
public virtual List<Part> PackArea(
Box box,
List<NestItem> items,
IProgress<NestProgress> progress,
CancellationToken token
)
{
return new List<Part>();
}
public virtual List<Part> Nest(
List<NestItem> items,
IProgress<NestProgress> progress,
CancellationToken token
)
{
return PlateFillOrchestrator.Nest(
Plate,
items,
Comparer,
(item, workArea, sink, cancellation) => Fill(item, workArea, sink, cancellation),
(workArea, packItems, sink, cancellation) =>
PackArea(workArea, packItems, sink, cancellation),
progress,
token
);
}
}