Files
OpenNest/OpenNest.IO.Tests/BreakoffPersistenceTests.cs
T
ajandClaude Opus 5.5 6f555345e3 feat(cad): cut breakoff joining lines as tabbed open break cuts
Breakoffs are now picked by their joining line, default to the smaller side as scrap
(flippable), and cut that line between tabs on a new Break layer instead of slots.
Plan Cutting and Assign Lead-ins enter each cut from the scrap with the nest's internal
lead-in, or pierce on the line without one; checks treat break cuts as intentional open
cuts that are not material.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 17:46:35 -04:00

112 lines
3.9 KiB
C#

using System.IO.Compression;
using System.Text;
using System.Text.Json.Nodes;
using OpenNest.Breakoffs;
namespace OpenNest.IO.Tests;
public class BreakoffPersistenceTests
{
private static Drawing WithBreakoff()
{
var drawing = DrawingPackageFixture.Build();
drawing.Breakoffs.Add(new Breakoff
{
EdgeIds = [Guid.NewGuid(), Guid.NewGuid()],
TabWidth = 0.05,
Flip = true,
TabCenters = [0.025, 1.5, 3.2],
});
return drawing;
}
[Fact]
public void Breakoffs_RoundTripEveryField()
{
var drawing = WithBreakoff();
var original = drawing.Breakoffs[0];
var (restored, warnings) = RoundTrip(Write(drawing));
Assert.Empty(warnings);
var breakoff = Assert.Single(Assert.Single(restored.Drawings).Breakoffs);
Assert.Equal(original.EdgeIds, breakoff.EdgeIds);
Assert.Equal((0.05, true), (breakoff.TabWidth, breakoff.Flip));
Assert.Equal(original.TabCenters, breakoff.TabCenters);
}
[Fact]
public void BreakCutMoves_KeepTheirLayer()
{
var drawing = DrawingPackageFixture.Build();
drawing.Program.MoveTo(4, 5);
drawing.Program.Codes.Add(new OpenNest.CNC.LinearMove(1, 0) { Layer = OpenNest.CNC.LayerType.Break });
drawing.Program.Codes.Add(new OpenNest.CNC.ArcMove(1, 1, 0, 1) { Layer = OpenNest.CNC.LayerType.Break });
var (restored, _) = RoundTrip(Write(drawing));
var moves = Assert.Single(restored.Drawings).Program.Codes.OfType<OpenNest.CNC.Motion>()
.Where(m => m is OpenNest.CNC.LinearMove { Layer: OpenNest.CNC.LayerType.Break }
or OpenNest.CNC.ArcMove { Layer: OpenNest.CNC.LayerType.Break });
Assert.Equal(2, moves.Count());
}
[Fact]
public void NoBreakoffs_AreOmittedFromTheFile()
{
var archive = Write(DrawingPackageFixture.Build());
Assert.False(NestJson(archive)["drawings"]![0]!.AsObject().ContainsKey("breakoffs"));
Assert.Empty(Assert.Single(RoundTrip(archive).Nest.Drawings).Breakoffs);
}
[Fact]
public void UnreadableBreakoff_IsDroppedWithAWarning_AndTheDrawingLoads()
{
var archive = Write(WithBreakoff());
var json = NestJson(archive);
json["drawings"]![0]!["breakoffs"]![0]!["tabWidth"] = 0;
var (restored, warnings) = RoundTrip(ReplaceNestJson(archive, json));
Assert.Empty(Assert.Single(restored.Drawings).Breakoffs);
Assert.Contains(warnings, w => w.Contains("breakoff 1 could not be read"));
}
private static byte[] Write(Drawing drawing)
{
var nest = new Nest(drawing.Name) { Units = Units.Inches };
nest.Drawings.Add(drawing);
using var stream = new MemoryStream();
Assert.True(new NestWriter(nest).Write(stream));
return stream.ToArray();
}
private static (Nest Nest, IReadOnlyList<string> Warnings) RoundTrip(byte[] archive)
{
var reader = new NestReader(new MemoryStream(archive));
return (reader.Read(), reader.Warnings);
}
private static JsonObject NestJson(byte[] archive)
{
using var zip = new ZipArchive(new MemoryStream(archive), ZipArchiveMode.Read);
using var reader = new StreamReader(zip.GetEntry("nest.json")!.Open(), Encoding.UTF8);
return JsonNode.Parse(reader.ReadToEnd())!.AsObject();
}
private static byte[] ReplaceNestJson(byte[] archive, JsonObject json)
{
using var output = new MemoryStream();
output.Write(archive);
using (var zip = new ZipArchive(output, ZipArchiveMode.Update, leaveOpen: true))
{
zip.GetEntry("nest.json")!.Delete();
using var writer = new StreamWriter(zip.CreateEntry("nest.json").Open(), new UTF8Encoding(false));
writer.Write(json.ToJsonString());
}
return output.ToArray();
}
}