fix(io): omit removedClamps from sheets without a removal

Every plate was written with "removedClamps": [], so files without the
feature changed. The key is now omitted unless a clamp was removed, like
the other additive sections; nest-file-format.md documents removedClamps
and removalMinutes and their lenient reading.

PM 6ded8ac4-84fd-4e8b-9a3b-6f41e494542c item (c)
This commit is contained in:
aj committed 2026-10-13 18:15:50 -04:00
1 parent 65754957cc
commit 04d538b0a4
4 files changed
+33 -8

No files matched your search

+14 -2
View File
@@ -236,8 +236,10 @@ public class ClampRailPersistenceTests
[Fact]
public void LegacyFileWithoutRemovedClampsKeyLoadsWithNoneRemoved()
{
var withoutKey = RewriteNestJson(Write(RailNest()),
text => RemoveJsonProperty(text, "removedClamps"));
// Sheets without a removal are written without the key, exactly like a file from
// before the feature.
var withoutKey = Write(RailNest());
Assert.DoesNotContain("\"removedClamps\"", ReadNestJson(withoutKey), StringComparison.Ordinal);
var reopened = new NestReader(withoutKey).Read();
@@ -309,6 +311,16 @@ public class ClampRailPersistenceTests
Assert.NotNull(plate.ClampRail);
}
// PM 6ded8ac4 (c): sheets without a removal write no removedClamps key at all.
[Fact]
public void SheetsWithoutRemovedClampsWriteNoRemovedClampsKey()
{
var json = ReadNestJson(Write(RailNest()));
Assert.DoesNotContain("\"removedClamps\"", json, StringComparison.Ordinal);
Assert.Contains("\"removedClamps\"", ReadNestJson(RemovedClamp3Nest()), StringComparison.Ordinal);
}
/// <summary>Removes a named property wherever it appears in the document, simulating a
/// key an older writer never emitted. Rebuilds via <see cref="JsonNode"/> so the result
/// is always valid JSON regardless of whether the property was first, last or only one
+3 -2
View File
@@ -206,11 +206,12 @@ namespace OpenNest.IO
public List<ClampZoneDto> Clamps { get; init; } = new();
/// <summary>
/// Rail clamp ids removed from this sheet as a last resort (D8). Missing or empty
/// means none were removed; <see cref="Clamps"/> already excludes them. Read
/// Rail clamp ids removed from this sheet as a last resort (D8). Omitted when none
/// were removed; missing or empty means none; <see cref="Clamps"/> already excludes them. Read
/// leniently: a wrongly shaped value reads as none removed, non-string entries
/// are skipped, and the saved zones load either way.
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonConverter(typeof(LenientStringListConverter))]
public List<string> RemovedClamps { get; init; } = new();
}
+8 -4
View File
@@ -334,10 +334,14 @@ namespace OpenNest.IO
Width = z.Bounds.Width,
})
.ToList(),
RemovedClamps = plate.RemovedClamps
.OrderBy(ClampIndex)
.ThenBy(id => id, StringComparer.Ordinal)
.ToList(),
// Omitted unless a clamp was removed (files without the feature
// stay unchanged).
RemovedClamps = plate.RemovedClamps.Count == 0
? null
: plate.RemovedClamps
.OrderBy(ClampIndex)
.ThenBy(id => id, StringComparer.Ordinal)
.ToList(),
}
);
}
+8
View File
@@ -80,6 +80,14 @@ malformed rail loads as none. On load a plate follows the rail again only when
its saved `clamps` are exactly the rail's zones for that sheet; any other saved
zones are kept as written.
The rail's optional `removalMinutes` is the time to take one clamp off and put
it back; a missing or invalid value loads as five minutes. A plate on which
Auto Nest removed clamps as a last resort lists their rail ids in an optional
`removedClamps` array (omitted when none were removed); its `clamps` already
exclude them, and it follows the rail minus those ids. A malformed
`removedClamps` value reads as none removed (non-string entries are skipped);
the saved `clamps` load either way.
An empty plate (no parts, no cut-offs) is omitted from the file unless that
would lose clamp zones: it is omitted like any other empty plate only when a
new plate from the saved defaults would recreate it (same size and quadrant,