From ecd0bba4ebc6c70c09fbd64f31bf76ed5ef75b65 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Thu, 12 Nov 2026 08:41:12 -0500 Subject: [PATCH] refactor(file-menu): regroup the export and report commands The File menu ran six output commands together with no separators, and "Export" / "Export All" didn't say what they exported. Group them by what they produce: - Save Copy as .nest File... (was Export .nest...) moves next to Save As, since it writes the nest file rather than exporting a plate. - Export Plate... / Export All Plates... (were Export / Export All) get their own group and the "..." their save dialogs call for. - Nest Report... / Part Printouts... (were View ...) form a reports group. - Storage Mode... gets its own separator above Exit. Export Nest Report... is removed: the report viewer already has Save As (available even without WebView2), so it was a second path to the same PDF. Its guard and failure tests now drive the viewer path, and the atomic-write test calls WriteNestReport directly. The Avalonia prototype's matching command is renamed too. Co-Authored-By: Claude Opus 5.5 --- OpenNest.Avalonia/MainWindow.axaml | 2 +- OpenNest.Avalonia/MainWindow.axaml.cs | 12 +-- .../Forms/NestReportExportTests.cs | 100 +++++------------- OpenNest/Forms/EditNestForm.cs | 8 +- OpenNest/Forms/MainForm.Designer.cs | 27 +++-- OpenNest/Forms/MainForm.File.cs | 46 ++------ OpenNest/Forms/MainForm.Status.cs | 3 +- OpenNest/Forms/MainForm.cs | 9 +- docs/nest-reports.md | 47 ++++---- docs/nest-storage.md | 6 +- 10 files changed, 90 insertions(+), 170 deletions(-) diff --git a/OpenNest.Avalonia/MainWindow.axaml b/OpenNest.Avalonia/MainWindow.axaml index de1e2faf..84af0654 100644 --- a/OpenNest.Avalonia/MainWindow.axaml +++ b/OpenNest.Avalonia/MainWindow.axaml @@ -10,7 +10,7 @@ - + diff --git a/OpenNest.Avalonia/MainWindow.axaml.cs b/OpenNest.Avalonia/MainWindow.axaml.cs index 0ed9e87f..ab79f4ad 100644 --- a/OpenNest.Avalonia/MainWindow.axaml.cs +++ b/OpenNest.Avalonia/MainWindow.axaml.cs @@ -426,25 +426,25 @@ namespace OpenNest.Avalonia writingFile = true; try { - var path = await PickNestFileAsync(tab, "Export .nest"); + var path = await PickNestFileAsync(tab, "Save Copy as .nest File"); if (path == null || IsNesting(tab, "exporting")) return; try { // Export never changes the nest's file, name or unsaved mark. tabs.ExportCopy(tab, path); - ShowNotice($"Exported .nest to {path}"); + ShowNotice($"Saved a copy to {path}"); } catch (SaveTargetRefusedException ex) { await ConfirmDialog.ShowAsync( - this, "Export .nest", $"The nest was not exported. {ex.Message}"); + this, "Save Copy", $"The copy was not saved. {ex.Message}"); } catch (Exception ex) { - await ConfirmDialog.ShowAsync(this, "Export .nest", - $"The nest was not exported.\n\n{path}\n\n{ex.Message}\n\nChoose " - + "File > Export .nest again to pick another folder or name."); + await ConfirmDialog.ShowAsync(this, "Save Copy", + $"The copy was not saved.\n\n{path}\n\n{ex.Message}\n\nChoose " + + "File > Save Copy as .nest File again to pick another folder or name."); } } finally diff --git a/OpenNest.WinForms.Tests/Forms/NestReportExportTests.cs b/OpenNest.WinForms.Tests/Forms/NestReportExportTests.cs index 708cd253..20065b29 100644 --- a/OpenNest.WinForms.Tests/Forms/NestReportExportTests.cs +++ b/OpenNest.WinForms.Tests/Forms/NestReportExportTests.cs @@ -47,25 +47,21 @@ public class NestReportExportTests { using var host = new TestMainForm(); host.Show(); - var item = Menu(host, "mnuFileExportNestReport"); var view = Menu(host, "mnuFileViewNestReport"); - Assert.False(item.Enabled); Assert.False(view.Enabled); using var form = new EditNestForm(new Nest("enabled job")) { MdiParent = host }; form.PlateView.SetOverlapAutoCheck(null); // Background overlap workers are irrelevant here. form.Show(); - Assert.True(item.Enabled); Assert.True(view.Enabled); form.Close(); host.Activate(); - Assert.False(item.Enabled); Assert.False(view.Enabled); }); [Fact] - public void JobBusyRejectsBeforeAnyDialog() => RunSta(() => + public void JobBusyRejectsBeforeRendering() => RunSta(() => { using var host = new TestMainForm(); host.Show(); @@ -75,14 +71,14 @@ public class NestReportExportTests SetField(host, "nestingInProgress", true); try { - Menu(host, "mnuFileExportNestReport").PerformClick(); + Menu(host, "mnuFileViewNestReport").PerformClick(); } finally { SetField(host, "nestingInProgress", false); } - Assert.Empty(host.DialogRequests); + Assert.Empty(host.Viewed); var failure = Assert.Single(host.Failures); Assert.Contains("busy job", failure); Assert.Contains("Finish or cancel", failure); @@ -99,8 +95,8 @@ public class NestReportExportTests SetBackingField(form.PlateView, "IsFillInProgress", true); try { - Menu(host, "mnuFileExportNestReport").PerformClick(); - Assert.Empty(host.DialogRequests); + Menu(host, "mnuFileViewNestReport").PerformClick(); + Assert.Empty(host.Viewed); Assert.Contains("Finish or cancel", Assert.Single(host.Failures)); } finally @@ -108,9 +104,9 @@ public class NestReportExportTests SetBackingField(form.PlateView, "IsFillInProgress", false); } - // Cleared guard: the command now reaches the (test-intercepted) dialog. - Menu(host, "mnuFileExportNestReport").PerformClick(); - Assert.Single(host.DialogRequests); + // Cleared guard: the command now reaches the (test-intercepted) viewer. + Menu(host, "mnuFileViewNestReport").PerformClick(); + NestReportViewerForm.DeleteReportFolder(Assert.Single(host.Viewed)); }); [Fact] @@ -126,8 +122,8 @@ public class NestReportExportTests Assert.True(form.PlateView.Actions.CurrentAction!.IsBusy()); try { - Menu(host, "mnuFileExportNestReport").PerformClick(); - Assert.Empty(host.DialogRequests); + Menu(host, "mnuFileViewNestReport").PerformClick(); + Assert.Empty(host.Viewed); Assert.Contains("Finish or cancel", Assert.Single(host.Failures)); } finally @@ -138,7 +134,7 @@ public class NestReportExportTests }); [Fact] - public void InvalidNestNameRejectsBeforeAnyDialog() => RunSta(() => + public void InvalidNestNameRejectsBeforeRendering() => RunSta(() => { using var host = new TestMainForm(); host.Show(); @@ -146,87 +142,58 @@ public class NestReportExportTests form.PlateView.SetOverlapAutoCheck(null); // Background overlap workers are irrelevant here. form.Show(); - Menu(host, "mnuFileExportNestReport").PerformClick(); + Menu(host, "mnuFileViewNestReport").PerformClick(); - Assert.Empty(host.DialogRequests); + Assert.Empty(host.Viewed); Assert.Contains("bad/name", Assert.Single(host.Failures)); }); [Fact] - public void CancelWritesNothingAndNotifiesNothing() => RunSta(() => + public void ViewLeavesJobStateUntouched() => RunSta(() => { - using var host = new TestMainForm { NextDialogPath = null }; + using var host = new TestMainForm(); host.Show(); - using var form = new EditNestForm(CreateJob("cancel job")) { MdiParent = host }; - form.PlateView.SetOverlapAutoCheck(null); // Background overlap workers are irrelevant here. - form.Show(); - var path = Path.Combine(Path.GetTempPath(), $"opennest-report-cancel-{Guid.NewGuid():N}.pdf"); - - host.NextDialogPath = path; - host.NextDialogResult = DialogResult.Cancel; - Menu(host, "mnuFileExportNestReport").PerformClick(); - - Assert.False(File.Exists(path)); - Assert.Empty(host.Failures); - Assert.Empty(host.Successes); - }); - - [Fact] - public void SuccessfulExportWritesPdfAndLeavesJobStateUntouched() => RunSta(() => - { - using var host = new TestMainForm { NextDialogResult = DialogResult.OK }; - host.Show(); - var nest = CreateJob("export job"); + var nest = CreateJob("state job"); using var form = new EditNestForm(nest) { MdiParent = host }; form.PlateView.SetOverlapAutoCheck(null); // Background overlap workers are irrelevant here. form.Show(); form.PlateManager.LoadAt(1); // EnsureSentinel already appended a trailing empty plate behind the two loaded here. - var plateCountBeforeExport = nest.Plates.Count; - var path = Path.Combine(Path.GetTempPath(), $"opennest-report-ok-{Guid.NewGuid():N}.pdf"); + var plateCountBeforeView = nest.Plates.Count; + + Menu(host, "mnuFileViewNestReport").PerformClick(); + + var path = Assert.Single(host.Viewed); try { - host.NextDialogPath = path; - Menu(host, "mnuFileExportNestReport").PerformClick(); - - Assert.Equal(path, Assert.Single(host.Successes)); - var bytes = File.ReadAllBytes(path); - Assert.Equal("%PDF-", System.Text.Encoding.ASCII.GetString(bytes, 0, 5)); - Assert.True(bytes.Length > 1000); // Selected plate and job contents are untouched by reporting. Assert.Equal(1, form.PlateManager.CurrentIndex); - Assert.Equal(plateCountBeforeExport, nest.Plates.Count); + Assert.Equal(plateCountBeforeView, nest.Plates.Count); Assert.Single(nest.Plates[0].Parts); Assert.Equal(2, nest.Drawings.Count); } finally { - File.Delete(path); + NestReportViewerForm.DeleteReportFolder(path); } }); [Fact] public void RenderFailureLeavesExistingDestinationUnchanged() => RunSta(() => { - using var host = new TestMainForm { NextDialogResult = DialogResult.OK }; - host.Show(); var nest = CreateJob("failure job"); // Outside the report's supported text contract: fails font validation before layout. nest.Notes = "em\u2014dash"; - using var form = new EditNestForm(nest) { MdiParent = host }; - form.PlateView.SetOverlapAutoCheck(null); // Background overlap workers are irrelevant here. - form.Show(); + using var form = new EditNestForm(nest); + form.PlateView.SetOverlapAutoCheck(null); + var targets = form.CaptureReportTargets(() => false); var path = Path.Combine(Path.GetTempPath(), $"opennest-report-fail-{Guid.NewGuid():N}.pdf"); var original = new byte[] { 1, 2, 3, 4 }; File.WriteAllBytes(path, original); try { - host.NextDialogPath = path; - Menu(host, "mnuFileExportNestReport").PerformClick(); + Assert.ThrowsAny(() => form.WriteNestReport(targets, path, () => false)); - Assert.Empty(host.Successes); - var failure = Assert.Single(host.Failures); - Assert.Contains("failure job", failure); Assert.Equal(original, File.ReadAllBytes(path)); Assert.Empty(Directory.GetFiles(Path.GetDirectoryName(path)!, ".*report-fail*.tmp")); } @@ -248,7 +215,6 @@ public class NestReportExportTests Menu(host, "mnuFileViewNestReport").PerformClick(); - Assert.Empty(host.DialogRequests); Assert.Empty(host.Failures); var path = Assert.Single(host.Viewed); try @@ -421,28 +387,16 @@ public class NestReportExportTests // Same reflection precedent PlateViewFillLifetimeTests uses for fill-lifetime state. private sealed class TestMainForm : MainForm { - public List DialogRequests { get; } = []; - public string? NextDialogPath { get; set; } - public DialogResult NextDialogResult { get; set; } = DialogResult.OK; public List Failures { get; } = []; - public List Successes { get; } = []; public List Viewed { get; } = []; // No startup migrations/automatic new document, and no settings save at close. protected override void OnLoad(EventArgs e) { } protected override void OnClosing(System.ComponentModel.CancelEventArgs e) { } - internal override string? ShowReportSaveDialog(string suggestedFileName) - { - DialogRequests.Add(suggestedFileName); - return NextDialogResult == DialogResult.OK ? NextDialogPath : null; - } - internal override void ReportFailure(EditNestForm.NestReportTargets? targets, Exception error) => Failures.Add($"{targets?.Nest.Name}|{error.Message}"); - internal override void ReportSuccess(string destination) => Successes.Add(destination); - internal override void ShowReportViewer(string pdfPath, EditNestForm.NestReportTargets targets) => Viewed.Add(pdfPath); diff --git a/OpenNest/Forms/EditNestForm.cs b/OpenNest/Forms/EditNestForm.cs index 754bbae4..853f1385 100644 --- a/OpenNest/Forms/EditNestForm.cs +++ b/OpenNest/Forms/EditNestForm.cs @@ -815,16 +815,16 @@ namespace OpenNest.Forms if (isJobBusy() || IsReportTargetBusy(nest)) throw new InvalidOperationException( - $"Finish or cancel the current nesting or plate action before exporting the report for '{nest.Name}'." + $"Finish or cancel the current nesting or plate action before creating the report for '{nest.Name}'." ); return new NestReportTargets(nest, $"{nest.Name}.report.pdf"); } /// - /// Revalidate the fixed target after the dialog closed, then synchronously capture the - /// snapshot on the UI thread and write it. The dialog pumps messages, so an async fill - /// could have committed or started meanwhile; once capture begins nothing yields. + /// Revalidate the fixed target, then synchronously capture the snapshot on the UI thread + /// and write it. A caller that pumps messages between capture and write (a dialog) lets an + /// async fill commit or start meanwhile; once capture begins nothing yields. /// internal void WriteNestReport( NestReportTargets targets, diff --git a/OpenNest/Forms/MainForm.Designer.cs b/OpenNest/Forms/MainForm.Designer.cs index 80cf6547..5d8970bc 100644 --- a/OpenNest/Forms/MainForm.Designer.cs +++ b/OpenNest/Forms/MainForm.Designer.cs @@ -51,7 +51,7 @@ namespace OpenNest.Forms mnuFileExport = new System.Windows.Forms.ToolStripMenuItem(); mnuFileExportAll = new System.Windows.Forms.ToolStripMenuItem(); mnuFileViewNestReport = new System.Windows.Forms.ToolStripMenuItem(); - mnuFileExportNestReport = new System.Windows.Forms.ToolStripMenuItem(); + mnuFileReportsSeparator = new System.Windows.Forms.ToolStripSeparator(); mnuFileViewPartPrintouts = new System.Windows.Forms.ToolStripMenuItem(); toolStripMenuItem3 = new System.Windows.Forms.ToolStripSeparator(); mnuFileExit = new System.Windows.Forms.ToolStripMenuItem(); @@ -222,7 +222,7 @@ namespace OpenNest.Forms // // mnuFile // - mnuFile.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { mnuFileNew, mnuFileOpen, mnuFileImportBom, mnuFileDrawingLibrary, mnuFileDrawingSets, mnuFileReleases, toolStripMenuItem1, mnuFileSave, mnuFileSaveAs, toolStripMenuItem2, mnuFileExport, mnuFileExportAll, mnuFileViewNestReport, mnuFileExportNestReport, mnuFileViewPartPrintouts, toolStripMenuItem3, mnuFileExit }); + mnuFile.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { mnuFileNew, mnuFileOpen, mnuFileImportBom, mnuFileDrawingLibrary, mnuFileDrawingSets, mnuFileReleases, toolStripMenuItem1, mnuFileSave, mnuFileSaveAs, toolStripMenuItem2, mnuFileExport, mnuFileExportAll, mnuFileReportsSeparator, mnuFileViewNestReport, mnuFileViewPartPrintouts, toolStripMenuItem3, mnuFileExit }); mnuFile.Name = "mnuFile"; mnuFile.DropDownOpening += FileMenu_Opening; mnuFile.Size = new System.Drawing.Size(37, 20); @@ -307,38 +307,35 @@ namespace OpenNest.Forms // mnuFileExport.Name = "mnuFileExport"; mnuFileExport.Size = new System.Drawing.Size(146, 22); - mnuFileExport.Text = "Export"; + mnuFileExport.Text = "Export Plate..."; mnuFileExport.Click += Export_Click; // // mnuFileExportAll // mnuFileExportAll.Name = "mnuFileExportAll"; mnuFileExportAll.Size = new System.Drawing.Size(146, 22); - mnuFileExportAll.Text = "Export All"; + mnuFileExportAll.Text = "Export All Plates..."; mnuFileExportAll.Click += ExportAll_Click; // + // mnuFileReportsSeparator + // + mnuFileReportsSeparator.Name = "mnuFileReportsSeparator"; + mnuFileReportsSeparator.Size = new System.Drawing.Size(143, 6); + // // mnuFileViewNestReport // mnuFileViewNestReport.Enabled = false; mnuFileViewNestReport.Name = "mnuFileViewNestReport"; mnuFileViewNestReport.Size = new System.Drawing.Size(146, 22); - mnuFileViewNestReport.Text = "View Nest Report..."; + mnuFileViewNestReport.Text = "Nest Report..."; mnuFileViewNestReport.Click += ViewNestReport_Click; // - // mnuFileExportNestReport - // - mnuFileExportNestReport.Enabled = false; - mnuFileExportNestReport.Name = "mnuFileExportNestReport"; - mnuFileExportNestReport.Size = new System.Drawing.Size(146, 22); - mnuFileExportNestReport.Text = "Export Nest Report..."; - mnuFileExportNestReport.Click += ExportNestReport_Click; - // // mnuFileViewPartPrintouts // mnuFileViewPartPrintouts.Enabled = false; mnuFileViewPartPrintouts.Name = "mnuFileViewPartPrintouts"; mnuFileViewPartPrintouts.Size = new System.Drawing.Size(146, 22); - mnuFileViewPartPrintouts.Text = "View Part Printouts..."; + mnuFileViewPartPrintouts.Text = "Part Printouts..."; mnuFileViewPartPrintouts.Click += ViewPartPrintouts_Click; // // toolStripMenuItem3 @@ -1538,7 +1535,7 @@ namespace OpenNest.Forms private System.Windows.Forms.ToolStripStatusLabel plateUtilStatusLabel; private System.Windows.Forms.ToolStripMenuItem mnuFileExportAll; private System.Windows.Forms.ToolStripMenuItem mnuFileViewNestReport; - private System.Windows.Forms.ToolStripMenuItem mnuFileExportNestReport; + private System.Windows.Forms.ToolStripSeparator mnuFileReportsSeparator; private System.Windows.Forms.ToolStripMenuItem mnuFileViewPartPrintouts; private System.Windows.Forms.ToolStripMenuItem openNestToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem pEPToolStripMenuItem; diff --git a/OpenNest/Forms/MainForm.File.cs b/OpenNest/Forms/MainForm.File.cs index 6367dee4..297f58b0 100644 --- a/OpenNest/Forms/MainForm.File.cs +++ b/OpenNest/Forms/MainForm.File.cs @@ -304,7 +304,7 @@ namespace OpenNest.Forms { // Export never changes the document's save path, remote id, or nest name. new NestWriter(form.Nest) { BestFitStore = BestFitCacheStore.Shared }.Write(dlg.FileName); - statusLabel1.Text = $"Exported .nest to {dlg.FileName}"; + statusLabel1.Text = $"Saved a copy to {dlg.FileName}"; } catch (Exception ex) { @@ -316,23 +316,10 @@ namespace OpenNest.Forms private void ExportAll_Click(object sender, EventArgs e) => WithActive(f => f.ExportAll()); - private void ExportNestReport_Click(object sender, EventArgs e) - { - var form = activeForm; - if (form == null || !TryCaptureReportTargets(form, out var targets)) - return; - - var destination = ShowReportSaveDialog(targets.SuggestedFileName); - if (destination == null) - return; // Cancel: nothing written, job untouched. - - if (!TryWriteNestReport(form, targets, destination)) - return; - statusLabel1.Text = $"Saved nest report to {destination}"; - ReportSuccess(destination); - } - - /// Renders the report to a private temporary file and opens it in a viewer to save or print. + /// + /// Renders the report to a private temporary file and opens it in a viewer to save or + /// print. The viewer's Save As is the only way to keep the PDF, with or without WebView2. + /// private void ViewNestReport_Click(object sender, EventArgs e) { var form = activeForm; @@ -467,32 +454,13 @@ namespace OpenNest.Forms viewer.Show(this); } - /// Returns the chosen path, or null on cancel. Overwrite consent is the dialog prompt. - internal virtual string ShowReportSaveDialog(string suggestedFileName) - { - using var dlg = new SaveFileDialog - { - Filter = "PDF report (*.pdf)|*.pdf", - FileName = suggestedFileName, - AddExtension = true, - DefaultExt = ".pdf", - OverwritePrompt = true, - }; - return dlg.ShowDialog(this) == DialogResult.OK ? dlg.FileName : null; - } - /// Failure notice naming the fixed target; internal so tests observe it without dialogs. internal virtual void ReportFailure( EditNestForm.NestReportTargets targets, Exception error) => MessageBox.Show(this, - $"Could not export the nest report{(targets == null ? "" : $" for '{targets.Nest.Name}'")}: {error.Message}", - "Export Nest Report", MessageBoxButtons.OK, MessageBoxIcon.Warning); - - /// Success notice; internal so tests observe completion without dialogs. - internal virtual void ReportSuccess(string destination) => - MessageBox.Show(this, $"Nest report saved to:\n\n{destination}", "Export Nest Report", - MessageBoxButtons.OK, MessageBoxIcon.Information); + $"Could not create the nest report{(targets == null ? "" : $" for '{targets.Nest.Name}'")}: {error.Message}", + "Nest Report", MessageBoxButtons.OK, MessageBoxIcon.Warning); private void Exit_Click(object sender, EventArgs e) { diff --git a/OpenNest/Forms/MainForm.Status.cs b/OpenNest/Forms/MainForm.Status.cs index 6813c941..aea10692 100644 --- a/OpenNest/Forms/MainForm.Status.cs +++ b/OpenNest/Forms/MainForm.Status.cs @@ -22,8 +22,7 @@ namespace OpenNest.Forms exportNestMenu.Enabled = hasValue; mnuFileExport.Enabled = hasValue; mnuFileExportAll.Enabled = hasValue; - mnuFileExportNestReport.Enabled = hasValue && !storage.IsSaving; - mnuFileViewNestReport.Enabled = mnuFileExportNestReport.Enabled; + mnuFileViewNestReport.Enabled = hasValue && !storage.IsSaving; mnuFileViewPartPrintouts.Enabled = hasValue; mnuEdit.Visible = hasValue; mnuView.Visible = hasValue; diff --git a/OpenNest/Forms/MainForm.cs b/OpenNest/Forms/MainForm.cs index da08dbce..daa79316 100644 --- a/OpenNest/Forms/MainForm.cs +++ b/OpenNest/Forms/MainForm.cs @@ -25,7 +25,7 @@ namespace OpenNest.Forms private CancellationTokenSource nestingCts; private readonly NestStorageController storage; private readonly ToolStripMenuItem storageModeMenu = new("Storage Mode..."); - private readonly ToolStripMenuItem exportNestMenu = new("Export .nest..."); + private readonly ToolStripMenuItem exportNestMenu = new("Save Copy as .nest File..."); private readonly ToolStripButton btnCutPlanner; private readonly ToolStripButton btnSimulateCutting; private readonly ToolStripButton btnSequenceByPath; @@ -131,8 +131,11 @@ namespace OpenNest.Forms toolStrip1.Items.Add(postButton); storageModeMenu.Click += StorageMode_Click; exportNestMenu.Click += ExportNest_Click; - mnuFile.DropDownItems.Insert(mnuFile.DropDownItems.IndexOf(mnuFileExport), exportNestMenu); - mnuFile.DropDownItems.Insert(mnuFile.DropDownItems.IndexOf(mnuFileExit), storageModeMenu); + // A local .nest copy is a save, not a plate export, so it sits with Save and Save As. + mnuFile.DropDownItems.Insert(mnuFile.DropDownItems.IndexOf(mnuFileSaveAs) + 1, exportNestMenu); + var exitIndex = mnuFile.DropDownItems.IndexOf(mnuFileExit); + mnuFile.DropDownItems.Insert(exitIndex, new ToolStripSeparator()); + mnuFile.DropDownItems.Insert(exitIndex, storageModeMenu); InitializeAlignMenu(); InitializeHelpMenu(); UpdateOverlapMenu(); diff --git a/docs/nest-reports.md b/docs/nest-reports.md index d781b5cb..20b83e55 100644 --- a/docs/nest-reports.md +++ b/docs/nest-reports.md @@ -1,23 +1,23 @@ # Nest report PDF export -Desktop: **File -> Export Nest Report...** writes `.report.pdf` for the -active nest's whole job through an overwrite-confirming save dialog. The command -is disabled without an open document and during a background database save, and -it refuses to run while whole-job nesting, an open progress window, interactive -fill or a busy plate action holds the nest or any second window sharing it. After -the dialog closes, the target and those conditions are revalidated; the snapshot -is then captured synchronously on the UI thread and only that detached snapshot -reaches the renderer. Export never changes the selected plate, dirty state, -timestamps or quantities, and it never touches post selection, verification or -CNC output. There are no persisted report settings or templates. +Desktop: **File -> Nest Report...** renders `.report.pdf` for the +active nest's whole job. The command is disabled without an open document and +during a background database save, and it refuses to run while whole-job +nesting, an open progress window, interactive fill or a busy plate action holds +the nest or any second window sharing it. The target and those conditions are +revalidated before the write; the snapshot is then captured synchronously on the +UI thread and only that detached snapshot reaches the renderer. Reporting never +changes the selected plate, dirty state, timestamps or quantities, and it never +touches post selection, verification or CNC output. There are no persisted report +settings or templates. -**File -> View Nest Report...** runs the same guards and capture without a dialog, -renders to a private folder under `%TEMP%\OpenNest\Reports\` and opens a modeless -viewer window (WebView2's PDF viewer, sharing Help's WebView2 data folder) with -**Save As...**, which copies the rendered file to a chosen path, and **Print...**, -the WebView2 print dialog. Closing the window deletes its temporary folder; a -render failure opens no viewer and leaves no folder. Without the WebView2 Runtime -the window still offers Save As. +The report renders to a private folder under `%TEMP%\OpenNest\Reports\` and opens +a modeless viewer window (WebView2's PDF viewer, sharing Help's WebView2 data +folder) with **Save As...**, which copies the rendered file to a chosen path, and +**Print...**, the WebView2 print dialog. Save As is the only way to keep the PDF; +there is no separate direct-save command. Closing the window deletes its temporary +folder; a render failure opens no viewer and leaves no folder. Without the WebView2 +Runtime the window still offers Save As. The same two calls back the command and any future integration: @@ -31,7 +31,7 @@ the selected plate, or certify that a layout passed geometry/pre-post checks. ## Part printouts -**File -> View Part Printouts...** prints every non-cutoff drawing of the active +**File -> Part Printouts...** prints every non-cutoff drawing of the active nest; **View Part Printout...** on the drawing list's right-click menu prints the selected one. Both render to `%TEMP%\OpenNest\Reports\` and open the report viewer, suggesting `.printout.pdf` or `.printout.pdf`. Each part @@ -191,7 +191,7 @@ concurrent calls cannot lay out text differently from a sequential export. ## Estimated cut time -When the caller passes `CutParameters` (the desktop export always does, from the +When the caller passes `CutParameters` (the desktop always does, from the settings last used in Calculate Cut Time or the simulator), each plate gets an estimated time per copy from `CutSimulation`, plus clamp-removal minutes, the same as Calculate Cut Time (see [cut-time estimates](cut-time-estimates.md)). The summary @@ -253,10 +253,9 @@ Pixel inspection supplements geometry assertions; it is not a CNC-validation result. Windows compilation is not runtime acceptance. `OpenNest.WinForms.Tests/Forms/NestReportExportTests.cs` -covers enablement, busy/cancel/failure/success adapter behavior, but it only -compiles on Linux. Windows runtime acceptance still owes: exporting a real nest +covers enablement, busy/failure/viewer adapter behavior, but it only +compiles on Linux. Windows runtime acceptance still owes: reporting a real nest (and the packaged application, so bundled fonts/notices are verified), checking page sizes/labels/copies and embedded fonts with `pdffonts`, a tabbed part with a -hole, opening and printing the PDF, and exercising fill/nesting rejection, cancel, -overwrite and invalid-path behavior with job state and any existing destination -unchanged. +hole, saving and printing the PDF from the viewer, and exercising fill/nesting +rejection with job state unchanged. diff --git a/docs/nest-storage.md b/docs/nest-storage.md index d9d189f8..29a04abd 100644 --- a/docs/nest-storage.md +++ b/docs/nest-storage.md @@ -44,9 +44,9 @@ The desktop app can save nests two ways: the record from ordinary browsing but retains every archive; **Deleted nests...** opens an explicit recovery view. Neither action purges data. An old desktop without a revision precondition cannot update or delete on an upgraded server. - A separate **File > Export .nest...** command is always available (in both - modes) for producing a local file to share or back up. Export does not change - the document's file save path or database record association. + A separate **File > Save Copy as .nest File...** command is always available + (in both modes) for producing a local file to share or back up. The copy does + not change the document's file save path or database record association. Dragging one or more `.nest` files onto the main window (or onto an open plate) opens them from disk directly, in either mode — this is the only way