From 36fd8df1ac448a47d6bc48f9e8edbcaa92ac00e5 Mon Sep 17 00:00:00 2001 From: AJ Date: Sun, 23 Nov 2025 18:03:35 -0500 Subject: [PATCH] Add bin grouping to consolidate identical bins in results MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements grouping of identical bins (same items in same order) to reduce visual clutter in the results grid. Instead of showing 10 identical bins as separate rows, they now appear as a single row with a count. - Add BinComparer for deep equality comparison of bins - Add BinGroup to represent grouped bins with count - Update ResultsForm to display grouped bins in grid - Add Count column to show how many of each bin pattern - Maintain original bins list for file export This improves readability when the optimizer generates multiple identical cutting patterns. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- CutList/Forms/ResultsForm.Designer.cs | 18 +++- CutList/Forms/ResultsForm.cs | 21 +++-- SawCut/BinComparer.cs | 113 ++++++++++++++++++++++++++ SawCut/BinGroup.cs | 51 ++++++++++++ 4 files changed, 192 insertions(+), 11 deletions(-) create mode 100644 SawCut/BinComparer.cs create mode 100644 SawCut/BinGroup.cs diff --git a/CutList/Forms/ResultsForm.Designer.cs b/CutList/Forms/ResultsForm.Designer.cs index af422ff..9c654d5 100644 --- a/CutList/Forms/ResultsForm.Designer.cs +++ b/CutList/Forms/ResultsForm.Designer.cs @@ -31,6 +31,7 @@ this.components = new System.ComponentModel.Container(); System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle1 = new System.Windows.Forms.DataGridViewCellStyle(); this.dataGridView1 = new System.Windows.Forms.DataGridView(); + this.countDataGridViewTextBoxColumn = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.spacingDataGridViewTextBoxColumn = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.lengthDataGridViewTextBoxColumn = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.usedLengthDataGridViewTextBoxColumn = new System.Windows.Forms.DataGridViewTextBoxColumn(); @@ -68,6 +69,7 @@ this.dataGridView1.ColumnHeadersBorderStyle = System.Windows.Forms.DataGridViewHeaderBorderStyle.Single; this.dataGridView1.ColumnHeadersHeight = 30; this.dataGridView1.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { + this.countDataGridViewTextBoxColumn, this.spacingDataGridViewTextBoxColumn, this.lengthDataGridViewTextBoxColumn, this.usedLengthDataGridViewTextBoxColumn, @@ -83,9 +85,16 @@ this.dataGridView1.Size = new System.Drawing.Size(994, 253); this.dataGridView1.TabIndex = 0; this.dataGridView1.RowEnter += new System.Windows.Forms.DataGridViewCellEventHandler(this.dataGridView1_RowEnter); - // + // + // countDataGridViewTextBoxColumn + // + this.countDataGridViewTextBoxColumn.DataPropertyName = "Count"; + this.countDataGridViewTextBoxColumn.HeaderText = "Count"; + this.countDataGridViewTextBoxColumn.Name = "countDataGridViewTextBoxColumn"; + this.countDataGridViewTextBoxColumn.Width = 60; + // // spacingDataGridViewTextBoxColumn - // + // this.spacingDataGridViewTextBoxColumn.DataPropertyName = "Spacing"; this.spacingDataGridViewTextBoxColumn.HeaderText = "Spacing"; this.spacingDataGridViewTextBoxColumn.Name = "spacingDataGridViewTextBoxColumn"; @@ -121,8 +130,8 @@ this.utilizationDataGridViewTextBoxColumn.ReadOnly = true; // // binBindingSource - // - this.binBindingSource.DataSource = typeof(SawCut.Bin); + // + this.binBindingSource.DataSource = typeof(SawCut.BinGroup); // // splitContainer1 // @@ -260,6 +269,7 @@ private System.Windows.Forms.BindingSource binBindingSource; private System.Windows.Forms.SplitContainer splitContainer1; private System.Windows.Forms.BindingSource uIItemBindingSource; + private System.Windows.Forms.DataGridViewTextBoxColumn countDataGridViewTextBoxColumn; private System.Windows.Forms.DataGridViewTextBoxColumn spacingDataGridViewTextBoxColumn; private System.Windows.Forms.DataGridViewTextBoxColumn lengthDataGridViewTextBoxColumn; private System.Windows.Forms.DataGridViewTextBoxColumn usedLengthDataGridViewTextBoxColumn; diff --git a/CutList/Forms/ResultsForm.cs b/CutList/Forms/ResultsForm.cs index afdfb4b..0cfdda9 100644 --- a/CutList/Forms/ResultsForm.cs +++ b/CutList/Forms/ResultsForm.cs @@ -11,6 +11,7 @@ namespace CutList.Forms public partial class ResultsForm : Form { private string filename; + private List _originalBins = new List(); public ResultsForm(string filename) { @@ -23,21 +24,27 @@ namespace CutList.Forms private void dataGridView1_RowEnter(object sender, DataGridViewCellEventArgs e) { - var selectedBin = dataGridView1.Rows[e.RowIndex].DataBoundItem as Bin; + var selectedGroup = dataGridView1.Rows[e.RowIndex].DataBoundItem as BinGroup; - if (selectedBin == null) + if (selectedGroup == null) return; - binLayoutView1.Bin = selectedBin; + var representativeBin = selectedGroup.RepresentativeBin; + binLayoutView1.Bin = representativeBin; binLayoutView1.Invalidate(); - dataGridView2.DataSource = selectedBin.Items; + dataGridView2.DataSource = representativeBin.Items; } public List Bins { - get { return dataGridView1.DataSource as List; } - set { dataGridView1.DataSource = value; } + get { return _originalBins; } + set + { + _originalBins = value; + var groupedBins = BinGroupingHelper.GroupIdenticalBins(value); + dataGridView1.DataSource = groupedBins; + } } private void saveToolStripMenuItem_Click(object sender, EventArgs e) @@ -47,7 +54,7 @@ namespace CutList.Forms public void Save(string filepath) { - var writer = new BinFileSaver(Bins); + var writer = new BinFileSaver(_originalBins); writer.SaveBinsToFile(filepath); } diff --git a/SawCut/BinComparer.cs b/SawCut/BinComparer.cs new file mode 100644 index 0000000..37fc8bc --- /dev/null +++ b/SawCut/BinComparer.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace SawCut +{ + /// + /// Compares bins to determine if they are identical (same items in same order). + /// + public class BinComparer : IEqualityComparer + { + public bool Equals(Bin x, Bin y) + { + if (ReferenceEquals(x, y)) return true; + if (x == null || y == null) return false; + + // Check basic properties + if (Math.Abs(x.Length - y.Length) > 0.0001) return false; + if (Math.Abs(x.Spacing - y.Spacing) > 0.0001) return false; + + // Check item count + if (x.Items.Count != y.Items.Count) return false; + + // Check each item in order + for (int i = 0; i < x.Items.Count; i++) + { + var itemX = x.Items[i]; + var itemY = y.Items[i]; + + if (itemX.Name != itemY.Name) return false; + if (Math.Abs(itemX.Length - itemY.Length) > 0.0001) return false; + } + + return true; + } + + public int GetHashCode(Bin bin) + { + if (bin == null) return 0; + + unchecked + { + int hash = 17; + hash = hash * 23 + bin.Length.GetHashCode(); + hash = hash * 23 + bin.Spacing.GetHashCode(); + hash = hash * 23 + bin.Items.Count.GetHashCode(); + + // Include first and last item in hash for better distribution + if (bin.Items.Count > 0) + { + var firstItem = bin.Items[0]; + hash = hash * 23 + (firstItem.Name?.GetHashCode() ?? 0); + hash = hash * 23 + firstItem.Length.GetHashCode(); + + if (bin.Items.Count > 1) + { + var lastItem = bin.Items[bin.Items.Count - 1]; + hash = hash * 23 + (lastItem.Name?.GetHashCode() ?? 0); + hash = hash * 23 + lastItem.Length.GetHashCode(); + } + } + + return hash; + } + } + } + + /// + /// Helper methods for grouping bins. + /// + public static class BinGroupingHelper + { + /// + /// Groups identical bins together and returns a list of BinGroup objects. + /// + public static List GroupIdenticalBins(List bins) + { + if (bins == null || bins.Count == 0) + return new List(); + + var comparer = new BinComparer(); + var groups = new List(); + var processed = new HashSet(); + + for (int i = 0; i < bins.Count; i++) + { + if (processed.Contains(i)) + continue; + + var currentBin = bins[i]; + int count = 1; + + // Find all identical bins + for (int j = i + 1; j < bins.Count; j++) + { + if (processed.Contains(j)) + continue; + + if (comparer.Equals(currentBin, bins[j])) + { + count++; + processed.Add(j); + } + } + + processed.Add(i); + groups.Add(new BinGroup(currentBin, count)); + } + + return groups; + } + } +} diff --git a/SawCut/BinGroup.cs b/SawCut/BinGroup.cs new file mode 100644 index 0000000..ec115e7 --- /dev/null +++ b/SawCut/BinGroup.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace SawCut +{ + /// + /// Represents a group of identical bins (bins with the same items in the same order). + /// Used for displaying consolidated results. + /// + public class BinGroup + { + public BinGroup(Bin representativeBin, int count) + { + if (representativeBin == null) + throw new ArgumentNullException(nameof(representativeBin)); + + if (count <= 0) + throw new ArgumentException("Count must be greater than zero", nameof(count)); + + RepresentativeBin = representativeBin; + Count = count; + } + + /// + /// A representative bin from this group (all bins in the group are identical). + /// + public Bin RepresentativeBin { get; } + + /// + /// The number of identical bins in this group. + /// + public int Count { get; } + + // Properties that delegate to the representative bin for data binding + public double Spacing => RepresentativeBin.Spacing; + public double Length => RepresentativeBin.Length; + public double UsedLength => RepresentativeBin.UsedLength; + public double RemainingLength => RepresentativeBin.RemainingLength; + public double Utilization => RepresentativeBin.Utilization; + public IReadOnlyList Items => RepresentativeBin.Items; + + public override string ToString() + { + if (Count == 1) + return RepresentativeBin.ToString(); + + return $"{RepresentativeBin} (x{Count})"; + } + } +}