Files
OpenNest/OpenNest.Engine/Jobs/Remote/RecordingNestingEngine.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

56 lines
2.0 KiB
C#

using System;
using System.Threading;
using OpenNest.Engine.Jobs.Cutouts;
namespace OpenNest.Engine.Jobs.Remote;
/// <summary>
/// Passes every solve to another engine and records what it was asked and what it returned
/// in <see cref="Transcript"/>. A solve that throws records nothing. The nest server's worker
/// wraps the selected engine in one, so the client can replay the run (<see cref="RemoteNestSubmission"/>).
/// </summary>
public sealed class RecordingNestingEngine : INestingEngine
{
private readonly INestingEngine inner;
public RecordingNestingEngine(INestingEngine inner)
{
this.inner = inner ?? throw new ArgumentNullException(nameof(inner));
}
public NestSolveTranscript Transcript { get; } = new();
/// <summary>
/// Also receives each solve's own progress, before the pipeline turns plans into sheets:
/// the worker's live reports. A bundled cutout trial's commits count proxies, so it passes
/// only candidates and plans, as the pipeline's own progress does.
/// </summary>
public IProgress<NestJobProgress> Progress { get; init; }
public NestJobResult Solve(NestJob job, IProgress<NestJobProgress> progress = null, CancellationToken token = default)
{
if (Progress != null)
{
var tap = CutoutPipelinePrepass.HasProxies(job) || CommonLines.CommonLinePipelinePrepass.HasProxies(job) ? new NestPipeline.PreviewProgress(Progress) : Progress;
progress = progress == null ? tap : new BothProgress(progress, tap);
}
var result = inner.Solve(job, progress, token);
if (result != null)
{
lock (Transcript)
Transcript.Add(job, result);
}
return result;
}
private sealed class BothProgress(IProgress<NestJobProgress> first, IProgress<NestJobProgress> second)
: IProgress<NestJobProgress>
{
public void Report(NestJobProgress value)
{
first.Report(value);
second.Report(value);
}
}
}