#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using OpenNest.Engine.Jobs;
using OpenNest.Engine.RectanglePacking;
namespace OpenNest.Engine.NestingEngines.Rectangles;
///
/// Rectangle-lane nesting engine: every part is nested as the axis-aligned box of its material at
/// its minimum-area rotations, packed with a maximal-rectangles free list (Jylänki 2010).
///
/// Built for jobs of plain and near-rectangular parts, where a part's box wastes almost nothing
/// and exact box packing beats contour-sliding engines on both speed and density. Irregular parts
/// are still placed validly, only as their bounding boxes; they are not nested into each other.
///
/// Sheet by sheet (), each available stock is packed under several
/// free-space scoring rules and two pick modes (best-fitting box anywhere, or largest type first),
/// and the candidate with the lowest estimated whole-job cost wins.
/// Deterministic: no clocks or randomness; the only stop besides completion is the host token.
///
public sealed class RectanglesNestingEngine : INestingEngine
{
private static readonly FitRule[] Rules =
{
FitRule.BestShortSide, FitRule.BestLongSide, FitRule.BestArea,
FitRule.BottomLeft, FitRule.LeftBottom, FitRule.ContactPoint,
};
private static readonly PickMode[] Modes = { PickMode.Global, PickMode.Ordered };
// Instance-owned diagnostics; no process-global counters or mutable solver state.
internal Action? SheetPacked { get; init; }
internal Action? BatchSelected { get; init; }
public NestJobResult Solve(
NestJob job,
IProgress? progress = null,
CancellationToken token = default
)
{
ArgumentNullException.ThrowIfNull(job);
token.ThrowIfCancellationRequested();
var types = BoxCatalog.Build(job);
var remaining = types.Select(t => t.Part.Quantity).ToArray();
// Parts with unreadable geometry or no box that fits any offered sheet can never be placed.
foreach (var t in types)
if (t.Orientations.Count == 0 || !job.Plates.Any(stock => t.Orientations.Any(o => FitsStock(stock, o))))
remaining[t.Index] = 0;
var demand = types.Select(t => new SheetDemand(t.Index, t.Id, t.Priority, t.MaterialArea)).ToArray();
return SheetByStock.Run(job, demand, remaining, Pack, progress, token, BatchSelected);
IEnumerable Pack(NestPlateStock stock, IReadOnlyList left)
{
// Both private MaxRects modes return only after their normal demand/no-fit search;
// neither returns partial/budget-limited output. This is not a geometric impossibility proof.
foreach (var mode in Modes)
foreach (var rule in Rules)
{
var plan = SheetPacker.Pack(types, left, stock, rule, mode, token);
SheetPacked?.Invoke(mode);
yield return new SheetLayout(stock, plan.Poses().ToArray(), plan.MaterialArea, plan.Envelope);
}
}
}
private static bool FitsStock(NestPlateStock stock, BoxOrientation o)
{
var work = stock.WorkArea;
return o.Width <= work.Right - work.Left + SheetPacker.OverhangAllowance
&& o.Height <= work.Top - work.Bottom + SheetPacker.OverhangAllowance;
}
}