mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-11-14 03:25:07 -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>
81 lines
3.3 KiB
C#
81 lines
3.3 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.Linq;
|
|
using OpenNest.Geometry;
|
|
|
|
namespace OpenNest.Engine.Jobs.Adapters;
|
|
|
|
/// <summary>A detached mutable domain nest plus explicit requirement identity (never inferred from names).</summary>
|
|
public sealed class MaterializedNestResult
|
|
{
|
|
internal MaterializedNestResult(Nest nest, Dictionary<string, Drawing> drawings)
|
|
{
|
|
Nest = nest;
|
|
DrawingsByPartId = new ReadOnlyDictionary<string, Drawing>(drawings);
|
|
}
|
|
|
|
public Nest Nest { get; }
|
|
public IReadOnlyDictionary<string, Drawing> DrawingsByPartId { get; }
|
|
}
|
|
|
|
/// <summary>Materializes a result from the same job. Geometry safety remains the solver's future validation boundary.</summary>
|
|
public static class NestResultMaterializer
|
|
{
|
|
public static MaterializedNestResult Materialize(NestJob job, NestJobResult result)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(job);
|
|
ArgumentNullException.ThrowIfNull(result);
|
|
var nest = new Nest();
|
|
var drawings = job.Parts.ToDictionary(
|
|
p => p.Id,
|
|
DrawingJobMapper.CreateDrawing,
|
|
StringComparer.Ordinal
|
|
);
|
|
foreach (var drawing in drawings.Values)
|
|
nest.Drawings.Add(drawing);
|
|
foreach (var sheet in result.Plates)
|
|
{
|
|
var plate = DrawingJobMapper.CreatePlate(sheet.Stock);
|
|
foreach (var pose in sheet.Placements)
|
|
{
|
|
if (!drawings.TryGetValue(pose.PartId, out var drawing))
|
|
throw new ArgumentException(
|
|
"Result contains a requirement not present in the job.",
|
|
nameof(result)
|
|
);
|
|
// Do not use CreateAtOrigin: it normalizes bounds and would change the snapshot frame.
|
|
var part = new Part(drawing);
|
|
part.Rotate(pose.Rotation);
|
|
part.Location = new Vector(pose.X, pose.Y);
|
|
part.UpdateBounds();
|
|
// Quantity=1 is set before the only attachment; Plate's event owns Nested accounting.
|
|
plate.Parts.Add(part);
|
|
}
|
|
AddCommonLines(plate, plate.Parts.ToList(), sheet.CommonLines);
|
|
nest.Plates.Add(plate);
|
|
}
|
|
return new MaterializedNestResult(nest, drawings);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a sheet's explicit common-line groups to <paramref name="plate"/>; members are the
|
|
/// sheet's placements by position in <paramref name="parts"/>. A group naming a position
|
|
/// outside the sheet, a part twice, a part of an earlier group, or fewer than two parts throws:
|
|
/// such a result is malformed.
|
|
/// </summary>
|
|
internal static void AddCommonLines(Plate plate, IReadOnlyList<Part> parts,
|
|
IReadOnlyList<NestJobCommonLine> groups)
|
|
{
|
|
if (groups.Count == 0)
|
|
return;
|
|
var taken = new HashSet<int>();
|
|
foreach (var group in groups)
|
|
{
|
|
if (group.Placements.Count < 2 || group.Placements.Any(i => i < 0 || i >= parts.Count || !taken.Add(i)))
|
|
throw new ArgumentException("A common-line group names a placement that is missing, repeated or in another group.");
|
|
plate.CommonLines.Add(new CommonLineGroup(group.Placements.Select(i => parts[i]), group.Kerf));
|
|
}
|
|
}
|
|
}
|