diff --git a/OpenNest/Controls/PlateView.cs b/OpenNest/Controls/PlateView.cs index a099c1d..f093930 100644 --- a/OpenNest/Controls/PlateView.cs +++ b/OpenNest/Controls/PlateView.cs @@ -33,6 +33,7 @@ namespace OpenNest.Controls private List temporaryParts = new List(); private Point middleMouseDownPoint; private Box activeWorkArea; + private List debugRemnants; public Box ActiveWorkArea { @@ -44,6 +45,18 @@ namespace OpenNest.Controls } } + public List DebugRemnants + { + get => debugRemnants; + set + { + debugRemnants = value; + Invalidate(); + } + } + + public List DebugRemnantPriorities { get; set; } + public List SelectedParts; public ReadOnlyCollection Parts; @@ -374,6 +387,7 @@ namespace OpenNest.Controls DrawPlate(e.Graphics); DrawParts(e.Graphics); DrawActiveWorkArea(e.Graphics); + DrawDebugRemnants(e.Graphics); base.OnPaint(e); } @@ -632,6 +646,51 @@ namespace OpenNest.Controls g.DrawRectangle(pen, rect.X, rect.Y, rect.Width, rect.Height); } + // Priority 0 = green (preferred), 1 = yellow (extend), 2 = red (last resort) + private static readonly Color[] PriorityFills = + { + Color.FromArgb(60, Color.LimeGreen), + Color.FromArgb(60, Color.Gold), + Color.FromArgb(60, Color.Salmon), + }; + + private static readonly Color[] PriorityBorders = + { + Color.FromArgb(180, Color.Green), + Color.FromArgb(180, Color.DarkGoldenrod), + Color.FromArgb(180, Color.DarkRed), + }; + + private void DrawDebugRemnants(Graphics g) + { + if (debugRemnants == null || debugRemnants.Count == 0) + return; + + for (var i = 0; i < debugRemnants.Count; i++) + { + var box = debugRemnants[i]; + var loc = PointWorldToGraph(box.Location); + var w = LengthWorldToGui(box.Width); + var h = LengthWorldToGui(box.Length); + var rect = new RectangleF(loc.X, loc.Y - h, w, h); + + var priority = DebugRemnantPriorities != null && i < DebugRemnantPriorities.Count + ? System.Math.Min(DebugRemnantPriorities[i], 2) + : 0; + + using var brush = new SolidBrush(PriorityFills[priority]); + g.FillRectangle(brush, rect); + + using var pen = new Pen(PriorityBorders[priority], 1.5f); + g.DrawRectangle(pen, rect.X, rect.Y, rect.Width, rect.Height); + + var label = $"P{priority} {box.Width:F1}x{box.Length:F1}"; + using var font = new Font("Segoe UI", 8f); + using var sf = new StringFormat { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center }; + g.DrawString(label, font, Brushes.Black, rect, sf); + } + } + public LayoutPart GetPartAtControlPoint(Point pt) { var pt2 = PointControlToGraph(pt); diff --git a/OpenNest/Forms/MainForm.Designer.cs b/OpenNest/Forms/MainForm.Designer.cs index 6aad83f..28cb553 100644 --- a/OpenNest/Forms/MainForm.Designer.cs +++ b/OpenNest/Forms/MainForm.Designer.cs @@ -149,6 +149,7 @@ engineLabel = new System.Windows.Forms.ToolStripLabel(); engineComboBox = new System.Windows.Forms.ToolStripComboBox(); btnAutoNest = new System.Windows.Forms.ToolStripButton(); + btnShowRemnants = new System.Windows.Forms.ToolStripButton(); pEPToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); openNestToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); menuStrip1.SuspendLayout(); @@ -888,7 +889,7 @@ // toolStrip1 // toolStrip1.AutoSize = false; - toolStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { btnNew, btnOpen, btnSave, btnSaveAs, toolStripSeparator1, btnFirstPlate, btnPreviousPlate, btnNextPlate, btnLastPlate, toolStripSeparator3, btnZoomOut, btnZoomIn, btnZoomToFit, toolStripSeparator4, engineLabel, engineComboBox, btnAutoNest }); + toolStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { btnNew, btnOpen, btnSave, btnSaveAs, toolStripSeparator1, btnFirstPlate, btnPreviousPlate, btnNextPlate, btnLastPlate, toolStripSeparator3, btnZoomOut, btnZoomIn, btnZoomToFit, toolStripSeparator4, engineLabel, engineComboBox, btnAutoNest, btnShowRemnants }); toolStrip1.Location = new System.Drawing.Point(0, 24); toolStrip1.Name = "toolStrip1"; toolStrip1.Size = new System.Drawing.Size(1281, 40); @@ -1067,7 +1068,15 @@ btnAutoNest.Size = new System.Drawing.Size(64, 37); btnAutoNest.Text = "Auto Nest"; btnAutoNest.Click += RunAutoNest_Click; - // + // + // btnShowRemnants + // + btnShowRemnants.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text; + btnShowRemnants.Name = "btnShowRemnants"; + btnShowRemnants.Size = new System.Drawing.Size(64, 37); + btnShowRemnants.Text = "Remnants"; + btnShowRemnants.Click += ShowRemnants_Click; + // // pEPToolStripMenuItem // pEPToolStripMenuItem.Name = "pEPToolStripMenuItem"; @@ -1232,5 +1241,6 @@ private System.Windows.Forms.ToolStripLabel engineLabel; private System.Windows.Forms.ToolStripComboBox engineComboBox; private System.Windows.Forms.ToolStripButton btnAutoNest; + private System.Windows.Forms.ToolStripButton btnShowRemnants; } } \ No newline at end of file diff --git a/OpenNest/Forms/MainForm.cs b/OpenNest/Forms/MainForm.cs index df40213..d198886 100644 --- a/OpenNest/Forms/MainForm.cs +++ b/OpenNest/Forms/MainForm.cs @@ -737,6 +737,49 @@ namespace OpenNest.Forms activeForm.LoadNextPlate(); } + private RemnantViewerForm remnantViewer; + + private void ShowRemnants_Click(object sender, EventArgs e) + { + if (activeForm?.PlateView?.Plate == null) + return; + + var plate = activeForm.PlateView.Plate; + + // Minimum remnant dimension = smallest part bbox dimension on the plate. + var minDim = 0.0; + var nest = activeForm.Nest; + if (nest != null) + { + foreach (var drawing in nest.Drawings) + { + var bbox = drawing.Program.BoundingBox(); + var dim = System.Math.Min(bbox.Width, bbox.Length); + if (minDim == 0 || dim < minDim) + minDim = dim; + } + } + + var finder = RemnantFinder.FromPlate(plate); + var tiered = finder.FindTieredRemnants(minDim); + + if (remnantViewer == null || remnantViewer.IsDisposed) + { + remnantViewer = new RemnantViewerForm(); + remnantViewer.Owner = this; + + // Position next to the main form's right edge. + var screen = Screen.FromControl(this); + remnantViewer.Location = new Point( + System.Math.Min(Right, screen.WorkingArea.Right - remnantViewer.Width), + Top); + } + + remnantViewer.LoadRemnants(tiered, activeForm.PlateView); + remnantViewer.Show(); + remnantViewer.BringToFront(); + } + private async void RunAutoNest_Click(object sender, EventArgs e) { var form = new AutoNestForm(activeForm.Nest); @@ -744,7 +787,7 @@ namespace OpenNest.Forms if (form.ShowDialog() != System.Windows.Forms.DialogResult.OK) return; - + var items = form.GetNestItems(); if (!items.Any(it => it.Quantity > 0)) diff --git a/OpenNest/Forms/RemnantViewerForm.cs b/OpenNest/Forms/RemnantViewerForm.cs new file mode 100644 index 0000000..010850b --- /dev/null +++ b/OpenNest/Forms/RemnantViewerForm.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Windows.Forms; +using OpenNest.Controls; +using OpenNest.Geometry; + +namespace OpenNest.Forms +{ + public class RemnantViewerForm : Form + { + private ListView listView; + private PlateView plateView; + private List remnants = new(); + private int selectedIndex = -1; + + public RemnantViewerForm() + { + Text = "Remnants"; + Size = new System.Drawing.Size(360, 400); + StartPosition = FormStartPosition.Manual; + FormBorderStyle = FormBorderStyle.SizableToolWindow; + ShowInTaskbar = false; + TopMost = true; + + listView = new ListView + { + Dock = DockStyle.Fill, + View = View.Details, + FullRowSelect = true, + GridLines = true, + MultiSelect = false, + HideSelection = false, + }; + + listView.Columns.Add("P", 28, HorizontalAlignment.Center); + listView.Columns.Add("Size", 110, HorizontalAlignment.Left); + listView.Columns.Add("Area", 65, HorizontalAlignment.Right); + listView.Columns.Add("Location", 110, HorizontalAlignment.Left); + + listView.SelectedIndexChanged += ListView_SelectedIndexChanged; + + Controls.Add(listView); + } + + protected override bool ProcessDialogKey(Keys keyData) + { + if (keyData == Keys.Escape) + { + Close(); + return true; + } + return base.ProcessDialogKey(keyData); + } + + public void LoadRemnants(List tieredRemnants, PlateView view) + { + plateView = view; + remnants = tieredRemnants; + selectedIndex = -1; + + listView.BeginUpdate(); + listView.Items.Clear(); + + foreach (var tr in remnants) + { + var item = new ListViewItem(tr.Priority.ToString()); + item.SubItems.Add($"{tr.Box.Width:F2} x {tr.Box.Length:F2}"); + item.SubItems.Add($"{tr.Box.Area():F1}"); + item.SubItems.Add($"({tr.Box.X:F2}, {tr.Box.Y:F2})"); + + switch (tr.Priority) + { + case 0: item.BackColor = Color.FromArgb(220, 255, 220); break; + case 1: item.BackColor = Color.FromArgb(255, 255, 210); break; + default: item.BackColor = Color.FromArgb(255, 220, 220); break; + } + + listView.Items.Add(item); + } + + listView.EndUpdate(); + } + + private void ListView_SelectedIndexChanged(object sender, EventArgs e) + { + if (plateView == null) + return; + + if (listView.SelectedIndices.Count == 0) + { + selectedIndex = -1; + plateView.DebugRemnants = null; + plateView.DebugRemnantPriorities = null; + return; + } + + selectedIndex = listView.SelectedIndices[0]; + + if (selectedIndex >= 0 && selectedIndex < remnants.Count) + { + var tr = remnants[selectedIndex]; + plateView.DebugRemnants = new List { tr.Box }; + plateView.DebugRemnantPriorities = new List { tr.Priority }; + } + } + + protected override void OnFormClosing(FormClosingEventArgs e) + { + if (plateView != null) + { + plateView.DebugRemnants = null; + plateView.DebugRemnantPriorities = null; + } + + base.OnFormClosing(e); + } + } +}