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 <noreply@anthropic.com>
This commit is contained in:
ajandClaude Opus 5.5 committed 2026-10-29 00:36:07 -04:00
1 parent 101d7b5a50
commit a9396ba065
7 files changed
+45 -9

No files matched your search

+13
View File
@@ -10,6 +10,8 @@ namespace OpenNest.Forms
/// <summary>
/// Playback controls for a <see cref="CutSimulationOverlay"/> 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.
/// </summary>
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.
+1 -1
View File
@@ -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();
+3
View File
@@ -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();
+1 -1
View File
@@ -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()
{
+17
View File
@@ -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; }
/// <summary>True while Plan Cutting or the Cut Simulator holds the nest view-only.</summary>
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;
+3 -4
View File
@@ -75,13 +75,12 @@ namespace OpenNest.Forms
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
var inspection = MdiChildren.OfType<EditNestForm>()
.Select(form => form.CuttingInspection).FirstOrDefault(panel => panel != null);
if (inspection != null)
var inspected = MdiChildren.OfType<EditNestForm>().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.
+7 -3
View File
@@ -6,7 +6,11 @@ using OpenNest.Controls;
namespace OpenNest.Forms;
/// <summary>Keep live validation inputs stable while allowing navigation of the main canvas.</summary>
/// <summary>
/// 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
/// <c>keepLive</c> and the zoom buttons.
/// </summary>
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;