mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-11-14 12:35:07 -05:00
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>
70 lines
2.8 KiB
C#
70 lines
2.8 KiB
C#
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;
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public class RectanglesSqueezeTests
|
|
{
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
[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);
|
|
});
|
|
}
|