mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-27 02:41:06 -04:00
A nest drawing copied from the drawing library has to remember exactly which revision it is a copy of, so later slices can tell when the library moved on (D2b) and so a save can target the right drawing. `Drawing.Library` holds the server key, id, revision and a snapshot of that revision's cut type, thickness, material, grade, units, notes and package hash. It is immutable, so copies can share it. `DrawingDto.library` is additive and omitted when null. It is read leniently: anything that cannot be trusted as a link drops the link with a reader warning and the drawing still loads, because a nest must never fail to open over provenance. Enum names are checked as single names since Enum.TryParse would accept numbers and comma lists. Copy paths: converter edits update drawings in place and so keep the link; canonical engine copies carry it; split pieces are new geometry and get none. Packages strip it so linking never changes a drawing's package bytes or hash. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
128 lines
5.2 KiB
C#
128 lines
5.2 KiB
C#
using System.Collections.Generic;
|
|
using System.Runtime.CompilerServices;
|
|
using OpenNest.CNC;
|
|
using OpenNest.Geometry;
|
|
using OpenNest.Math;
|
|
|
|
namespace OpenNest.Engine
|
|
{
|
|
/// <summary>
|
|
/// Produces transient canonical (MBR-axis-aligned) copies of drawings for engine consumption
|
|
/// and un-rotates placed parts back to the drawing's original frame.
|
|
/// </summary>
|
|
public static class CanonicalFrame
|
|
{
|
|
// Maps each canonical copy to the drawing it was ultimately copied from, so caches keyed
|
|
// by drawing identity hit across the transient copies each fill makes.
|
|
private static readonly ConditionalWeakTable<Drawing, Drawing> sourceOf = new();
|
|
|
|
/// <summary>
|
|
/// Returns the drawing a canonical copy was made from (following copies of copies back to
|
|
/// the root), or <paramref name="drawing"/> itself when it is not a canonical copy.
|
|
/// </summary>
|
|
internal static Drawing SourceOf(Drawing drawing)
|
|
{
|
|
if (drawing == null)
|
|
return null;
|
|
|
|
return sourceOf.TryGetValue(drawing, out var root) ? root : drawing;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a new Drawing whose Program geometry is rotated to the canonical frame.
|
|
/// The source drawing is not mutated.
|
|
/// </summary>
|
|
public static Drawing AsCanonicalCopy(Drawing drawing)
|
|
{
|
|
if (drawing == null)
|
|
return null;
|
|
|
|
var angle = drawing.Source?.Angle ?? 0.0;
|
|
|
|
// Clone program (never mutate the source).
|
|
var pgm =
|
|
(drawing.Program.Clone() as OpenNest.CNC.Program) ?? new OpenNest.CNC.Program();
|
|
|
|
if (!Tolerance.IsEqualTo(angle, 0))
|
|
pgm.Rotate(angle, pgm.BoundingBox().Center);
|
|
|
|
var copy = new Drawing(drawing.Name ?? string.Empty, pgm)
|
|
{
|
|
Color = drawing.Color,
|
|
Constraints = drawing.Constraints,
|
|
Material = drawing.Material,
|
|
Priority = drawing.Priority,
|
|
Customer = drawing.Customer,
|
|
IsCutOff = drawing.IsCutOff,
|
|
Library = drawing.Library,
|
|
Source = new SourceInfo
|
|
{
|
|
Path = drawing.Source?.Path,
|
|
Offset = drawing.Source?.Offset ?? new Vector(0, 0),
|
|
Angle = 0.0,
|
|
},
|
|
};
|
|
sourceOf.AddOrUpdate(copy, SourceOf(drawing));
|
|
return copy;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Rebinds canonical-frame placed parts to the original drawing while preserving each
|
|
/// part's world footprint.
|
|
///
|
|
/// <see cref="Part.Rotation"/> is cumulative: it includes the rotation already baked into
|
|
/// the drawing's program. The canonical copy carries original + sourceAngle, so a canonical
|
|
/// part's rotation minus the original program's rotation is exactly the rotation (engine
|
|
/// rotation plus sourceAngle) that turns the original into the same shape. Each part is
|
|
/// rebuilt from the original at that rotation and translated to sit where the canonical
|
|
/// part did. Rotating a finished part about its Location instead would shift it out of place.
|
|
/// </summary>
|
|
public static List<Part> RebindToOriginal(List<Part> canonicalParts, Drawing original)
|
|
{
|
|
if (canonicalParts == null || canonicalParts.Count == 0)
|
|
return canonicalParts;
|
|
|
|
var baseRotation = original.Program.Rotation;
|
|
for (var i = 0; i < canonicalParts.Count; i++)
|
|
{
|
|
var canonical = canonicalParts[i];
|
|
var rebound = Part.CreateAtOrigin(
|
|
original,
|
|
Angle.NormalizeRad(canonical.Rotation - baseRotation)
|
|
);
|
|
rebound.Offset(canonical.BoundingBox.Location - rebound.BoundingBox.Location);
|
|
rebound.UpdateBounds();
|
|
canonicalParts[i] = rebound;
|
|
}
|
|
|
|
return canonicalParts;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Composes the source drawing's canonical angle onto each placed part so the
|
|
/// returned list is in the drawing's original (visible) frame.
|
|
///
|
|
/// Derivation: let sourceAngle = S (rotation mapping source -> canonical).
|
|
/// Canonical part at rotation R shows visible orientation R.
|
|
/// Source part at rotation R' shows visible orientation R' + (-S), because the
|
|
/// source geometry is already rotated by -S relative to canonical.
|
|
/// Setting equal gives R' = R + S, so we ADD sourceAngle to each placed part.
|
|
///
|
|
/// Rotation is performed around the part's Location so its placement position is preserved;
|
|
/// only the orientation composes.
|
|
/// </summary>
|
|
public static List<Part> FromCanonical(List<Part> placed, double sourceAngle)
|
|
{
|
|
if (placed == null || placed.Count == 0)
|
|
return placed;
|
|
if (Tolerance.IsEqualTo(sourceAngle, 0))
|
|
return placed;
|
|
|
|
foreach (var p in placed)
|
|
p.Rotate(sourceAngle, p.Location);
|
|
|
|
return placed;
|
|
}
|
|
}
|
|
}
|