using System.Collections.Generic; using System.Linq; using OpenNest.BestFit; using OpenNest.Geometry; namespace OpenNest.Engine.BestFit { /// /// Saves and restores entries for .nest archives. Only kept /// results are exported; imported sets are added without replacing computed ones. /// Each set records the canonical angle its pairs were computed in, and import drops sets /// whose angle no longer matches the drawing's recomputed one. /// public sealed class BestFitCacheStore : IBestFitStore { /// /// Largest difference (radians) at which a saved canonical angle still matches. The /// angle round-trips exactly, so this only absorbs floating-point noise in recomputing it. /// private const double FrameTolerance = 1e-9; /// The store over the process-wide . public static BestFitCacheStore Shared { get; } = new(); private BestFitCacheStore() { } public IReadOnlyList Export( Drawing drawing, IReadOnlySet<(double Width, double Length, double Spacing)> plateKeys ) { var allBestFits = BestFitCache.GetAllForDrawing(drawing); var sets = new List(); foreach (var kvp in allBestFits) { if (!plateKeys.Contains((kvp.Key.PlateWidth, kvp.Key.PlateHeight, kvp.Key.Spacing))) continue; var results = kvp .Value.Where(r => r.Keep) .Select(r => new SavedPairResult( r.Candidate.Part1Rotation, r.Candidate.Part2Rotation, r.Candidate.Part2Offset.X, r.Candidate.Part2Offset.Y, r.Candidate.StrategyIndex, r.Candidate.TestNumber, r.Candidate.Spacing, r.RotatedArea, r.BoundingWidth, r.BoundingHeight, r.OptimalRotation, r.Keep, r.Reason, r.TrueArea, r.HullAngles )) .ToList(); sets.Add(new SavedPairSet( kvp.Key.PlateWidth, kvp.Key.PlateHeight, kvp.Key.Spacing, results, FrameAngle(drawing))); } return sets; } public void Import(Drawing drawing, IReadOnlyList sets) { // Saved pairs are in the canonical frame, so their candidates hold a canonical copy, // as computed ones do; the source drawing would rebuild a different pair. Drawing canonical = null; foreach (var set in sets) { if (set.Results.Count == 0 || !MatchesFrame(set, drawing)) continue; canonical ??= CanonicalFrame.AsCanonicalCopy(drawing); var results = set .Results.Select(r => new BestFitResult { Candidate = new PairCandidate { Drawing = canonical, Part1Rotation = r.Part1Rotation, Part2Rotation = r.Part2Rotation, Part2Offset = new Vector(r.Part2OffsetX, r.Part2OffsetY), StrategyIndex = r.StrategyType, TestNumber = r.TestNumber, Spacing = r.CandidateSpacing, }, RotatedArea = r.RotatedArea, BoundingWidth = r.BoundingWidth, BoundingHeight = r.BoundingHeight, OptimalRotation = r.OptimalRotation, Keep = r.Keep, Reason = r.Reason, TrueArea = r.TrueArea, HullAngles = r.HullAngles?.ToList(), }) .ToList(); BestFitCache.Populate(drawing, set.PlateWidth, set.PlateHeight, set.Spacing, results); } } private static double FrameAngle(Drawing drawing) => drawing.Source?.Angle ?? 0.0; /// /// True when the set was saved in the drawing's current canonical frame. A changed /// canonical-angle computation would replay old pairs in a turned frame, where they can /// overlap; sets with no saved angle (older files) are treated the same way. /// private static bool MatchesFrame(SavedPairSet set, Drawing drawing) { if (set.CanonicalAngle is not double saved || !double.IsFinite(saved)) return false; var difference = System.Math.IEEERemainder(saved - FrameAngle(drawing), 2.0 * System.Math.PI); return System.Math.Abs(difference) <= FrameTolerance; } } }