Files
OpenNest/OpenNest.Core/Diagnostics/ClampCutDiagnostics.cs
T
ajandClaude Opus 5.5 993555dba6 feat(cutoffs): head-locked overcut at the far edge, reversible by Plan Cutting
Stock can arrive up to 1.5 in wider than nominal, so only the origin-side
edge is certain. A cut-off with a head-locked overcut cuts past the nominal
far edge with the head height locked, covering the oversize. With that lock,
Plan Cutting may start the far-edge segment at the far edge: overcut outward,
rapid back still locked, unlock, cut toward the origin. Without it the
direction stays fixed, as decided in Project Memory a20ea455.

- CutOff.HeadLockOvercut / FarEdgeFirst, persisted in .nest; HEAD HEIGHT
  LOCK/UNLOCK comment markers; far edge is quadrant-aware.
- Planner offers the reversed far-edge segment in the joint search, the
  safest route and segment insertion; Apply records the direction.
- Cincinnati CL and CI Fiber posts gain lock/unlock code settings and refuse
  head-locked cut-offs while either is empty.
- Automatic cut-off dialog setting (off by default, 1.5 in), kept between
  sessions and used for new cut-offs.

Lock codes and machine behaviour with the head locked are unconfirmed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 09:51:59 -04:00

89 lines
4.2 KiB
C#

using System;
using System.Collections.Generic;
using OpenNest.Geometry;
namespace OpenNest.Diagnostics;
/// <summary>A nominal manual cut-off line meeting an active, physical clamp footprint.</summary>
public sealed record CutOffClampFinding(int CutOffIndex, string ClampId, Vector Location);
/// <summary>
/// Read-only axis-aligned clamp contact checks. Nominal cut-off definitions are checked
/// even if their current regenerated program is stale or trimmed by nearby parts.
/// A removed clamp is absent from Plate.ClampZones and therefore cannot be reported.
/// Zones are compared in the plate's signed placement frame (<see cref="ClampZoneFrame"/>).
/// </summary>
public static class ClampCutDiagnostics
{
public static IReadOnlyList<CutOffClampFinding> Find(Plate plate)
{
ArgumentNullException.ThrowIfNull(plate);
var findings = new List<CutOffClampFinding>();
if (plate.ClampZones.Count == 0 || plate.CutOffs.Count == 0)
return findings.AsReadOnly();
var zones = ClampZoneFrame.ToPlacement(plate);
var sheet = plate.BoundingBox(false);
for (var index = 0; index < plate.CutOffs.Count; index++)
{
var cut = plate.CutOffs[index];
if (cut == null || !Enum.IsDefined(cut.Axis))
throw new ArgumentException("Cut-off has an invalid axis.", nameof(plate));
// A head-locked overcut runs on past the far edge, where an off-sheet clamp can be.
var overcut = cut.HeadLockOvercut > 0 && double.IsFinite(cut.HeadLockOvercut) ? cut.HeadLockOvercut : 0;
var farHigh = CutOff.FarEdgeIsHigh(cut.Axis, plate.Quadrant);
var low = farHigh ? 0 : overcut;
var high = farHigh ? overcut : 0;
var start = cut.Axis == CutOffAxis.Vertical
? new Vector(cut.Position.X, cut.StartLimit ?? sheet.Bottom - low)
: new Vector(cut.StartLimit ?? sheet.Left - low, cut.Position.Y);
var end = cut.Axis == CutOffAxis.Vertical
? new Vector(cut.Position.X, cut.EndLimit ?? sheet.Top + high)
: new Vector(cut.EndLimit ?? sheet.Right + high, cut.Position.Y);
foreach (var zone in zones)
if (Intersects(start, end, zone.Bounds, out var location))
findings.Add(new CutOffClampFinding(index, zone.Id, location));
}
return findings.AsReadOnly();
}
/// <summary>True for contact with the whole rectangle, including a boundary touch.</summary>
public static bool Intersects(Vector from, Vector to, Box box, out Vector location)
{
location = default;
if (box == null || !Finite(from) || !Finite(to) || !Finite(box.Location)
|| !double.IsFinite(box.Right) || !double.IsFinite(box.Top)
|| box.Length <= 0 || box.Width <= 0)
throw new ArgumentException("Clamp contact geometry must be finite and nonempty.");
var dx = to.X - from.X;
var dy = to.Y - from.Y;
if (!double.IsFinite(dx) || !double.IsFinite(dy)
|| !double.IsFinite(from.X - box.Left) || !double.IsFinite(box.Right - from.X)
|| !double.IsFinite(from.Y - box.Bottom) || !double.IsFinite(box.Top - from.Y))
throw new ArgumentException("Clamp contact motion is too large.");
var enter = 0.0;
var exit = 1.0;
if (!Clip(-dx, from.X - box.Left, ref enter, ref exit)
|| !Clip(dx, box.Right - from.X, ref enter, ref exit)
|| !Clip(-dy, from.Y - box.Bottom, ref enter, ref exit)
|| !Clip(dy, box.Top - from.Y, ref enter, ref exit))
return false;
var midpoint = (enter + exit) * 0.5;
location = new Vector(from.X + dx * midpoint, from.Y + dy * midpoint);
return true;
}
private static bool Clip(double p, double q, ref double enter, ref double exit)
{
if (p == 0)
return q >= 0;
var ratio = q / p;
if (p < 0)
enter = System.Math.Max(enter, ratio);
else
exit = System.Math.Min(exit, ratio);
return enter <= exit;
}
private static bool Finite(Vector value) => double.IsFinite(value.X) && double.IsFinite(value.Y);
}