Add bin grouping to consolidate identical bins in results

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 <noreply@anthropic.com>
This commit is contained in:
AJ
2025-11-23 18:03:35 -05:00
parent 1c8a9e8315
commit 36fd8df1ac
4 changed files with 192 additions and 11 deletions

View File

@@ -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,
@@ -84,6 +86,13 @@
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";
@@ -122,7 +131,7 @@
//
// 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;

View File

@@ -11,6 +11,7 @@ namespace CutList.Forms
public partial class ResultsForm : Form
{
private string filename;
private List<Bin> _originalBins = new List<Bin>();
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<Bin> Bins
{
get { return dataGridView1.DataSource as List<Bin>; }
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);
}

113
SawCut/BinComparer.cs Normal file
View File

@@ -0,0 +1,113 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace SawCut
{
/// <summary>
/// Compares bins to determine if they are identical (same items in same order).
/// </summary>
public class BinComparer : IEqualityComparer<Bin>
{
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;
}
}
}
/// <summary>
/// Helper methods for grouping bins.
/// </summary>
public static class BinGroupingHelper
{
/// <summary>
/// Groups identical bins together and returns a list of BinGroup objects.
/// </summary>
public static List<BinGroup> GroupIdenticalBins(List<Bin> bins)
{
if (bins == null || bins.Count == 0)
return new List<BinGroup>();
var comparer = new BinComparer();
var groups = new List<BinGroup>();
var processed = new HashSet<int>();
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;
}
}
}

51
SawCut/BinGroup.cs Normal file
View File

@@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace SawCut
{
/// <summary>
/// Represents a group of identical bins (bins with the same items in the same order).
/// Used for displaying consolidated results.
/// </summary>
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;
}
/// <summary>
/// A representative bin from this group (all bins in the group are identical).
/// </summary>
public Bin RepresentativeBin { get; }
/// <summary>
/// The number of identical bins in this group.
/// </summary>
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<BinItem> Items => RepresentativeBin.Items;
public override string ToString()
{
if (Count == 1)
return RepresentativeBin.ToString();
return $"{RepresentativeBin} (x{Count})";
}
}
}