Files
OpenNest/OpenNest.Tests/Engine/CutoutPipelinePersistenceTests.cs
T
ajandClaude Opus 5.5 4a1587d6bd feat(engine): fill cutouts first in every whole-job run
NestPipeline.Run now bundles parts that fit other parts' enclosed cutouts
into their frames before the selected engine, for desktop, Console, MCP and
API alike. A valid complete bundled trial is kept; a refused trial falls back
to the ordinary solve (reason on RefusedCutoutTrial), and an incomplete trial
is compared with it, keeping whichever valid proposal places more. An
incomplete but valid trial is now keepable like any incomplete result.
Expanded sheets list each frame's inserts before the frame, so the committed
order cuts every insert before the cutout that releases its slug.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 12:51:18 -04:00

76 lines
3.4 KiB
C#

using OpenNest.CNC;
using OpenNest.Engine;
using OpenNest.Engine.Jobs;
using OpenNest.Geometry;
using OpenNest.IO;
namespace OpenNest.Tests.Engine;
public class CutoutPipelinePersistenceTests
{
[Fact]
public void ExpandedMultiSheetProposalCommitsAndReloadsAsSeparateDrawings()
{
var frame = new OpenNest.Shapes.RingShape { OuterDiameter = 20, InnerDiameter = 10 }
.GetDrawing();
frame.Name = "neutral-frame";
var program = new Program();
program.MoveTo(0, 0);
program.LineTo(6, 0);
program.LineTo(6, 6);
program.LineTo(0, 6);
program.LineTo(0, 0);
var insert = new Drawing("neutral-insert", program);
var request = new NestPipelineRequest("Irregular", new[] {
new NestItem { Drawing = frame, Quantity = 2 },
new NestItem { Drawing = insert, Quantity = 2 } },
new[] { new NestPlateStock("sheet", new Size(22, 22), 2, 0.25) },
new NestJobOptions(maxPlates: 2));
var ordinary = NestPipeline.RunWithoutCutouts(request);
Assert.Contains(ordinary.Raw.Fulfillment, f => f.Unplaced > 0);
var result = NestPipeline.Run(request);
Assert.Empty(result.RefusedCutoutTrial);
Assert.True(result.IsValid, string.Join("; ", result.Violations));
Assert.Equal(2, result.Raw.Plates.Count);
Assert.All(result.Raw.Plates, p => Assert.Equal(new[] { "part-1", "part-0" },
p.Placements.Select(x => x.PartId)));
var nest = new Nest();
nest.Drawings.Add(frame);
nest.Drawings.Add(insert);
using (var manager = new PlateManager(nest))
{
var committed = NestPipelineCommit.ApplyToEmptyPlates(result, manager);
Assert.Equal(2, committed.Sum(p => p.Quantity));
Assert.All(committed, plate => Assert.Equal(new[] { insert, frame },
plate.Parts.Select(p => p.BaseDrawing)));
}
Assert.Equal(2, frame.Quantity.Nested);
Assert.Equal(2, insert.Quantity.Nested);
using var stream = new MemoryStream();
Assert.True(new NestWriter(nest).Write(stream));
var loaded = new NestReader(new MemoryStream(stream.ToArray())).Read();
Assert.Equal(2, loaded.Plates.Sum(p => p.Quantity));
for (var i = 0; i < loaded.Plates.Count; i++)
{
var expected = nest.Plates[i];
var actual = loaded.Plates[i];
Assert.Equal(expected.Quantity, actual.Quantity);
Assert.Equal(expected.Size, actual.Size);
Assert.Equal(expected.PartSpacing, actual.PartSpacing);
Assert.Equal(expected.Quadrant, actual.Quadrant);
Assert.Equal(expected.EdgeSpacing, actual.EdgeSpacing);
Assert.Equal(new[] { "neutral-insert", "neutral-frame" },
actual.Parts.Select(p => p.BaseDrawing.Name));
for (var j = 0; j < actual.Parts.Count; j++)
{
Assert.Equal(expected.Parts[j].Location.X, actual.Parts[j].Location.X, 6);
Assert.Equal(expected.Parts[j].Location.Y, actual.Parts[j].Location.Y, 6);
Assert.Equal(expected.Parts[j].Rotation, actual.Parts[j].Rotation, 6);
}
}
Assert.Equal(2, loaded.Drawings.Single(d => d.Name == "neutral-frame").Quantity.Nested);
Assert.Equal(2, loaded.Drawings.Single(d => d.Name == "neutral-insert").Quantity.Nested);
}
}