feat(engine): squeeze the Rectangles sheet that finishes the job

Auto Nest left N26-2A5 (14 door stops, 14 lock plates on a clamped 26x26
sheet) 8.0 high, while the shop's hand layout holds all 28 under 7.5 and
leaves a half-inch-deeper drop. Every MaxRects fit rule packs into the
whole sheet, so with room to spare it spreads the parts out and never
looks for the shortest strip.

When a rule places all the remaining demand, Rectangles now repacks it
into shorter and narrower bins, every rule and mode per probe, and offers
the tightest complete layout per axis to the stock loop, which still
picks by cost and offcut. Fit is not monotonic in bin size (7.5 packs,
7.62 and 7.74 do not), so probes walk down through stack heights (sums of
spaced part sides from the bin edge or a reached clamp) instead of
bisecting. A placement budget skips sheets finishing ~2000+ parts.

Benchmark, Rectangles on 80 recent production nests with salvage credit:
11 cheaper, 0 worse, all valid, total time 14.7 s -> 13.2 s.

The zero-clamp hash is re-captured (the squeezed layout is narrower);
the far-clamp test now compares poses, since the clamp can reorder an
identical layout.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ajandClaude Opus 5.5 committed 2026-11-10 11:10:06 -05:00
1 parent 7dc471bbf8
commit 9273a44258
6 files changed
+362 -13

No files matched your search

@@ -64,14 +64,29 @@ public sealed class RectanglesNestingEngine : INestingEngine
{
// 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.
var plans = PackAll(stock, left, null, null);
foreach (var plan in plans)
yield return Layout(plan);
// A sheet that finishes the job is also tried squeezed into a shorter or narrower bin.
foreach (var plan in SheetSqueeze.Run(types, left, stock, plans,
(width, height) => PackAll(stock, left, width, height), token))
yield return Layout(plan);
}
IReadOnlyList<SheetPlan> PackAll(NestPlateStock stock, IReadOnlyList<int> left, double? width, double? height)
{
var plans = new List<SheetPlan>(Modes.Length * Rules.Length);
foreach (var mode in Modes)
foreach (var rule in Rules)
{
var plan = SheetPacker.Pack(types, left, stock, rule, mode, token);
plans.Add(SheetPacker.Pack(types, left, stock, rule, mode, token, width, height));
SheetPacked?.Invoke(mode);
yield return new SheetLayout(stock, plan.Poses().ToArray(), plan.MaterialArea, plan.Envelope);
}
return plans;
}
static SheetLayout Layout(SheetPlan plan) =>
new(plan.Stock, plan.Poses().ToArray(), plan.MaterialArea, plan.Envelope);
}
private static bool FitsStock(NestPlateStock stock, BoxOrientation o)