From 65754957cc991a88147ba6fc612e674e574369d8 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Tue, 13 Oct 2026 17:06:29 -0400 Subject: [PATCH] fix(io): keep the clamp rail when its saved removal time is invalid A negative removalMinutes in a saved nest made the ClampRail constructor throw, so the reader dropped the whole rail and every sheet lost its removed-clamp state and time. An invalid value now falls back to the default five minutes, like a missing one. PM 6ded8ac4-84fd-4e8b-9a3b-6f41e494542c item (b) --- .../ClampRailPersistenceTests.cs | 23 +++++++++++++++++++ OpenNest.IO/NestReader.cs | 15 ++++++------ 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/OpenNest.IO.Tests/ClampRailPersistenceTests.cs b/OpenNest.IO.Tests/ClampRailPersistenceTests.cs index 76995d2..33e0b63 100644 --- a/OpenNest.IO.Tests/ClampRailPersistenceTests.cs +++ b/OpenNest.IO.Tests/ClampRailPersistenceTests.cs @@ -286,6 +286,29 @@ public class ClampRailPersistenceTests Assert.Equal(keepsRemoval, plate.ClampRail != null); } + // PM 6ded8ac4 (b): an invalid removal time falls back to the default instead of + // dropping the rail and every sheet's removal state with it. + [Theory] + [InlineData("-1")] + [InlineData("\"x\"")] + public void InvalidRemovalMinutesFallBackToTheDefault(string minutes) + { + var tampered = RewriteNestJson(RemovedClamp3Nest(), text => + { + var root = JsonNode.Parse(text)!; + root["plateDefaults"]!["clampRail"]!["removalMinutes"] = JsonNode.Parse(minutes); + return root.ToJsonString(); + }); + + var reopened = new NestReader(tampered).Read(); + + Assert.NotNull(reopened.PlateDefaults.ClampRail); + Assert.Equal(ClampRail.DefaultRemovalMinutes, reopened.PlateDefaults.ClampRail.RemovalMinutes); + var plate = Assert.Single(reopened.Plates); + Assert.Equal(new[] { "clamp-3" }, plate.RemovedClamps); + Assert.NotNull(plate.ClampRail); + } + /// Removes a named property wherever it appears in the document, simulating a /// key an older writer never emitted. Rebuilds via so the result /// is always valid JSON regardless of whether the property was first, last or only one diff --git a/OpenNest.IO/NestReader.cs b/OpenNest.IO/NestReader.cs index 054dc1e..9aff8a6 100644 --- a/OpenNest.IO/NestReader.cs +++ b/OpenNest.IO/NestReader.cs @@ -330,7 +330,7 @@ namespace OpenNest.IO /// /// The saved machine clamp rail, or null when absent, empty or malformed (a bad rail /// is ignored rather than failing the nest; each plate's saved zones still load). A - /// missing removal-minutes value falls back to . + /// missing or invalid removal-minutes value falls back to . /// private static ClampRail ReadClampRail(ClampRailDto dto) { @@ -338,12 +338,13 @@ namespace OpenNest.IO return null; try { - return new ClampRail( - dto.Width, - dto.Depth, - dto.Centers, - dto.RemovalMinutes ?? ClampRail.DefaultRemovalMinutes - ); + // An invalid removal time (negative or not finite) falls back to the default + // rather than dropping the rail, which would also discard every sheet's + // removed-clamp state. + var minutes = dto.RemovalMinutes is double m && double.IsFinite(m) && m >= 0 + ? m + : ClampRail.DefaultRemovalMinutes; + return new ClampRail(dto.Width, dto.Depth, dto.Centers, minutes); } catch (ArgumentException) {