using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; using OpenNest.Engine.Jobs.Adapters; namespace OpenNest.Engine.Jobs; /// /// One automatic-nesting request: the named engine, the caller's items (drawing, quantity, /// priority, rotation), the stock it may use, and optional job options. Items with a /// nonpositive quantity are skipped. /// public sealed record NestPipelineRequest( string EngineName, IReadOnlyList Items, IReadOnlyList Stock, NestJobOptions Options = null ); /// A proposed sheet: new parts bound by reference to the caller's drawings. public sealed record ProposedPlate(int PlateIndex, NestPlateStock Stock, IReadOnlyList Parts); /// /// The engine's raw result, its independent validation, and the proposed plates. Nothing /// has been committed; the caller decides what to do with an invalid result. /// public sealed class NestPipelineResult { internal NestPipelineResult( string engineName, NestJob job, NestJobResult raw, IReadOnlyList plates, IReadOnlyList violations, bool canKeep, TimeSpan solveTime, TimeSpan validationTime ) { EngineName = engineName; Job = job; Raw = raw; Plates = plates; Violations = violations; CanKeep = canKeep; SolveTime = solveTime; ValidationTime = validationTime; } public string EngineName { get; } public NestJob Job { get; } public NestJobResult Raw { get; } public IReadOnlyList Plates { get; } public IReadOnlyList Violations { get; } public bool IsValid => Violations.Count == 0; /// True when every placement can be represented, even if layout rules fail. /// False for unknown/null requirement IDs or nonfinite poses; Plates is then empty. public bool CanKeep { get; } public NestJobStatus Status => Raw.Status; public NestJobStopReason StopReason => Raw.StopReason; public TimeSpan SolveTime { get; } public TimeSpan ValidationTime { get; } } /// /// The single automatic-nesting path shared by every front end: build the job, solve it /// with the named engine, validate the result with the benchmark's rules, then bind /// placements to the caller's drawings. The pipeline never mutates caller items, drawings, /// or plates, and it does not care which engine is selected. /// public static class NestPipeline { /// Resolves through /// ; unknown names throw . /// Engine cancellation propagates unchanged. public static NestPipelineResult Run( NestPipelineRequest request, IProgress progress = null, CancellationToken token = default ) { ArgumentNullException.ThrowIfNull(request); var engine = NestingEngineRegistry.Create(request.EngineName); return Run(engine, request.EngineName, request, progress, token); } /// Runs a caller-supplied engine instance through the same validation and binding. public static NestPipelineResult Run( INestingEngine engine, string engineName, NestPipelineRequest request, IProgress progress = null, CancellationToken token = default ) { ArgumentNullException.ThrowIfNull(engine); ArgumentNullException.ThrowIfNull(request); ArgumentNullException.ThrowIfNull(request.Items); ArgumentNullException.ThrowIfNull(request.Stock); token.ThrowIfCancellationRequested(); var drawingsByPartId = new Dictionary(StringComparer.Ordinal); var parts = new List(); for (var i = 0; i < request.Items.Count; i++) { var item = request.Items[i]; if (item == null || item.Quantity <= 0) continue; var partId = $"part-{i}"; parts.Add(DrawingJobMapper.FromItem(partId, item)); drawingsByPartId[partId] = item.Drawing; } var job = new NestJob(parts, request.Stock, request.Options); NestJobValidator.Validate(job); var clock = Stopwatch.StartNew(); var raw = engine.Solve(job, progress, token) ?? throw new InvalidOperationException($"Engine '{engineName}' returned no result."); var solveTime = clock.Elapsed; token.ThrowIfCancellationRequested(); clock.Restart(); var names = drawingsByPartId.ToDictionary( kv => kv.Key, kv => kv.Value.Name ?? kv.Key, StringComparer.Ordinal ); var violations = NestLayoutCheck.Violations(job, raw, names, out var canKeep); var validationTime = clock.Elapsed; var plates = canKeep ? raw.Plates.Select(sheet => new ProposedPlate( sheet.PlateIndex, sheet.Stock, NestResultBinder.Bind(sheet, drawingsByPartId) )) .ToList() : new List(); token.ThrowIfCancellationRequested(); return new NestPipelineResult( engineName, job, raw, plates, violations, canKeep, solveTime, validationTime ); } }