Files
OpenNest/OpenNest.IO.Tests/CommonLinePersistenceTests.cs
T
ajandClaude Opus 5.5 1d87d212de feat(common-line): place parts one kerf apart to share an edge
Expensive alloy jobs (N26-295, 310 stainless) could not fit their last
PT42 at full part spacing; the plate was squeezed to 0.2 spacing with a
part 0.14 from the edge. Parts whose straight edges face each other one
kerf apart are cut on the same centerline, saving the spacing along each
shared edge.

Plate.CommonLines holds groups of parts and the kerf they were joined
at. Shared spans are derived from the placed clean perimeters, never
stored, so a moved member breaks its group instead of leaving a stale
span. Plate > Arrange > Join Common Line slides the selected parts
together at the plate's kerf; Remove Common Line ungroups them. The
plate draws shared spans as a dashed centerline.

Both spacing checks hold group members to the kerf instead of the part
spacing, and a new pre-post Common lines check reports broken groups, a
changed kerf, and leads that run into a partner. Groups persist as part
positions in an optional commonLines array that older readers ignore.

This first slice still cuts every shared edge twice; single-pass cutting
through Plan Cutting and the posts comes next.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 14:16:13 -05:00

87 lines
2.9 KiB
C#

using System.IO.Compression;
using System.Text;
using System.Text.Json.Nodes;
using OpenNest.CNC;
using OpenNest.Geometry;
using static OpenNest.IO.Tests.NestArchiveJson;
namespace OpenNest.IO.Tests;
/// <summary>Common-line groups persist as part positions and a kerf, and only when there are any.</summary>
public class CommonLinePersistenceTests
{
private const double Kerf = 0.012;
private static Drawing Rectangle()
{
var program = new Program();
program.Codes.Add(new RapidMove(0, 0));
program.Codes.Add(new LinearMove(6, 0));
program.Codes.Add(new LinearMove(6, 12));
program.Codes.Add(new LinearMove(0, 12));
program.Codes.Add(new LinearMove(0, 0));
return new Drawing("rect", program);
}
private static (Nest Nest, Part[] Parts) NestWithRow()
{
var nest = new Nest("common") { Units = Units.Inches };
var drawing = Rectangle();
nest.Drawings.Add(drawing);
var plate = nest.CreatePlate();
plate.PartSpacing = 0.25;
var parts = new[]
{
new Part(drawing, new Vector(1, 1)),
new Part(drawing, new Vector(20, 1)),
new Part(drawing, new Vector(7 + Kerf, 1)),
};
foreach (var part in parts)
plate.Parts.Add(part);
// A cut-off part sits between parts in the sequence but never in the parts array.
plate.CutOffs.Add(new CutOff(new Vector(40, 0), CutOffAxis.Vertical));
plate.RegenerateCutOffs(new CutOffSettings());
plate.JoinCommonLine(new[] { parts[0], parts[2] }, Kerf);
return (nest, parts);
}
[Fact]
public void GroupsRoundTripByPartPosition()
{
var (nest, _) = NestWithRow();
var plate = Assert.Single(new NestReader(Write(nest)).Read().Plates);
var group = Assert.Single(plate.CommonLines);
Assert.Equal(Kerf, group.Kerf);
Assert.Equal(new[] { 1.0, 7 + Kerf }, group.Members.Select(m => m.Location.X).OrderBy(x => x));
Assert.True(CommonLineGeometry.Check(group).IsIntact);
}
[Fact]
public void NoGroups_WritesNoKey()
{
var (nest, _) = NestWithRow();
nest.Plates[0].CommonLines.Clear();
Assert.Null(PlateJson(Write(nest))["commonLines"]);
}
[Fact]
public void GroupsWriteKerfAndPositions()
{
var (nest, _) = NestWithRow();
var group = PlateJson(Write(nest))["commonLines"]![0]!;
Assert.Equal(Kerf, group["kerf"]!.GetValue<double>());
Assert.Equal(new[] { 0, 2 }, group["parts"]!.AsArray().Select(n => n!.GetValue<int>()));
}
private static JsonNode PlateJson(MemoryStream zip)
{
using var archive = new ZipArchive(zip, ZipArchiveMode.Read, leaveOpen: true);
using var reader = new StreamReader(archive.GetEntry("nest.json")!.Open(), Encoding.UTF8);
return JsonNode.Parse(reader.ReadToEnd())!["plates"]![0]!;
}
}