Files
OpenNest/OpenNest.Core/DrawingProgramSnapshot.cs
T
ajandClaude Opus 5.5 0baa737162 fix(nest): anchor placed parts when an edit moves origin
Converter edits rebuild a drawing's program around its first pierce, so
Source.Offset can move (legacy drawings with no source entities, or a
tabbed/hidden contour changing the first cut). Part.Update kept the old
Location, shifting placed parts by the rotated origin delta - about 43
inches on a 270-degree part in the reported nest. Offset rebuilt parts
by that delta so their geometry stays put on the plate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 22:55:37 -04:00

91 lines
3.5 KiB
C#

using System;
using System.Collections.Generic;
using OpenNest.CNC;
using OpenNest.Geometry;
namespace OpenNest;
/// <summary>
/// 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.
/// </summary>
public sealed class DrawingProgramSnapshot
{
private readonly Dictionary<Drawing, Captured> programs;
private readonly Func<Program, string> fingerprint;
private DrawingProgramSnapshot(
Dictionary<Drawing, Captured> programs,
Func<Program, string> fingerprint)
{
this.programs = programs;
this.fingerprint = fingerprint;
}
/// <summary>
/// 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.
/// </summary>
public static DrawingProgramSnapshot Capture(
IEnumerable<Drawing> drawings,
Func<Program, string> fingerprint)
{
ArgumentNullException.ThrowIfNull(drawings);
ArgumentNullException.ThrowIfNull(fingerprint);
var programs = new Dictionary<Drawing, Captured>(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);
}
/// <summary>
/// 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.
/// </summary>
public IReadOnlyList<Part> UpdateChangedParts(IEnumerable<Plate> plates)
{
ArgumentNullException.ThrowIfNull(plates);
var originShifts = new Dictionary<Drawing, Vector>(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<Part>();
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);
}