Files
OpenNest/OpenNest.Core/Diagnostics/PostVerificationReport.cs
T
ajandClaude Opus 5.5 4aa035af2f feat(diagnostics): mark plate-edge findings on the plate and say "outside the work area"
The Layout label counted parts off the plate or inside the edge spacing, but
nothing showed which part or where, so the user had to hunt for it. Each edge
finding now gets a canvas marker like the overlap and spacing ones: a red
crosshair where a part leaves the sheet and an orange ring at the gap to the
edge, in every visible display mode, with hover text that carries the finding's
message. Markers are retained across edits like the other overlay findings
(only a changed part's marker drops), and a sheet size, edge spacing or outline
change clears them. Validate Nest already located these findings through
PostVerificationFinding.Location.

"Near edge" / "inside the edge spacing" read as a spacing rule rather than a
placement problem, so the label, overlay messages, PlateEdgeReview findings
and the Validate Nest category now say the part is outside the work area. Off
the plate keeps its wording. The PostVerificationKind.EdgeSpacing name stays.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 12:28:52 -05:00

141 lines
6.2 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OpenNest.Geometry;
namespace OpenNest.Diagnostics;
public enum PostVerificationKind
{
Overlap,
MissingLeadIn,
RapidCrossing,
Incomplete,
/// <summary>
/// A violation inside a fixed emission (a locked program, or a contour cut exactly as its
/// drawing pins it): accepted as given, never repaired, always reported for review.
/// </summary>
FixedLead,
/// <summary>A part inside another part's cutout is cut after that host, whose cutout releases it.</summary>
NestedCutOrder,
/// <summary>
/// A lead of a nested part or of its host contacts or enters the other's material; for a host
/// this is typically an enclosing-hole lead crossing the insert.
/// </summary>
NestedLead,
/// <summary>A lead-in pierce is closer to part material than half the pierce diameter.</summary>
PierceZone,
/// <summary>
/// Two parts are closer than the plate's part spacing without sharing material, judged as
/// Auto Nest's layout check judges them.
/// </summary>
PartSpacing,
/// <summary>
/// A part its plate's support bed cannot hold level has no tab that keeps it level, so it may
/// tip once cut free (see docs/support-tabs.md).
/// </summary>
PartSupport,
/// <summary>
/// A part's cuts, leads or scribes run past the sheet, or its material lies outside an outlined
/// sheet or over one of its obstacles.
/// </summary>
OffPlate,
/// <summary>
/// A part's clean material is closer to the sheet's edge than the plate's edge spacing, judged as
/// Auto Nest's layout check judges it.
/// </summary>
EdgeSpacing,
/// <summary>
/// A common-line group (see docs/common-line-cutting.md) no longer shares its spans one kerf
/// apart, was placed for another kerf, or a member's lead runs into its partner.
/// </summary>
CommonLine
}
/// <summary>Plate and part numbers are one-based; plate zero denotes a whole-post limitation.</summary>
public sealed record PostVerificationFinding(PostVerificationKind Kind, int PlateNumber,
int? PartNumber, int? OtherPartNumber, string Message)
{
public Vector? Location { get; init; }
public Vector? RapidStart { get; init; }
public Vector? RapidEnd { get; init; }
/// <summary>Rapid boundary contacts in travel order; collinear contact spans use their endpoints.</summary>
public IReadOnlyList<Vector> ContactPoints { get; init; } = Array.Empty<Vector>();
public PlateOverlapPair Overlap { get; init; }
/// <summary>Active clamp contacted by a cut-off or direct XY rapid, if applicable.</summary>
public string ClampId { get; init; }
}
/// <summary>Owned, immutable findings. Consent is evaluated afresh, never stored.</summary>
public sealed class PostVerificationReport
{
internal PostVerificationReport(IEnumerable<PostVerificationFinding> findings)
{
Findings = Array.AsReadOnly(findings.ToArray());
}
public IReadOnlyList<PostVerificationFinding> Findings { get; }
public bool HasWarnings => Findings.Count != 0;
public bool CanPost(bool risksAcknowledged) => !HasWarnings || risksAcknowledged;
public string ToDisplayText() => ToDisplayText("Pre-post verification");
public string ToDisplayText(string title)
{
var text = new StringBuilder();
text.AppendLine(title);
var incomplete = Findings.Any(finding => finding.Kind == PostVerificationKind.Incomplete);
Summary(PostVerificationKind.Overlap, "Overlap");
Summary(PostVerificationKind.MissingLeadIn, "Missing lead-ins");
Summary(PostVerificationKind.RapidCrossing, "Rapid crossings");
// Only nests with fixed-lead findings gain the line, so other reports read as before.
if (Findings.Any(finding => finding.Kind == PostVerificationKind.FixedLead))
Summary(PostVerificationKind.FixedLead, "Fixed leads");
if (Findings.Any(finding => finding.Kind == PostVerificationKind.NestedCutOrder))
Summary(PostVerificationKind.NestedCutOrder, "Nested part order");
if (Findings.Any(finding => finding.Kind == PostVerificationKind.NestedLead))
Summary(PostVerificationKind.NestedLead, "Nested part leads");
if (Findings.Any(finding => finding.Kind == PostVerificationKind.PierceZone))
Summary(PostVerificationKind.PierceZone, "Pierce zones");
if (Findings.Any(finding => finding.Kind == PostVerificationKind.PartSpacing))
Summary(PostVerificationKind.PartSpacing, "Part spacing");
if (Findings.Any(finding => finding.Kind == PostVerificationKind.PartSupport))
Summary(PostVerificationKind.PartSupport, "Part support");
if (Findings.Any(finding => finding.Kind == PostVerificationKind.OffPlate))
Summary(PostVerificationKind.OffPlate, "Off the plate");
if (Findings.Any(finding => finding.Kind == PostVerificationKind.EdgeSpacing))
Summary(PostVerificationKind.EdgeSpacing, "Outside work area");
if (Findings.Any(finding => finding.Kind == PostVerificationKind.CommonLine))
Summary(PostVerificationKind.CommonLine, "Common lines");
foreach (var finding in Findings)
{
text.Append(finding.PlateNumber == 0 ? "Post processor" : $"Plate {finding.PlateNumber}");
if (finding.PartNumber is { } part)
text.Append($", part {part}");
if (finding.OtherPartNumber is { } other)
text.Append($", other part {other}");
text.AppendLine($": {finding.Kind}: {finding.Message}");
}
text.AppendLine("These checks cover direct XY rapids in plate/program order; the post may " +
"change order, routing or retracts. Inspect the posted machine program and machine setup " +
"before cutting, and confirm actual contour gaps give adequate retention.");
return text.ToString();
void Summary(PostVerificationKind kind, string label)
{
var count = Findings.Count(finding => finding.Kind == kind);
text.AppendLine($"{label}: {count} warning(s)" +
(incomplete ? "; verification incomplete — review before posting." : "."));
}
}
}