Files
OpenNest/OpenNest.Desktop/AutoNestJob.cs
T
ajandClaude Opus 5.5 b026bd19d2 feat(common-line): place parts on common lines automatically at a chosen level
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>
2026-11-05 19:38:00 -05:00

97 lines
4.3 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using OpenNest.Engine;
using OpenNest.Engine.Jobs;
namespace OpenNest.Desktop
{
/// <summary>
/// Auto Nest with the desktop dialog's defaults and no dialog: every drawing's remaining
/// quantity, with its priority and rotation constraints, on sheets like a template plate,
/// through <see cref="NestPipeline"/>. Only a valid, keepable result that places something
/// is committed, onto the nest's empty plates first and then new plates from its defaults.
/// </summary>
public static class AutoNestJob
{
/// <summary>
/// The job for <paramref name="nest"/>'s remaining quantities on stock like
/// <paramref name="template"/>; null when nothing remains. The nest is not changed, so
/// the caller can solve it off its thread.
/// </summary>
public static NestPipelineRequest ForRemaining(Nest nest, Plate template, string engineName)
{
ArgumentNullException.ThrowIfNull(nest);
ArgumentNullException.ThrowIfNull(template);
var items = nest.Drawings
.Where(d => !d.IsCutOff && d.Quantity.Remaining > 0)
.Select(d => new NestItem
{
Drawing = d,
Quantity = d.Quantity.Remaining,
Priority = d.Priority,
RotationStart = d.Constraints.StartAngle,
RotationEnd = d.Constraints.EndAngle,
StepAngle = d.Constraints.StepAngle,
})
.ToList();
if (items.Count == 0)
return null;
return new NestPipelineRequest(engineName, items,
NestStockBuilder.FromTemplate(template, null),
new NestJobOptions(maxPlates: NestRenest.MaxPlates, salvageRate: 0,
minimumSalvageDimension: nest.MinimumRemnantDimension)
{
CommonLineUsage = nest.CommonLineUsage,
CommonLineKerf = CommonLineKerf(template),
});
}
/// <summary>
/// The template plate's saved cutting kerf, or zero (no common lines) when it has none.
/// This toolkit-neutral path has no machine defaults to fall back on.
/// </summary>
private static double CommonLineKerf(Plate template)
{
var kerf = template.CuttingParameters?.Kerf ?? 0;
return double.IsFinite(kerf) && kerf > 0 ? kerf : 0;
}
/// <summary>
/// Commits <paramref name="result"/> to <paramref name="nest"/> on the nest's own thread
/// when it is valid, keepable and places something; otherwise leaves the nest unchanged
/// and says why in the note.
/// </summary>
public static NestRenestResult Keep(Nest nest, NestPipelineResult result)
{
ArgumentNullException.ThrowIfNull(nest);
return NestRenest.Keep(result,
valid => NestPipelineCommit.ApplyToPlates(valid, () => EmptyOrNewPlate(nest)));
}
private static Plate EmptyOrNewPlate(Nest nest) =>
nest.Plates.FirstOrDefault(p => p.Parts.Count == 0) ?? nest.CreatePlate();
/// <summary>
/// Why <see cref="Keep"/> would commit nothing for <paramref name="result"/>, naming the
/// first problem; null when it would commit.
/// </summary>
public static string RefusalReason(NestPipelineResult result)
{
ArgumentNullException.ThrowIfNull(result);
if (!result.IsValid)
return $"Auto Nest found {result.Violations.Count} layout problem"
+ $"{(result.Violations.Count == 1 ? "" : "s")} (first: {result.Violations[0]}); nothing was changed.";
if (!result.CanKeep)
return "Auto Nest's layout cannot be kept faithfully; nothing was changed.";
if (result.Plates.All(p => p.Parts.Count == 0))
return "Auto Nest placed nothing; nothing was changed."
+ (result.Unplaced.Count > 0
? " Not placed: " + string.Join("; ", result.Unplaced.Select(u => u.Describe())) + "."
: "");
return null;
}
}
}