mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-11-04 04:41:35 -05:00
Reopening a nest regenerated its cut-offs with built-in defaults (no overtravel, a different minimum segment) rather than the machine's settings it was made with, so a nest saved, reopened and posted could cut scrap that never separates. Plates now record the settings their cut-offs were last generated with, and files carry them; older files still load with the defaults. Saving over a nest file truncated it before writing, so any failure part way lost the only good copy, and the error reached the user as an unhandled exception after the save path had already changed. Nest and settings files are now written beside the target and swapped in, and a failed save is reported with the old file left intact. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
247 lines
9.9 KiB
C#
247 lines
9.9 KiB
C#
using OpenNest.CNC;
|
|
using OpenNest.Geometry;
|
|
using OpenNest.IO;
|
|
|
|
namespace OpenNest.Tests.CutOffs;
|
|
|
|
public class CutOffSerializationTests
|
|
{
|
|
[Fact]
|
|
public void RoundTrip_SegmentsKeepTheirOwnPlacesInTheSequence()
|
|
{
|
|
var nest = new Nest();
|
|
var drawing = TestHelpers.MakeSquareDrawing();
|
|
nest.Drawings.Add(drawing);
|
|
var lower = new Part(drawing, new Vector(10, 10));
|
|
var upper = new Part(drawing, new Vector(10, 30));
|
|
var plate = TestHelpers.MakePlate(60, 120, lower, upper);
|
|
nest.Plates.Add(plate);
|
|
var cutOff = new CutOff(new Vector(15, 0), CutOffAxis.Vertical);
|
|
plate.CutOffs.Add(cutOff);
|
|
plate.RegenerateCutOffs(new CutOffSettings());
|
|
var segments = CutOffSegments.Of(plate, cutOff).Select(s => s.Part).ToArray();
|
|
plate.Parts.Reorder([segments[0], lower, segments[1], upper, segments[2]]);
|
|
|
|
using var stream = new MemoryStream();
|
|
Assert.True(new NestWriter(nest).Write(stream));
|
|
stream.Position = 0;
|
|
using (var archive = new System.IO.Compression.ZipArchive(stream, System.IO.Compression.ZipArchiveMode.Read, leaveOpen: true))
|
|
using (var reader = new StreamReader(archive.GetEntry("nest.json")!.Open()))
|
|
using (var json = System.Text.Json.JsonDocument.Parse(reader.ReadToEnd()))
|
|
{
|
|
var saved = Assert.Single(Assert.Single(json.RootElement.GetProperty("plates").EnumerateArray())
|
|
.GetProperty("cutOffs").EnumerateArray());
|
|
Assert.Equal(0, saved.GetProperty("sequence").GetInt32());
|
|
Assert.Equal(new[] { 0, 2, 4 }, saved.GetProperty("segments").EnumerateArray().Select(e => e.GetInt32()));
|
|
}
|
|
stream.Position = 0;
|
|
var loaded = new NestReader(stream).Read();
|
|
|
|
var loadedPlate = Assert.Single(loaded.Plates);
|
|
var loadedCutOff = Assert.Single(loadedPlate.CutOffs);
|
|
var loadedSegments = CutOffSegments.Of(loadedPlate, loadedCutOff);
|
|
Assert.Equal(3, loadedSegments.Count);
|
|
Assert.Equal(new[] { true, false, true, false, true }, loadedPlate.Parts.Select(p => p.BaseDrawing.IsCutOff));
|
|
Assert.Equal(new[] { 0, 2, 4 }, loadedSegments.Select(s => loadedPlate.Parts.IndexOf(s.Part)));
|
|
Assert.True(loadedSegments[0].End < loadedSegments[1].Start && loadedSegments[1].End < loadedSegments[2].Start);
|
|
}
|
|
|
|
[Fact]
|
|
public void RoundTrip_GapsAreKeptAndWrittenOnlyWhenPresent()
|
|
{
|
|
var nest = new Nest();
|
|
var plate = TestHelpers.MakePlate(48, 96);
|
|
nest.Plates.Add(plate);
|
|
var gapped = new CutOff(new Vector(0, 20), CutOffAxis.Horizontal) { Gaps = [new CutOffGap(35, 68.6)] };
|
|
var plain = new CutOff(new Vector(35, 0), CutOffAxis.Vertical);
|
|
plate.CutOffs.Add(gapped);
|
|
plate.CutOffs.Add(plain);
|
|
plate.RegenerateCutOffs(new CutOffSettings());
|
|
|
|
using var stream = new MemoryStream();
|
|
Assert.True(new NestWriter(nest) { KeepEmptyPlates = true }.Write(stream));
|
|
stream.Position = 0;
|
|
using (var archive = new System.IO.Compression.ZipArchive(stream, System.IO.Compression.ZipArchiveMode.Read, leaveOpen: true))
|
|
using (var reader = new StreamReader(archive.GetEntry("nest.json")!.Open()))
|
|
using (var json = System.Text.Json.JsonDocument.Parse(reader.ReadToEnd()))
|
|
{
|
|
var saved = Assert.Single(json.RootElement.GetProperty("plates").EnumerateArray())
|
|
.GetProperty("cutOffs").EnumerateArray().ToArray();
|
|
var gap = Assert.Single(saved[0].GetProperty("gaps").EnumerateArray());
|
|
Assert.Equal(35, gap.GetProperty("start").GetDouble());
|
|
Assert.Equal(68.6, gap.GetProperty("end").GetDouble());
|
|
Assert.False(saved[1].TryGetProperty("gaps", out _));
|
|
}
|
|
stream.Position = 0;
|
|
var loaded = Assert.Single(new NestReader(stream).Read().Plates);
|
|
|
|
Assert.Equal(new[] { new CutOffGap(35, 68.6) }, loaded.CutOffs[0].Gaps);
|
|
Assert.Empty(loaded.CutOffs[1].Gaps);
|
|
Assert.Equal(2, CutOffSegments.Of(loaded, loaded.CutOffs[0]).Count);
|
|
}
|
|
|
|
[Fact]
|
|
public void RoundTrip_RegeneratesWithTheSavedSettingsNotTheDefaults()
|
|
{
|
|
var nest = new Nest();
|
|
var plate = TestHelpers.MakePlate(48, 96);
|
|
nest.Plates.Add(plate);
|
|
plate.CutOffs.Add(new CutOff(new Vector(35, 0), CutOffAxis.Vertical));
|
|
var machine = new CutOffSettings
|
|
{
|
|
Overtravel = 0.75,
|
|
MinSegmentLength = 0.5,
|
|
PartClearance = 0.1,
|
|
CutDirection = CutDirection.TowardOrigin,
|
|
};
|
|
plate.RegenerateCutOffs(machine);
|
|
var before = Program(plate);
|
|
|
|
using var stream = new MemoryStream();
|
|
Assert.True(new NestWriter(nest) { KeepEmptyPlates = true }.Write(stream));
|
|
stream.Position = 0;
|
|
var loaded = Assert.Single(new NestReader(stream).Read().Plates);
|
|
|
|
// The defaults have no overtravel, so a reader using them would not cut through.
|
|
Assert.Equal(before, Program(loaded));
|
|
Assert.Equal(0.75, loaded.CutOffSettings.Overtravel);
|
|
Assert.Equal(CutDirection.TowardOrigin, loaded.CutOffSettings.CutDirection);
|
|
|
|
static string Program(Plate plate) => string.Join(";", plate.Parts.Where(p => p.BaseDrawing.IsCutOff)
|
|
.SelectMany(p => p.Program.Codes).OfType<Motion>().Select(m => m.EndPoint.ToString()));
|
|
}
|
|
|
|
[Fact]
|
|
public void Read_WithoutSavedSettings_UsesTheDefaults()
|
|
{
|
|
var nest = new Nest();
|
|
var plate = TestHelpers.MakePlate(48, 96);
|
|
nest.Plates.Add(plate);
|
|
plate.CutOffs.Add(new CutOff(new Vector(35, 0), CutOffAxis.Vertical));
|
|
plate.RegenerateCutOffs(new CutOffSettings { Overtravel = 0.75 });
|
|
plate.CutOffSettings = null;
|
|
|
|
using var stream = new MemoryStream();
|
|
Assert.True(new NestWriter(nest) { KeepEmptyPlates = true }.Write(stream));
|
|
stream.Position = 0;
|
|
var loaded = Assert.Single(new NestReader(stream).Read().Plates);
|
|
|
|
Assert.Equal(0, loaded.CutOffSettings.Overtravel);
|
|
}
|
|
|
|
[Fact]
|
|
public void RoundTrip_CutOffsPreserved()
|
|
{
|
|
var nest = new Nest();
|
|
nest.Name = "test";
|
|
nest.DateCreated = DateTime.Now;
|
|
nest.DateLastModified = DateTime.Now;
|
|
|
|
var pgm = new Program();
|
|
pgm.Codes.Add(new RapidMove(new Vector(0, 0)));
|
|
pgm.Codes.Add(new LinearMove(new Vector(10, 10)));
|
|
var drawing = new Drawing("part1", pgm);
|
|
nest.Drawings.Add(drawing);
|
|
|
|
var plate = new Plate(100, 50);
|
|
plate.Parts.Add(new Part(drawing));
|
|
plate.CutOffs.Add(new CutOff(new Vector(62.0, 24.0), CutOffAxis.Vertical));
|
|
plate.CutOffs.Add(new CutOff(new Vector(48.0, 30.0), CutOffAxis.Horizontal));
|
|
plate.RegenerateCutOffs(new CutOffSettings());
|
|
nest.Plates.Add(plate);
|
|
|
|
using var stream = new MemoryStream();
|
|
var writer = new NestWriter(nest);
|
|
writer.Write(stream);
|
|
|
|
stream.Position = 0;
|
|
var reader = new NestReader(stream);
|
|
var loaded = reader.Read();
|
|
|
|
Assert.Single(loaded.Plates);
|
|
var loadedPlate = loaded.Plates[0];
|
|
|
|
Assert.Equal(2, loadedPlate.CutOffs.Count);
|
|
Assert.Equal(CutOffAxis.Vertical, loadedPlate.CutOffs[0].Axis);
|
|
Assert.Equal(62.0, loadedPlate.CutOffs[0].Position.X, 5);
|
|
Assert.Equal(24.0, loadedPlate.CutOffs[0].Position.Y, 5);
|
|
Assert.Equal(CutOffAxis.Horizontal, loadedPlate.CutOffs[1].Axis);
|
|
|
|
Assert.Single(loadedPlate.Parts.Where(p => !p.BaseDrawing.IsCutOff));
|
|
Assert.Single(loaded.Drawings);
|
|
}
|
|
|
|
[Fact]
|
|
public void NestWriter_SkipsCutOffPartsInPartsList()
|
|
{
|
|
var nest = new Nest();
|
|
nest.Name = "test";
|
|
nest.DateCreated = DateTime.Now;
|
|
nest.DateLastModified = DateTime.Now;
|
|
|
|
var pgm = new Program();
|
|
pgm.Codes.Add(new RapidMove(new Vector(0, 0)));
|
|
pgm.Codes.Add(new LinearMove(new Vector(10, 10)));
|
|
var drawing = new Drawing("part1", pgm);
|
|
nest.Drawings.Add(drawing);
|
|
|
|
var plate = new Plate(100, 50);
|
|
plate.Parts.Add(new Part(drawing));
|
|
plate.CutOffs.Add(new CutOff(new Vector(50, 25), CutOffAxis.Vertical));
|
|
plate.RegenerateCutOffs(new CutOffSettings());
|
|
nest.Plates.Add(plate);
|
|
|
|
Assert.Equal(2, plate.Parts.Count);
|
|
|
|
using var stream = new MemoryStream();
|
|
var writer = new NestWriter(nest);
|
|
writer.Write(stream);
|
|
|
|
stream.Position = 0;
|
|
var reader = new NestReader(stream);
|
|
var loaded = reader.Read();
|
|
|
|
Assert.Single(loaded.Plates[0].Parts.Where(p => !p.BaseDrawing.IsCutOff));
|
|
}
|
|
|
|
[Fact]
|
|
public void RoundTrip_LimitsPreserved()
|
|
{
|
|
var nest = new Nest();
|
|
nest.Name = "test";
|
|
nest.DateCreated = DateTime.Now;
|
|
nest.DateLastModified = DateTime.Now;
|
|
|
|
var pgm = new Program();
|
|
pgm.Codes.Add(new RapidMove(new Vector(0, 0)));
|
|
pgm.Codes.Add(new LinearMove(new Vector(10, 10)));
|
|
var drawing = new Drawing("part1", pgm);
|
|
nest.Drawings.Add(drawing);
|
|
|
|
var plate = new Plate(100, 50);
|
|
plate.Parts.Add(new Part(drawing));
|
|
plate.CutOffs.Add(
|
|
new CutOff(new Vector(85, 30), CutOffAxis.Horizontal) { EndLimit = 85.0 }
|
|
);
|
|
plate.CutOffs.Add(
|
|
new CutOff(new Vector(85, 30), CutOffAxis.Vertical) { StartLimit = 30.0 }
|
|
);
|
|
plate.RegenerateCutOffs(new CutOffSettings());
|
|
nest.Plates.Add(plate);
|
|
|
|
using var stream = new MemoryStream();
|
|
var writer = new NestWriter(nest);
|
|
writer.Write(stream);
|
|
|
|
stream.Position = 0;
|
|
var reader = new NestReader(stream);
|
|
var loaded = reader.Read();
|
|
|
|
var loadedPlate = loaded.Plates[0];
|
|
Assert.Equal(85.0, loadedPlate.CutOffs[0].EndLimit);
|
|
Assert.Null(loadedPlate.CutOffs[0].StartLimit);
|
|
Assert.Equal(30.0, loadedPlate.CutOffs[1].StartLimit);
|
|
Assert.Null(loadedPlate.CutOffs[1].EndLimit);
|
|
}
|
|
}
|