using System; using System.Collections.Generic; using OpenNest.CNC; using OpenNest.Geometry; namespace OpenNest; /// /// Captures drawing program text before an edit and rebuilds only the parts whose /// drawing program changed. Drawing keys use reference identity because names are editable. /// public sealed class DrawingProgramSnapshot { private readonly Dictionary programs; private readonly Func fingerprint; private DrawingProgramSnapshot( Dictionary programs, Func fingerprint) { this.programs = programs; this.fingerprint = fingerprint; } /// /// Capture before handing drawings to an editor, including its load operation: /// programs can be edited in place. The callback must include both the main /// program text and its hole sub-programs, and must not mutate the program. /// public static DrawingProgramSnapshot Capture( IEnumerable drawings, Func fingerprint) { ArgumentNullException.ThrowIfNull(drawings); ArgumentNullException.ThrowIfNull(fingerprint); var programs = new Dictionary(ReferenceEqualityComparer.Instance); foreach (var drawing in drawings) { if (!drawing.IsCutOff && !programs.ContainsKey(drawing)) programs.Add(drawing, new Captured(fingerprint(drawing.Program), SourceOffset(drawing))); } return new DrawingProgramSnapshot(programs, fingerprint); } /// /// Complete the captured edit by rebuilding changed drawings' parts across all plates. /// Part.Update clears obsolete lead-ins, tabs and locks; the placement is anchored to the /// source frame, so a rebuild that re-origins the program (Source.Offset moved, e.g. a new /// first pierce) leaves the geometry where it was on the plate. /// Unchanged and uncaptured drawings' parts retain their program instances and state. /// Returns the rebuilt parts so a UI can invalidate just their graphics. /// public IReadOnlyList UpdateChangedParts(IEnumerable plates) { ArgumentNullException.ThrowIfNull(plates); var originShifts = new Dictionary(ReferenceEqualityComparer.Instance); foreach (var entry in programs) { if (!entry.Key.IsCutOff && !string.Equals(entry.Value.Fingerprint, fingerprint(entry.Key.Program), StringComparison.Ordinal)) originShifts.Add(entry.Key, SourceOffset(entry.Key) - entry.Value.SourceOffset); } var updated = new List(); foreach (var plate in plates) { foreach (var part in plate.Parts) { if (!originShifts.TryGetValue(part.BaseDrawing, out var shift)) continue; // A source point P sat at Location + R(P - old); keep it there with the new origin. var rotation = part.Rotation; part.Update(); if (shift != Vector.Zero) part.Offset(shift.Rotate(rotation)); updated.Add(part); } } return updated; } private static Vector SourceOffset(Drawing drawing) => drawing.Source?.Offset ?? Vector.Zero; private readonly record struct Captured(string Fingerprint, Vector SourceOffset); }