Files
OpenNest/OpenNest.Core/CNC/CuttingPlanning/NestedPartContainment.cs
T
ajandClaude Opus 5.5 e231e44f72 refactor(geometry): share box containment, union, rotation and cutout range checks
Private copies of tolerant box containment, box union, vector rotation and
the cutout proposals' coordinate-range check now use one helper each:
BoundingBox.IsWithin and BoundingBox.Union, Vector.Rotate, and
CutoutCoordinates.WithinRange. Tests pin the helpers bit-for-bit against
frozen copies of the removed bodies. No behavior change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 09:25:27 -05:00

59 lines
2.6 KiB
C#

using System.Collections.Generic;
using System.Linq;
using System.Threading;
using OpenNest.Geometry;
namespace OpenNest.CNC.CuttingPlanning;
/// <summary>
/// Whether one part's clean material lies strictly inside a cutout of another's. The cutting
/// planner's prerequisites and the pre-post review share this proof, so a pair the planner orders
/// inner-before-host is the pair posting checks. Bounds only select candidates; containment is
/// proven on native material, and any boundary contact or incomplete material is ambiguous.
/// </summary>
internal static class NestedPartContainment
{
private const int ContactBudget = 1000000;
/// <summary>
/// True when <paramref name="inner"/>'s perimeter lies strictly inside one cutout of
/// <paramref name="host"/>, false when it lies in none; otherwise null, with
/// <paramref name="ambiguity"/> saying why containment cannot be established.
/// </summary>
internal static bool? InsideCutout(LeadMaterialSnapshot inner, LeadMaterialSnapshot host,
CancellationToken token, out string ambiguity)
{
ambiguity = null;
if (!inner.IsComplete || !host.IsComplete)
{
ambiguity = inner.Reason ?? host.Reason;
return null;
}
var budget = ContactBudget;
foreach (var curve in inner.Rings[0])
foreach (var ring in host.Rings)
foreach (var boundary in ring)
{
token.ThrowIfCancellationRequested();
if (--budget < 0)
{
ambiguity = "the containment check exceeds the native query limit";
return null;
}
if (curve.Contacts(boundary, out var overlap).Count != 0 || overlap)
{
ambiguity = "their boundaries touch or cross";
return null;
}
}
var probe = inner.Rings[0][0].Start;
return host.Rings.Skip(1).Any(hole => LeadMaterialSnapshot.Inside(probe, hole, token));
}
/// <summary>Material extent of a clean program at a location: rapids and scribe marks excluded.</summary>
internal static Box CleanBounds(Program clean, Vector location, CancellationToken token) =>
ExecutionMotionReader.ReadSupported(clean, location, null, token).Motions
.Where(m => !m.Rapid && m.Layer is LayerType.Cut or LayerType.Display or LayerType.DisplayPerimeter && m.Curve != null)
.Select(m => m.Curve.ToEntity().BoundingBox).ToList().GetBoundingBox();
}