diff --git a/OpenNest.Reporting/NestPdfWriter.cs b/OpenNest.Reporting/NestPdfWriter.cs
index e66c5eae..0bbbd565 100644
--- a/OpenNest.Reporting/NestPdfWriter.cs
+++ b/OpenNest.Reporting/NestPdfWriter.cs
@@ -109,7 +109,7 @@ public static class NestPdfWriter
private static void AddSummary(Document document, NestReportSnapshot snapshot)
{
- var section = AddSection(document, Orientation.Portrait, PortraitWidth, $"Nest report: {snapshot.Name}");
+ var section = AddSection(document, snapshot, Orientation.Portrait, PortraitWidth, $"Nest report: {snapshot.Name}");
var title = section.AddParagraph("Nest Report");
title.Format.Font.Size = 14;
title.Format.Font.Bold = true;
@@ -125,7 +125,6 @@ public static class NestPdfWriter
left.Add(("Units:", UnitsName(snapshot)));
var right = new List<(string Key, string Value)>
{
- ("Generated:", snapshot.GeneratedAt.ToString("yyyy-MM-dd HH:mm zzz", Invariant)),
("Distinct layouts:", snapshot.Plates.Length.ToString(Invariant)),
("Total physical sheets:", snapshot.TotalSheets.ToString(Invariant)),
};
@@ -337,7 +336,7 @@ public static class NestPdfWriter
header.Add(Material(snapshot));
if (snapshot.Thickness > 0)
header.Add(Thickness(snapshot));
- var section = AddSection(document, Orientation.Landscape, LandscapeWidth, string.Join(" | ", header));
+ var section = AddSection(document, snapshot, Orientation.Landscape, LandscapeWidth, string.Join(" | ", header));
var title = section.AddParagraph(heading);
title.Format.Font.Size = 13;
title.Format.Font.Bold = true;
@@ -610,7 +609,8 @@ public static class NestPdfWriter
private static XFont CardFont(bool bold) =>
new(ReportFonts.Family, CardFontSize, bold ? XFontStyleEx.Bold : XFontStyleEx.Regular);
- private static Section AddSection(Document document, Orientation orientation, double width, string header)
+ private static Section AddSection(Document document, NestReportSnapshot snapshot, Orientation orientation,
+ double width, string header)
{
var section = document.AddSection();
var setup = section.PageSetup;
@@ -633,7 +633,12 @@ public static class NestPdfWriter
top.Format.Font.Color = Colors.DimGray;
AddLines(top, lines);
- var footer = section.Footers.Primary.AddParagraph("Page ");
+ // When it was generated on the left, in this computer's time zone; the page on the right.
+ var footer = section.Footers.Primary.AddParagraph();
+ footer.Format.TabStops.AddTabStop(Unit.FromPoint(width), TabAlignment.Right);
+ footer.AddText("Generated " + snapshot.GeneratedAt.ToLocalTime().ToString("yyyy-MM-dd HH:mm", Invariant));
+ footer.AddTab();
+ footer.AddText("Page ");
footer.AddPageField();
footer.AddText(" of ");
footer.AddNumPagesField();
@@ -724,8 +729,11 @@ public static class NestPdfWriter
}
// Zero thickness means "unset" in a new nest; never present it as a genuine value.
+ // A standard gauge or plate fraction leads, as the thickness box lists it: "16 ga - 0.0598 in".
private static string Thickness(NestReportSnapshot snapshot) =>
- snapshot.Thickness > 0 ? $"{Number(snapshot.Thickness)} {snapshot.Units}" : "Not specified";
+ snapshot.Thickness <= 0 ? "Not specified"
+ : snapshot.ThicknessName.Length > 0 ? $"{snapshot.ThicknessName} - {Number(snapshot.Thickness)} {snapshot.Units}"
+ : $"{Number(snapshot.Thickness)} {snapshot.Units}";
private static string SheetSize(NestReportSnapshot snapshot, ReportPlate plate) =>
$"{Number(plate.Bounds.Width)} x {Number(plate.Bounds.Length)} {snapshot.Units}";
diff --git a/OpenNest.Reporting/NestReportSnapshot.cs b/OpenNest.Reporting/NestReportSnapshot.cs
index 8ccb110d..46e9435f 100644
--- a/OpenNest.Reporting/NestReportSnapshot.cs
+++ b/OpenNest.Reporting/NestReportSnapshot.cs
@@ -62,6 +62,13 @@ public sealed record NestReportSnapshot(string Name, string Customer, string Not
{
public long TotalSheets => Plates.Aggregate(0L, (total, plate) => checked(total + plate.Copies));
+ ///
+ /// The standard gauge or plate fraction the thickness matches ("16 ga", "1/4\""), shown
+ /// before the decimal; empty when it matches none. Reporting has no thickness table of its
+ /// own, so the caller supplies it.
+ ///
+ public string ThicknessName { get; init; } = "";
+
/// "lb" for inch jobs, "kg" for millimeter jobs.
public string WeightUnits => Units == "mm" ? "kg" : "lb";
diff --git a/OpenNest.Tests/Reporting/NestPdfWriterTests.cs b/OpenNest.Tests/Reporting/NestPdfWriterTests.cs
index a57fdbbd..711be5fb 100644
--- a/OpenNest.Tests/Reporting/NestPdfWriterTests.cs
+++ b/OpenNest.Tests/Reporting/NestPdfWriterTests.cs
@@ -44,7 +44,12 @@ public sealed class NestPdfWriterTests : IDisposable
Assert.Contains("Test customer", summary);
Assert.Contains("Steel A36", summary);
Assert.Contains("0.125 in", summary);
- Assert.Contains("2026-09-29 12:00 +00:00", summary);
+ // The generation time is in every page's footer, in this computer's time zone, no offset.
+ var generated = "Generated " + NestReportTestData.GeneratedAt.ToLocalTime().ToString("yyyy-MM-dd HH:mm");
+ Assert.Contains(generated, summary);
+ Assert.Contains(generated, plate);
+ Assert.DoesNotContain("Generated:", summary);
+ Assert.DoesNotContain("+00:00", summary + plate);
Assert.Matches(@"Distinct layouts:\s+1", summary);
Assert.Matches(@"Total physical sheets:\s+2", summary);
Assert.Matches(@"1\s+24 x 48 in\s+2\s+4\s+\d+\.\d%", summary);
@@ -82,6 +87,19 @@ public sealed class NestPdfWriterTests : IDisposable
+ ReportPdf.Text(Path.Combine(directory, "again.pdf"), 2));
}
+ [SkippableFact]
+ public void Write_NamedThicknessLeadsTheDecimalOnSummaryAndPlatePages()
+ {
+ var path = Path.Combine(directory, "named-thickness.pdf");
+ var snapshot = NestReportBuilder.Capture(NestReportTestData.CreateNest(), NestReportTestData.GeneratedAt)
+ with
+ { ThicknessName = "1/8\"" };
+ NestPdfWriter.Write(snapshot, path);
+
+ Assert.Contains("1/8\" - 0.125 in", ReportPdf.Text(path, 1));
+ Assert.Contains("1/8\" - 0.125 in", ReportPdf.Text(path, 2));
+ }
+
[SkippableFact]
public void Write_EmptyAndDemandOnlyJobsProduceExplicitSummaryOnlyReports()
{
diff --git a/OpenNest.Tests/Reporting/ReportPdf.cs b/OpenNest.Tests/Reporting/ReportPdf.cs
index 4c8268a7..d7598d8b 100644
--- a/OpenNest.Tests/Reporting/ReportPdf.cs
+++ b/OpenNest.Tests/Reporting/ReportPdf.cs
@@ -81,7 +81,7 @@ public static class ReportPdf
var excluded = rows.Where(row => Join(row.Words) is var text
&& (text.StartsWith("Weights need", StringComparison.Ordinal)
|| text.StartsWith("Nest report:", StringComparison.Ordinal)
- || Regex.IsMatch(text, @"^Page \d+ of \d+$")))
+ || Regex.IsMatch(text, @"^(Generated \S+ \S+ )?Page \d+ of \d+$")))
.Select(row => row.Top).ToList();
for (var column = 0; column < CardsPerRow; column++)
{
diff --git a/OpenNest/Forms/EditNestForm.cs b/OpenNest/Forms/EditNestForm.cs
index db08a6f3..18b456e7 100644
--- a/OpenNest/Forms/EditNestForm.cs
+++ b/OpenNest/Forms/EditNestForm.cs
@@ -859,7 +859,11 @@ namespace OpenNest.Forms
DateTimeOffset.Now,
SavedCutParameters.Load(targets.Nest.Units),
catalogDensity
- );
+ ) with
+ {
+ ThicknessName =
+ Data.StandardThicknesses.Find(targets.Nest.Thickness, targets.Nest.Units)?.Name ?? "",
+ };
NestPdfWriter.Write(snapshot, destination);
}
diff --git a/docs/nest-reports.md b/docs/nest-reports.md
index a829de3b..4efd42fb 100644
--- a/docs/nest-reports.md
+++ b/docs/nest-reports.md
@@ -95,8 +95,9 @@ PartPrintoutWriter.Write(snapshot, destinationPath);
An empty/demand-only job, a single plate, or many plates and drawings all
produce one document: a Letter portrait summary followed by one Letter
-landscape section per plate, every page carrying a repeated header and
-"Page X of Y". Long notes, long drawing names, many drawings and many parts
+landscape section per plate, every page carrying a repeated header and a
+footer with the generation time (left, in the computer's time zone, no offset)
+and "Page X of Y" (right). Long notes, long drawing names, many drawings and many parts
per sheet paginate naturally: MigraDoc continues the Plates table and each
plate's part table across pages with the heading row repeated, the summary's
part cards continue row by row, and notes flow as an ordinary paragraph. No
@@ -110,8 +111,11 @@ beside the ID; complete drawings show no status. Cards without a weight show
"-" with a note under the Parts heading saying what is missing. A card row is
a table row, so a card never splits across pages.
-The summary's header lists the job and material on the left and the dates and
-totals on the right, to leave room for part cards on the first page.
+The summary's header lists the job and material on the left and the totals on
+the right, to leave room for part cards on the first page. A thickness that
+matches a standard gauge or plate fraction leads with its name, as the
+thickness box lists it ("16 ga - 0.0598 in"); the desktop supplies the name,
+since Reporting has no thickness table of its own.
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