From a9396ba06593b671adba11f2f16213799a093810 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Thu, 29 Oct 2026 00:35:54 -0400 Subject: [PATCH] feat(simulator): lock the nest to viewing while the cut simulator is open The simulator reuses Plan Cutting's inspection lock: the canvas pans and zooms and plates can be stepped through, but selection, part edits, menus, toolbars, lists and Undo/Redo are disabled until it closes. Escape or closing the nest closes the simulator first. Co-Authored-By: Claude Opus 5.5 --- OpenNest/Forms/CutSimulatorForm.cs | 13 +++++++++++++ OpenNest/Forms/CuttingInspectionPanel.cs | 2 +- OpenNest/Forms/EditNestForm.Designer.cs | 3 +++ OpenNest/Forms/EditNestForm.Undo.cs | 2 +- OpenNest/Forms/EditNestForm.cs | 17 +++++++++++++++++ OpenNest/Forms/MainForm.cs | 7 +++---- OpenNest/Forms/ValidationInspectionScope.cs | 10 +++++++--- 7 files changed, 45 insertions(+), 9 deletions(-) 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;