mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-27 19:41:08 -04:00
Desktop Auto Nest's "Best plan so far" strip now fills in for every built-in whole-job engine except FlatBar, not only Irregular. - Rectangles makes one sheet-by-sheet run, so it reports the finished job as its one plan, then replays its plate commits (token checked as before). - Circles reports when a stock-preference run or a tail re-plan beats its best result so far. - Default scores each candidate plan as it ranks results (valid, fewest unplaced, lowest cost, candidate order) and reports it only when it beats every plan already shown, so a later candidate never shows a worse plan. The chosen result is always the last plan reported. - Shared ReportPlan/SameLayout helpers on CandidateProgress; Irregular uses them too. Reporting never changes the search. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
84 lines
3.9 KiB
C#
84 lines
3.9 KiB
C#
#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;
|
|
|
|
/// <summary>
|
|
/// 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 (<see cref="SheetByStock"/>), 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. The finished job is reported as
|
|
/// its one whole-job plan (<see cref="NestJobStage.PlanImproved"/>) before its plate commits.
|
|
/// Deterministic: no clocks or randomness; the only stop besides completion is the host token.
|
|
/// </summary>
|
|
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<PickMode>? SheetPacked { get; init; }
|
|
internal Action<RepeatBatch>? BatchSelected { get; init; }
|
|
|
|
public NestJobResult Solve(
|
|
NestJob job,
|
|
IProgress<NestJobProgress>? 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();
|
|
// One run makes one whole-job plan: report it, then its commits, as the other engines do.
|
|
var forward = progress == null ? null : new CandidateProgress(progress);
|
|
var result = SheetByStock.Run(job, demand, remaining, Pack, forward, token, BatchSelected);
|
|
CandidateProgress.ReportPlan(result, progress);
|
|
CandidateProgress.ReportCommits(result, progress, token);
|
|
return result;
|
|
|
|
IEnumerable<SheetLayout> Pack(NestPlateStock stock, IReadOnlyList<int> 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;
|
|
}
|
|
}
|