From 4e846007a298475c9992dffb699341684f87f48f Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Sat, 10 Oct 2026 13:14:31 -0400 Subject: [PATCH] fix(report): use parts per plate consistently --- OpenNest.Reporting/NestPdfWriter.cs | 8 ++++---- OpenNest.Tests/Reporting/NestPdfLayoutTests.cs | 4 ++-- OpenNest.Tests/Reporting/NestPdfWriterTests.cs | 2 ++ docs/nest-reports.md | 4 +++- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/OpenNest.Reporting/NestPdfWriter.cs b/OpenNest.Reporting/NestPdfWriter.cs index 890833f..4b3ba2c 100644 --- a/OpenNest.Reporting/NestPdfWriter.cs +++ b/OpenNest.Reporting/NestPdfWriter.cs @@ -131,7 +131,7 @@ public static class NestPdfWriter else { var plates = AddTable(section, 60, 200, 80, 100, 100); - AddHeader(plates, "Plate", "Stock size", "Quantity", "Parts/sheet", "Utilization"); + AddHeader(plates, "Plate", "Stock size", "Quantity", "Parts per plate", "Utilization"); foreach (var plate in snapshot.Plates) AddRow(plates, $"Plate {plate.Number}", false, plate.Number.ToString(Invariant), SheetSize(snapshot, plate), plate.Copies.ToString(Invariant), plate.Parts.Length.ToString(Invariant), Percent(plate.Utilization)); @@ -172,13 +172,13 @@ public static class NestPdfWriter title.Format.Font.Size = 13; title.Format.Font.Bold = true; - var info = AddTable(section, 80, 280, 90, 270); + var info = AddTable(section, 100, 260, 90, 270); info.Borders.Visible = false; AddInfoRow(info, "Nest:", snapshot.Name, "Sheet size:", SheetSize(snapshot, plate)); AddInfoRow(info, "Material:", Material(snapshot), "Quantity:", plate.Copies.ToString(Invariant)); AddInfoRow(info, "Thickness:", Thickness(snapshot), "Part spacing:", $"{Number(plate.PartSpacing)} {snapshot.Units}"); - AddInfoRow(info, "Parts/sheet:", plate.Parts.Length.ToString(Invariant), "Utilization:", + AddInfoRow(info, "Parts per plate:", plate.Parts.Length.ToString(Invariant), "Utilization:", Percent(plate.Utilization) + " (net part area / full sheet)"); var overview = AddReservedArea(section, null, plan.Overview.Size.Height); @@ -204,7 +204,7 @@ public static class NestPdfWriter var perSheet = plate.Parts.GroupBy(part => part.ReportId, StringComparer.Ordinal) .ToDictionary(group => group.Key, group => (long)group.Count(), StringComparer.Ordinal); var parts = AddTable(section, 60, 400, 110, 150); - AddHeader(parts, "ID", "Drawing", "Qty/sheet", $"Total ({plate.Copies} plates)"); + AddHeader(parts, "ID", "Drawing", "Parts per plate", $"Total ({plate.Copies} plates)"); foreach (var drawing in snapshot.Drawings.Where(drawing => perSheet.ContainsKey(drawing.Id))) { var count = perSheet[drawing.Id]; diff --git a/OpenNest.Tests/Reporting/NestPdfLayoutTests.cs b/OpenNest.Tests/Reporting/NestPdfLayoutTests.cs index ef22830..9a7e37a 100644 --- a/OpenNest.Tests/Reporting/NestPdfLayoutTests.cs +++ b/OpenNest.Tests/Reporting/NestPdfLayoutTests.cs @@ -85,7 +85,7 @@ public sealed class NestPdfLayoutTests : IDisposable // Every page carrying plate-table rows repeats the table heading. var tablePages = platePages.Where(page => Regex.IsMatch(page, @"(?m)^R\d{3}\s+Demand \d{2}\s+1\s+2$")).ToList(); Assert.True(tablePages.Count >= 2, $"plate table pages: {tablePages.Count}"); - Assert.All(tablePages, page => Assert.Matches(@"ID\s+Drawing\s+Qty/sheet\s+Total \(2 plates\)", page)); + Assert.All(tablePages, page => Assert.Matches(@"ID\s+Drawing\s+Parts per plate\s+Total \(2 plates\)", page)); var plateText = string.Join("\n", platePages); for (var i = 0; i < 40; i++) Assert.Matches($@"R{i + 4:D3}\s+Demand {i:D2}\s+1\s+2", plateText); @@ -114,7 +114,7 @@ public sealed class NestPdfLayoutTests : IDisposable var platePage = Array.FindIndex(pages, page => page.Contains("Plate 1 of 4")); Assert.True(summaryPage >= 0 && platePage > summaryPage); Assert.Equal(longName.Replace(" ", ""), ColumnText(path, summaryPage + 1, "Drawing", "Required")); - Assert.Equal(longName.Replace(" ", ""), ColumnText(path, platePage + 1, "Drawing", "Qty/sheet")); + Assert.Equal(longName.Replace(" ", ""), ColumnText(path, platePage + 1, "Drawing", "Parts")); // Column headings stay on one line. Assert.Matches(@"ID\s+Part\s+Drawing\s+Required\s+Nested\s+Shortage\s+Extra\s+Plates", pages[summaryPage]); } diff --git a/OpenNest.Tests/Reporting/NestPdfWriterTests.cs b/OpenNest.Tests/Reporting/NestPdfWriterTests.cs index 76fa7d1..b19971d 100644 --- a/OpenNest.Tests/Reporting/NestPdfWriterTests.cs +++ b/OpenNest.Tests/Reporting/NestPdfWriterTests.cs @@ -53,6 +53,8 @@ public sealed class NestPdfWriterTests : IDisposable Assert.Matches(@"R003\s+Bracket\s+7\s+2\s+5\s+0\s+1", summary); Assert.Matches(@"R004\s+Unplaced\s+3\s+0\s+3\s+0\s+-", summary); Assert.Contains("Page 1 of 2", summary); + Assert.Matches(@"Stock size\s+Quantity\s+Parts per plate", summary); + Assert.DoesNotContain("Parts/sheet", summary + plate); Assert.DoesNotContain("Copies", summary + plate); Assert.Contains("Plate 1", plate); diff --git a/docs/nest-reports.md b/docs/nest-reports.md index e8febc4..f666264 100644 --- a/docs/nest-reports.md +++ b/docs/nest-reports.md @@ -54,7 +54,9 @@ each plate's part table across pages with the heading row repeated, and notes flow as an ordinary paragraph. No row, table or note text is ever dropped or truncated to fit a page. -Stock sizes read **width x length** in job units. Plate counts use **Quantity**. +Stock sizes read **width x length** in job units. Plate tables use **Quantity** +and **Parts per plate**; the plate quantity multiplies per-plate part counts for +the totals. Each part ID is centered on its part's pole of inaccessibility (`PolyLabel`, the same method `PlateView`'s `LayoutPart` uses), computed on a