using System.Collections.Generic; using System.Linq; using OpenNest.Geometry; namespace OpenNest.Diagnostics; /// An owned diagnostic result. An empty Pairs list is clear only if IsComplete is true. public sealed class PlateOverlapReport { internal PlateOverlapReport(List pairs, List issues, PlateOverlapSnapshot snapshot, List clampFindings = null, List cutOffClampFindings = null) { Pairs = pairs.AsReadOnly(); Issues = issues.AsReadOnly(); Snapshot = snapshot; ClampFindings = (clampFindings ?? new List()).AsReadOnly(); CutOffClampFindings = (cutOffClampFindings ?? new List()).AsReadOnly(); } /// The owned input this report was computed from; the baseline for incremental rechecks. internal PlateOverlapSnapshot Snapshot { get; } public IReadOnlyList Pairs { get; } public IReadOnlyList Issues { get; } /// /// Part-vs-clamp-zone findings (C1): the same owned-fragment shape as part pairs, with a /// negative slot encoding the zone ordinal. Findings /// are reported like other overlap findings; they do not change . /// public IReadOnlyList ClampFindings { get; } /// Nominal cut-off/clamp contacts. Lines have no material area or part pair. public IReadOnlyList CutOffClampFindings { get; } public bool IsComplete => Issues.Count == 0; public double ChordTolerance => PlateOverlapAnalyzer.ChordTolerance; } /// Shared material for two input positions, ordered by zero-based input index. public sealed class PlateOverlapPair { private readonly Box bounds; private PlateOverlapPair(PlateOverlapPair source, int partAId, int partBId, string partAName, string partBName) { PartAId = partAId; PartBId = partBId; PartAName = partAName; PartBName = partBName; Regions = source.Regions; Area = source.Area; Centroid = source.Centroid; Marker = source.Marker; bounds = source.bounds; } /// The same immutable geometry under new input positions and captured names. internal PlateOverlapPair Renumber(int partAId, int partBId, string partAName, string partBName) => new(this, partAId, partBId, partAName, partBName); internal PlateOverlapPair(int partAId, int partBId, string partAName, string partBName, List regions, Vector centroid, Vector marker) { PartAId = partAId; PartBId = partBId; PartAName = partAName; PartBName = partBName; Regions = regions.AsReadOnly(); Area = regions.Sum(region => region.Area); Centroid = centroid; Marker = marker; var points = regions.SelectMany(region => region.Vertices).ToArray(); var left = points.Min(point => point.X); var bottom = points.Min(point => point.Y); bounds = new Box(left, bottom, points.Max(point => point.X) - left, points.Max(point => point.Y) - bottom); } public int PartAId { get; } public int PartBId { get; } public string PartAName { get; } public string PartBName { get; } /// Connected islands of shared material; no mutable kernel polygons are exposed. public IReadOnlyList Regions { get; } public double Area { get; } /// /// Finite world-coordinate area centroid of all shared material, after hole subtraction. /// This can lie outside disconnected or concave shared material. Returned by value. /// public Vector Centroid { get; } /// /// Finite world-coordinate point on the shared material, inside the pair's largest island: /// its pole of inaccessibility, or an interior scan-line point when a thin island defeats /// the bounded pole search. Markers, hit testing and inspection rings use this point. /// A tolerance-free tiny-clamp finding (a clamp narrower than the chord tolerance) keeps /// its conservative whole-clamp region, so its marker is the clamp's centre, within half /// the clamp's size of the material. Returned by value. /// public Vector Marker { get; } /// A fresh world-coordinate bounds copy. public Box Bounds => new(bounds.X, bounds.Y, bounds.Length, bounds.Width); } /// /// One connected island of shared material in world coordinates, after hole subtraction: /// a counterclockwise outline and its clockwise holes. /// public sealed class PlateOverlapRegion { private static readonly IReadOnlyList> NoHoles = System.Array.AsReadOnly( System.Array.Empty>()); internal PlateOverlapRegion(IEnumerable vertices, double area, IEnumerable> holes = null) { Vertices = System.Array.AsReadOnly(vertices.ToArray()); Area = area; Holes = holes == null ? NoHoles : System.Array.AsReadOnly(holes .Select(hole => (IReadOnlyList)System.Array.AsReadOnly(hole.ToArray())) .ToArray()); } /// Read-only counterclockwise outline with an exactly repeated closing vertex. public IReadOnlyList Vertices { get; } /// Read-only clockwise hole rings, each with an exactly repeated closing vertex. public IReadOnlyList> Holes { get; } /// Positive net area: the outline's area minus its holes'. public double Area { get; } } /// An input or pair that could not be checked. IDs are zero-based input positions. public sealed record PlateOverlapIssue(int PartAId, int? PartBId, string Message); /// /// Owned clean geometry and poses. Capture while inputs are stable, then analyze on a worker. /// No live Part, Drawing, Program, or subprogram is retained. /// public sealed class PlateOverlapSnapshot { internal PlateOverlapSnapshot(List parts, List issues, List clamps = null, List cutOffClampFindings = null) { Parts = parts.AsReadOnly(); Issues = issues.AsReadOnly(); Clamps = (clamps ?? new List()).AsReadOnly(); CutOffClampFindings = (cutOffClampFindings ?? new List()).AsReadOnly(); } internal IReadOnlyList Parts { get; } internal IReadOnlyList Issues { get; } /// Owned clamp keep-out rectangles in plate coordinates, capture order. internal IReadOnlyList Clamps { get; } internal IReadOnlyList CutOffClampFindings { get; } } internal sealed record CapturedOverlapPart(int Id, string Name, OverlapSource Source, double Rotation, Vector Location); /// A clamp keep-out rectangle owned by the snapshot; Ordinal is its capture index. internal sealed record CapturedClamp(int Ordinal, string Id, Box Bounds); /// Deterministic negative PartBId for a part-vs-clamp finding's clamp slot. internal static class ClampFindingSlot { public static int IdFor(int ordinal) => -2 - ordinal; public static int OrdinalOf(int pairPartBId) => -2 - pairPartBId; }