mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-11-14 10:15:07 -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 />
|
<Separator />
|
||||||
<MenuItem x:Name="SaveMenuItem" Header="_Save" InputGesture="Ctrl+S" Click="OnSave" />
|
<MenuItem x:Name="SaveMenuItem" Header="_Save" InputGesture="Ctrl+S" Click="OnSave" />
|
||||||
<MenuItem x:Name="SaveAsMenuItem" Header="Save _As..." Click="OnSaveAs" />
|
<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 />
|
<Separator />
|
||||||
<MenuItem x:Name="CloseMenuItem" Header="_Close" Click="OnCloseNest" />
|
<MenuItem x:Name="CloseMenuItem" Header="_Close" Click="OnCloseNest" />
|
||||||
<MenuItem x:Name="CloseAllMenuItem" Header="Close A_ll" Click="OnCloseAll" />
|
<MenuItem x:Name="CloseAllMenuItem" Header="Close A_ll" Click="OnCloseAll" />
|
||||||
|
|||||||
@@ -426,25 +426,25 @@ namespace OpenNest.Avalonia
|
|||||||
writingFile = true;
|
writingFile = true;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var path = await PickNestFileAsync(tab, "Export .nest");
|
var path = await PickNestFileAsync(tab, "Save Copy as .nest File");
|
||||||
if (path == null || IsNesting(tab, "exporting"))
|
if (path == null || IsNesting(tab, "exporting"))
|
||||||
return;
|
return;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Export never changes the nest's file, name or unsaved mark.
|
// Export never changes the nest's file, name or unsaved mark.
|
||||||
tabs.ExportCopy(tab, path);
|
tabs.ExportCopy(tab, path);
|
||||||
ShowNotice($"Exported .nest to {path}");
|
ShowNotice($"Saved a copy to {path}");
|
||||||
}
|
}
|
||||||
catch (SaveTargetRefusedException ex)
|
catch (SaveTargetRefusedException ex)
|
||||||
{
|
{
|
||||||
await ConfirmDialog.ShowAsync(
|
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)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await ConfirmDialog.ShowAsync(this, "Export .nest",
|
await ConfirmDialog.ShowAsync(this, "Save Copy",
|
||||||
$"The nest was not exported.\n\n{path}\n\n{ex.Message}\n\nChoose "
|
$"The copy was not saved.\n\n{path}\n\n{ex.Message}\n\nChoose "
|
||||||
+ "File > Export .nest again to pick another folder or name.");
|
+ "File > Save Copy as .nest File again to pick another folder or name.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
|
|||||||
@@ -47,25 +47,21 @@ public class NestReportExportTests
|
|||||||
{
|
{
|
||||||
using var host = new TestMainForm();
|
using var host = new TestMainForm();
|
||||||
host.Show();
|
host.Show();
|
||||||
var item = Menu(host, "mnuFileExportNestReport");
|
|
||||||
var view = Menu(host, "mnuFileViewNestReport");
|
var view = Menu(host, "mnuFileViewNestReport");
|
||||||
Assert.False(item.Enabled);
|
|
||||||
Assert.False(view.Enabled);
|
Assert.False(view.Enabled);
|
||||||
|
|
||||||
using var form = new EditNestForm(new Nest("enabled job")) { MdiParent = host };
|
using var form = new EditNestForm(new Nest("enabled job")) { MdiParent = host };
|
||||||
form.PlateView.SetOverlapAutoCheck(null); // Background overlap workers are irrelevant here.
|
form.PlateView.SetOverlapAutoCheck(null); // Background overlap workers are irrelevant here.
|
||||||
form.Show();
|
form.Show();
|
||||||
Assert.True(item.Enabled);
|
|
||||||
Assert.True(view.Enabled);
|
Assert.True(view.Enabled);
|
||||||
|
|
||||||
form.Close();
|
form.Close();
|
||||||
host.Activate();
|
host.Activate();
|
||||||
Assert.False(item.Enabled);
|
|
||||||
Assert.False(view.Enabled);
|
Assert.False(view.Enabled);
|
||||||
});
|
});
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void JobBusyRejectsBeforeAnyDialog() => RunSta(() =>
|
public void JobBusyRejectsBeforeRendering() => RunSta(() =>
|
||||||
{
|
{
|
||||||
using var host = new TestMainForm();
|
using var host = new TestMainForm();
|
||||||
host.Show();
|
host.Show();
|
||||||
@@ -75,14 +71,14 @@ public class NestReportExportTests
|
|||||||
SetField(host, "nestingInProgress", true);
|
SetField(host, "nestingInProgress", true);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Menu(host, "mnuFileExportNestReport").PerformClick();
|
Menu(host, "mnuFileViewNestReport").PerformClick();
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
SetField(host, "nestingInProgress", false);
|
SetField(host, "nestingInProgress", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.Empty(host.DialogRequests);
|
Assert.Empty(host.Viewed);
|
||||||
var failure = Assert.Single(host.Failures);
|
var failure = Assert.Single(host.Failures);
|
||||||
Assert.Contains("busy job", failure);
|
Assert.Contains("busy job", failure);
|
||||||
Assert.Contains("Finish or cancel", failure);
|
Assert.Contains("Finish or cancel", failure);
|
||||||
@@ -99,8 +95,8 @@ public class NestReportExportTests
|
|||||||
SetBackingField(form.PlateView, "IsFillInProgress", true);
|
SetBackingField(form.PlateView, "IsFillInProgress", true);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Menu(host, "mnuFileExportNestReport").PerformClick();
|
Menu(host, "mnuFileViewNestReport").PerformClick();
|
||||||
Assert.Empty(host.DialogRequests);
|
Assert.Empty(host.Viewed);
|
||||||
Assert.Contains("Finish or cancel", Assert.Single(host.Failures));
|
Assert.Contains("Finish or cancel", Assert.Single(host.Failures));
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
@@ -108,9 +104,9 @@ public class NestReportExportTests
|
|||||||
SetBackingField(form.PlateView, "IsFillInProgress", false);
|
SetBackingField(form.PlateView, "IsFillInProgress", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleared guard: the command now reaches the (test-intercepted) dialog.
|
// Cleared guard: the command now reaches the (test-intercepted) viewer.
|
||||||
Menu(host, "mnuFileExportNestReport").PerformClick();
|
Menu(host, "mnuFileViewNestReport").PerformClick();
|
||||||
Assert.Single(host.DialogRequests);
|
NestReportViewerForm.DeleteReportFolder(Assert.Single(host.Viewed));
|
||||||
});
|
});
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -126,8 +122,8 @@ public class NestReportExportTests
|
|||||||
Assert.True(form.PlateView.Actions.CurrentAction!.IsBusy());
|
Assert.True(form.PlateView.Actions.CurrentAction!.IsBusy());
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Menu(host, "mnuFileExportNestReport").PerformClick();
|
Menu(host, "mnuFileViewNestReport").PerformClick();
|
||||||
Assert.Empty(host.DialogRequests);
|
Assert.Empty(host.Viewed);
|
||||||
Assert.Contains("Finish or cancel", Assert.Single(host.Failures));
|
Assert.Contains("Finish or cancel", Assert.Single(host.Failures));
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
@@ -138,7 +134,7 @@ public class NestReportExportTests
|
|||||||
});
|
});
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void InvalidNestNameRejectsBeforeAnyDialog() => RunSta(() =>
|
public void InvalidNestNameRejectsBeforeRendering() => RunSta(() =>
|
||||||
{
|
{
|
||||||
using var host = new TestMainForm();
|
using var host = new TestMainForm();
|
||||||
host.Show();
|
host.Show();
|
||||||
@@ -146,87 +142,58 @@ public class NestReportExportTests
|
|||||||
form.PlateView.SetOverlapAutoCheck(null); // Background overlap workers are irrelevant here.
|
form.PlateView.SetOverlapAutoCheck(null); // Background overlap workers are irrelevant here.
|
||||||
form.Show();
|
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));
|
Assert.Contains("bad/name", Assert.Single(host.Failures));
|
||||||
});
|
});
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CancelWritesNothingAndNotifiesNothing() => RunSta(() =>
|
public void ViewLeavesJobStateUntouched() => RunSta(() =>
|
||||||
{
|
{
|
||||||
using var host = new TestMainForm { NextDialogPath = null };
|
using var host = new TestMainForm();
|
||||||
host.Show();
|
host.Show();
|
||||||
using var form = new EditNestForm(CreateJob("cancel job")) { MdiParent = host };
|
var nest = CreateJob("state job");
|
||||||
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");
|
|
||||||
using var form = new EditNestForm(nest) { MdiParent = host };
|
using var form = new EditNestForm(nest) { MdiParent = host };
|
||||||
form.PlateView.SetOverlapAutoCheck(null); // Background overlap workers are irrelevant here.
|
form.PlateView.SetOverlapAutoCheck(null); // Background overlap workers are irrelevant here.
|
||||||
form.Show();
|
form.Show();
|
||||||
form.PlateManager.LoadAt(1);
|
form.PlateManager.LoadAt(1);
|
||||||
// EnsureSentinel already appended a trailing empty plate behind the two loaded here.
|
// EnsureSentinel already appended a trailing empty plate behind the two loaded here.
|
||||||
var plateCountBeforeExport = nest.Plates.Count;
|
var plateCountBeforeView = nest.Plates.Count;
|
||||||
var path = Path.Combine(Path.GetTempPath(), $"opennest-report-ok-{Guid.NewGuid():N}.pdf");
|
|
||||||
|
Menu(host, "mnuFileViewNestReport").PerformClick();
|
||||||
|
|
||||||
|
var path = Assert.Single(host.Viewed);
|
||||||
try
|
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.
|
// Selected plate and job contents are untouched by reporting.
|
||||||
Assert.Equal(1, form.PlateManager.CurrentIndex);
|
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.Single(nest.Plates[0].Parts);
|
||||||
Assert.Equal(2, nest.Drawings.Count);
|
Assert.Equal(2, nest.Drawings.Count);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
File.Delete(path);
|
NestReportViewerForm.DeleteReportFolder(path);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void RenderFailureLeavesExistingDestinationUnchanged() => RunSta(() =>
|
public void RenderFailureLeavesExistingDestinationUnchanged() => RunSta(() =>
|
||||||
{
|
{
|
||||||
using var host = new TestMainForm { NextDialogResult = DialogResult.OK };
|
|
||||||
host.Show();
|
|
||||||
var nest = CreateJob("failure job");
|
var nest = CreateJob("failure job");
|
||||||
// Outside the report's supported text contract: fails font validation before layout.
|
// Outside the report's supported text contract: fails font validation before layout.
|
||||||
nest.Notes = "em\u2014dash";
|
nest.Notes = "em\u2014dash";
|
||||||
using var form = new EditNestForm(nest) { MdiParent = host };
|
using var form = new EditNestForm(nest);
|
||||||
form.PlateView.SetOverlapAutoCheck(null); // Background overlap workers are irrelevant here.
|
form.PlateView.SetOverlapAutoCheck(null);
|
||||||
form.Show();
|
var targets = form.CaptureReportTargets(() => false);
|
||||||
var path = Path.Combine(Path.GetTempPath(), $"opennest-report-fail-{Guid.NewGuid():N}.pdf");
|
var path = Path.Combine(Path.GetTempPath(), $"opennest-report-fail-{Guid.NewGuid():N}.pdf");
|
||||||
var original = new byte[] { 1, 2, 3, 4 };
|
var original = new byte[] { 1, 2, 3, 4 };
|
||||||
File.WriteAllBytes(path, original);
|
File.WriteAllBytes(path, original);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
host.NextDialogPath = path;
|
Assert.ThrowsAny<Exception>(() => form.WriteNestReport(targets, path, () => false));
|
||||||
Menu(host, "mnuFileExportNestReport").PerformClick();
|
|
||||||
|
|
||||||
Assert.Empty(host.Successes);
|
|
||||||
var failure = Assert.Single(host.Failures);
|
|
||||||
Assert.Contains("failure job", failure);
|
|
||||||
Assert.Equal(original, File.ReadAllBytes(path));
|
Assert.Equal(original, File.ReadAllBytes(path));
|
||||||
Assert.Empty(Directory.GetFiles(Path.GetDirectoryName(path)!, ".*report-fail*.tmp"));
|
Assert.Empty(Directory.GetFiles(Path.GetDirectoryName(path)!, ".*report-fail*.tmp"));
|
||||||
}
|
}
|
||||||
@@ -248,7 +215,6 @@ public class NestReportExportTests
|
|||||||
|
|
||||||
Menu(host, "mnuFileViewNestReport").PerformClick();
|
Menu(host, "mnuFileViewNestReport").PerformClick();
|
||||||
|
|
||||||
Assert.Empty(host.DialogRequests);
|
|
||||||
Assert.Empty(host.Failures);
|
Assert.Empty(host.Failures);
|
||||||
var path = Assert.Single(host.Viewed);
|
var path = Assert.Single(host.Viewed);
|
||||||
try
|
try
|
||||||
@@ -421,28 +387,16 @@ public class NestReportExportTests
|
|||||||
// Same reflection precedent PlateViewFillLifetimeTests uses for fill-lifetime state.
|
// Same reflection precedent PlateViewFillLifetimeTests uses for fill-lifetime state.
|
||||||
private sealed class TestMainForm : MainForm
|
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> Failures { get; } = [];
|
||||||
public List<string> Successes { get; } = [];
|
|
||||||
public List<string> Viewed { get; } = [];
|
public List<string> Viewed { get; } = [];
|
||||||
|
|
||||||
// No startup migrations/automatic new document, and no settings save at close.
|
// No startup migrations/automatic new document, and no settings save at close.
|
||||||
protected override void OnLoad(EventArgs e) { }
|
protected override void OnLoad(EventArgs e) { }
|
||||||
protected override void OnClosing(System.ComponentModel.CancelEventArgs 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) =>
|
internal override void ReportFailure(EditNestForm.NestReportTargets? targets, Exception error) =>
|
||||||
Failures.Add($"{targets?.Nest.Name}|{error.Message}");
|
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) =>
|
internal override void ShowReportViewer(string pdfPath, EditNestForm.NestReportTargets targets) =>
|
||||||
Viewed.Add(pdfPath);
|
Viewed.Add(pdfPath);
|
||||||
|
|
||||||
|
|||||||
@@ -815,16 +815,16 @@ namespace OpenNest.Forms
|
|||||||
|
|
||||||
if (isJobBusy() || IsReportTargetBusy(nest))
|
if (isJobBusy() || IsReportTargetBusy(nest))
|
||||||
throw new InvalidOperationException(
|
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");
|
return new NestReportTargets(nest, $"{nest.Name}.report.pdf");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Revalidate the fixed target after the dialog closed, then synchronously capture the
|
/// Revalidate the fixed target, then synchronously capture the snapshot on the UI thread
|
||||||
/// snapshot on the UI thread and write it. The dialog pumps messages, so an async fill
|
/// and write it. A caller that pumps messages between capture and write (a dialog) lets an
|
||||||
/// could have committed or started meanwhile; once capture begins nothing yields.
|
/// async fill commit or start meanwhile; once capture begins nothing yields.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal void WriteNestReport(
|
internal void WriteNestReport(
|
||||||
NestReportTargets targets,
|
NestReportTargets targets,
|
||||||
|
|||||||
Generated
+12
-15
@@ -51,7 +51,7 @@ namespace OpenNest.Forms
|
|||||||
mnuFileExport = new System.Windows.Forms.ToolStripMenuItem();
|
mnuFileExport = new System.Windows.Forms.ToolStripMenuItem();
|
||||||
mnuFileExportAll = new System.Windows.Forms.ToolStripMenuItem();
|
mnuFileExportAll = new System.Windows.Forms.ToolStripMenuItem();
|
||||||
mnuFileViewNestReport = 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();
|
mnuFileViewPartPrintouts = new System.Windows.Forms.ToolStripMenuItem();
|
||||||
toolStripMenuItem3 = new System.Windows.Forms.ToolStripSeparator();
|
toolStripMenuItem3 = new System.Windows.Forms.ToolStripSeparator();
|
||||||
mnuFileExit = new System.Windows.Forms.ToolStripMenuItem();
|
mnuFileExit = new System.Windows.Forms.ToolStripMenuItem();
|
||||||
@@ -222,7 +222,7 @@ namespace OpenNest.Forms
|
|||||||
//
|
//
|
||||||
// mnuFile
|
// 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.Name = "mnuFile";
|
||||||
mnuFile.DropDownOpening += FileMenu_Opening;
|
mnuFile.DropDownOpening += FileMenu_Opening;
|
||||||
mnuFile.Size = new System.Drawing.Size(37, 20);
|
mnuFile.Size = new System.Drawing.Size(37, 20);
|
||||||
@@ -307,38 +307,35 @@ namespace OpenNest.Forms
|
|||||||
//
|
//
|
||||||
mnuFileExport.Name = "mnuFileExport";
|
mnuFileExport.Name = "mnuFileExport";
|
||||||
mnuFileExport.Size = new System.Drawing.Size(146, 22);
|
mnuFileExport.Size = new System.Drawing.Size(146, 22);
|
||||||
mnuFileExport.Text = "Export";
|
mnuFileExport.Text = "Export Plate...";
|
||||||
mnuFileExport.Click += Export_Click;
|
mnuFileExport.Click += Export_Click;
|
||||||
//
|
//
|
||||||
// mnuFileExportAll
|
// mnuFileExportAll
|
||||||
//
|
//
|
||||||
mnuFileExportAll.Name = "mnuFileExportAll";
|
mnuFileExportAll.Name = "mnuFileExportAll";
|
||||||
mnuFileExportAll.Size = new System.Drawing.Size(146, 22);
|
mnuFileExportAll.Size = new System.Drawing.Size(146, 22);
|
||||||
mnuFileExportAll.Text = "Export All";
|
mnuFileExportAll.Text = "Export All Plates...";
|
||||||
mnuFileExportAll.Click += ExportAll_Click;
|
mnuFileExportAll.Click += ExportAll_Click;
|
||||||
//
|
//
|
||||||
|
// mnuFileReportsSeparator
|
||||||
|
//
|
||||||
|
mnuFileReportsSeparator.Name = "mnuFileReportsSeparator";
|
||||||
|
mnuFileReportsSeparator.Size = new System.Drawing.Size(143, 6);
|
||||||
|
//
|
||||||
// mnuFileViewNestReport
|
// mnuFileViewNestReport
|
||||||
//
|
//
|
||||||
mnuFileViewNestReport.Enabled = false;
|
mnuFileViewNestReport.Enabled = false;
|
||||||
mnuFileViewNestReport.Name = "mnuFileViewNestReport";
|
mnuFileViewNestReport.Name = "mnuFileViewNestReport";
|
||||||
mnuFileViewNestReport.Size = new System.Drawing.Size(146, 22);
|
mnuFileViewNestReport.Size = new System.Drawing.Size(146, 22);
|
||||||
mnuFileViewNestReport.Text = "View Nest Report...";
|
mnuFileViewNestReport.Text = "Nest Report...";
|
||||||
mnuFileViewNestReport.Click += ViewNestReport_Click;
|
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
|
||||||
//
|
//
|
||||||
mnuFileViewPartPrintouts.Enabled = false;
|
mnuFileViewPartPrintouts.Enabled = false;
|
||||||
mnuFileViewPartPrintouts.Name = "mnuFileViewPartPrintouts";
|
mnuFileViewPartPrintouts.Name = "mnuFileViewPartPrintouts";
|
||||||
mnuFileViewPartPrintouts.Size = new System.Drawing.Size(146, 22);
|
mnuFileViewPartPrintouts.Size = new System.Drawing.Size(146, 22);
|
||||||
mnuFileViewPartPrintouts.Text = "View Part Printouts...";
|
mnuFileViewPartPrintouts.Text = "Part Printouts...";
|
||||||
mnuFileViewPartPrintouts.Click += ViewPartPrintouts_Click;
|
mnuFileViewPartPrintouts.Click += ViewPartPrintouts_Click;
|
||||||
//
|
//
|
||||||
// toolStripMenuItem3
|
// toolStripMenuItem3
|
||||||
@@ -1538,7 +1535,7 @@ namespace OpenNest.Forms
|
|||||||
private System.Windows.Forms.ToolStripStatusLabel plateUtilStatusLabel;
|
private System.Windows.Forms.ToolStripStatusLabel plateUtilStatusLabel;
|
||||||
private System.Windows.Forms.ToolStripMenuItem mnuFileExportAll;
|
private System.Windows.Forms.ToolStripMenuItem mnuFileExportAll;
|
||||||
private System.Windows.Forms.ToolStripMenuItem mnuFileViewNestReport;
|
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 mnuFileViewPartPrintouts;
|
||||||
private System.Windows.Forms.ToolStripMenuItem openNestToolStripMenuItem;
|
private System.Windows.Forms.ToolStripMenuItem openNestToolStripMenuItem;
|
||||||
private System.Windows.Forms.ToolStripMenuItem pEPToolStripMenuItem;
|
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.
|
// Export never changes the document's save path, remote id, or nest name.
|
||||||
new NestWriter(form.Nest) { BestFitStore = BestFitCacheStore.Shared }.Write(dlg.FileName);
|
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)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -316,23 +316,10 @@ namespace OpenNest.Forms
|
|||||||
|
|
||||||
private void ExportAll_Click(object sender, EventArgs e) => WithActive(f => f.ExportAll());
|
private void ExportAll_Click(object sender, EventArgs e) => WithActive(f => f.ExportAll());
|
||||||
|
|
||||||
private void ExportNestReport_Click(object sender, EventArgs e)
|
/// <summary>
|
||||||
{
|
/// Renders the report to a private temporary file and opens it in a viewer to save or
|
||||||
var form = activeForm;
|
/// print. The viewer's Save As is the only way to keep the PDF, with or without WebView2.
|
||||||
if (form == null || !TryCaptureReportTargets(form, out var targets))
|
/// </summary>
|
||||||
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>
|
|
||||||
private void ViewNestReport_Click(object sender, EventArgs e)
|
private void ViewNestReport_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
var form = activeForm;
|
var form = activeForm;
|
||||||
@@ -467,32 +454,13 @@ namespace OpenNest.Forms
|
|||||||
viewer.Show(this);
|
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>
|
/// <summary>Failure notice naming the fixed target; internal so tests observe it without dialogs.</summary>
|
||||||
internal virtual void ReportFailure(
|
internal virtual void ReportFailure(
|
||||||
EditNestForm.NestReportTargets targets,
|
EditNestForm.NestReportTargets targets,
|
||||||
Exception error) =>
|
Exception error) =>
|
||||||
MessageBox.Show(this,
|
MessageBox.Show(this,
|
||||||
$"Could not export the nest report{(targets == null ? "" : $" for '{targets.Nest.Name}'")}: {error.Message}",
|
$"Could not create the nest report{(targets == null ? "" : $" for '{targets.Nest.Name}'")}: {error.Message}",
|
||||||
"Export Nest Report", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
"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);
|
|
||||||
|
|
||||||
private void Exit_Click(object sender, EventArgs e)
|
private void Exit_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -22,8 +22,7 @@ namespace OpenNest.Forms
|
|||||||
exportNestMenu.Enabled = hasValue;
|
exportNestMenu.Enabled = hasValue;
|
||||||
mnuFileExport.Enabled = hasValue;
|
mnuFileExport.Enabled = hasValue;
|
||||||
mnuFileExportAll.Enabled = hasValue;
|
mnuFileExportAll.Enabled = hasValue;
|
||||||
mnuFileExportNestReport.Enabled = hasValue && !storage.IsSaving;
|
mnuFileViewNestReport.Enabled = hasValue && !storage.IsSaving;
|
||||||
mnuFileViewNestReport.Enabled = mnuFileExportNestReport.Enabled;
|
|
||||||
mnuFileViewPartPrintouts.Enabled = hasValue;
|
mnuFileViewPartPrintouts.Enabled = hasValue;
|
||||||
mnuEdit.Visible = hasValue;
|
mnuEdit.Visible = hasValue;
|
||||||
mnuView.Visible = hasValue;
|
mnuView.Visible = hasValue;
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ namespace OpenNest.Forms
|
|||||||
private CancellationTokenSource nestingCts;
|
private CancellationTokenSource nestingCts;
|
||||||
private readonly NestStorageController storage;
|
private readonly NestStorageController storage;
|
||||||
private readonly ToolStripMenuItem storageModeMenu = new("Storage Mode...");
|
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 btnCutPlanner;
|
||||||
private readonly ToolStripButton btnSimulateCutting;
|
private readonly ToolStripButton btnSimulateCutting;
|
||||||
private readonly ToolStripButton btnSequenceByPath;
|
private readonly ToolStripButton btnSequenceByPath;
|
||||||
@@ -131,8 +131,11 @@ namespace OpenNest.Forms
|
|||||||
toolStrip1.Items.Add(postButton);
|
toolStrip1.Items.Add(postButton);
|
||||||
storageModeMenu.Click += StorageMode_Click;
|
storageModeMenu.Click += StorageMode_Click;
|
||||||
exportNestMenu.Click += ExportNest_Click;
|
exportNestMenu.Click += ExportNest_Click;
|
||||||
mnuFile.DropDownItems.Insert(mnuFile.DropDownItems.IndexOf(mnuFileExport), exportNestMenu);
|
// 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(mnuFileExit), storageModeMenu);
|
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();
|
InitializeAlignMenu();
|
||||||
InitializeHelpMenu();
|
InitializeHelpMenu();
|
||||||
UpdateOverlapMenu();
|
UpdateOverlapMenu();
|
||||||
|
|||||||
+23
-24
@@ -1,23 +1,23 @@
|
|||||||
# Nest report PDF export
|
# Nest report PDF export
|
||||||
|
|
||||||
Desktop: **File -> Export Nest Report...** writes `<nest-name>.report.pdf` for the
|
Desktop: **File -> Nest Report...** renders `<nest-name>.report.pdf` for the
|
||||||
active nest's whole job through an overwrite-confirming save dialog. The command
|
active nest's whole job. The command is disabled without an open document and
|
||||||
is disabled without an open document and during a background database save, and
|
during a background database save, and it refuses to run while whole-job
|
||||||
it refuses to run while whole-job nesting, an open progress window, interactive
|
nesting, an open progress window, interactive fill or a busy plate action holds
|
||||||
fill or a busy plate action holds the nest or any second window sharing it. After
|
the nest or any second window sharing it. The target and those conditions are
|
||||||
the dialog closes, the target and those conditions are revalidated; the snapshot
|
revalidated before the write; the snapshot is then captured synchronously on the
|
||||||
is then captured synchronously on the UI thread and only that detached snapshot
|
UI thread and only that detached snapshot reaches the renderer. Reporting never
|
||||||
reaches the renderer. Export never changes the selected plate, dirty state,
|
changes the selected plate, dirty state, timestamps or quantities, and it never
|
||||||
timestamps or quantities, and it never touches post selection, verification or
|
touches post selection, verification or CNC output. There are no persisted report
|
||||||
CNC output. There are no persisted report settings or templates.
|
settings or templates.
|
||||||
|
|
||||||
**File -> View Nest Report...** runs the same guards and capture without a dialog,
|
The report renders to a private folder under `%TEMP%\OpenNest\Reports\` and opens
|
||||||
renders to a private folder under `%TEMP%\OpenNest\Reports\` and opens a modeless
|
a modeless viewer window (WebView2's PDF viewer, sharing Help's WebView2 data
|
||||||
viewer window (WebView2's PDF viewer, sharing Help's WebView2 data folder) with
|
folder) with **Save As...**, which copies the rendered file to a chosen path, and
|
||||||
**Save As...**, which copies the rendered file to a chosen path, and **Print...**,
|
**Print...**, the WebView2 print dialog. Save As is the only way to keep the PDF;
|
||||||
the WebView2 print dialog. Closing the window deletes its temporary folder; a
|
there is no separate direct-save command. Closing the window deletes its temporary
|
||||||
render failure opens no viewer and leaves no folder. Without the WebView2 Runtime
|
folder; a render failure opens no viewer and leaves no folder. Without the WebView2
|
||||||
the window still offers Save As.
|
Runtime the window still offers Save As.
|
||||||
|
|
||||||
The same two calls back the command and any future integration:
|
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
|
## 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
|
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,
|
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
|
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
|
## 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
|
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
|
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
|
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.
|
result.
|
||||||
|
|
||||||
Windows compilation is not runtime acceptance. `OpenNest.WinForms.Tests/Forms/NestReportExportTests.cs`
|
Windows compilation is not runtime acceptance. `OpenNest.WinForms.Tests/Forms/NestReportExportTests.cs`
|
||||||
covers enablement, busy/cancel/failure/success adapter behavior, but it only
|
covers enablement, busy/failure/viewer adapter behavior, but it only
|
||||||
compiles on Linux. Windows runtime acceptance still owes: exporting a real nest
|
compiles on Linux. Windows runtime acceptance still owes: reporting a real nest
|
||||||
(and the packaged application, so bundled fonts/notices are verified), checking
|
(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
|
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,
|
hole, saving and printing the PDF from the viewer, and exercising fill/nesting
|
||||||
overwrite and invalid-path behavior with job state and any existing destination
|
rejection with job state unchanged.
|
||||||
unchanged.
|
|
||||||
@@ -44,9 +44,9 @@ The desktop app can save nests two ways:
|
|||||||
the record from ordinary browsing but retains every archive; **Deleted nests...**
|
the record from ordinary browsing but retains every archive; **Deleted nests...**
|
||||||
opens an explicit recovery view. Neither action purges data. An old desktop
|
opens an explicit recovery view. Neither action purges data. An old desktop
|
||||||
without a revision precondition cannot update or delete on an upgraded server.
|
without a revision precondition cannot update or delete on an upgraded server.
|
||||||
A separate **File > Export .nest...** command is always available (in both
|
A separate **File > Save Copy as .nest File...** command is always available
|
||||||
modes) for producing a local file to share or back up. Export does not change
|
(in both modes) for producing a local file to share or back up. The copy does
|
||||||
the document's file save path or database record association.
|
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
|
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
|
plate) opens them from disk directly, in either mode — this is the only way
|
||||||
|
|||||||
Reference in new issue
Block a user