diff --git a/OpenNest/Controls/BestFitCell.cs b/OpenNest/Controls/BestFitCell.cs index eed1fab..05ef630 100644 --- a/OpenNest/Controls/BestFitCell.cs +++ b/OpenNest/Controls/BestFitCell.cs @@ -9,9 +9,16 @@ namespace OpenNest.Controls public class BestFitCell : PlateView { private string[] metadataLines; + private Color? partColor; public BestFitResult Result { get; set; } + public Color? PartColor + { + get => partColor; + set => partColor = value; + } + public BestFitCell(ColorScheme colorScheme) : base(colorScheme) { @@ -22,6 +29,8 @@ namespace OpenNest.Controls AllowZoom = false; AllowDrop = false; Cursor = Cursors.Hand; + + Plate.PartAdded += (s, e) => ApplyPartColor(); } public void SetMetadata(BestFitResult result, int rank) @@ -59,13 +68,22 @@ namespace OpenNest.Controls PaintMetadata(e.Graphics); } + private void ApplyPartColor() + { + if (!partColor.HasValue) + return; + + foreach (var lp in parts) + lp.Color = partColor.Value; + } + private void PaintMetadata(Graphics g) { if (metadataLines == null) return; var font = Font; - var brush = Brushes.White; + var textColor = IsDarkBackground() ? Brushes.White : Brushes.Black; var lineHeight = font.GetHeight(g) + 1; var y = 2f; @@ -74,9 +92,16 @@ namespace OpenNest.Controls if (line.Length == 0) continue; - g.DrawString(line, font, brush, 2, y); + g.DrawString(line, font, textColor, 2, y); y += lineHeight; } } + + private bool IsDarkBackground() + { + var bg = ColorScheme.BackgroundColor; + var brightness = bg.R * 0.299 + bg.G * 0.587 + bg.B * 0.114; + return brightness < 128; + } } } diff --git a/OpenNest/Controls/PlateView.cs b/OpenNest/Controls/PlateView.cs index e0f06e9..728ea53 100644 --- a/OpenNest/Controls/PlateView.cs +++ b/OpenNest/Controls/PlateView.cs @@ -30,7 +30,7 @@ namespace OpenNest.Controls private Plate plate; private Action currentAction; private Action previousAction; - private List parts; + protected List parts; private List stationaryParts = new List(); private List activeParts = new List(); private Point middleMouseDownPoint; diff --git a/OpenNest/Forms/BestFitViewerForm.Designer.cs b/OpenNest/Forms/BestFitViewerForm.Designer.cs index 724d19a..ed23f65 100644 --- a/OpenNest/Forms/BestFitViewerForm.Designer.cs +++ b/OpenNest/Forms/BestFitViewerForm.Designer.cs @@ -14,11 +14,15 @@ namespace OpenNest.Forms private void InitializeComponent() { this.gridPanel = new System.Windows.Forms.TableLayoutPanel(); + this.toolbarPanel = new System.Windows.Forms.Panel(); + this.lblDrawing = new System.Windows.Forms.Label(); + this.cboDrawing = new System.Windows.Forms.ComboBox(); this.navPanel = new System.Windows.Forms.Panel(); this.btnPrev = new System.Windows.Forms.Button(); this.btnNext = new System.Windows.Forms.Button(); this.txtPage = new System.Windows.Forms.TextBox(); this.lblPageCount = new System.Windows.Forms.Label(); + this.toolbarPanel.SuspendLayout(); this.navPanel.SuspendLayout(); this.SuspendLayout(); // @@ -31,15 +35,42 @@ namespace OpenNest.Forms this.gridPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 20F)); this.gridPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 20F)); this.gridPanel.Dock = System.Windows.Forms.DockStyle.Fill; - this.gridPanel.Location = new System.Drawing.Point(0, 0); + this.gridPanel.Location = new System.Drawing.Point(0, 32); this.gridPanel.Name = "gridPanel"; this.gridPanel.RowCount = 3; this.gridPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 33.33F)); this.gridPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 33.34F)); this.gridPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 33.33F)); - this.gridPanel.Size = new System.Drawing.Size(1200, 764); + this.gridPanel.Size = new System.Drawing.Size(1200, 732); this.gridPanel.TabIndex = 0; // + // toolbarPanel + // + this.toolbarPanel.Controls.Add(this.lblDrawing); + this.toolbarPanel.Controls.Add(this.cboDrawing); + this.toolbarPanel.Dock = System.Windows.Forms.DockStyle.Top; + this.toolbarPanel.Location = new System.Drawing.Point(0, 0); + this.toolbarPanel.Name = "toolbarPanel"; + this.toolbarPanel.Size = new System.Drawing.Size(1200, 32); + this.toolbarPanel.TabIndex = 2; + // + // lblDrawing + // + this.lblDrawing.Location = new System.Drawing.Point(6, 0); + this.lblDrawing.Name = "lblDrawing"; + this.lblDrawing.Size = new System.Drawing.Size(55, 32); + this.lblDrawing.TabIndex = 0; + this.lblDrawing.Text = "Drawing:"; + this.lblDrawing.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // cboDrawing + // + this.cboDrawing.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cboDrawing.Location = new System.Drawing.Point(64, 5); + this.cboDrawing.Name = "cboDrawing"; + this.cboDrawing.Size = new System.Drawing.Size(250, 21); + this.cboDrawing.TabIndex = 1; + // // navPanel // this.navPanel.Controls.Add(this.btnPrev); @@ -54,9 +85,7 @@ namespace OpenNest.Forms // // btnPrev // - this.btnPrev.Anchor = System.Windows.Forms.AnchorStyles.Top; this.btnPrev.FlatStyle = System.Windows.Forms.FlatStyle.Flat; - this.btnPrev.Location = new System.Drawing.Point(480, 4); this.btnPrev.Name = "btnPrev"; this.btnPrev.Size = new System.Drawing.Size(80, 28); this.btnPrev.TabIndex = 0; @@ -65,8 +94,6 @@ namespace OpenNest.Forms // // txtPage // - this.txtPage.Anchor = System.Windows.Forms.AnchorStyles.Top; - this.txtPage.Location = new System.Drawing.Point(564, 7); this.txtPage.Name = "txtPage"; this.txtPage.Size = new System.Drawing.Size(40, 20); this.txtPage.TabIndex = 1; @@ -76,8 +103,6 @@ namespace OpenNest.Forms // // lblPageCount // - this.lblPageCount.Anchor = System.Windows.Forms.AnchorStyles.Top; - this.lblPageCount.Location = new System.Drawing.Point(606, 4); this.lblPageCount.Name = "lblPageCount"; this.lblPageCount.Size = new System.Drawing.Size(50, 28); this.lblPageCount.TabIndex = 2; @@ -86,9 +111,7 @@ namespace OpenNest.Forms // // btnNext // - this.btnNext.Anchor = System.Windows.Forms.AnchorStyles.Top; this.btnNext.FlatStyle = System.Windows.Forms.FlatStyle.Flat; - this.btnNext.Location = new System.Drawing.Point(660, 4); this.btnNext.Name = "btnNext"; this.btnNext.Size = new System.Drawing.Size(80, 28); this.btnNext.TabIndex = 3; @@ -101,18 +124,23 @@ namespace OpenNest.Forms this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(1200, 800); this.Controls.Add(this.gridPanel); + this.Controls.Add(this.toolbarPanel); this.Controls.Add(this.navPanel); this.KeyPreview = true; this.Name = "BestFitViewerForm"; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; this.Text = "Best-Fit Viewer"; this.WindowState = System.Windows.Forms.FormWindowState.Maximized; + this.toolbarPanel.ResumeLayout(false); this.navPanel.ResumeLayout(false); this.navPanel.PerformLayout(); this.ResumeLayout(false); } private System.Windows.Forms.TableLayoutPanel gridPanel; + private System.Windows.Forms.Panel toolbarPanel; + private System.Windows.Forms.Label lblDrawing; + private System.Windows.Forms.ComboBox cboDrawing; private System.Windows.Forms.Panel navPanel; private System.Windows.Forms.Button btnPrev; private System.Windows.Forms.Button btnNext; diff --git a/OpenNest/Forms/BestFitViewerForm.cs b/OpenNest/Forms/BestFitViewerForm.cs index a0f4358..f483a16 100644 --- a/OpenNest/Forms/BestFitViewerForm.cs +++ b/OpenNest/Forms/BestFitViewerForm.cs @@ -1,10 +1,14 @@ +using OpenNest.Collections; using OpenNest.Controls; using OpenNest.Engine.BestFit; using System; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; +using System.Linq; using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; using System.Windows.Forms; namespace OpenNest.Forms @@ -20,12 +24,15 @@ namespace OpenNest.Forms private const int Rows = 3; private const int ItemsPerPage = Columns * Rows; - private static readonly Color KeptColor = Color.FromArgb(0, 0, 100); - private static readonly Color DroppedColor = Color.FromArgb(100, 0, 0); + private static readonly Color KeptBackground = Color.FromArgb(240, 240, 240); + private static readonly Color DroppedBackground = Color.FromArgb(255, 235, 235); + private static readonly Color KeptPartColor = Color.FromArgb(50, 120, 190); + private static readonly Color DroppedPartColor = Color.FromArgb(180, 80, 80); - private readonly Drawing drawing; + private readonly List drawings; private readonly Plate plate; + private Drawing activeDrawing; private List results; private int totalResults; private int keptCount; @@ -33,30 +40,37 @@ namespace OpenNest.Forms private double totalSeconds; private int currentPage; private int pageCount; + private CancellationTokenSource computeCts; public BestFitResult SelectedResult { get; private set; } + public Drawing SelectedDrawing => activeDrawing; - public BestFitViewerForm(Drawing drawing, Plate plate) + public BestFitViewerForm(DrawingCollection drawings, Plate plate) { - this.drawing = drawing; + this.drawings = drawings.ToList(); this.plate = plate; + this.activeDrawing = this.drawings[0]; DoubleBuffered = true; InitializeComponent(); + + foreach (var d in drawings) + cboDrawing.Items.Add(d.Name); + cboDrawing.SelectedIndex = 0; + cboDrawing.SelectedIndexChanged += cboDrawing_SelectedIndexChanged; + + navPanel.SizeChanged += (s, ev) => CenterNavControls(); Shown += BestFitViewerForm_Shown; } - private void BestFitViewerForm_Shown(object sender, System.EventArgs e) + private void BestFitViewerForm_Shown(object sender, EventArgs e) { - Cursor = Cursors.WaitCursor; - try - { - ComputeResults(); - ShowPage(0); - } - finally - { - Cursor = Cursors.Default; - } + LoadResultsAsync(); + } + + protected override void OnFormClosed(FormClosedEventArgs e) + { + computeCts?.Cancel(); + base.OnFormClosed(e); } protected override bool ProcessCmdKey(ref Message msg, Keys keyData) @@ -79,27 +93,97 @@ namespace OpenNest.Forms return base.ProcessCmdKey(ref msg, keyData); } - private void ComputeResults() + private void cboDrawing_SelectedIndexChanged(object sender, EventArgs e) + { + var index = cboDrawing.SelectedIndex; + if (index < 0 || index >= drawings.Count) + return; + + activeDrawing = drawings[index]; + LoadResultsAsync(); + } + + private async void LoadResultsAsync() + { + computeCts?.Cancel(); + var cts = new CancellationTokenSource(); + computeCts = cts; + + SetLoading(true); + + try + { + var drawing = activeDrawing; + var length = plate.Size.Length; + var width = plate.Size.Width; + var spacing = plate.PartSpacing; + + var result = await Task.Run(() => ComputeResults(drawing, length, width, spacing), cts.Token); + + if (cts.Token.IsCancellationRequested) + return; + + results = result.Results; + totalResults = result.TotalResults; + keptCount = result.KeptCount; + computeSeconds = result.ComputeSeconds; + totalSeconds = result.TotalSeconds; + pageCount = System.Math.Max(1, (int)System.Math.Ceiling(results.Count / (double)ItemsPerPage)); + + ShowPage(0); + } + catch (OperationCanceledException) + { + } + finally + { + if (cts == computeCts) + SetLoading(false); + } + } + + private void SetLoading(bool loading) + { + Cursor = loading ? Cursors.WaitCursor : Cursors.Default; + cboDrawing.Enabled = !loading; + btnPrev.Enabled = !loading; + btnNext.Enabled = !loading; + txtPage.Enabled = !loading; + + if (loading) + { + Text = "Best-Fit Viewer — Computing..."; + gridPanel.SuspendLayout(); + gridPanel.Controls.Clear(); + gridPanel.ResumeLayout(true); + } + } + + private static ComputeResult ComputeResults(Drawing drawing, double length, double width, double spacing) { var sw = Stopwatch.StartNew(); - var all = BestFitCache.GetOrCompute( - drawing, plate.Size.Length, plate.Size.Width, plate.PartSpacing); + var all = BestFitCache.GetOrCompute(drawing, length, width, spacing); - computeSeconds = sw.ElapsedMilliseconds / 1000.0; - totalResults = all.Count; - keptCount = 0; + var computeMs = sw.ElapsedMilliseconds; + var total = all.Count; + var kept = 0; foreach (var r in all) { - if (r.Keep) keptCount++; + if (r.Keep) kept++; } - results = all; - pageCount = System.Math.Max(1, (int)System.Math.Ceiling(results.Count / (double)ItemsPerPage)); - sw.Stop(); - totalSeconds = sw.Elapsed.TotalSeconds; + + return new ComputeResult + { + Results = all, + TotalResults = total, + KeptCount = kept, + ComputeSeconds = computeMs / 1000.0, + TotalSeconds = sw.Elapsed.TotalSeconds + }; } private void ShowPage(int page) @@ -122,7 +206,7 @@ namespace OpenNest.Forms for (var i = 0; i < count; i++) { var result = results[start + i]; - var cell = CreateCell(result, drawing, start + i + 1); + var cell = CreateCell(result, activeDrawing, start + i + 1); gridPanel.Controls.Add(cell, i % Columns, i / Columns); } @@ -144,9 +228,28 @@ namespace OpenNest.Forms start + 1, start + count, results.Count); } - private void btnPrev_Click(object sender, System.EventArgs e) => NavigatePage(-1); + private void btnPrev_Click(object sender, EventArgs e) => NavigatePage(-1); - private void btnNext_Click(object sender, System.EventArgs e) => NavigatePage(1); + private void btnNext_Click(object sender, EventArgs e) => NavigatePage(1); + + private void CenterNavControls() + { + var gap = 6; + var groupWidth = btnPrev.Width + gap + txtPage.Width + gap + lblPageCount.Width + gap + btnNext.Width; + var x = (navPanel.Width - groupWidth) / 2; + var midY = navPanel.Height / 2; + + btnPrev.Location = new Point(x, midY - btnPrev.Height / 2); + x += btnPrev.Width + gap; + + txtPage.Location = new Point(x, midY - txtPage.Height / 2); + x += txtPage.Width + gap; + + lblPageCount.Location = new Point(x, midY - lblPageCount.Height / 2); + x += lblPageCount.Width + gap; + + btnNext.Location = new Point(x, midY - btnNext.Height / 2); + } private void NavigatePage(int delta) { @@ -169,12 +272,14 @@ namespace OpenNest.Forms private BestFitCell CreateCell(BestFitResult result, Drawing drawing, int rank) { - var bgColor = result.Keep ? KeptColor : DroppedColor; + var kept = result.Keep; + var bgColor = kept ? KeptBackground : DroppedBackground; + var partColor = kept ? KeptPartColor : DroppedPartColor; var colorScheme = new ColorScheme { BackgroundColor = bgColor, - LayoutOutlineColor = bgColor, + LayoutOutlineColor = Color.Gray, LayoutFillColor = bgColor, BoundingBoxColor = bgColor, RapidColor = Color.DodgerBlue, @@ -183,10 +288,11 @@ namespace OpenNest.Forms }; var cell = new BestFitCell(colorScheme); + cell.PartColor = partColor; cell.Dock = DockStyle.Fill; cell.Plate.Size = new Geometry.Size( - result.BoundingWidth, - result.BoundingHeight); + result.BoundingHeight, + result.BoundingWidth); var parts = result.BuildParts(drawing); @@ -204,5 +310,14 @@ namespace OpenNest.Forms return cell; } + + private struct ComputeResult + { + public List Results; + public int TotalResults; + public int KeptCount; + public double ComputeSeconds; + public double TotalSeconds; + } } } diff --git a/OpenNest/Forms/MainForm.cs b/OpenNest/Forms/MainForm.cs index 463dff2..e22f0be 100644 --- a/OpenNest/Forms/MainForm.cs +++ b/OpenNest/Forms/MainForm.cs @@ -576,22 +576,20 @@ namespace OpenNest.Forms return; var plate = activeForm.PlateView.Plate; - var drawing = activeForm.Nest.Drawings.Count > 0 - ? activeForm.Nest.Drawings.First() - : null; + var drawings = activeForm.Nest.Drawings; - if (drawing == null) + if (drawings.Count == 0) { MessageBox.Show("No drawings available.", "Best-Fit Viewer", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } - using (var form = new BestFitViewerForm(drawing, plate)) + using (var form = new BestFitViewerForm(drawings, plate)) { if (form.ShowDialog(this) == DialogResult.OK && form.SelectedResult != null) { - var parts = form.SelectedResult.BuildParts(drawing); + var parts = form.SelectedResult.BuildParts(form.SelectedDrawing); activeForm.PlateView.SetAction(typeof(ActionClone), parts); } }