Add NestPipeline: one validated engine path for all front ends

build NestJob -> resolve engine by name -> Solve -> independent
NestLayoutCheck validation -> bind placements to caller drawings.
The pipeline never commits to plates and never mutates caller items;
validation failures are returned as messages naming real drawings
(unknown-requirement placements are reported, not dropped). Console,
MCP, API and desktop Auto Nest will adopt this path in later phases.

- NestPipeline.Run(request): registry-resolved engine, unknown names
  list the registered engines; cancellation propagates untouched.
- NestPipeline.Run(engine, ...): stub/plug-in engines take the same path.
- NestResultBinder: pose semantics identical to NestResultMaterializer.
- NestLayoutCheck: Violations overload with per-requirement display names.

Tests: overlap stub -> violations w/ drawing names, no throw; ghost
placement -> violation + excluded from further checks; unknown engine
-> NotSupportedException listing engines; cancelled token -> no result;
happy path -> bound by reference, caller quantity untouched.
This commit is contained in:
aj
2026-09-29 10:23:19 -04:00
parent 481c3e5128
commit c914d2bbd9
4 changed files with 356 additions and 2 deletions
@@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using OpenNest.Geometry;
namespace OpenNest.Engine.Jobs.Adapters;
/// <summary>
/// Binds one result sheet's poses to the caller's own drawings. The pose semantics match
/// <see cref="NestResultMaterializer"/>: rotate about the snapshot origin, then translate.
/// Placements whose requirement ID is not in the map are skipped here; the pipeline reports
/// them as violations instead of dropping them silently.
/// </summary>
public static class NestResultBinder
{
public static IReadOnlyList<Part> Bind(
NestJobPlateResult sheet,
IReadOnlyDictionary<string, Drawing> drawingsByPartId
)
{
ArgumentNullException.ThrowIfNull(sheet);
ArgumentNullException.ThrowIfNull(drawingsByPartId);
var parts = new List<Part>(sheet.Placements.Count);
foreach (var pose in sheet.Placements)
{
if (!drawingsByPartId.TryGetValue(pose.PartId, out var drawing))
continue;
var part = new Part(drawing);
part.Rotate(pose.Rotation);
part.Location = new Vector(pose.X, pose.Y);
part.UpdateBounds();
parts.Add(part);
}
return parts;
}
}