diff --git a/OpenNest.Engine/CuttingPlanning/CuttingPlanBatch.cs b/OpenNest.Engine/CuttingPlanning/CuttingPlanBatch.cs index e91fa5a..a7b2353 100644 --- a/OpenNest.Engine/CuttingPlanning/CuttingPlanBatch.cs +++ b/OpenNest.Engine/CuttingPlanning/CuttingPlanBatch.cs @@ -236,6 +236,10 @@ public sealed class CuttingPlanPlateResult Result.ProposedOrder.Select((proposal, index) => proposal.SourceOrdinal != index).Any(changed => changed); } +/// A diagnostic tied to captured plate and source part positions, not display text. +public sealed record CuttingPlanDiagnosticRow(int PlateIndex, int PlateNumber, int? PartNumber, + int? OtherPartNumber, string Message); + /// /// The outcome of a batch. Apply is all-or-nothing. Unverified output needs explicit acceptance. /// @@ -263,6 +267,35 @@ public sealed class CuttingPlanProposal public bool RequiresWarningAcceptance => CanApplyWithWarnings && !CanApply; + /// All findings, including ones hidden by a blocked plate's abbreviated summary. + public IReadOnlyList DiagnosticRows() + { + var rows = new List(); + for (var index = 0; index < Plates.Count; index++) + { + var plate = Plates[index]; + if (plate.Overlap != null) + { + foreach (var pair in plate.Overlap.Pairs) + rows.Add(new(index, plate.PlateNumber, pair.PartAId + 1, pair.PartBId + 1, + $"Part {pair.PartAId + 1}{Name(pair.PartAName)} overlaps part " + + $"{pair.PartBId + 1}{Name(pair.PartBName)}.")); + foreach (var issue in plate.Overlap.Issues) + rows.Add(new(index, plate.PlateNumber, issue.PartAId + 1, + issue.PartBId is int other ? other + 1 : null, + $"Overlap check incomplete for part {issue.PartAId + 1}" + + (issue.PartBId is int second ? $" and part {second + 1}" : string.Empty) + + $": {issue.Message}")); + } + foreach (var finding in plate.Result.Findings) + rows.Add(new(index, plate.PlateNumber, + finding.SourceOrdinal is int part ? part + 1 : null, + finding.OtherSourceOrdinal is int other ? other + 1 : null, + DescribeFinding(finding))); + } + return rows; + } + /// /// Installs every plate's replayed proposal through , on the /// thread that owns the plates. Only after every plate is applied does each one keep an owned copy diff --git a/OpenNest.Tests/CuttingPlanning/CuttingPlanBatchTests.cs b/OpenNest.Tests/CuttingPlanning/CuttingPlanBatchTests.cs index c3604a7..2a990de 100644 --- a/OpenNest.Tests/CuttingPlanning/CuttingPlanBatchTests.cs +++ b/OpenNest.Tests/CuttingPlanning/CuttingPlanBatchTests.cs @@ -90,6 +90,10 @@ public class CuttingPlanBatchTests var text = string.Join("\n", proposal.Describe("in")); Assert.Contains("Plate 1: blocked: parts overlap or could not be checked for overlap.", text); Assert.Contains("- Part 1 (first) overlaps part 2 (second).", text); + var row = Assert.Single(proposal.DiagnosticRows()); + Assert.Equal((0, 1, 1, 2), + (row.PlateIndex, row.PlateNumber, row.PartNumber, row.OtherPartNumber)); + Assert.Contains("overlaps part 2", row.Message); Assert.Equal(CuttingCommitStatus.InvalidInput, proposal.Apply().Status); Assert.Equal(programs, plate.Parts.Select(p => p.Program)); Assert.All(plate.Parts, part => Assert.False(part.HasManualLeadIns)); diff --git a/OpenNest.WinForms.Tests/Forms/CuttingPlanFormTests.cs b/OpenNest.WinForms.Tests/Forms/CuttingPlanFormTests.cs index 8e300b0..057e7b3 100644 --- a/OpenNest.WinForms.Tests/Forms/CuttingPlanFormTests.cs +++ b/OpenNest.WinForms.Tests/Forms/CuttingPlanFormTests.cs @@ -59,10 +59,12 @@ public class CuttingPlanFormTests Assert.False(Control