From 7a893ef50f0e407cc7d266c40023036190999078 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Thu, 2 Apr 2026 14:34:20 -0400 Subject: [PATCH] refactor: replace floating tool window with docked side panel - Add general-purpose ShowSidePanel/HideSidePanel to EditNestForm - CuttingPanel uses Dock.Top layout so collapsible panels reflow - Add loop selection step: click contour to lock before placing lead-in - Stay on selected part after placing a lead-in - Delete unused LeadInToolWindow Co-Authored-By: Claude Opus 4.6 (1M context) --- OpenNest/Actions/ActionLeadIn.cs | 128 +++++++++++++++-------- OpenNest/Controls/CuttingPanel.cs | 56 +++++----- OpenNest/Forms/EditNestForm.cs | 43 +++++++- OpenNest/Forms/LeadInToolWindow.cs | 110 ------------------- OpenNest/Properties/Settings.Designer.cs | 24 ----- OpenNest/Properties/Settings.settings | 6 -- 6 files changed, 158 insertions(+), 209 deletions(-) delete mode 100644 OpenNest/Forms/LeadInToolWindow.cs diff --git a/OpenNest/Actions/ActionLeadIn.cs b/OpenNest/Actions/ActionLeadIn.cs index 63e0678..8f4aadb 100644 --- a/OpenNest/Actions/ActionLeadIn.cs +++ b/OpenNest/Actions/ActionLeadIn.cs @@ -31,10 +31,12 @@ namespace OpenNest.Actions private bool hasSnap; private SnapType activeSnapType; private ShapeInfo hoveredContour; + private ShapeInfo lockedContour; private ContextMenuStrip contextMenu; - private LeadInToolWindow toolWindow; + private CuttingPanel cuttingPanel; private static readonly Brush grayOverlay = new SolidBrush(Color.FromArgb(160, 180, 180, 180)); private static readonly Pen highlightPen = new Pen(Color.Cyan, 2.5f); + private static readonly Pen lockedPen = new Pen(Color.Yellow, 3.0f); public ActionLeadIn(PlateView plateView) : base(plateView) @@ -48,7 +50,7 @@ namespace OpenNest.Actions plateView.MouseDown += OnMouseDown; plateView.KeyDown += OnKeyDown; plateView.Paint += OnPaint; - ShowToolWindow(); + ShowSidePanel(); } public override void DisconnectEvents() @@ -58,7 +60,7 @@ namespace OpenNest.Actions plateView.KeyDown -= OnKeyDown; plateView.Paint -= OnPaint; - HideToolWindow(); + HideSidePanel(); contextMenu?.Dispose(); contextMenu = null; @@ -77,18 +79,20 @@ namespace OpenNest.Actions public override bool IsBusy() => selectedPart != null; - private void ShowToolWindow() + private void ShowSidePanel() { - if (toolWindow == null) - { - toolWindow = new LeadInToolWindow(); - toolWindow.AutoAssignClicked += OnAutoAssignClicked; - } + var form = plateView.FindForm() as EditNestForm; + if (form == null) + return; + + cuttingPanel = new CuttingPanel { ShowAutoAssign = true }; + cuttingPanel.AutoAssignClicked += OnAutoAssignClicked; + cuttingPanel.ParametersChanged += OnToolParametersChanged; // Load current parameters or defaults var plate = plateView.Plate; if (plate?.CuttingParameters != null) - toolWindow.LoadFromParameters(plate.CuttingParameters); + cuttingPanel.LoadFromParameters(plate.CuttingParameters); else { var json = Properties.Settings.Default.CuttingParametersJson; @@ -97,46 +101,42 @@ namespace OpenNest.Actions try { var saved = CuttingParametersSerializer.Deserialize(json); - toolWindow.LoadFromParameters(saved); + cuttingPanel.LoadFromParameters(saved); } catch { /* use defaults */ } } } - toolWindow.ParametersChanged += OnToolParametersChanged; - - var mainForm = plateView.FindForm(); - if (mainForm != null) - toolWindow.Owner = mainForm; - - toolWindow.Show(); + form.ShowSidePanel(cuttingPanel); } - private void HideToolWindow() + private void HideSidePanel() { - if (toolWindow == null) + if (cuttingPanel == null) return; SaveParameters(); - toolWindow.ParametersChanged -= OnToolParametersChanged; - toolWindow.AutoAssignClicked -= OnAutoAssignClicked; - toolWindow.Close(); - toolWindow.Dispose(); - toolWindow = null; + cuttingPanel.ParametersChanged -= OnToolParametersChanged; + cuttingPanel.AutoAssignClicked -= OnAutoAssignClicked; + + var form = plateView.FindForm() as EditNestForm; + form?.HideSidePanel(); + + cuttingPanel = null; } private CuttingParameters GetCurrentParameters() { - return toolWindow?.BuildParameters() ?? plateView.Plate?.CuttingParameters ?? new CuttingParameters(); + return cuttingPanel?.BuildParameters() ?? plateView.Plate?.CuttingParameters ?? new CuttingParameters(); } private void SaveParameters() { - if (toolWindow == null) + if (cuttingPanel == null) return; - var parameters = toolWindow.BuildParameters(); + var parameters = cuttingPanel.BuildParameters(); var json = CuttingParametersSerializer.Serialize(parameters); Properties.Settings.Default.CuttingParametersJson = json; Properties.Settings.Default.Save(); @@ -169,7 +169,12 @@ namespace OpenNest.Actions activeSnapType = SnapType.None; hoveredContour = null; - foreach (var info in contours) + // When a contour is locked, only snap within that contour + var searchContours = lockedContour != null + ? new List { lockedContour } + : contours; + + foreach (var info in searchContours) { var closest = info.Shape.ClosestPointTo(localPt, out var entity); var dist = closest.DistanceTo(localPt); @@ -191,9 +196,9 @@ namespace OpenNest.Actions { TrySnapToEntityPoints(localPt); - // Auto-switch tool window tab - if (toolWindow != null) - toolWindow.ActiveContourType = snapContourType; + // Auto-switch tool window tab only when no contour is locked + if (cuttingPanel != null && lockedContour == null) + cuttingPanel.ActiveContourType = snapContourType; } plateView.Invalidate(); @@ -208,15 +213,22 @@ namespace OpenNest.Actions // First click: select a part SelectPartAtCursor(); } - else if (hasSnap) + else if (lockedContour == null && hasSnap) { - // Second click: commit lead-in at snap point + // Second click: lock the hovered contour + LockContour(hoveredContour); + } + else if (lockedContour != null && hasSnap) + { + // Third click: commit lead-in at snap point on locked contour CommitLeadIn(); } } else if (e.Button == MouseButtons.Right) { - if (selectedPart != null && selectedPart.HasManualLeadIns) + if (lockedContour != null) + UnlockContour(); + else if (selectedPart != null && selectedPart.HasManualLeadIns) ShowContextMenu(e.Location); else DeselectPart(); @@ -227,7 +239,9 @@ namespace OpenNest.Actions { if (e.KeyCode == Keys.Escape) { - if (selectedPart != null) + if (lockedContour != null) + UnlockContour(); + else if (selectedPart != null) DeselectPart(); else plateView.SetAction(typeof(ActionSelect)); @@ -243,6 +257,23 @@ namespace OpenNest.Actions DrawLeadInPreview(g); } + private void LockContour(ShapeInfo contour) + { + lockedContour = contour; + + // Lock the tab to this contour type + if (cuttingPanel != null) + cuttingPanel.ActiveContourType = contour.ContourType; + + plateView.Invalidate(); + } + + private void UnlockContour() + { + lockedContour = null; + plateView.Invalidate(); + } + private void DrawOverlay(Graphics g) { foreach (var lp in plateView.LayoutParts) @@ -254,10 +285,24 @@ namespace OpenNest.Actions private void DrawHoveredContour(Graphics g) { - if (hoveredContour == null || selectedPart == null) + if (selectedPart == null) return; - using var contourPath = hoveredContour.Shape.GetGraphicsPath(); + // Draw locked contour with distinct pen + if (lockedContour != null) + { + DrawContourHighlight(g, lockedContour.Shape, lockedPen); + return; + } + + // Draw hovered contour + if (hoveredContour != null) + DrawContourHighlight(g, hoveredContour.Shape, highlightPen); + } + + private void DrawContourHighlight(Graphics g, Shape shape, Pen pen) + { + using var contourPath = shape.GetGraphicsPath(); using var contourMatrix = new Matrix(); contourMatrix.Translate((float)selectedPart.Location.X, (float)selectedPart.Location.Y); contourMatrix.Multiply(plateView.Matrix, MatrixOrder.Append); @@ -265,7 +310,7 @@ namespace OpenNest.Actions var prevSmooth = g.SmoothingMode; g.SmoothingMode = SmoothingMode.AntiAlias; - g.DrawPath(highlightPen, contourPath); + g.DrawPath(pen, contourPath); g.SmoothingMode = prevSmooth; } @@ -468,7 +513,7 @@ namespace OpenNest.Actions selectedPart.LeadInsLocked = true; selectedLayoutPart.IsDirty = true; - DeselectPart(); + UnlockContour(); plateView.Invalidate(); } @@ -488,7 +533,7 @@ namespace OpenNest.Actions selectedPart.LeadInsLocked = true; selectedLayoutPart.IsDirty = true; - DeselectPart(); + UnlockContour(); plateView.Invalidate(); } @@ -515,6 +560,7 @@ namespace OpenNest.Actions selectedPart = null; profile = null; contours = null; + lockedContour = null; hasSnap = false; activeSnapType = SnapType.None; hoveredContour = null; diff --git a/OpenNest/Controls/CuttingPanel.cs b/OpenNest/Controls/CuttingPanel.cs index 695721e..69f3567 100644 --- a/OpenNest/Controls/CuttingPanel.cs +++ b/OpenNest/Controls/CuttingPanel.cs @@ -76,14 +76,10 @@ namespace OpenNest.Controls AutoScroll = true; BackColor = Color.White; - var y = 0; - - // Tab control for contour types + // Tab control for contour types — wrapped in a fixed-height panel for Dock.Top tabControl = new TabControl { - Location = new Point(4, y), - Size = new Size(372, 340), - Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right + Dock = DockStyle.Fill }; var tabExternal = new TabPage("External") { Padding = new Padding(4) }; @@ -101,18 +97,20 @@ namespace OpenNest.Controls tabControl.Controls.Add(tabInternal); tabControl.Controls.Add(tabArcCircle); - Controls.Add(tabControl); - y += tabControl.Height + 4; + var tabWrapper = new Panel + { + Dock = DockStyle.Top, + Height = 340 + }; + tabWrapper.Controls.Add(tabControl); // Tabs section var tabsPanel = new CollapsiblePanel { HeaderText = "Tabs", - Location = new Point(0, y), - Size = new Size(380, 120), + Dock = DockStyle.Top, ExpandedHeight = 120, - IsExpanded = true, - Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right + IsExpanded = false }; chkTabsEnabled = new CheckBox @@ -159,18 +157,13 @@ namespace OpenNest.Controls nudAutoTabMax = CreateNumeric(140, 55, 0, 0.0625); tabsPanel.ContentPanel.Controls.Add(nudAutoTabMax); - Controls.Add(tabsPanel); - y += tabsPanel.Height + 4; - // Pierce section var piercePanel = new CollapsiblePanel { HeaderText = "Pierce", - Location = new Point(0, y), - Size = new Size(380, 60), + Dock = DockStyle.Top, ExpandedHeight = 60, - IsExpanded = true, - Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right + IsExpanded = true }; piercePanel.ContentPanel.Controls.Add(new Label @@ -183,20 +176,29 @@ namespace OpenNest.Controls nudPierceClearance = CreateNumeric(130, 3, 0.0625, 0.0625); piercePanel.ContentPanel.Controls.Add(nudPierceClearance); - Controls.Add(piercePanel); - y += piercePanel.Height + 4; - - // Auto-Assign button + // Auto-Assign button — wrapped in a panel for Dock.Top with padding btnAutoAssign = new Button { Text = "Auto-Assign Lead-ins", - Location = new Point(4, y), - Size = new Size(372, 32), - Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right, + Dock = DockStyle.Top, + Height = 32, Visible = false }; btnAutoAssign.Click += (s, e) => AutoAssignClicked?.Invoke(this, EventArgs.Empty); - Controls.Add(btnAutoAssign); + + var btnWrapper = new Panel + { + Dock = DockStyle.Top, + Height = 36, + Padding = new Padding(4, 2, 4, 2) + }; + btnWrapper.Controls.Add(btnAutoAssign); + + // Add in reverse order — Dock.Top stacks top-down + Controls.Add(btnWrapper); + Controls.Add(piercePanel); + Controls.Add(tabsPanel); + Controls.Add(tabWrapper); // Wire up change events PopulateDropdowns(); diff --git a/OpenNest/Forms/EditNestForm.cs b/OpenNest/Forms/EditNestForm.cs index e857d5d..6198454 100644 --- a/OpenNest/Forms/EditNestForm.cs +++ b/OpenNest/Forms/EditNestForm.cs @@ -37,6 +37,9 @@ namespace OpenNest.Forms private Button btnNextPlate; private Button btnLastPlate; + private SplitContainer viewSplitContainer; + private Panel sidePanel; + /// /// Used to distinguish between single/double click on drawing within drawinglistbox. /// If double click, this is set to false so the single click action won't be triggered. @@ -53,8 +56,9 @@ namespace OpenNest.Forms InitializeComponent(); CreatePlateHeader(); + CreateSidePanel(); - splitContainer.Panel2.Controls.Add(PlateView); + splitContainer.Panel2.Controls.Add(viewSplitContainer); splitContainer.Panel2.Controls.Add(plateHeaderPanel); var renderer = new ToolStripRenderer(ToolbarTheme.Toolbar); @@ -146,6 +150,43 @@ namespace OpenNest.Forms navPanel.Top = (plateHeaderPanel.Height - navPanel.Height) / 2; } + private void CreateSidePanel() + { + sidePanel = new Panel + { + Dock = DockStyle.Fill, + AutoScroll = true, + BackColor = Color.White + }; + + viewSplitContainer = new SplitContainer + { + Dock = DockStyle.Fill, + Orientation = Orientation.Vertical, + FixedPanel = FixedPanel.Panel2, + Panel2MinSize = 0 + }; + + viewSplitContainer.Panel1.Controls.Add(PlateView); + viewSplitContainer.Panel2.Controls.Add(sidePanel); + viewSplitContainer.Panel2Collapsed = true; + } + + public void ShowSidePanel(Control content, int width = 390) + { + sidePanel.Controls.Clear(); + content.Dock = DockStyle.Fill; + sidePanel.Controls.Add(content); + viewSplitContainer.SplitterDistance = viewSplitContainer.Width - width; + viewSplitContainer.Panel2Collapsed = false; + } + + public void HideSidePanel() + { + viewSplitContainer.Panel2Collapsed = true; + sidePanel.Controls.Clear(); + } + private static Button CreateNavButton(System.Drawing.Image image) { return new Button diff --git a/OpenNest/Forms/LeadInToolWindow.cs b/OpenNest/Forms/LeadInToolWindow.cs deleted file mode 100644 index a179006..0000000 --- a/OpenNest/Forms/LeadInToolWindow.cs +++ /dev/null @@ -1,110 +0,0 @@ -using OpenNest.CNC.CuttingStrategy; -using OpenNest.Controls; -using System; -using System.Drawing; -using System.Windows.Forms; - -namespace OpenNest.Forms -{ - public class LeadInToolWindow : Form - { - private readonly CuttingPanel cuttingPanel; - - public CuttingPanel CuttingPanel => cuttingPanel; - - public event EventHandler ParametersChanged - { - add => cuttingPanel.ParametersChanged += value; - remove => cuttingPanel.ParametersChanged -= value; - } - - public event EventHandler AutoAssignClicked - { - add => cuttingPanel.AutoAssignClicked += value; - remove => cuttingPanel.AutoAssignClicked -= value; - } - - public LeadInToolWindow() - { - Text = "Lead-In Properties"; - FormBorderStyle = FormBorderStyle.SizableToolWindow; - ShowInTaskbar = false; - StartPosition = FormStartPosition.Manual; - MinimumSize = new Size(300, 400); - Size = new Size(400, 580); - - cuttingPanel = new CuttingPanel - { - Dock = DockStyle.Fill, - ShowAutoAssign = true - }; - Controls.Add(cuttingPanel); - - RestorePosition(); - } - - public CuttingParameters BuildParameters() => cuttingPanel.BuildParameters(); - - public void LoadFromParameters(CuttingParameters p) => cuttingPanel.LoadFromParameters(p); - - public ContourType? ActiveContourType - { - get => cuttingPanel.ActiveContourType; - set => cuttingPanel.ActiveContourType = value; - } - - protected override void OnFormClosing(FormClosingEventArgs e) - { - if (e.CloseReason == CloseReason.UserClosing) - { - e.Cancel = true; - Hide(); - } - - SavePosition(); - base.OnFormClosing(e); - } - - protected override void OnMove(EventArgs e) - { - base.OnMove(e); - if (WindowState == FormWindowState.Normal) - SavePosition(); - } - - protected override void OnResize(EventArgs e) - { - base.OnResize(e); - if (WindowState == FormWindowState.Normal) - SavePosition(); - } - - private void SavePosition() - { - if (WindowState != FormWindowState.Normal) - return; - - Properties.Settings.Default.LeadInToolWindowLocation = Location; - Properties.Settings.Default.LeadInToolWindowSize = Size; - Properties.Settings.Default.Save(); - } - - private void RestorePosition() - { - var savedLocation = Properties.Settings.Default.LeadInToolWindowLocation; - var savedSize = Properties.Settings.Default.LeadInToolWindowSize; - - if (savedSize.Width > 0 && savedSize.Height > 0) - Size = savedSize; - - if (savedLocation.X != 0 || savedLocation.Y != 0) - { - var screen = Screen.FromPoint(savedLocation); - if (screen.WorkingArea.Contains(savedLocation)) - Location = savedLocation; - else - CenterToParent(); - } - } - } -} diff --git a/OpenNest/Properties/Settings.Designer.cs b/OpenNest/Properties/Settings.Designer.cs index 52b42c5..7e44f3c 100644 --- a/OpenNest/Properties/Settings.Designer.cs +++ b/OpenNest/Properties/Settings.Designer.cs @@ -238,29 +238,5 @@ namespace OpenNest.Properties { this["CuttingParametersJson"] = value; } } - - [global::System.Configuration.UserScopedSettingAttribute()] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("0, 0")] - public global::System.Drawing.Point LeadInToolWindowLocation { - get { - return ((global::System.Drawing.Point)(this["LeadInToolWindowLocation"])); - } - set { - this["LeadInToolWindowLocation"] = value; - } - } - - [global::System.Configuration.UserScopedSettingAttribute()] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("0, 0")] - public global::System.Drawing.Size LeadInToolWindowSize { - get { - return ((global::System.Drawing.Size)(this["LeadInToolWindowSize"])); - } - set { - this["LeadInToolWindowSize"] = value; - } - } } } diff --git a/OpenNest/Properties/Settings.settings b/OpenNest/Properties/Settings.settings index 5ec56b9..cc3fd9b 100644 --- a/OpenNest/Properties/Settings.settings +++ b/OpenNest/Properties/Settings.settings @@ -56,11 +56,5 @@ - - 0, 0 - - - 0, 0 - \ No newline at end of file