mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-11-13 23:55:08 -05:00
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 <noreply@anthropic.com>
This commit is contained in:
1 parent
755f021092
commit
ecd0bba4eb
10 files changed
+90
-170
No files matched your search
@@ -10,7 +10,7 @@
|
||||
<Separator />
|
||||
<MenuItem x:Name="SaveMenuItem" Header="_Save" InputGesture="Ctrl+S" Click="OnSave" />
|
||||
<MenuItem x:Name="SaveAsMenuItem" Header="Save _As..." Click="OnSaveAs" />
|
||||
<MenuItem x:Name="ExportNestMenuItem" Header="_Export .nest..." Click="OnExportNest" />
|
||||
<MenuItem x:Name="ExportNestMenuItem" Header="Save _Copy as .nest File..." Click="OnExportNest" />
|
||||
<Separator />
|
||||
<MenuItem x:Name="CloseMenuItem" Header="_Close" Click="OnCloseNest" />
|
||||
<MenuItem x:Name="CloseAllMenuItem" Header="Close A_ll" Click="OnCloseAll" />
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<Exception>(() => 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<string> DialogRequests { get; } = [];
|
||||
public string? NextDialogPath { get; set; }
|
||||
public DialogResult NextDialogResult { get; set; } = DialogResult.OK;
|
||||
public List<string> Failures { get; } = [];
|
||||
public List<string> Successes { get; } = [];
|
||||
public List<string> 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);
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
internal void WriteNestReport(
|
||||
NestReportTargets targets,
|
||||
|
||||
Generated
+12
-15
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/// <summary>Renders the report to a private temporary file and opens it in a viewer to save or print.</summary>
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
private void ViewNestReport_Click(object sender, EventArgs e)
|
||||
{
|
||||
var form = activeForm;
|
||||
@@ -467,32 +454,13 @@ namespace OpenNest.Forms
|
||||
viewer.Show(this);
|
||||
}
|
||||
|
||||
/// <summary>Returns the chosen path, or null on cancel. Overwrite consent is the dialog prompt.</summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>Failure notice naming the fixed target; internal so tests observe it without dialogs.</summary>
|
||||
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);
|
||||
|
||||
/// <summary>Success notice; internal so tests observe completion without dialogs.</summary>
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
+23
-24
@@ -1,23 +1,23 @@
|
||||
# Nest report PDF export
|
||||
|
||||
Desktop: **File -> Export Nest Report...** writes `<nest-name>.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 `<nest-name>.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 `<nest-name>.printout.pdf` or `<drawing-name>.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.
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user