diff --git a/OpenNest.Engine.Tests/NestingEngines/RectanglesClampZoneTests.cs b/OpenNest.Engine.Tests/NestingEngines/RectanglesClampZoneTests.cs
index 9ca9baa2..5a002aa9 100644
--- a/OpenNest.Engine.Tests/NestingEngines/RectanglesClampZoneTests.cs
+++ b/OpenNest.Engine.Tests/NestingEngines/RectanglesClampZoneTests.cs
@@ -36,8 +36,10 @@ public class RectanglesClampZoneTests
[Theory]
// Pre-clamp baseline hashes, captured from the unchanged packer at commit a3f1124 and
- // re-captured when equal-cost sheet layouts began preferring the larger free offcut.
- [InlineData("3D389D6B993B69634EB45FC2CB0309273DE1BCF57A7C2BD980C63B535667B0C6")]
+ // re-captured when equal-cost sheet layouts began preferring the larger free offcut, then
+ // when the finishing sheet began squeezing into a narrower bin (twelve parts now reach
+ // 20.25 along X instead of spreading over the sheet).
+ [InlineData("578D350691FF80C768BD4AE245F03863885669B7E9AB86A9CDCB0302B1F1DD01")]
public void ZeroClampLayoutIsBitIdenticalToBaseline(string hash) =>
Assert.Equal(hash, LayoutHash(Engine().Solve(Job(
new[] { Part("rect", Rectangle(10, 5), 12) },
@@ -65,6 +67,13 @@ public class RectanglesClampZoneTests
new[] { Part("square", Rectangle(10, 10), 30) },
new[] { Stock("sheet", 25, 45, spacing: 0.25) }))));
+ private static string[] Poses(NestJobResult result) => result.Plates
+ .SelectMany(sheet => sheet.Placements.Select(p => string.Join('|', sheet.PlateIndex, sheet.StockId, p.PartId,
+ p.X.ToString("R", CultureInfo.InvariantCulture), p.Y.ToString("R", CultureInfo.InvariantCulture),
+ p.Rotation.ToString("R", CultureInfo.InvariantCulture))))
+ .Order(StringComparer.Ordinal)
+ .ToArray();
+
// ---- clamp avoidance ---------------------------------------------------------------
[Fact]
@@ -149,7 +158,10 @@ public class RectanglesClampZoneTests
var plain = Engine().Solve(jobPlain);
LayoutAssert.Valid(jobClamped, clamped);
- Assert.Equal(LayoutHash(plain), LayoutHash(clamped));
+ // No part may move. The zone splits the packer's free list, so the finishing-sheet
+ // squeeze can reach the same poses through a different rule and list them in another
+ // order; compare the poses, not their order.
+ Assert.Equal(Poses(plain), Poses(clamped));
}
// ---- large part spacing vs shallow clamp spans (PM d86d22c9, decision D11) --------
diff --git a/OpenNest.Engine.Tests/NestingEngines/RectanglesSqueezeTests.cs b/OpenNest.Engine.Tests/NestingEngines/RectanglesSqueezeTests.cs
new file mode 100644
index 00000000..63f49604
--- /dev/null
+++ b/OpenNest.Engine.Tests/NestingEngines/RectanglesSqueezeTests.cs
@@ -0,0 +1,69 @@
+using OpenNest.Converters;
+using OpenNest.Engine.Jobs;
+using OpenNest.Engine.Jobs.Adapters;
+using OpenNest.Engine.NestingEngines.Rectangles;
+using OpenNest.Geometry;
+using static OpenNest.Engine.Tests.NestingEngines.JobBuilder;
+using static OpenNest.Engine.Tests.NestingEngines.Shapes;
+
+namespace OpenNest.Engine.Tests.NestingEngines;
+
+///
+/// The sheet that finishes a job is re-packed into a shorter or narrower bin, so the drop it
+/// leaves is as large as the packer can make it.
+///
+public class RectanglesSqueezeTests
+{
+ ///
+ /// N26-2A5: 14 door stops (4.168 x 1.5) and 14 lock plates (2 x 2) on a 26 x 26 sheet with a
+ /// front clamp. Every fit rule on the whole sheet stopped at 8.0 high; the shop's hand layout
+ /// holds all 28 under 7.5 (three rows of four stops, two stops on end over the clamp, a 2 x 2
+ /// block of plates, then a row of plates across the top), leaving an 18" drop.
+ ///
+ [Fact]
+ public void FinishingSheetPacksIntoTheShortestStripItFinds()
+ {
+ var clamp = new ClampZone("clamp-1", new Box(19.25, 0, 1.5, 0.625));
+ var job = Job(
+ [
+ Part("door stop", Rectangle(4.168108, 1.5), 14),
+ Part("lock plate", Rectangle(2, 2), 14),
+ ],
+ [Stock("sheet", 26, 26, spacing: 0.25, edge: new Spacing(0.25, 0.25, 0.25, 0.25), clampZones: [clamp])]);
+
+ var result = new RectanglesNestingEngine().Solve(job);
+
+ LayoutAssert.Valid(job, result);
+ Assert.Equal(NestJobStatus.Complete, result.Status);
+ Assert.Single(result.Plates);
+ Assert.True(Top(job, result.Plates[0]) <= 7.5 + 1e-9, $"layout top {Top(job, result.Plates[0])}");
+ }
+
+ [Fact]
+ public void SheetsThatDoNotFinishTheJobAreNotSqueezed()
+ {
+ // Four 9 x 9 squares fill each 20 x 20 sheet; only the last sheet could be squeezed, and
+ // it repeats the full layout, so every sheet keeps one pass of the twelve rule/mode packs.
+ var job = Job([Rectangle("a", 9, 9, 40)], [Stock("s", 20, 20, spacing: 0.2)]);
+ var calls = 0;
+
+ var result = new RectanglesNestingEngine { SheetPacked = _ => calls++ }.Solve(job);
+
+ LayoutAssert.Valid(job, result);
+ Assert.Equal(12, calls);
+ }
+
+ private static double Top(NestJob job, NestJobPlateResult sheet) =>
+ sheet.Placements.Max(pose =>
+ {
+ var part = job.Parts.Single(p => p.Id == pose.PartId);
+ var geometry = ConvertProgram.ToGeometry(DrawingJobMapper.ToProgram(part.Geometry))
+ .Where(e => SpecialLayers.IsMaterial(e.Layer)).ToArray();
+ foreach (var entity in geometry)
+ {
+ entity.Rotate(pose.Rotation);
+ entity.Offset(pose.X, pose.Y);
+ }
+ return geometry.Max(e => e.Top);
+ });
+}
diff --git a/OpenNest.Engine/NestingEngines/Rectangles/RectanglesNestingEngine.cs b/OpenNest.Engine/NestingEngines/Rectangles/RectanglesNestingEngine.cs
index 3c71f8d6..76479a7e 100644
--- a/OpenNest.Engine/NestingEngines/Rectangles/RectanglesNestingEngine.cs
+++ b/OpenNest.Engine/NestingEngines/Rectangles/RectanglesNestingEngine.cs
@@ -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 PackAll(NestPlateStock stock, IReadOnlyList left, double? width, double? height)
+ {
+ var plans = new List(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)
diff --git a/OpenNest.Engine/NestingEngines/Rectangles/SheetPacker.cs b/OpenNest.Engine/NestingEngines/Rectangles/SheetPacker.cs
index 2c746498..4408ea1c 100644
--- a/OpenNest.Engine/NestingEngines/Rectangles/SheetPacker.cs
+++ b/OpenNest.Engine/NestingEngines/Rectangles/SheetPacker.cs
@@ -49,19 +49,36 @@ internal static class SheetPacker
public static double PackSize(double size, double sheet) =>
size > sheet && size <= sheet + OverhangAllowance ? sheet : size;
+ /// Packing bin size for a stock: its work area grown by one part spacing per axis.
+ public static (double Width, double Height) BinSize(NestPlateStock stock)
+ {
+ var work = stock.WorkArea;
+ return (work.Right - work.Left + stock.PartSpacing, work.Top - work.Bottom + stock.PartSpacing);
+ }
+
+ /// When set, a squeeze limit: bin-local packing width (box plus its
+ /// spacing tail) no box may pass, so material ends one part spacing short of it.
+ /// The same limit along Y.
public static SheetPlan Pack(
IReadOnlyList types, IReadOnlyList remaining, NestPlateStock stock,
- FitRule rule, PickMode mode, CancellationToken token)
+ FitRule rule, PickMode mode, CancellationToken token,
+ double? maxBinWidth = null, double? maxBinHeight = null)
{
var work = stock.WorkArea;
var s = stock.PartSpacing;
- var sheet = new MaxRectsSheet(work.Right - work.Left + s, work.Top - work.Bottom + s);
- BlockClamps(sheet, stock, work, s);
+ var (binWidth, binHeight) = BinSize(stock);
+ // A squeeze packs a smaller bin from the same corner, exactly as on a smaller sheet (the
+ // contact rule scores its edges too); the overhang allowance and clamp side choice still
+ // measure the real sheet.
+ var sheet = new MaxRectsSheet(
+ maxBinWidth is double w && w < binWidth ? w : binWidth,
+ maxBinHeight is double h && h < binHeight ? h : binHeight);
+ BlockClamps(sheet, stock, work, s, binWidth, binHeight);
var packTypes = types
.Select(t => new PackType(
t.Priority,
t.Orientations
- .Select(o => (PackSize(o.Width + s, sheet.Width), PackSize(o.Height + s, sheet.Height)))
+ .Select(o => (PackSize(o.Width + s, binWidth), PackSize(o.Height + s, binHeight)))
.ToList(),
t.BoxArea))
.ToList();
@@ -104,7 +121,8 @@ internal static class SheetPacker
/// exactly on the centerline with material inside the clamp.
/// Empty zone lists change nothing: the free list evolves bit-identically (C2).
///
- private static void BlockClamps(MaxRectsSheet sheet, NestPlateStock stock, Box work, double s)
+ private static void BlockClamps(
+ MaxRectsSheet sheet, NestPlateStock stock, Box work, double s, double binWidth, double binHeight)
{
if (stock.ClampZones.Count == 0)
return;
@@ -121,7 +139,7 @@ internal static class SheetPacker
{
var center = x0((b.Left + b.Right) * 0.5);
// D11: the interior-facing clamp edge (toward the farther sheet edge).
- var line = center < sheet.Width * 0.5
+ var line = center < binWidth * 0.5
? x0(b.Right) + clearance
: x0(b.Left) - clearance + s;
left = line;
@@ -132,7 +150,7 @@ internal static class SheetPacker
if (top <= bottom)
{
var center = y0((b.Bottom + b.Top) * 0.5);
- var line = center < sheet.Height * 0.5
+ var line = center < binHeight * 0.5
? y0(b.Top) + clearance
: y0(b.Bottom) - clearance + s;
bottom = line;
diff --git a/OpenNest.Engine/NestingEngines/Rectangles/SheetSqueeze.cs b/OpenNest.Engine/NestingEngines/Rectangles/SheetSqueeze.cs
new file mode 100644
index 00000000..86c8ff68
--- /dev/null
+++ b/OpenNest.Engine/NestingEngines/Rectangles/SheetSqueeze.cs
@@ -0,0 +1,215 @@
+#nullable enable
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using OpenNest.Engine.Jobs;
+
+namespace OpenNest.Engine.NestingEngines.Rectangles;
+
+///
+/// Squeezes the sheet that finishes the job. The fit rules pack into the whole sheet, so when the
+/// remaining demand leaves room they spread it out and rarely find the shortest strip that holds
+/// it: on N26-2A5 (14 door stops and 14 lock plates on a 26 x 26 clamped sheet) every rule
+/// stopped at 8.0 high while a 7.5 strip holds all 28. When some packing places all the remaining
+/// demand, this re-packs it into a bin cut shorter (then, separately, narrower), running every rule
+/// and mode at each probe, and returns the tightest complete layout per axis when it beats the
+/// unsqueezed ones; the stock loop then picks by cost and edge drop as usual.
+///
+/// Whether a greedy packing fits is far from monotonic in the bin size (that job packs at 7.5 but
+/// not at 7.62 or 7.74), and a tight layout ends where a stack of spaced part sides ends. So the
+/// probes are those stack heights: sums of the parts' spaced sides along the axis, starting from
+/// the bin edge or the far edge of a clamp keep-out the layout reaches. They walk down from the
+/// best extent so far, each step spread over what is left of the range for the probes left, and a
+/// failed probe never raises the floor. When there are too many sums to list, the walk steps
+/// through the size range instead. A probe that places everything moves the walk down to that
+/// layout's own extent, so the answer never keeps a probe's slack.
+/// Deterministic, and bounded by a work budget over demand size: large jobs skip it entirely.
+///
+internal static class SheetSqueeze
+{
+ /// Most probes per axis.
+ internal const int MaxProbes = 12;
+
+ /// Box placements the squeeze may spend: probes x variants x demand, both axes.
+ internal const long PlacementBudget = 50_000;
+
+ /// Most stack heights listed before the walk falls back to stepping the size range.
+ private const int MaxStackHeights = 2048;
+
+ /// A size-range walk stops once less than this fraction of the bin is left to try,
+ /// and a squeezed layout must beat the whole-sheet ones by at least as much.
+ private const double Resolution = 1e-3;
+
+ ///
+ /// Probes sit this far past a stack height. MaxRects already lets a box overrun by its Eps
+ /// (1e-9), which covers a stack's float rounding; a margin past that Eps would hide the bin's
+ /// top edge from the contact-point rule, which scores edge contact within Eps.
+ ///
+ private const double ProbeMargin = 1e-10;
+
+ /// The unsqueezed layouts of on this stock.
+ /// Every rule and mode packed under a bin limit (width, height).
+ public static IEnumerable Run(
+ IReadOnlyList types, IReadOnlyList remaining, NestPlateStock stock,
+ IReadOnlyList plans, Func> packAll,
+ CancellationToken token)
+ {
+ var demand = remaining.Sum();
+ bool Complete(SheetPlan p) => p.Parts.Count == demand && p.Envelope != null;
+ var complete = plans.Where(Complete).ToList();
+ if (demand == 0 || complete.Count == 0)
+ yield break;
+
+ var probes = (int)System.Math.Min(MaxProbes, PlacementBudget / (2L * demand * plans.Count));
+ if (probes == 0)
+ yield break;
+
+ var work = stock.WorkArea;
+ var s = stock.PartSpacing;
+ var (binWidth, binHeight) = SheetPacker.BinSize(stock);
+
+ foreach (var vertical in new[] { true, false })
+ {
+ double Extent(SheetPlan p) => vertical ? p.Envelope!.Top - work.Bottom : p.Envelope!.Right - work.Left;
+
+ var bin = vertical ? binHeight : binWidth;
+ var across = vertical ? binWidth : binHeight;
+ var baseline = complete.Min(Extent);
+ var hi = baseline + s;
+ var lo = LowerBound(types, remaining, across, s, vertical);
+ // How far across the squeezed axis the tightest whole-sheet layout reaches: only
+ // clamps within it can hold a stack, so a zone the demand never reaches changes nothing.
+ var widest = complete.Where(p => Extent(p) == baseline).Max(p => vertical
+ ? p.Envelope!.Right - work.Left
+ : p.Envelope!.Top - work.Bottom);
+ var heights = StackHeights(types, remaining, stock, across, vertical, hi, widest);
+ SheetPlan? tightest = null;
+ // Probes walk down from the best extent so far, each spread over what is left of the
+ // range for the probes left. A failed probe only moves the walk below it: a smaller bin
+ // may still fit, so it never raises the floor.
+ var cursor = hi;
+ for (var probe = 0; probe < probes; probe++)
+ {
+ token.ThrowIfCancellationRequested();
+ var target = cursor - (cursor - lo) / (probes - probe);
+ double limit;
+ if (heights != null)
+ {
+ // The highest stack height at or under the target, else the next one down.
+ var next = heights.LastOrDefault(h => h > lo && h <= target + ProbeMargin);
+ if (next == 0)
+ next = heights.LastOrDefault(h => h > lo && h < cursor - ProbeMargin);
+ if (next == 0)
+ break;
+ limit = next + ProbeMargin;
+ }
+ else if (cursor - lo > Resolution * bin)
+ limit = target;
+ else
+ break;
+
+ var fits = packAll(vertical ? null : limit, vertical ? limit : null).Where(Complete).ToList();
+ if (fits.Count == 0)
+ {
+ cursor = limit - ProbeMargin;
+ continue;
+ }
+ // First of the tightest keeps enumeration order as the tie-break.
+ var best = fits.Aggregate((a, b) => Extent(b) < Extent(a) ? b : a);
+ tightest = best;
+ cursor = System.Math.Min(limit, Extent(best) + s) - ProbeMargin;
+ }
+
+ if (tightest != null && Extent(tightest) < baseline - Resolution * bin)
+ yield return tightest;
+ }
+ }
+
+ ///
+ /// A bin extent along the squeezed axis below which the demand cannot fit: every part needs its
+ /// shortest fitting side plus spacing, and the boxes' spaced area must fit across the bin.
+ /// Clamps only add to it, so ignoring them keeps the bound valid.
+ ///
+ private static double LowerBound(
+ IReadOnlyList types, IReadOnlyList remaining, double across, double s, bool vertical)
+ {
+ double fit = 0, area = 0;
+ foreach (var t in types)
+ {
+ if (remaining[t.Index] == 0 || t.Orientations.Count == 0)
+ continue;
+ var sizes = Sizes(t, s, vertical).ToList();
+ var fitting = sizes.Where(z => z.Across <= across + SheetPacker.OverhangAllowance).ToList();
+ if (fitting.Count > 0)
+ fit = System.Math.Max(fit, fitting.Min(z => z.Along));
+ area += remaining[t.Index] * sizes.Min(z => z.Along * z.Across);
+ }
+ return System.Math.Max(fit, area / across);
+ }
+
+ ///
+ /// Every bin extent below that a stack of spaced part sides reaches from
+ /// the bin edge or the far edge of a clamp keep-out that starts within
+ /// across the axis, sorted; null when there are more than
+ /// . Stacks ignore part counts, so some heights are unreachable;
+ /// they cost a probe each, never a missed layout.
+ ///
+ private static List? StackHeights(
+ IReadOnlyList types, IReadOnlyList remaining, NestPlateStock stock,
+ double across, bool vertical, double below, double reach)
+ {
+ var s = stock.PartSpacing;
+ var sides = types
+ .Where(t => remaining[t.Index] > 0)
+ .SelectMany(t => Sizes(t, s, vertical))
+ .Where(z => z.Across <= across + SheetPacker.OverhangAllowance && z.Along > 0)
+ .Select(z => z.Along)
+ .Distinct()
+ .ToArray();
+
+ var work = stock.WorkArea;
+ var bases = new List { 0 };
+ foreach (var zone in stock.ClampZones)
+ {
+ var edge = (vertical ? zone.Bounds.Top - work.Bottom : zone.Bounds.Right - work.Left)
+ + stock.ClampClearance;
+ var near = (vertical ? zone.Bounds.Left - work.Left : zone.Bounds.Bottom - work.Bottom)
+ - stock.ClampClearance;
+ if (edge > 0 && edge < below && near < reach)
+ bases.Add(edge);
+ }
+
+ // Keys on a 1e-6 grid merge sums that differ only by float rounding; each keeps its
+ // largest float value so a probe just past it holds every stack it stands for.
+ var seen = new Dictionary();
+ var queue = new Queue(bases);
+ while (queue.Count > 0)
+ {
+ var v = queue.Dequeue();
+ foreach (var side in sides)
+ {
+ var w = v + side;
+ if (w >= below - ProbeMargin)
+ continue;
+ var key = System.Math.Round(w, 6);
+ if (seen.TryGetValue(key, out var kept))
+ {
+ if (w > kept)
+ seen[key] = w;
+ continue;
+ }
+ if (seen.Count >= MaxStackHeights)
+ return null;
+ seen.Add(key, w);
+ queue.Enqueue(w);
+ }
+ }
+ return seen.Values.OrderBy(h => h).ToList();
+ }
+
+ private static IEnumerable<(double Along, double Across)> Sizes(BoxType type, double s, bool vertical) =>
+ type.Orientations.Select(o => vertical
+ ? (o.Height + s, o.Width + s)
+ : (o.Width + s, o.Height + s));
+}
diff --git a/docs/nesting-engines.md b/docs/nesting-engines.md
index d5a28f1a..20acfc42 100644
--- a/docs/nesting-engines.md
+++ b/docs/nesting-engines.md
@@ -12,7 +12,7 @@ in `OpenNest.Engine/NestingEngines//`, its tests in `OpenNest.Engine.Tests
| Engine | Best for | Method |
|---|---|---|
| Default | Any job; used when no engine is named | Runs Irregular, Rectangles and Circles, checks each layout with the layout check and keeps the best: valid first, then fewest unplaced parts, then lowest salvage-credited cost, then the largest compactness offcut; remaining ties keep the engine listed first here |
-| Rectangles | Plain and near-rectangular plates | Each part packed as the box of its material at its minimum-area rotation, using a maximal-rectangles free list; stock chosen sheet by sheet by salvage-credited look-ahead cost |
+| Rectangles | Plain and near-rectangular plates | Each part packed as the box of its material at its minimum-area rotation, using a maximal-rectangles free list; the sheet that finishes the job is also squeezed into a shorter or narrower bin; stock chosen sheet by sheet by salvage-credited look-ahead cost |
| Circles | Round parts: blanks, washers, flanges, gears by their tip circle | Each part packed as its minimum enclosing circle at centers tangent to two of walls, placed discs and clamp keep-outs, with hexagonal and square lattice seeds; bids only when every part is round; stock chosen as Rectangles does, plus whole-job runs preferring each stock and a tail re-plan |
| Irregular | Irregular profiles | No-fit-polygon frontier packing with gap filling and best-fit pairs, six whole-job strategy variants and a tail re-plan, then grouped variants that pack like parts together, then a sheet-reduction pass that packs edge parts first and turns a key part by its contacts; under a time limit it keeps trying seeded variants until the limit |
| FlatBar | Flat bar: parts about as wide as the bar | Each stock read as a bar and cut in one row: the CutList cut-list packer and a best-fill search on part lengths less their self-nesting gain, then a slide-left pass that nests shaped ends and refills freed length; stock chosen bar by bar by whole-plan cost |
@@ -56,6 +56,26 @@ Rectangles places irregular parts validly, but only as their bounding boxes; it
notch or hole. Box sides account for how the layout check flattens arcs, so round-edged parts stay
valid at box contact.
+#### Squeezing the last sheet
+
+The fit rules pack into the whole sheet, so when the last sheet has room to spare they spread the
+remaining parts out and rarely find the shortest strip that holds them. On N26-2A5 (14 door stops
+and 14 lock plates on a clamped 26 x 26 sheet) every rule stopped 8.0 high, while the hand layout
+holds all 28 under 7.5 and leaves a half-inch-deeper drop. So when some rule places all the
+remaining demand on a sheet, Rectangles packs that demand again into a bin cut shorter, and
+separately narrower, running every rule and mode at each size. It keeps the tightest complete
+layout along each axis that beats the whole-sheet ones, and the stock loop then picks among all of
+them by cost and offcut as before (`SheetSqueeze`).
+
+Whether a greedy packing fits is far from monotonic in the bin size (N26-2A5 packs at 7.5 but not
+at 7.62 or 7.74), so the sizes probed are stack heights, not halves of a range: sums of the parts'
+spaced sides along the axis, starting at the bin edge or at the far side of a clamp the layout
+reaches. The probes walk down from the best extent so far, spread over the probes left, and a probe
+that fails never raises the floor. With more than 2048 stack heights the walk steps through the
+size range instead. Each probe repacks every rule and mode, so the squeeze is capped by a count of
+box placements (at most 12 probes per axis and 50,000 placements in all): a sheet finishing more
+than about 2,000 parts is not squeezed. Sheets that do not finish the job are never squeezed.
+
### Best plan so far
Whole-job engines report each better whole-job plan as `NestJobStage.PlanImproved` with the plan