fix(diagnostics): check placed cut-off programs against clamps

Pre-post clamp checks looked only at nominal cut-off definitions and
rapids. A definition moved without regenerating its placed program
left a cut through an active clamp that posted with no warning, because
cut-off material is excluded from the overlap check.

Also check each cut-off part's placed cutting motions against the
clamps in the placement frame (lines exactly, arcs by their extent).
Report one warning per cut-off part and clamp, so a current definition
does not produce a duplicate of its nominal finding.

Project Memory: opennest cdbe2629-b562-46ee-9509-2b120a511654
This commit is contained in:
aj committed 2026-10-13 18:15:49 -04:00
1 parent 9a293bf221
commit c18db3f896
4 files changed
+76

No files matched your search

@@ -69,6 +69,9 @@ public static class PostVerificationAnalyzer
findings.Add(new(PostVerificationKind.Incomplete, plateNumber, null, null,
"Clamp check: " + exception.Message));
}
// One clamp warning per cut-off part and clamp: the placed-program check below
// reports only what the nominal definition check has not already reported.
var cutOffClampContacts = new HashSet<(int PartNumber, string ClampId)>();
if (clamps != null)
{
try
@@ -83,6 +86,8 @@ public static class PostVerificationAnalyzer
partIndex = partSlot;
break;
}
if (partIndex >= 0)
cutOffClampContacts.Add((partIndex + 1, contact.ClampId));
findings.Add(new(PostVerificationKind.Overlap, plateNumber,
partIndex < 0 ? null : partIndex + 1, null,
$"Manual cut-off {contact.CutOffIndex + 1} crosses active clamp '{contact.ClampId}'. "
@@ -156,6 +161,25 @@ public static class PostVerificationAnalyzer
ClampId = zone.Id
});
}
// The placed program is what posts: a definition edited without
// regenerating it can still cut through a clamp its nominal line misses.
if (cutoff && clamps is { Count: > 0 })
foreach (var move in moves)
{
cancellationToken.ThrowIfCancellationRequested();
if (move.Rapid || move.Start is not { } from)
continue;
foreach (var zone in clamps)
if (!cutOffClampContacts.Contains((partNumber, zone.Id))
&& CutTouches(move, from, zone.Bounds, out var contact))
{
cutOffClampContacts.Add((partNumber, zone.Id));
findings.Add(new(PostVerificationKind.Overlap, plateNumber, partNumber, null,
$"The placed cut-off program crosses active clamp '{zone.Id}'. "
+ "Regenerate or move the cut-off before posting; this warning is not a repair.")
{ Location = contact, ClampId = zone.Id });
}
}
position = moves[^1].End;
}
catch (Exception exception) when (IsInvalid(exception))
@@ -190,6 +214,19 @@ public static class PostVerificationAnalyzer
return new PostVerificationReport(findings);
}
// Lines are tested exactly; an arc conservatively by its whole supporting extent.
private static bool CutTouches(ExecutionMotion move, Vector from, Box zone, out Vector contact)
{
if (move.Curve == null || move.Curve.IsLine)
return ClampCutDiagnostics.Intersects(from, move.End, zone, out contact);
var extent = move.Curve.Extent;
contact = new Vector(
System.Math.Clamp(move.Curve.Midpoint.X, zone.Left, zone.Right),
System.Math.Clamp(move.Curve.Midpoint.Y, zone.Bottom, zone.Top));
return !(extent.MinX > zone.Right || extent.MaxX < zone.Left
|| extent.MinY > zone.Top || extent.MaxY < zone.Bottom);
}
private static bool IsInvalid(Exception exception) => exception is
ArgumentException or InvalidOperationException or NotSupportedException or ArithmeticException;
@@ -147,6 +147,7 @@ internal static class PostVerificationGeometry
private double Radius { get; }
private double Sweep { get; }
internal double Length => Center.HasValue ? Radius * System.Math.Abs(Sweep) : Start.DistanceTo(End);
internal bool IsLine => !Center.HasValue;
// Native entities are freshly allocated; the immutable curve never exposes state.
internal Entity ToEntity() => Center is { } center
@@ -199,6 +199,42 @@ public class ClampZonePlacementFrameTests
Assert.DoesNotContain(report.Findings, f => f.ClampId != null || f.Message.Contains("clamp"));
}
[Theory]
[InlineData(1)]
[InlineData(2)]
[InlineData(3)]
[InlineData(4)]
public void StalePlacedCutoffAcrossRailClampStillRequiresConsent(int quadrant)
{
// The placed vertical cut runs across the whole sheet width, through the clamp.
var plate = RailPlate(quadrant, 35);
var cut = new CutOff(new Vector(XSign(quadrant) * 35, 0), CutOffAxis.Vertical);
plate.CutOffs.Add(cut);
plate.RegenerateCutOffs(new CutOffSettings { CutDirection = CutDirection.TowardOrigin });
// In quadrants 3 and 4 the incoming rapid also reaches the clamp; only the cut is asserted.
var current = PostVerificationAnalyzer.Analyze(NestOf(plate));
var nominal = Assert.Single(current.Findings, f => f.Kind == PostVerificationKind.Overlap);
Assert.Equal("clamp-1", nominal.ClampId);
Assert.Equal(1, nominal.PartNumber);
// The definition moves clear of the clamp, but the placed program is not regenerated.
cut.Position = new Vector(XSign(quadrant) * 50, 0);
Assert.Empty(ClampCutDiagnostics.Find(plate));
var stale = PostVerificationAnalyzer.Analyze(NestOf(plate));
var placed = Assert.Single(stale.Findings, f => f.Kind == PostVerificationKind.Overlap);
Assert.Equal("clamp-1", placed.ClampId);
Assert.Equal(1, placed.PartNumber);
Assert.Contains("placed", placed.Message);
var footprint = WorldFootprint(quadrant, 35, 1.5, 0.625);
var location = Assert.NotNull(placed.Location);
Assert.InRange(location.X, footprint.Left, footprint.Right);
Assert.InRange(location.Y, footprint.Bottom, footprint.Top);
Assert.False(stale.CanPost(false));
Assert.True(stale.CanPost(true));
}
[Theory]
[InlineData("NaN")]
[InlineData("negative")]
+2
View File
@@ -57,6 +57,8 @@ shows all three check categories, even when there are no findings:
a sufficiently large cutout is not itself an overlap. The same review also warns if a nominal
manual cut-off line crosses an active clamp, even when its regenerated cut is currently trimmed;
a line contact has no material-overlap area. Removed clamps on that sheet are not checked.
The placed cut-off program is checked too, so a definition edited without regenerating its
cut still warns where the posted cut would cross a clamp (one warning per cut-off and clamp).
Clamp zones are compared in the plate's quadrant frame. A missing or malformed clamp zone
makes the clamp checks incomplete (a warning), never a clear result.
This uses clean drawing geometry, not added lead-in/out paths.