mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-14 14:51:56 -04:00
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:
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")]
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in new issue
Block a user