diff --git a/OpenNest/Forms/CutSimulatorForm.cs b/OpenNest/Forms/CutSimulatorForm.cs
index 34d8c8c1..3b79882d 100644
--- a/OpenNest/Forms/CutSimulatorForm.cs
+++ b/OpenNest/Forms/CutSimulatorForm.cs
@@ -10,6 +10,8 @@ namespace OpenNest.Forms
///
/// Playback controls for a on a nest's plate view. Follows
/// the current plate and rebuilds the timeline when parts are added, removed or resequenced.
+ /// While it is open the nest is view-only, as in Plan Cutting: the canvas pans and zooms and
+ /// plates can be stepped through, but nothing that edits is enabled.
///
public sealed class CutSimulatorForm : Form
{
@@ -26,6 +28,7 @@ namespace OpenNest.Forms
private readonly NumericUpDown feedBox, holeFeedBox, rapidBox, pierceBox;
private readonly CheckBox skipEtchBox;
private readonly CutTimeMachineBox machineBox;
+ private readonly ValidationInspectionScope scope;
private Plate plate;
private bool updatingTrack;
private bool rebuildQueued;
@@ -184,6 +187,9 @@ namespace OpenNest.Forms
nestForm.FormClosed += NestForm_FormClosed;
AttachPlate(nestForm.PlateView.Plate);
Rebuild();
+
+ scope = new ValidationInspectionScope(nestForm, Close, nestForm.PlateNavigation);
+ nestForm.PlateView.InspectionCloseRequested = Close;
}
private static NumericUpDown NewNumber(decimal value, decimal min, decimal max, int decimals, string suffix) =>
@@ -447,9 +453,16 @@ namespace OpenNest.Forms
nestForm.FormClosed -= NestForm_FormClosed;
AttachPlate(null);
if (!nestForm.PlateView.IsDisposed)
+ {
overlay.Clear();
+ nestForm.PlateView.InspectionCloseRequested = null;
+ }
base.OnFormClosed(e);
+
+ // Unlock after the editor has forgotten this form, then finish a close it held up.
+ scope.Dispose();
+ scope.ResumeHostClose();
}
// Playback rewrites the status every frame; a plain label erases before it redraws, so it flashes.
diff --git a/OpenNest/Forms/CuttingInspectionPanel.cs b/OpenNest/Forms/CuttingInspectionPanel.cs
index 44e2a2f6..457b6643 100644
--- a/OpenNest/Forms/CuttingInspectionPanel.cs
+++ b/OpenNest/Forms/CuttingInspectionPanel.cs
@@ -401,7 +401,7 @@ internal sealed class CuttingInspectionPanel : UserControl
internal void Open()
{
- scope = new ValidationInspectionScope(editor, this, editor.SidePanelHeader, RequestClose);
+ scope = new ValidationInspectionScope(editor, RequestClose, this, editor.SidePanelHeader);
view.InspectionCloseRequested = RequestClose;
view.OverlapDisplay = OverlapDisplayMode.Off;
view.ClearValidationFinding();
diff --git a/OpenNest/Forms/EditNestForm.Designer.cs b/OpenNest/Forms/EditNestForm.Designer.cs
index 711eb95b..78986040 100644
--- a/OpenNest/Forms/EditNestForm.Designer.cs
+++ b/OpenNest/Forms/EditNestForm.Designer.cs
@@ -16,6 +16,9 @@
// A planning worker owns a captured inspection until its owner-thread join.
if (disposing && DeferCuttingDisposal())
return;
+ // The simulator's lock disables the main window until it closes.
+ if (disposing)
+ cutSimulatorForm?.Close();
if (disposing && (components != null))
{
components.Dispose();
diff --git a/OpenNest/Forms/EditNestForm.Undo.cs b/OpenNest/Forms/EditNestForm.Undo.cs
index 57a8b975..dc13dba0 100644
--- a/OpenNest/Forms/EditNestForm.Undo.cs
+++ b/OpenNest/Forms/EditNestForm.Undo.cs
@@ -58,7 +58,7 @@ namespace OpenNest.Forms
public bool CanRedo => History.CanRedo && CanRestore;
- private bool CanRestore => !IsChangeInProgress && CuttingInspection == null;
+ private bool CanRestore => !IsChangeInProgress && !IsInspecting;
public void Undo()
{
diff --git a/OpenNest/Forms/EditNestForm.cs b/OpenNest/Forms/EditNestForm.cs
index 3bef33d6..ddffe263 100644
--- a/OpenNest/Forms/EditNestForm.cs
+++ b/OpenNest/Forms/EditNestForm.cs
@@ -176,6 +176,7 @@ namespace OpenNest.Forms
plateButtons.Controls.AddRange(
new Control[] { btnFirstPlate, btnPreviousPlate, btnNextPlate, btnLastPlate });
ZoomControls = [zoomOutButton, zoomInButton, zoomToFitButton];
+ PlateNavigation = plateButtons;
zoomOutButton.Location = new Point(btnSize.Width * 4 + 8, 0);
zoomInButton.Location = new Point(btnSize.Width * 5 + 8, 0);
@@ -281,7 +282,20 @@ namespace OpenNest.Forms
internal Control SidePanelHeader { get; private set; }
internal Control[] ZoomControls { get; private set; }
+ internal Control PlateNavigation { get; private set; }
internal CuttingInspectionPanel CuttingInspection { get; private set; }
+
+ /// True while Plan Cutting or the Cut Simulator holds the nest view-only.
+ internal bool IsInspecting => CuttingInspection != null || cutSimulatorForm != null;
+
+ internal void EndInspection()
+ {
+ if (CuttingInspection != null)
+ CuttingInspection.RequestClose();
+ else
+ cutSimulatorForm?.Close();
+ }
+
private bool disposeAfterCuttingInspection;
private bool closeAfterCuttingInspection;
internal bool DeferCuttingDisposal()
@@ -1118,7 +1132,10 @@ namespace OpenNest.Forms
cutSimulatorForm.Activate();
return;
}
+ if (CuttingInspection != null)
+ return;
+ PlateView.EndAction();
cutSimulatorForm = new CutSimulatorForm(this);
cutSimulatorForm.FormClosed += (_, _) => cutSimulatorForm = null;
var owner = MdiParent ?? (Form)this;
diff --git a/OpenNest/Forms/MainForm.cs b/OpenNest/Forms/MainForm.cs
index 61b27b53..9e02ac13 100644
--- a/OpenNest/Forms/MainForm.cs
+++ b/OpenNest/Forms/MainForm.cs
@@ -75,13 +75,12 @@ namespace OpenNest.Forms
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
- var inspection = MdiChildren.OfType()
- .Select(form => form.CuttingInspection).FirstOrDefault(panel => panel != null);
- if (inspection != null)
+ var inspected = MdiChildren.OfType().FirstOrDefault(form => form.IsInspecting);
+ if (inspected != null)
{
if (keyData == Keys.Escape)
{
- inspection.RequestClose();
+ inspected.EndInspection();
return true;
}
// Standard inspection navigation reaches the canvas/panel; menu shortcuts cannot mutate.
diff --git a/OpenNest/Forms/ValidationInspectionScope.cs b/OpenNest/Forms/ValidationInspectionScope.cs
index c23c1292..5a60f1ca 100644
--- a/OpenNest/Forms/ValidationInspectionScope.cs
+++ b/OpenNest/Forms/ValidationInspectionScope.cs
@@ -6,7 +6,11 @@ using OpenNest.Controls;
namespace OpenNest.Forms;
-/// Keep live validation inputs stable while allowing navigation of the main canvas.
+///
+/// Keep live validation inputs stable while allowing navigation of the main canvas. Locks the
+/// canvas to viewing and disables every other control of the main window except
+/// keepLive and the zoom buttons.
+///
internal sealed class ValidationInspectionScope : IDisposable
{
private readonly List<(Control Control, bool Enabled)> controls = new();
@@ -18,11 +22,11 @@ internal sealed class ValidationInspectionScope : IDisposable
private Form closingHost;
private readonly bool previousInspection;
- public ValidationInspectionScope(EditNestForm editor, Control inspector, Control header, Action close)
+ public ValidationInspectionScope(EditNestForm editor, Action close, params Control[] keepLive)
{
view = editor.PlateView;
// The header zoom buttons only change the viewport, like the canvas wheel.
- live = new[] { view, inspector, header }.Concat(editor.ZoomControls).Where(control => control != null).ToArray();
+ live = keepLive.Prepend(view).Concat(editor.ZoomControls).Where(control => control != null).ToArray();
this.close = close;
previousInspection = view.InspectionOnly;
view.InspectionOnly = true;