mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-11-06 18:05:44 -05:00
Slice 3 of common-line cutting. On costly plate (N26-295: 3/16 RA310S at about $2,500 a sheet) pairing parts one kerf apart fits the part the spacing cannot: the last plate's 38 parts fit on one 48x120 with Auto Nest only with pairs. - Drawing Properties gains "Allow common line" (Drawing.AllowCommonLine, off by default because customer edge-quality needs vary). The nest's CommonLineUsage (Off / When it adds parts / Whenever available) is set in Auto Nest and seeded from Tools > Nest Defaults; both persist (nest.json, defaults.json). - NestPipeline runs the ordinary solve, then, with a level on, trials where eligible parts are offered as kerf-gap pair compounds. Placed pairs expand into parts plus explicit NestJobCommonLine groups, are validated again, and CommonLineRanking keeps a trial only by the level: count first, then sheets; "when it adds parts" keeps the plain layout on a tie, "whenever" also takes a shorter cut or less material, never at the cost of a part. Engines, FillScore and their comparers are unchanged; Off asks the engine exactly what it did. - NestLayoutCheck exempts members only through a sheet's explicit group list and only when the job allows common lines; materializing and committing create Plate.CommonLines. - Server nesting writes common-line jobs as version 3 and replays the recorded trial answers, so the desktop binds the same groups. - Fill Area honors the level through a PlateFillService overload. - The benchmark gains --common-lines <level> --kerf <k>. Benchmark (synthetic nests + N26-295, --parallel 1): with no flag and with --common-lines off every engine matches the base commit; with a level on no layout becomes invalid and three runs save a sheet at equal nested area. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
106 lines
3.8 KiB
C#
106 lines
3.8 KiB
C#
using System.IO.Compression;
|
|
using System.Text;
|
|
using System.Text.Json.Nodes;
|
|
|
|
namespace OpenNest.IO.Tests;
|
|
|
|
public class CommonLineUsagePersistenceTests
|
|
{
|
|
[Theory]
|
|
[InlineData(true)]
|
|
[InlineData(false)]
|
|
public void AllowCommonLine_RoundTrips_AndIsOmittedWhenFalse(bool allow)
|
|
{
|
|
var drawing = DrawingPackageFixture.Build();
|
|
drawing.AllowCommonLine = allow;
|
|
var nest = new Nest(drawing.Name);
|
|
nest.Drawings.Add(drawing);
|
|
|
|
var bytes = Write(nest);
|
|
var reader = new NestReader(new MemoryStream(bytes));
|
|
var copy = Assert.Single(reader.Read().Drawings);
|
|
|
|
Assert.Empty(reader.Warnings);
|
|
Assert.Equal(allow, copy.AllowCommonLine);
|
|
var json = NestJson(bytes)["drawings"]!.AsArray().Single()!.AsObject();
|
|
Assert.Equal(allow, json.ContainsKey("allowCommonLine"));
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(CommonLineUsage.WhenItAddsParts, "whenItAddsParts")]
|
|
[InlineData(CommonLineUsage.Whenever, "whenever")]
|
|
public void CommonLineUsage_RoundTrips(CommonLineUsage usage, string saved)
|
|
{
|
|
var nest = new Nest("usage") { CommonLineUsage = usage };
|
|
|
|
var bytes = Write(nest);
|
|
|
|
Assert.Equal(saved, NestJson(bytes)["commonLineUsage"]!.GetValue<string>());
|
|
Assert.Equal(usage, new NestReader(new MemoryStream(bytes)).Read().CommonLineUsage);
|
|
}
|
|
|
|
[Fact]
|
|
public void CommonLineUsage_Off_IsOmitted()
|
|
{
|
|
var bytes = Write(new Nest("off"));
|
|
|
|
Assert.False(NestJson(bytes).ContainsKey("commonLineUsage"));
|
|
Assert.Equal(CommonLineUsage.Off, new NestReader(new MemoryStream(bytes)).Read().CommonLineUsage);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("\"sometimes\"")]
|
|
[InlineData("\"\"")]
|
|
[InlineData("null")]
|
|
public void CommonLineUsage_Unknown_ReadsOff(string value)
|
|
{
|
|
var bytes = Write(new Nest("unknown") { CommonLineUsage = CommonLineUsage.Whenever });
|
|
var edited = ReplaceNestJson(bytes, json => json.Replace("\"commonLineUsage\":\"whenever\"",
|
|
$"\"commonLineUsage\":{value}"));
|
|
|
|
Assert.Equal(CommonLineUsage.Off, new NestReader(new MemoryStream(edited)).Read().CommonLineUsage);
|
|
}
|
|
|
|
private static byte[] Write(Nest nest)
|
|
{
|
|
using var stream = new MemoryStream();
|
|
Assert.True(new NestWriter(nest).Write(stream));
|
|
return stream.ToArray();
|
|
}
|
|
|
|
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, Func<string, string> edit)
|
|
{
|
|
using var output = new MemoryStream();
|
|
using (var source = new ZipArchive(new MemoryStream(archive), ZipArchiveMode.Read))
|
|
using (var target = new ZipArchive(output, ZipArchiveMode.Create, leaveOpen: true))
|
|
{
|
|
foreach (var entry in source.Entries)
|
|
{
|
|
using var input = entry.Open();
|
|
using var buffer = new MemoryStream();
|
|
input.CopyTo(buffer);
|
|
var bytes = buffer.ToArray();
|
|
if (entry.FullName == "nest.json")
|
|
{
|
|
var text = new StreamReader(new MemoryStream(bytes), Encoding.UTF8).ReadToEnd();
|
|
var compact = JsonNode.Parse(text)!.ToJsonString();
|
|
var changed = edit(compact);
|
|
Assert.NotEqual(compact, changed);
|
|
bytes = Encoding.UTF8.GetBytes(changed);
|
|
}
|
|
var copy = target.CreateEntry(entry.FullName);
|
|
using var write = copy.Open();
|
|
write.Write(bytes);
|
|
}
|
|
}
|
|
return output.ToArray();
|
|
}
|
|
}
|