mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-28 11:31:08 -04:00
D1a (user, 2026-10-14): one locked cutout must not refuse the whole layout. With confirmed parameters a locked program is now a fixed emission, accepted as given. The replay checker and the lead validator take a per-motion owner: a violation inside the fixed emission (a lead meeting another part's or its own material, a rapid inside the locked program crossing a cut contour, an uncertifiable tab gap or a partial/reversed/retraced cut) is recorded apart instead of failing the candidate. The search never rejects or learns an order rule from them, so fixed emissions never steer it; the planner's rapid into the locked program and every regenerated lead keep every strict check. Replay turns those violations into typed FixedLead findings that name the part, contour and the part hit. A plate whose only findings are these is BestEffort with IndependentlyReplayed set: never Ready, applicable only with the per-proposal acceptance, and CuttingPlanService.Apply still rejects it. Overlap, unreadable programs, stale input and cancellation still block, and unlocked programs a caller left ineligible and the no-parameter route keep refusing as before. Pre-post review now re-checks locked leads against every part's clean material, so the same violations reappear before posting. Existing tests that encoded the old locked refusal now assert the warning; two batch tests that used a locked crossing as their hard block use an overlap instead. The 5.1 digests are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
84 lines
3.5 KiB
C#
84 lines
3.5 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>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");
|
|
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." : "."));
|
|
}
|
|
}
|
|
}
|