using System; using System.Collections.Generic; using System.Linq; using System.Threading; namespace OpenNest.Engine.Jobs.Remote; /// /// The client's side of a nest solved elsewhere. Created when the job is sent, it fingerprints /// each item's geometry; then replays the pipeline on this machine with /// the engine answers recorded remotely (), so the proposal is /// validated against, and bound to, the caller's own drawings exactly as a local run would be. /// A job reviewed in a later session is rebuilt from what was sent (). /// Nothing is committed: the caller reviews and keeps the result as it would a local one. /// public sealed class RemoteNestSubmission { private readonly List<(NestItem Item, string Fingerprint)> sent = new(); public RemoteNestSubmission(NestPipelineRequest request) { Request = request ?? throw new ArgumentNullException(nameof(request)); ArgumentNullException.ThrowIfNull(request.Items); foreach (var item in request.Items) { if (item != null && item.Quantity > 0) sent.Add((item, Fingerprint(item.Drawing))); } } private RemoteNestSubmission(NestPipelineRequest request, IEnumerable<(NestItem Item, string Fingerprint)> sent) { Request = request; this.sent.AddRange(sent); } public NestPipelineRequest Request { get; } /// /// The geometry fingerprint the freshness check compares (); /// empty for a drawing without supported geometry, which never matches. /// public static string Fingerprint(Drawing drawing) { if (drawing?.Program == null) return ""; try { return PartGeometrySnapshot.FromProgram(drawing.Program).Fingerprint(); } catch (NotSupportedException) { return ""; } } /// /// Rebuilds a job sent earlier (its job.json) against , the /// drawings of the nest it is reviewed in: each sent drawing is the one with its name, the one /// whose geometry is unchanged when several share it. then refuses the /// result if any of them changed since it was sent, as it does in the session that sent it. /// Throws when a drawing is missing or the job /// predates the fingerprints. /// public static RemoteNestSubmission Reopen(NestJobDocument document, IEnumerable drawings) { ArgumentNullException.ThrowIfNull(document); ArgumentNullException.ThrowIfNull(drawings); var names = document.DrawingNames ?? new List(); var fingerprints = document.DrawingFingerprints; if (fingerprints == null || fingerprints.Count != names.Count) throw new NestReplayMismatchException( "This job was sent by an older OpenNest, so it can only be reviewed in the session that sent it."); var available = drawings.Where(d => d != null).ToList(); var chosen = new List(names.Count); for (var i = 0; i < names.Count; i++) { var named = available.Where(d => string.Equals(d.Name ?? "", names[i], StringComparison.Ordinal)).ToList(); if (named.Count == 0) throw new NestReplayMismatchException($"The nest has no drawing named '{names[i]}'."); chosen.Add(named.FirstOrDefault(d => fingerprints[i].Length > 0 && Fingerprint(d) == fingerprints[i]) ?? named[0]); } NestPipelineRequest request; try { request = document.ToRequest(chosen); } catch (FormatException ex) { throw new NestReplayMismatchException($"The job sent to the server cannot be read here: {ex.Message}"); } var items = document.Items ?? new List(); var sent = new List<(NestItem, string)>(); for (var i = 0; i < request.Items.Count; i++) { var item = request.Items[i]; if (item != null && item.Quantity > 0) sent.Add((item, fingerprints[items[i].Drawing])); } return new RemoteNestSubmission(request, sent); } /// /// Replays the remote run. Throws when a drawing /// changed after the job was sent or the run does not replay here; the caller should run /// the nest again rather than keep anything. /// public NestPipelineResult Complete(NestSolveTranscript transcript, CancellationToken token = default) { ArgumentNullException.ThrowIfNull(transcript); foreach (var (item, fingerprint) in sent) { if (fingerprint.Length == 0 || Fingerprint(item.Drawing) != fingerprint) throw new NestReplayMismatchException( $"Drawing '{item.Drawing?.Name}' changed after the nest was sent to the server."); } var replay = new ReplayNestingEngine(transcript); var result = NestPipeline.Run(replay, Request.EngineName, Request, progress: null, token); if (!replay.Finished) throw new NestReplayMismatchException("The nest needed fewer engine runs here than it did on the server."); return result; } }