feat(reporting): name the thickness on part printouts

The nest report already leads its thickness with the machine config's
Name / Gauge; the part printout's THICKNESS cell now does the same
(16 ga - 0.0598"), from the same lookup in the desktop.

Thickness names are typed into the machine config, so both writers now check
them against the bundled font's Latin-1 limit like other free text, instead
of failing partway through rendering.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ajandClaude Opus 5.5 committed 2026-11-12 07:51:40 -05:00
1 parent 2f3f105a5e
commit 72e16139ae
6 files changed
+65 -6

No files matched your search

+1
View File
@@ -103,6 +103,7 @@ public static class NestPdfWriter
ReportFonts.ValidateText(snapshot.Notes, "Notes");
ReportFonts.ValidateText(snapshot.Material, "Material");
ReportFonts.ValidateText(snapshot.Grade, "Material grade");
ReportFonts.ValidateText(snapshot.ThicknessName, "Thickness name");
foreach (var drawing in snapshot.Drawings)
ReportFonts.ValidateText(drawing.Name, $"Drawing {drawing.Id} name");
}
@@ -20,6 +20,12 @@ public sealed record PrintoutPart(string Name, string Customer, double Width, do
public sealed record PartPrintoutSnapshot(string Title, string Material, double Thickness, string Units,
DateTimeOffset GeneratedAt, ImmutableArray<PrintoutPart> Parts)
{
/// <summary>
/// The name the nest machine's config gives this thickness ("16 ga"), shown before the
/// decimal; empty when it has none. The caller supplies it, as for the nest report.
/// </summary>
public string ThicknessName { get; init; } = "";
/// <summary>"lb" for inch jobs, "kg" for millimeter jobs.</summary>
public string WeightUnits => Units == "mm" ? "kg" : "lb";
}
+11 -1
View File
@@ -62,6 +62,7 @@ public static class PartPrintoutWriter
{
ReportFonts.ValidateText(snapshot.Title, "Printout title");
ReportFonts.ValidateText(snapshot.Material, "Material");
ReportFonts.ValidateText(snapshot.ThicknessName, "Thickness name");
foreach (var part in snapshot.Parts)
{
ReportFonts.ValidateText(part.Name, $"Drawing '{part.Name}' name");
@@ -99,7 +100,7 @@ public static class PartPrintoutWriter
var rows = new (string, string, string, string)[]
{
("DIMENSIONS", $"{Length(part.Width, units)} x {Length(part.Length, units)}", "MATERIAL", Dash(snapshot.Material)),
("THICKNESS", snapshot.Thickness > 0 ? Length(snapshot.Thickness, units, "0.####") : "-",
("THICKNESS", Thickness(snapshot),
"CUT LENGTH", Length(part.CutLength, units)),
("PIERCE COUNT", part.PierceCount.ToString(Invariant), "WEIGHT",
part.Weight is { } weight ? $"{Number(weight)} {snapshot.WeightUnits}" : "-"),
@@ -177,6 +178,15 @@ public static class PartPrintoutWriter
private static string Length(double value, string units, string format = "0.00") =>
units == "mm" ? $"{value.ToString(format, Invariant)} mm" : $"{value.ToString(format, Invariant)}\"";
/// <summary>The machine config's name for the thickness leads: 16 ga - 0.0598".</summary>
private static string Thickness(PartPrintoutSnapshot snapshot)
{
if (snapshot.Thickness <= 0)
return "-";
var value = Length(snapshot.Thickness, snapshot.Units, "0.####");
return snapshot.ThicknessName.Length > 0 ? $"{snapshot.ThicknessName} - {value}" : value;
}
private static string Number(double value) => value.ToString("0.00", Invariant);
private static string Dash(string text) => string.IsNullOrWhiteSpace(text) ? "-" : text.Trim();
@@ -122,6 +122,40 @@ public sealed class PartPrintoutTests : IDisposable
Assert.Contains("Generated 9/29/2026 12:00 PM", text);
}
[SkippableFact]
public void Write_NamedThicknessLeadsTheDecimal()
{
var nest = NestReportTestData.CreateNest();
var bracket = nest.Drawings.Single(drawing => drawing.Name == "Bracket");
var path = Path.Combine(directory, "named.pdf");
PartPrintoutWriter.Write(PartPrintoutBuilder.Capture(nest, [bracket], NestReportTestData.GeneratedAt)
with
{ ThicknessName = "11 ga" }, path);
Assert.Matches(@"THICKNESS\s+11 ga - 0\.125""\s+CUT LENGTH", ReportPdf.Text(path, 1));
}
[Fact]
public void Write_RejectsAThicknessNameTheBundledFontCannotShow()
{
var nest = NestReportTestData.CreateNest();
var printout = Path.Combine(directory, "bad-printout.pdf");
var report = Path.Combine(directory, "bad-report.pdf");
var printoutError = Assert.Throws<InvalidOperationException>(() => PartPrintoutWriter.Write(
PartPrintoutBuilder.Capture(nest, null, NestReportTestData.GeneratedAt) with { ThicknessName = "1/4—plate" },
printout));
var reportError = Assert.Throws<InvalidOperationException>(() => NestPdfWriter.Write(
NestReportBuilder.Capture(nest, NestReportTestData.GeneratedAt) with { ThicknessName = "1/4—plate" },
report));
Assert.Contains("Thickness name", printoutError.Message);
Assert.Contains("Thickness name", reportError.Message);
Assert.False(File.Exists(printout));
Assert.False(File.Exists(report));
}
[Fact]
public void Write_RejectsTextTheBundledFontCannotShow()
{
+11 -4
View File
@@ -862,12 +862,15 @@ namespace OpenNest.Forms
catalogDensity
) with
{
// The name the machine config gives the nest's thickness ("16 ga"), if any.
ThicknessName = Data.MachineCuttingDefaults.Find(machine, targets.Nest)?.Name?.Trim() ?? "",
ThicknessName = MachineThicknessName(machine, targets.Nest),
};
NestPdfWriter.Write(snapshot, destination);
}
/// <summary>The name the machine config gives the nest's thickness ("16 ga"), or empty.</summary>
private static string MachineThicknessName(Data.MachineConfig machine, Nest nest) =>
Data.MachineCuttingDefaults.Find(machine, nest)?.Name?.Trim() ?? "";
/// <summary>
/// Writes one printout page per drawing, in order. Capture is synchronous on the UI thread
/// and reads only drawings, so it has no accounting or dirty-state side effects.
@@ -877,13 +880,17 @@ namespace OpenNest.Forms
ArgumentNullException.ThrowIfNull(drawings);
ArgumentException.ThrowIfNullOrWhiteSpace(destination);
var material = Nest.Material;
var machine = MachineSettings.FindMachine(Nest.Machine);
var catalogDensity = Data.MaterialDensityLookup.Resolve(
material?.Name,
material?.Grade,
MachineSettings.FindMachine(Nest.Machine),
machine,
MachineSettings.LoadMaterials()
);
var snapshot = PartPrintoutBuilder.Capture(Nest, drawings, DateTimeOffset.Now, catalogDensity);
var snapshot = PartPrintoutBuilder.Capture(Nest, drawings, DateTimeOffset.Now, catalogDensity) with
{
ThicknessName = MachineThicknessName(machine, Nest),
};
PartPrintoutWriter.Write(snapshot, destination);
}
+2 -1
View File
@@ -38,7 +38,8 @@ suggesting `<nest-name>.printout.pdf` or `<drawing-name>.printout.pdf`. Each par
gets one Letter portrait page, bookmarked by name, with the name and customer (the drawing's, else the
nest's) as the header, the part's material contours stroked to fit (leads, rapids
and marks omitted), a details table and the generated time. The table shows the
bounding box (Y extent x X extent), nest material and thickness, cut length and
bounding box (Y extent x X extent), nest material and thickness (led by the
machine config's thickness name, as in the nest report), cut length and
pierce count, net area, weight
(the nest report's density rules) and required quantity. Cut length sums the moves the head cuts (cut, break, common-line and lead layers); each run of
them is one pierce, so scribe marks, tab spans, display moves and a display