mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-11-06 11:15:44 -05:00
Expensive alloy jobs (N26-295, 310 stainless) could not fit their last PT42 at full part spacing; the plate was squeezed to 0.2 spacing with a part 0.14 from the edge. Parts whose straight edges face each other one kerf apart are cut on the same centerline, saving the spacing along each shared edge. Plate.CommonLines holds groups of parts and the kerf they were joined at. Shared spans are derived from the placed clean perimeters, never stored, so a moved member breaks its group instead of leaving a stale span. Plate > Arrange > Join Common Line slides the selected parts together at the plate's kerf; Remove Common Line ungroups them. The plate draws shared spans as a dashed centerline. Both spacing checks hold group members to the kerf instead of the part spacing, and a new pre-post Common lines check reports broken groups, a changed kerf, and leads that run into a partner. Groups persist as part positions in an optional commonLines array that older readers ignore. This first slice still cuts every shared edge twice; single-pass cutting through Plan Cutting and the posts comes next. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
141 lines
6.2 KiB
C#
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, "Edge spacing");
|
|
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." : "."));
|
|
}
|
|
}
|
|
}
|