From aea51b5e1d5cfcd1978b335c44ccf9d1c9572ef5 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Mon, 12 Oct 2026 19:00:50 -0400 Subject: [PATCH] fix(engine): stop following a rail that does not recreate solved zones When a commit installed solved explicit zones on a plate that followed a different nest clamp rail (an extra sheet created from the defaults), the plate kept the rail, so a later Size or Quadrant assignment (Edit Plate assigns Size even when unchanged) or a stock-option build swapped the validated zones for the rail's clamps. Install solved zones as explicit zones and keep the rail only when it recreates them. Review finding 1, Project Memory 4e5f566d-626e-4db5-9352-14ddd9051181. --- .../Jobs/ClampZoneCommitTests.cs | 60 +++++++++++++++++++ OpenNest.Engine/Jobs/NestPipelineCommit.cs | 9 ++- 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/OpenNest.Engine.Tests/Jobs/ClampZoneCommitTests.cs b/OpenNest.Engine.Tests/Jobs/ClampZoneCommitTests.cs index db1e8d0..867eb5c 100644 --- a/OpenNest.Engine.Tests/Jobs/ClampZoneCommitTests.cs +++ b/OpenNest.Engine.Tests/Jobs/ClampZoneCommitTests.cs @@ -93,6 +93,66 @@ public class ClampZoneCommitTests Assert.Equal(0, changes); } + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + [InlineData(4)] + public void SolvedZonesARailCannotRecreateStopFollowingThatRail(int quadrant) + { + // The nest rail puts a clamp at 6"; the template carries a different explicit zone, + // so the extra sheet the commit creates must not keep following the nest rail. + var drawing = new Drawing("part", TestDrawingFactory.Rectangle()); + var nest = new Nest(); + nest.Drawings.Add(drawing); + nest.PlateDefaults.Size = new Size(48, 96); + nest.PlateDefaults.Quadrant = quadrant; + nest.SetClampRail(new ClampRail(1.5, 0.625, new[] { 6.0 })); + var template = nest.CreatePlate(); + template.SetClampRail(null!); + var manual = new ClampZone("manual", new Box(10, 0, 2, 0.5)); + template.ClampZones.Add(manual); + using var manager = new PlateManager(nest); + + var applied = NestPipelineCommit.ApplyToEmptyPlates( + Result(drawing, quadrant, template.ClampZones.ToArray()), manager); + + Assert.Equal(2, applied.Count); + Assert.All(applied, plate => + { + Assert.Null(plate.ClampRail); + plate.Size = plate.Size; // the Edit Plate dialog assigns Size even when unchanged + plate.Quadrant = plate.Quadrant; + Assert.Equal("manual", Assert.Single(plate.ClampZones).Id); + Assert.Equal("manual", Assert.Single(Assert.Single( + NestStockBuilder.FromTemplate(plate, [new PlateOption { Width = 48, Length = 96 }])).ClampZones).Id); + }); + } + + [Fact] + public void SolvedZonesTheRailRecreatesKeepFollowingIt() + { + var drawing = new Drawing("part", TestDrawingFactory.Rectangle()); + var nest = new Nest(); + nest.Drawings.Add(drawing); + nest.PlateDefaults.Size = new Size(48, 96); + var rail = new ClampRail(1.5, 0.625, new[] { 6.0 }); + nest.SetClampRail(rail); + var template = nest.CreatePlate(); + using var manager = new PlateManager(nest); + + var applied = NestPipelineCommit.ApplyToEmptyPlates( + Result(drawing, 1, template.ClampZones.ToArray()), manager); + + Assert.Equal(2, applied.Count); + Assert.All(applied, plate => + { + Assert.Same(rail, plate.ClampRail); + plate.Size = new Size(48, 4); + Assert.Empty(plate.ClampZones); // still re-resolves: no clamp reaches a 4" sheet + }); + } + [Fact] public void ZonesTheLayoutWasNotSolvedWithAreRemoved() { diff --git a/OpenNest.Engine/Jobs/NestPipelineCommit.cs b/OpenNest.Engine/Jobs/NestPipelineCommit.cs index fbfc476..85a1ae7 100644 --- a/OpenNest.Engine/Jobs/NestPipelineCommit.cs +++ b/OpenNest.Engine/Jobs/NestPipelineCommit.cs @@ -58,7 +58,9 @@ public static class NestPipelineCommit /// The live plate keeps exactly the clamp keep-outs its layout was solved and validated /// against, including sheets created for this commit. Stock zones live in the placement /// frame; plate zones in plate coordinates, so the stock's translation is inverted. Zones - /// already equal in the placement frame are left untouched (no change events). + /// already equal in the placement frame are left untouched (no change events). Replaced + /// zones stay tied to the plate's clamp rail only when that rail recreates them; + /// otherwise a later size or quadrant assignment would silently swap in other clamps. /// private static void SyncClampZones(Plate plate, NestPlateStock stock) { @@ -67,9 +69,12 @@ public static class NestPipelineCommit if (SameInPlacementFrame(plate.ClampZones, stock.ClampZones, left, bottom)) return; - plate.ClampZones.Clear(); + var rail = plate.ClampRail; + plate.SetClampRail(null); // explicit zones from here on foreach (var zone in stock.ClampZones) plate.ClampZones.Add(new ClampZone(zone.Id, zone.Bounds.Translate(-left, -bottom))); + if (rail != null && rail.Matches(plate)) + plate.SetClampRail(rail); } private static bool SameInPlacementFrame(