diff --git a/OpenNest.Engine.Tests/Jobs/FixedStrategyNestingEngineTests.cs b/OpenNest.Engine.Tests/Jobs/FixedStrategyNestingEngineTests.cs index 2c4483b0..47a107ca 100644 --- a/OpenNest.Engine.Tests/Jobs/FixedStrategyNestingEngineTests.cs +++ b/OpenNest.Engine.Tests/Jobs/FixedStrategyNestingEngineTests.cs @@ -51,6 +51,36 @@ public class FixedStrategyNestingEngineTests Assert.Single(result.Plates); } + public static TheoryData Strategies => + new() { "Lattice", "Strip", "Vertical Remnant", "Horizontal Remnant" }; + + [Theory] + [MemberData(nameof(Strategies))] + public void ClampedSheet_KeepsTheClampClearance(string strategy) + { + // N26-2A5 rev 5: a 26 x 26 sheet with one rail clamp on its front edge. Strip used to + // lay its strips and pack its leftovers across the clamp, so the runner's validation + // threw every layout away. + var clamp = new ClampZone("clamp-1", new Box(19.25, 0, 1.5, 0.625)); + var stock = new NestPlateStock("sheet", new Size(26, 26), 1, 0.25, + new Spacing(0.25, 0.25, 0.25, 0.25), 1, null, [clamp]); + var parts = new[] + { + new NestJobPart("stop", + PartGeometrySnapshot.FromProgram(TestDrawingFactory.Rectangle(3, 1.5)), 14), + new NestJobPart("plate", + PartGeometrySnapshot.FromProgram(TestDrawingFactory.Rectangle(4, 2.5)), 14), + }; + var job = new NestJob(parts, new[] { stock }, new NestJobOptions(strategy)); + + var result = new FixedStrategyNestingEngine(strategy).Solve(job); + + Assert.Equal(NestJobStatus.Complete, result.Status); + Assert.Equal(28, result.Plates.Sum(p => p.Placements.Count)); + // NestLayoutCheck holds every part the clamp clearance away from the zone. + LayoutAssert.Valid(job, result); + } + [Fact] public void RejectsNullOrWhitespaceStrategyAtConstruction() { diff --git a/OpenNest.Engine/Fill/IterativeShrinkFiller.cs b/OpenNest.Engine/Fill/IterativeShrinkFiller.cs index 9918e584..c97b4903 100644 --- a/OpenNest.Engine/Fill/IterativeShrinkFiller.cs +++ b/OpenNest.Engine/Fill/IterativeShrinkFiller.cs @@ -34,7 +34,8 @@ namespace OpenNest.Engine.Fill CancellationToken token = default, IProgress progress = null, int plateNumber = 0, - Func> widthFillFunc = null + Func> widthFillFunc = null, + IEnumerable keepOuts = null ) { if (items == null || items.Count == 0) @@ -71,7 +72,7 @@ namespace OpenNest.Engine.Fill } } - var filler = new RemnantFiller(workArea, spacing); + var filler = new RemnantFiller(workArea, spacing, keepOuts); // Track parts placed by previous items so ShrinkFiller can // include them in progress reports. diff --git a/OpenNest.Engine/Jobs/Placement/Fillers/PlateFiller.cs b/OpenNest.Engine/Jobs/Placement/Fillers/PlateFiller.cs index 060292d7..9bf9c30e 100644 --- a/OpenNest.Engine/Jobs/Placement/Fillers/PlateFiller.cs +++ b/OpenNest.Engine/Jobs/Placement/Fillers/PlateFiller.cs @@ -515,6 +515,9 @@ internal class PlateFiller return new List(); var workArea = Plate.WorkArea(); + // Clamp keep-outs (zones grown by the clamp clearance) are occupied space for the + // strips, the settle and the pack remnant alike, as in the Lattice orchestration. + var clampKeepOuts = InteractiveClamps.ClearanceRectangles(Plate); var fillItems = items .Where(item => item.Quantity != 1) .OrderByDescending(item => item.Priority) @@ -546,11 +549,15 @@ internal class PlateFiller token, progress, PlateNumber, - widthFillFunc + widthFillFunc, + clampKeepOuts ); allParts.AddRange(shrinkResult.Parts); - Compactor.Settle(allParts, workArea, Plate.PartSpacing); + if (clampKeepOuts.Count > 0) + Compactor.Settle(allParts, Plate, workArea); + else + Compactor.Settle(allParts, workArea, Plate.PartSpacing); packItems.AddRange(shrinkResult.Leftovers); } @@ -558,11 +565,12 @@ internal class PlateFiller if (packItems.Count > 0 && !token.IsCancellationRequested) { var packArea = workArea; - if (allParts.Count > 0) + if (allParts.Count > 0 || clampKeepOuts.Count > 0) { var obstacles = allParts .Select(part => part.BoundingBox.Offset(Plate.PartSpacing)) .ToList(); + obstacles.AddRange(clampKeepOuts); var finder = new RemnantFinder(workArea, obstacles); var remnants = finder.FindRemnants(); packArea = remnants.Count > 0 ? remnants[0] : new Box(0, 0, 0, 0);