Compare commits
44 Commits
04494a6744
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 21d50e7c20 | |||
| f723661696 | |||
| c795c129e5 | |||
| 30071469bc | |||
| c9a2583f26 | |||
| 0e5b63c557 | |||
| 6388e003d3 | |||
| c5da5dda98 | |||
| 21cddb22c7 | |||
| 3b036308c8 | |||
| 4f6d986dc9 | |||
| 254066c989 | |||
| ce14dd50cb | |||
| dfc767320a | |||
| 5cc088ea6b | |||
| 6797d1e4fd | |||
| c4fc88f7d2 | |||
| 9929d82768 | |||
| 0ded77ce8b | |||
| 8e73d630d5 | |||
| 079f5b1085 | |||
| 97fa90357b | |||
| bf6c4764ed | |||
| ed911a13ba | |||
| c99de55fe1 | |||
| 8b16cbd79f | |||
| cad5ab790a | |||
| f8020549fe | |||
| 66ed19a1ac | |||
| 051b866c6d | |||
| 3d80adbfff | |||
| b7b98d4338 | |||
| ced272d3e3 | |||
| 35b26e673e | |||
| cca569ae81 | |||
| fa36d82285 | |||
| b0c9470bb7 | |||
| 9868df162d | |||
| 6db8ab21f4 | |||
| b19ecf3610 | |||
| 6e8469be4b | |||
| 046976c429 | |||
| 4d208f6411 | |||
| 88d67336d9 |
BIN
.claude/mcp/Azure.Core.dll
Normal file
BIN
.claude/mcp/Azure.Core.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/Azure.Identity.dll
Normal file
BIN
.claude/mcp/Azure.Identity.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/CutList.Core.dll
Normal file
BIN
.claude/mcp/CutList.Core.dll
Normal file
Binary file not shown.
1399
.claude/mcp/CutList.Mcp.deps.json
Normal file
1399
.claude/mcp/CutList.Mcp.deps.json
Normal file
File diff suppressed because it is too large
Load Diff
BIN
.claude/mcp/CutList.Mcp.dll
Normal file
BIN
.claude/mcp/CutList.Mcp.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/CutList.Mcp.exe
Normal file
BIN
.claude/mcp/CutList.Mcp.exe
Normal file
Binary file not shown.
20
.claude/mcp/CutList.Mcp.runtimeconfig.json
Normal file
20
.claude/mcp/CutList.Mcp.runtimeconfig.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"runtimeOptions": {
|
||||
"tfm": "net8.0",
|
||||
"frameworks": [
|
||||
{
|
||||
"name": "Microsoft.NETCore.App",
|
||||
"version": "8.0.0"
|
||||
},
|
||||
{
|
||||
"name": "Microsoft.AspNetCore.App",
|
||||
"version": "8.0.0"
|
||||
}
|
||||
],
|
||||
"configProperties": {
|
||||
"System.Reflection.Metadata.MetadataUpdater.IsSupported": false,
|
||||
"System.Reflection.NullabilityInfoContext.IsSupported": true,
|
||||
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
.claude/mcp/CutList.Web.dll
Normal file
BIN
.claude/mcp/CutList.Web.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/CutList.Web.exe
Normal file
BIN
.claude/mcp/CutList.Web.exe
Normal file
Binary file not shown.
21
.claude/mcp/CutList.Web.runtimeconfig.json
Normal file
21
.claude/mcp/CutList.Web.runtimeconfig.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"runtimeOptions": {
|
||||
"tfm": "net8.0",
|
||||
"frameworks": [
|
||||
{
|
||||
"name": "Microsoft.NETCore.App",
|
||||
"version": "8.0.0"
|
||||
},
|
||||
{
|
||||
"name": "Microsoft.AspNetCore.App",
|
||||
"version": "8.0.0"
|
||||
}
|
||||
],
|
||||
"configProperties": {
|
||||
"System.GC.Server": true,
|
||||
"System.Reflection.Metadata.MetadataUpdater.IsSupported": false,
|
||||
"System.Reflection.NullabilityInfoContext.IsSupported": true,
|
||||
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
.claude/mcp/Microsoft.Bcl.AsyncInterfaces.dll
Normal file
BIN
.claude/mcp/Microsoft.Bcl.AsyncInterfaces.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/Microsoft.Data.SqlClient.dll
Normal file
BIN
.claude/mcp/Microsoft.Data.SqlClient.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/Microsoft.EntityFrameworkCore.Abstractions.dll
Normal file
BIN
.claude/mcp/Microsoft.EntityFrameworkCore.Abstractions.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/Microsoft.EntityFrameworkCore.Relational.dll
Normal file
BIN
.claude/mcp/Microsoft.EntityFrameworkCore.Relational.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/Microsoft.EntityFrameworkCore.SqlServer.dll
Normal file
BIN
.claude/mcp/Microsoft.EntityFrameworkCore.SqlServer.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/Microsoft.EntityFrameworkCore.dll
Normal file
BIN
.claude/mcp/Microsoft.EntityFrameworkCore.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/Microsoft.Extensions.AI.Abstractions.dll
Normal file
BIN
.claude/mcp/Microsoft.Extensions.AI.Abstractions.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/Microsoft.Extensions.Caching.Memory.dll
Normal file
BIN
.claude/mcp/Microsoft.Extensions.Caching.Memory.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/Microsoft.Extensions.Configuration.Abstractions.dll
Normal file
BIN
.claude/mcp/Microsoft.Extensions.Configuration.Abstractions.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/Microsoft.Extensions.Configuration.Binder.dll
Normal file
BIN
.claude/mcp/Microsoft.Extensions.Configuration.Binder.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/Microsoft.Extensions.Configuration.CommandLine.dll
Normal file
BIN
.claude/mcp/Microsoft.Extensions.Configuration.CommandLine.dll
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
.claude/mcp/Microsoft.Extensions.Configuration.Json.dll
Normal file
BIN
.claude/mcp/Microsoft.Extensions.Configuration.Json.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/Microsoft.Extensions.Configuration.UserSecrets.dll
Normal file
BIN
.claude/mcp/Microsoft.Extensions.Configuration.UserSecrets.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/Microsoft.Extensions.Configuration.dll
Normal file
BIN
.claude/mcp/Microsoft.Extensions.Configuration.dll
Normal file
Binary file not shown.
Binary file not shown.
BIN
.claude/mcp/Microsoft.Extensions.DependencyInjection.dll
Normal file
BIN
.claude/mcp/Microsoft.Extensions.DependencyInjection.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/Microsoft.Extensions.Diagnostics.Abstractions.dll
Normal file
BIN
.claude/mcp/Microsoft.Extensions.Diagnostics.Abstractions.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/Microsoft.Extensions.Diagnostics.dll
Normal file
BIN
.claude/mcp/Microsoft.Extensions.Diagnostics.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/Microsoft.Extensions.FileProviders.Abstractions.dll
Normal file
BIN
.claude/mcp/Microsoft.Extensions.FileProviders.Abstractions.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/Microsoft.Extensions.FileProviders.Physical.dll
Normal file
BIN
.claude/mcp/Microsoft.Extensions.FileProviders.Physical.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/Microsoft.Extensions.FileSystemGlobbing.dll
Normal file
BIN
.claude/mcp/Microsoft.Extensions.FileSystemGlobbing.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/Microsoft.Extensions.Hosting.Abstractions.dll
Normal file
BIN
.claude/mcp/Microsoft.Extensions.Hosting.Abstractions.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/Microsoft.Extensions.Hosting.dll
Normal file
BIN
.claude/mcp/Microsoft.Extensions.Hosting.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/Microsoft.Extensions.Logging.Abstractions.dll
Normal file
BIN
.claude/mcp/Microsoft.Extensions.Logging.Abstractions.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/Microsoft.Extensions.Logging.Configuration.dll
Normal file
BIN
.claude/mcp/Microsoft.Extensions.Logging.Configuration.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/Microsoft.Extensions.Logging.Console.dll
Normal file
BIN
.claude/mcp/Microsoft.Extensions.Logging.Console.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/Microsoft.Extensions.Logging.Debug.dll
Normal file
BIN
.claude/mcp/Microsoft.Extensions.Logging.Debug.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/Microsoft.Extensions.Logging.EventLog.dll
Normal file
BIN
.claude/mcp/Microsoft.Extensions.Logging.EventLog.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/Microsoft.Extensions.Logging.EventSource.dll
Normal file
BIN
.claude/mcp/Microsoft.Extensions.Logging.EventSource.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/Microsoft.Extensions.Logging.dll
Normal file
BIN
.claude/mcp/Microsoft.Extensions.Logging.dll
Normal file
Binary file not shown.
Binary file not shown.
BIN
.claude/mcp/Microsoft.Extensions.Options.dll
Normal file
BIN
.claude/mcp/Microsoft.Extensions.Options.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/Microsoft.Extensions.Primitives.dll
Normal file
BIN
.claude/mcp/Microsoft.Extensions.Primitives.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/Microsoft.Identity.Client.Extensions.Msal.dll
Normal file
BIN
.claude/mcp/Microsoft.Identity.Client.Extensions.Msal.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/Microsoft.Identity.Client.dll
Normal file
BIN
.claude/mcp/Microsoft.Identity.Client.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/Microsoft.IdentityModel.Abstractions.dll
Normal file
BIN
.claude/mcp/Microsoft.IdentityModel.Abstractions.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/Microsoft.IdentityModel.JsonWebTokens.dll
Normal file
BIN
.claude/mcp/Microsoft.IdentityModel.JsonWebTokens.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/Microsoft.IdentityModel.Logging.dll
Normal file
BIN
.claude/mcp/Microsoft.IdentityModel.Logging.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/Microsoft.IdentityModel.Protocols.OpenIdConnect.dll
Normal file
BIN
.claude/mcp/Microsoft.IdentityModel.Protocols.OpenIdConnect.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/Microsoft.IdentityModel.Protocols.dll
Normal file
BIN
.claude/mcp/Microsoft.IdentityModel.Protocols.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/Microsoft.IdentityModel.Tokens.dll
Normal file
BIN
.claude/mcp/Microsoft.IdentityModel.Tokens.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/Microsoft.SqlServer.Server.dll
Normal file
BIN
.claude/mcp/Microsoft.SqlServer.Server.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/Microsoft.Win32.SystemEvents.dll
Normal file
BIN
.claude/mcp/Microsoft.Win32.SystemEvents.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/ModelContextProtocol.Core.dll
Normal file
BIN
.claude/mcp/ModelContextProtocol.Core.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/ModelContextProtocol.dll
Normal file
BIN
.claude/mcp/ModelContextProtocol.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/System.ClientModel.dll
Normal file
BIN
.claude/mcp/System.ClientModel.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/System.Configuration.ConfigurationManager.dll
Normal file
BIN
.claude/mcp/System.Configuration.ConfigurationManager.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/System.Diagnostics.DiagnosticSource.dll
Normal file
BIN
.claude/mcp/System.Diagnostics.DiagnosticSource.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/System.Diagnostics.EventLog.dll
Normal file
BIN
.claude/mcp/System.Diagnostics.EventLog.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/System.Drawing.Common.dll
Normal file
BIN
.claude/mcp/System.Drawing.Common.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/System.IO.Pipelines.dll
Normal file
BIN
.claude/mcp/System.IO.Pipelines.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/System.IdentityModel.Tokens.Jwt.dll
Normal file
BIN
.claude/mcp/System.IdentityModel.Tokens.Jwt.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/System.Memory.Data.dll
Normal file
BIN
.claude/mcp/System.Memory.Data.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/System.Net.ServerSentEvents.dll
Normal file
BIN
.claude/mcp/System.Net.ServerSentEvents.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/System.Runtime.Caching.dll
Normal file
BIN
.claude/mcp/System.Runtime.Caching.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/System.Security.Cryptography.ProtectedData.dll
Normal file
BIN
.claude/mcp/System.Security.Cryptography.ProtectedData.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/System.Security.Permissions.dll
Normal file
BIN
.claude/mcp/System.Security.Permissions.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/System.Text.Encodings.Web.dll
Normal file
BIN
.claude/mcp/System.Text.Encodings.Web.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/System.Text.Json.dll
Normal file
BIN
.claude/mcp/System.Text.Json.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/System.Windows.Extensions.dll
Normal file
BIN
.claude/mcp/System.Windows.Extensions.dll
Normal file
Binary file not shown.
8
.claude/mcp/appsettings.Development.json
Normal file
8
.claude/mcp/appsettings.Development.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
13
.claude/mcp/appsettings.json
Normal file
13
.claude/mcp/appsettings.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning",
|
||||
"Microsoft.EntityFrameworkCore.Database.Command": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=CutListDb;Trusted_Connection=True;MultipleActiveResultSets=true"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
BIN
.claude/mcp/runtimes/unix/lib/net6.0/System.Drawing.Common.dll
Normal file
BIN
.claude/mcp/runtimes/unix/lib/net6.0/System.Drawing.Common.dll
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
.claude/mcp/runtimes/win/lib/net6.0/Microsoft.Data.SqlClient.dll
Normal file
BIN
.claude/mcp/runtimes/win/lib/net6.0/Microsoft.Data.SqlClient.dll
Normal file
Binary file not shown.
Binary file not shown.
BIN
.claude/mcp/runtimes/win/lib/net6.0/System.Drawing.Common.dll
Normal file
BIN
.claude/mcp/runtimes/win/lib/net6.0/System.Drawing.Common.dll
Normal file
Binary file not shown.
BIN
.claude/mcp/runtimes/win/lib/net6.0/System.Runtime.Caching.dll
Normal file
BIN
.claude/mcp/runtimes/win/lib/net6.0/System.Runtime.Caching.dll
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
28
.claude/settings.local.json
Normal file
28
.claude/settings.local.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Skill(roslyn-bridge)",
|
||||
"Bash(dotnet build:*)",
|
||||
"SlashCommand(/rb)",
|
||||
"mcp__roslyn-bridge__get_projects",
|
||||
"Bash(dotnet tool install:*)",
|
||||
"Bash(dotnet ilspy:*)",
|
||||
"Bash(dotnet add package:*)",
|
||||
"Bash(git -C /c/Users/AJ/Desktop/Projects/CutList add CutList.Core/BinComparer.cs CutList.Core/BinItem.cs CutList.Core/MultiBin.cs CutList/Tool.cs)",
|
||||
"Bash(git -C /c/Users/AJ/Desktop/Projects/CutList commit --amend --no-edit)",
|
||||
"mcp__roslyn-bridge__get_code_smells",
|
||||
"mcp__roslyn-bridge__get_duplicates",
|
||||
"mcp__roslyn-bridge__get_code_smell_summary",
|
||||
"mcp__cutlist__create_cutlist",
|
||||
"Bash(dotnet run:*)",
|
||||
"mcp__roslyn-bridge__get_files",
|
||||
"mcp__roslyn-bridge__refresh_workspace",
|
||||
"mcp__roslyn-bridge__get_diagnostics",
|
||||
"Bash(dotnet ef database update:*)",
|
||||
"mcp__roslyn-bridge__search_symbol",
|
||||
"Bash(dotnet ef migrations add:*)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
}
|
||||
}
|
||||
@@ -36,23 +36,25 @@ namespace CutList.Core
|
||||
|
||||
unchecked
|
||||
{
|
||||
// Prime multiplier reduces collisions in hash-based collections
|
||||
const int HashMultiplier = 23;
|
||||
int hash = 17;
|
||||
hash = hash * 23 + bin.Length.GetHashCode();
|
||||
hash = hash * 23 + bin.Spacing.GetHashCode();
|
||||
hash = hash * 23 + bin.Items.Count.GetHashCode();
|
||||
hash = hash * HashMultiplier + bin.Length.GetHashCode();
|
||||
hash = hash * HashMultiplier + bin.Spacing.GetHashCode();
|
||||
hash = hash * HashMultiplier + 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();
|
||||
hash = hash * HashMultiplier + (firstItem.Name?.GetHashCode() ?? 0);
|
||||
hash = hash * HashMultiplier + 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();
|
||||
hash = hash * HashMultiplier + (lastItem.Name?.GetHashCode() ?? 0);
|
||||
hash = hash * HashMultiplier + lastItem.Length.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
171
CutList.Core/BinFileSaver.cs
Normal file
171
CutList.Core/BinFileSaver.cs
Normal file
@@ -0,0 +1,171 @@
|
||||
using CutList.Core.Formatting;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace CutList.Core
|
||||
{
|
||||
public class BinFileSaver
|
||||
{
|
||||
private readonly IEnumerable<Bin> _bins;
|
||||
|
||||
private int PaddingWidthOfItemLength { get; set; }
|
||||
|
||||
public bool OpenFileAfterSave { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The cutting method/tool used (e.g., "Miter Saw", "Table Saw").
|
||||
/// </summary>
|
||||
public string? CutMethod { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The material shape (e.g., "Round Tube", "Square Tube", "Flat Bar").
|
||||
/// </summary>
|
||||
public string? MaterialShape { get; set; }
|
||||
|
||||
public BinFileSaver(IEnumerable<Bin> bins)
|
||||
{
|
||||
_bins = bins ?? throw new ArgumentNullException(nameof(bins));
|
||||
}
|
||||
|
||||
public void SaveBinsToFile(string file)
|
||||
{
|
||||
using (var writer = new StreamWriter(file))
|
||||
{
|
||||
writer.AutoFlush = true;
|
||||
WriteToWriter(writer);
|
||||
}
|
||||
|
||||
if (OpenFileAfterSave)
|
||||
{
|
||||
OpenFile(file);
|
||||
}
|
||||
}
|
||||
|
||||
public string GenerateReport()
|
||||
{
|
||||
using var writer = new StringWriter();
|
||||
WriteToWriter(writer);
|
||||
return writer.ToString();
|
||||
}
|
||||
|
||||
private void WriteToWriter(TextWriter writer)
|
||||
{
|
||||
PaddingWidthOfItemLength = _bins
|
||||
.SelectMany(b => b.Items)
|
||||
.Select(i => FormatHelper.ConvertToMixedFraction(i.Length).Length)
|
||||
.DefaultIfEmpty(0)
|
||||
.Max();
|
||||
|
||||
WriteHeader(writer);
|
||||
|
||||
var id = 1;
|
||||
foreach (var bin in _bins)
|
||||
{
|
||||
WriteBinSummary(writer, bin, id++);
|
||||
}
|
||||
|
||||
WriteFinalSummary(writer);
|
||||
}
|
||||
|
||||
private void OpenFile(string file)
|
||||
{
|
||||
try
|
||||
{
|
||||
Process.Start("notepad.exe", file);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Process.Start(file);
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteHeader(TextWriter writer)
|
||||
{
|
||||
var totalBars = _bins.Count();
|
||||
var totalItems = _bins.Sum(b => b.Items.Count);
|
||||
|
||||
writer.WriteLine("CUT LIST");
|
||||
WriteSeparator(writer, '=');
|
||||
writer.WriteLine();
|
||||
WriteAlignedLine(writer, "Date", DateTime.Now.ToString("g"));
|
||||
|
||||
if (!string.IsNullOrEmpty(CutMethod))
|
||||
WriteAlignedLine(writer, "Cut Method", CutMethod);
|
||||
|
||||
if (!string.IsNullOrEmpty(MaterialShape))
|
||||
WriteAlignedLine(writer, "Material", MaterialShape);
|
||||
|
||||
WriteAlignedLine(writer, "Stock Bars Needed", totalBars.ToString());
|
||||
WriteAlignedLine(writer, "Total Pieces", totalItems.ToString());
|
||||
writer.WriteLine();
|
||||
}
|
||||
|
||||
private static void WriteAlignedLine(TextWriter writer, string label, string value, int labelWidth = 20)
|
||||
{
|
||||
writer.WriteLine($" {label.PadRight(labelWidth)} {value}");
|
||||
}
|
||||
|
||||
private void WriteBinSummary(TextWriter writer, Bin bin, int id)
|
||||
{
|
||||
var stockLength = FormatHelper.ConvertToMixedFraction(bin.Length);
|
||||
var dropLength = FormatHelper.ConvertToMixedFraction(bin.RemainingLength);
|
||||
|
||||
WriteSeparator(writer);
|
||||
|
||||
writer.WriteLine($"BAR #{id} - Length: {stockLength}\"");
|
||||
writer.WriteLine();
|
||||
writer.WriteLine(" Cut these pieces:");
|
||||
|
||||
WriteBinItems(writer, bin);
|
||||
|
||||
writer.WriteLine();
|
||||
writer.WriteLine($" Remaining drop: {dropLength}\"");
|
||||
writer.WriteLine();
|
||||
}
|
||||
|
||||
private static void WriteSeparator(TextWriter writer, char c = '─', int length = 50)
|
||||
{
|
||||
writer.WriteLine(new string(c, length));
|
||||
}
|
||||
|
||||
private void WriteBinItems(TextWriter writer, Bin bin)
|
||||
{
|
||||
var groups = bin.Items
|
||||
.GroupBy(i => new { i.Name, i.Length })
|
||||
.OrderBy(g => g.Key.Name)
|
||||
.ThenBy(g => g.Key.Length);
|
||||
|
||||
var lengthHeader = "LENGTH".PadLeft(PaddingWidthOfItemLength + 1);
|
||||
writer.WriteLine($" QTY {lengthHeader} LABEL");
|
||||
|
||||
foreach (var group in groups)
|
||||
{
|
||||
var first = group.First();
|
||||
var count = group.Count();
|
||||
var length = FormatHelper
|
||||
.ConvertToMixedFraction(first.Length)
|
||||
.PadLeft(PaddingWidthOfItemLength) + "\"";
|
||||
|
||||
writer.WriteLine($" {count,3} {length} {first.Name}");
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteFinalSummary(TextWriter writer)
|
||||
{
|
||||
var totalBars = _bins.Count();
|
||||
var totalItems = _bins.Sum(b => b.Items.Count);
|
||||
var totalStock = _bins.Sum(b => b.Length);
|
||||
var totalDrop = _bins.Sum(b => b.RemainingLength);
|
||||
|
||||
string fmt(double v) => FormatHelper.ConvertToMixedFraction(v);
|
||||
|
||||
WriteSeparator(writer, '=');
|
||||
writer.WriteLine("SUMMARY");
|
||||
writer.WriteLine();
|
||||
WriteAlignedLine(writer, "Stock Bars Needed", totalBars.ToString());
|
||||
WriteAlignedLine(writer, "Total Pieces", totalItems.ToString());
|
||||
WriteAlignedLine(writer, "Total Material Used", $"{fmt(totalStock)}\"");
|
||||
WriteAlignedLine(writer, "Total Drop/Waste", $"{fmt(totalDrop)}\"");
|
||||
WriteSeparator(writer, '=');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,9 +77,11 @@
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
// Prime multiplier reduces collisions in hash-based collections
|
||||
const int HashMultiplier = 23;
|
||||
int hash = 17;
|
||||
hash = hash * 23 + (Name?.GetHashCode() ?? 0);
|
||||
hash = hash * 23 + Length.GetHashCode();
|
||||
hash = hash * HashMultiplier + (Name?.GetHashCode() ?? 0);
|
||||
hash = hash * HashMultiplier + Length.GetHashCode();
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,17 @@ namespace CutList.Core.Formatting
|
||||
var match2 = regex.Match(input);
|
||||
|
||||
if (!match2.Success)
|
||||
{
|
||||
// If no unit symbols, try to parse as plain inches (e.g., "0.5" or "1/2" converted to "0.5")
|
||||
if (!input.Contains("'") && !input.Contains("\""))
|
||||
{
|
||||
if (double.TryParse(input.Trim(), out var plainInches))
|
||||
{
|
||||
return Math.Round(plainInches, 8);
|
||||
}
|
||||
}
|
||||
throw new Exception("Input is not in a valid format.");
|
||||
}
|
||||
|
||||
var feet = match2.Groups["Feet"];
|
||||
var inches = match2.Groups["Inches"];
|
||||
|
||||
@@ -39,6 +39,12 @@ namespace CutList.Core.Formatting
|
||||
return wholeNumber.ToString();
|
||||
}
|
||||
|
||||
// If whole number is 0, just show the fraction
|
||||
if (wholeNumber == 0)
|
||||
{
|
||||
return $"{numerator}/{denominator}";
|
||||
}
|
||||
|
||||
return $"{wholeNumber}-{numerator}/{denominator}";
|
||||
}
|
||||
|
||||
|
||||
@@ -112,10 +112,12 @@
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
// Prime multiplier reduces collisions in hash-based collections
|
||||
const int HashMultiplier = 23;
|
||||
int hash = 17;
|
||||
hash = hash * 23 + Quantity.GetHashCode();
|
||||
hash = hash * 23 + Length.GetHashCode();
|
||||
hash = hash * 23 + Priority.GetHashCode();
|
||||
hash = hash * HashMultiplier + Quantity.GetHashCode();
|
||||
hash = hash * HashMultiplier + Length.GetHashCode();
|
||||
hash = hash * HashMultiplier + Priority.GetHashCode();
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,248 +1,33 @@
|
||||
using System.Data;
|
||||
using CutList.Core.Nesting.Pipeline;
|
||||
|
||||
namespace CutList.Core.Nesting
|
||||
{
|
||||
/// <summary>
|
||||
/// Advanced bin packing engine using First-Fit Decreasing with optimization.
|
||||
/// This is a stateless engine that uses a composable pipeline of steps.
|
||||
/// </summary>
|
||||
public class AdvancedFitEngine : IEngine
|
||||
{
|
||||
private readonly PackingPipeline _pipeline;
|
||||
|
||||
public AdvancedFitEngine()
|
||||
{
|
||||
Bins = new List<Bin>();
|
||||
_pipeline = new PackingPipeline()
|
||||
.AddStep(new FilterOversizedItemsStep())
|
||||
.AddStep(new SortItemsDescendingStep())
|
||||
.AddStep(new FirstFitDecreasingStep())
|
||||
.AddStep(new OptimizationStep())
|
||||
.AddStep(new SortBinItemsStep())
|
||||
.AddStep(new DuplicateBinsStep())
|
||||
.AddStep(new SortBinsByUtilizationStep());
|
||||
}
|
||||
|
||||
public double StockLength { get; set; }
|
||||
|
||||
public double Spacing { get; set; }
|
||||
|
||||
public int MaxBinCount { get; set; } = int.MaxValue;
|
||||
|
||||
private List<BinItem> Items { get; set; }
|
||||
|
||||
private List<Bin> Bins { get; set; }
|
||||
|
||||
public Result Pack(List<BinItem> items)
|
||||
/// <summary>
|
||||
/// Packs items into bins using the FFD algorithm with optimization passes.
|
||||
/// </summary>
|
||||
public PackResult Pack(PackingRequest request)
|
||||
{
|
||||
if (StockLength <= 0)
|
||||
throw new Exception("Stock length must be greater than 0");
|
||||
|
||||
Items = items.OrderByDescending(i => i.Length).ToList();
|
||||
|
||||
var result = new Result();
|
||||
var itemsTooLarge = Items.Where(i => i.Length > StockLength).ToList();
|
||||
result.AddItemsNotUsed(itemsTooLarge);
|
||||
|
||||
Items.RemoveAll(item => itemsTooLarge.Contains(item));
|
||||
|
||||
CreateBins();
|
||||
|
||||
var finalItemsTooLarge = Items.Where(i => i.Length > StockLength).ToList();
|
||||
result.AddItemsNotUsed(finalItemsTooLarge);
|
||||
result.AddBins(Bins);
|
||||
|
||||
foreach (var bin in result.Bins)
|
||||
{
|
||||
foreach (var item in bin.Items)
|
||||
{
|
||||
Items.Remove(item);
|
||||
return _pipeline.Execute(request);
|
||||
}
|
||||
}
|
||||
|
||||
result.AddItemsNotUsed(Items);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void CreateBins()
|
||||
{
|
||||
while (Items.Count > 0 && CanAddMoreBins())
|
||||
{
|
||||
var bin = new Bin(StockLength)
|
||||
{
|
||||
Spacing = Spacing
|
||||
};
|
||||
|
||||
FillBin(bin);
|
||||
|
||||
while (TryImprovePacking(bin))
|
||||
{
|
||||
}
|
||||
|
||||
bin.SortItems((a, b) =>
|
||||
{
|
||||
int comparison = b.Length.CompareTo(a.Length);
|
||||
return comparison != 0 ? comparison : a.Length.CompareTo(b.Length);
|
||||
});
|
||||
|
||||
Bins.Add(bin);
|
||||
|
||||
CreateDuplicateBins(bin);
|
||||
}
|
||||
|
||||
Bins = Bins
|
||||
.OrderByDescending(b => b.Utilization)
|
||||
.ThenBy(b => b.Items.Count)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private bool CanAddMoreBins()
|
||||
{
|
||||
if (MaxBinCount == -1)
|
||||
return true;
|
||||
|
||||
if (Bins.Count < MaxBinCount)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void FillBin(Bin bin)
|
||||
{
|
||||
for (int i = 0; i < Items.Count; i++)
|
||||
{
|
||||
if (bin.RemainingLength >= Items[i].Length)
|
||||
{
|
||||
bin.AddItem(Items[i]);
|
||||
Items.RemoveAt(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateDuplicateBins(Bin originalBin)
|
||||
{
|
||||
// Count how many times the bin can be duplicated
|
||||
int duplicateCount = GetDuplicateCount(originalBin);
|
||||
|
||||
for (int i = 0; i < duplicateCount; i++)
|
||||
{
|
||||
if (!CanAddMoreBins())
|
||||
break;
|
||||
|
||||
var newBin = new Bin(originalBin.Length)
|
||||
{
|
||||
Spacing = Spacing
|
||||
};
|
||||
|
||||
foreach (var item in originalBin.Items)
|
||||
{
|
||||
var newItem = Items.FirstOrDefault(a => a.Length == item.Length);
|
||||
newBin.AddItem(newItem);
|
||||
Items.Remove(newItem);
|
||||
}
|
||||
|
||||
Bins.Add(newBin);
|
||||
}
|
||||
}
|
||||
|
||||
private int GetDuplicateCount(Bin bin)
|
||||
{
|
||||
int count = int.MaxValue;
|
||||
|
||||
foreach (var item in bin.Items.GroupBy(i => i.Length))
|
||||
{
|
||||
int availableCount = Items.Count(i => i.Length == item.Key);
|
||||
count = Math.Min(count, availableCount / item.Count());
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
private bool TryImprovePacking(Bin bin)
|
||||
{
|
||||
if (bin.Items.Count == 0)
|
||||
return false;
|
||||
|
||||
if (Items.Count < 2)
|
||||
return false;
|
||||
|
||||
var lengthGroups = GroupItemsByLength(bin.Items);
|
||||
|
||||
var shortestLengthItemAvailable = Items.Min(i => i.Length);
|
||||
|
||||
foreach (var group in lengthGroups)
|
||||
{
|
||||
var minRemainingLength = bin.RemainingLength;
|
||||
var firstItem = group.Items.FirstOrDefault();
|
||||
bin.RemoveItem(firstItem);
|
||||
|
||||
for (int i = 0; i < Items.Count; i++)
|
||||
{
|
||||
var item1 = Items[i];
|
||||
|
||||
if (Items[i].Length > bin.RemainingLength)
|
||||
continue;
|
||||
|
||||
var bin2 = new Bin(bin.RemainingLength);
|
||||
bin2.Spacing = bin.Spacing;
|
||||
bin2.AddItem(item1);
|
||||
|
||||
for (int j = i + 1; j < Items.Count; j++)
|
||||
{
|
||||
if (bin2.RemainingLength < shortestLengthItemAvailable)
|
||||
break;
|
||||
|
||||
var item2 = Items[j];
|
||||
|
||||
if (item2.Length > bin2.RemainingLength)
|
||||
continue;
|
||||
|
||||
bin2.AddItem(item2);
|
||||
}
|
||||
|
||||
if (bin2.RemainingLength < minRemainingLength)
|
||||
{
|
||||
Items.Add(firstItem);
|
||||
bin.AddItems(bin2.Items);
|
||||
|
||||
foreach (var item in bin2.Items)
|
||||
{
|
||||
Items.Remove(item);
|
||||
}
|
||||
|
||||
// improvement made
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bin.AddItem(firstItem);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private List<LengthGroup> GroupItemsByLength(IEnumerable<BinItem> items)
|
||||
{
|
||||
var groups = new List<LengthGroup>();
|
||||
var groupMap = new Dictionary<double, LengthGroup>();
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (!groupMap.TryGetValue(item.Length, out var group))
|
||||
{
|
||||
group = new LengthGroup
|
||||
{
|
||||
Length = item.Length,
|
||||
Items = new List<BinItem>()
|
||||
};
|
||||
groupMap[item.Length] = group;
|
||||
groups.Add(group);
|
||||
}
|
||||
group.Items.Add(item);
|
||||
}
|
||||
|
||||
groups.Sort((a, b) => b.Length.CompareTo(a.Length));
|
||||
if (groups.Count > 0)
|
||||
{
|
||||
groups.RemoveAt(0); // Remove the largest length group
|
||||
}
|
||||
|
||||
return groups;
|
||||
}
|
||||
}
|
||||
|
||||
internal class LengthGroup
|
||||
{
|
||||
public double Length { get; set; }
|
||||
public List<BinItem> Items { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,92 +1,70 @@
|
||||
using System.Data;
|
||||
|
||||
namespace CutList.Core.Nesting
|
||||
{
|
||||
/// <summary>
|
||||
/// Best-Fit Decreasing bin packing engine.
|
||||
/// Places each item in the bin with the least remaining space that can still fit it.
|
||||
/// This is a stateless engine - all state is passed via PackingRequest.
|
||||
/// </summary>
|
||||
public class BestFitEngine : IEngine
|
||||
{
|
||||
public double StockLength { get; set; }
|
||||
|
||||
public double Spacing { get; set; }
|
||||
|
||||
public int MaxBinCount { get; set; } = int.MaxValue;
|
||||
|
||||
private List<BinItem> Items { get; set; }
|
||||
|
||||
public Result Pack(List<BinItem> items)
|
||||
/// <summary>
|
||||
/// Packs items into bins using the Best-Fit Decreasing algorithm.
|
||||
/// </summary>
|
||||
public PackResult Pack(PackingRequest request)
|
||||
{
|
||||
if (StockLength <= 0)
|
||||
throw new Exception("Stock length must be greater than 0");
|
||||
var result = new PackResult();
|
||||
var items = request.Items.OrderByDescending(i => i.Length).ToList();
|
||||
var bins = new List<Bin>();
|
||||
|
||||
Items = items.OrderByDescending(i => i.Length).ToList();
|
||||
|
||||
var result = new Result();
|
||||
var itemsTooLarge = Items.Where(i => i.Length > StockLength).ToList();
|
||||
result.AddItemsNotUsed(itemsTooLarge);
|
||||
|
||||
foreach (var item in itemsTooLarge)
|
||||
// Filter oversized items
|
||||
var oversizedItems = items.Where(i => i.Length > request.StockLength).ToList();
|
||||
foreach (var item in oversizedItems)
|
||||
{
|
||||
Items.Remove(item);
|
||||
items.Remove(item);
|
||||
result.AddItemNotUsed(item);
|
||||
}
|
||||
|
||||
var bins = GetBins();
|
||||
result.AddBins(bins);
|
||||
|
||||
foreach (var bin in bins)
|
||||
// Pack remaining items using best-fit
|
||||
foreach (var item in items)
|
||||
{
|
||||
foreach (var item in bin.Items)
|
||||
if (!TryFindBestBin(bins, item.Length, out var bestBin))
|
||||
{
|
||||
Items.Remove(item);
|
||||
if (bins.Count < request.MaxBinCount)
|
||||
{
|
||||
bestBin = CreateBin(request);
|
||||
bins.Add(bestBin);
|
||||
}
|
||||
}
|
||||
|
||||
result.AddItemsNotUsed(Items);
|
||||
if (bestBin != null)
|
||||
{
|
||||
bestBin.AddItem(item);
|
||||
}
|
||||
else
|
||||
{
|
||||
result.AddItemNotUsed(item);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort bins by utilization
|
||||
var sortedBins = bins
|
||||
.OrderByDescending(b => b.Utilization)
|
||||
.ThenBy(b => b.Items.Count);
|
||||
|
||||
result.AddBins(sortedBins);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<Bin> GetBins()
|
||||
private static Bin CreateBin(PackingRequest request)
|
||||
{
|
||||
var bins = new List<Bin>();
|
||||
|
||||
foreach (var item in Items)
|
||||
return new Bin(request.StockLength)
|
||||
{
|
||||
Bin best_bin;
|
||||
|
||||
if (!FindBin(bins.ToArray(), item.Length, out best_bin))
|
||||
{
|
||||
if (item.Length > StockLength)
|
||||
continue;
|
||||
|
||||
if (bins.Count < MaxBinCount)
|
||||
{
|
||||
best_bin = CreateBin();
|
||||
bins.Add(best_bin);
|
||||
}
|
||||
}
|
||||
|
||||
if (best_bin != null)
|
||||
best_bin.AddItem(item);
|
||||
|
||||
|
||||
}
|
||||
|
||||
return bins
|
||||
.OrderByDescending(b => b.Utilization)
|
||||
.ThenBy(b => b.Items.Count)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private Bin CreateBin()
|
||||
{
|
||||
var length = StockLength;
|
||||
|
||||
return new Bin(length)
|
||||
{
|
||||
Spacing = Spacing
|
||||
Spacing = request.Spacing
|
||||
};
|
||||
}
|
||||
|
||||
private static bool FindBin(IEnumerable<Bin> bins, double length, out Bin found)
|
||||
private static bool TryFindBestBin(IEnumerable<Bin> bins, double length, out Bin? found)
|
||||
{
|
||||
found = null;
|
||||
|
||||
@@ -95,14 +73,13 @@ namespace CutList.Core.Nesting
|
||||
if (bin.RemainingLength < length)
|
||||
continue;
|
||||
|
||||
if (found == null)
|
||||
found = bin;
|
||||
|
||||
if (bin.RemainingLength < found.RemainingLength)
|
||||
if (found == null || bin.RemainingLength < found.RemainingLength)
|
||||
{
|
||||
found = bin;
|
||||
}
|
||||
}
|
||||
|
||||
return (found != null);
|
||||
return found != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,19 @@
|
||||
namespace CutList.Core.Nesting
|
||||
{
|
||||
/// <summary>
|
||||
/// Default implementation of IEngineFactory that creates AdvancedFitEngine instances.
|
||||
/// Can be extended to support different engine types based on configuration.
|
||||
/// Default implementation of IEngineFactory that creates packing engines
|
||||
/// based on the specified strategy.
|
||||
/// </summary>
|
||||
public class EngineFactory : IEngineFactory
|
||||
{
|
||||
public IEngine CreateEngine(double stockLength, double spacing, int maxBinCount)
|
||||
public IEngine CreateEngine(PackingStrategy strategy = PackingStrategy.AdvancedFit)
|
||||
{
|
||||
return new AdvancedFitEngine
|
||||
return strategy switch
|
||||
{
|
||||
StockLength = stockLength,
|
||||
Spacing = spacing,
|
||||
MaxBinCount = maxBinCount
|
||||
PackingStrategy.AdvancedFit => new AdvancedFitEngine(),
|
||||
PackingStrategy.BestFit => new BestFitEngine(),
|
||||
PackingStrategy.Exhaustive => new ExhaustiveFitEngine(),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(strategy), strategy, "Unknown packing strategy")
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
198
CutList.Core/Nesting/ExhaustiveFitEngine.cs
Normal file
198
CutList.Core/Nesting/ExhaustiveFitEngine.cs
Normal file
@@ -0,0 +1,198 @@
|
||||
namespace CutList.Core.Nesting
|
||||
{
|
||||
/// <summary>
|
||||
/// Exhaustive bin packing engine that tries all possible combinations
|
||||
/// to find the optimal solution. Falls back to AdvancedFitEngine for
|
||||
/// item counts exceeding the threshold due to exponential complexity.
|
||||
/// </summary>
|
||||
public class ExhaustiveFitEngine : IEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// Default maximum number of items before falling back to AdvancedFitEngine.
|
||||
/// Testing showed 20 items is safe (~100ms worst case), while 21+ can take seconds.
|
||||
/// </summary>
|
||||
public const int DefaultMaxItems = 20;
|
||||
|
||||
private readonly IEngine _fallbackEngine;
|
||||
private readonly int _maxItems;
|
||||
|
||||
public ExhaustiveFitEngine() : this(DefaultMaxItems)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an exhaustive engine with a custom item threshold for testing.
|
||||
/// </summary>
|
||||
/// <param name="maxItems">Maximum items before falling back. Use int.MaxValue to disable fallback.</param>
|
||||
public ExhaustiveFitEngine(int maxItems)
|
||||
{
|
||||
_maxItems = maxItems;
|
||||
_fallbackEngine = new AdvancedFitEngine();
|
||||
}
|
||||
|
||||
public PackResult Pack(PackingRequest request)
|
||||
{
|
||||
// Filter oversized items first
|
||||
var validItems = new List<BinItem>();
|
||||
var oversizedItems = new List<BinItem>();
|
||||
|
||||
foreach (var item in request.Items)
|
||||
{
|
||||
if (item.Length > request.StockLength)
|
||||
oversizedItems.Add(item);
|
||||
else
|
||||
validItems.Add(item);
|
||||
}
|
||||
|
||||
// Fall back to AdvancedFit for large item counts
|
||||
if (validItems.Count > _maxItems)
|
||||
{
|
||||
var fallbackResult = _fallbackEngine.Pack(request);
|
||||
return fallbackResult;
|
||||
}
|
||||
|
||||
// Sort items descending for better pruning
|
||||
var sortedItems = validItems.OrderByDescending(i => i.Length).ToList();
|
||||
|
||||
// Find optimal solution using exhaustive search
|
||||
var bestSolution = new SearchState
|
||||
{
|
||||
Bins = new List<List<BinItem>>(),
|
||||
BinCount = int.MaxValue
|
||||
};
|
||||
|
||||
var currentState = new SearchState
|
||||
{
|
||||
Bins = new List<List<BinItem>>(),
|
||||
BinCount = 0
|
||||
};
|
||||
|
||||
Search(sortedItems, 0, currentState, bestSolution, request);
|
||||
|
||||
// Build result from best solution
|
||||
var result = new PackResult();
|
||||
result.AddItemsNotUsed(oversizedItems);
|
||||
|
||||
foreach (var binItems in bestSolution.Bins)
|
||||
{
|
||||
var bin = new Bin(request.StockLength) { Spacing = request.Spacing };
|
||||
foreach (var item in binItems.OrderByDescending(i => i.Length))
|
||||
{
|
||||
bin.AddItem(item);
|
||||
}
|
||||
result.AddBin(bin);
|
||||
}
|
||||
|
||||
// Sort bins by utilization
|
||||
var sortedBins = result.Bins
|
||||
.OrderByDescending(b => b.Utilization)
|
||||
.ThenBy(b => b.Items.Count)
|
||||
.ToList();
|
||||
|
||||
var finalResult = new PackResult();
|
||||
finalResult.AddItemsNotUsed(oversizedItems);
|
||||
finalResult.AddBins(sortedBins);
|
||||
|
||||
return finalResult;
|
||||
}
|
||||
|
||||
private void Search(
|
||||
List<BinItem> items,
|
||||
int itemIndex,
|
||||
SearchState current,
|
||||
SearchState best,
|
||||
PackingRequest request)
|
||||
{
|
||||
// All items placed - check if this is better
|
||||
if (itemIndex >= items.Count)
|
||||
{
|
||||
if (current.BinCount < best.BinCount ||
|
||||
(current.BinCount == best.BinCount && GetTotalWaste(current, request) < GetTotalWaste(best, request)))
|
||||
{
|
||||
best.BinCount = current.BinCount;
|
||||
best.Bins = current.Bins.Select(b => b.ToList()).ToList();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Pruning: if we already have more bins than best, stop
|
||||
if (current.BinCount >= best.BinCount)
|
||||
return;
|
||||
|
||||
// Respect max bin count
|
||||
if (current.BinCount >= request.MaxBinCount)
|
||||
return;
|
||||
|
||||
var item = items[itemIndex];
|
||||
|
||||
// Symmetry breaking: if this item has the same length as the previous item,
|
||||
// only place it in bins with index >= where previous item went.
|
||||
// This avoids redundant exploration of equivalent permutations.
|
||||
int minBinIndex = 0;
|
||||
if (itemIndex > 0 && items[itemIndex - 1].Length == item.Length)
|
||||
{
|
||||
minBinIndex = current.LastBinIndexUsed;
|
||||
}
|
||||
|
||||
// Try placing in each existing bin (respecting symmetry constraint)
|
||||
for (int i = minBinIndex; i < current.Bins.Count; i++)
|
||||
{
|
||||
var binUsed = GetBinUsedLength(current.Bins[i], request.Spacing);
|
||||
var remaining = request.StockLength - binUsed;
|
||||
|
||||
// Item fits if adding it (with spacing) stays within tolerance
|
||||
// Bin class allows going over by up to spacing amount
|
||||
if (item.Length <= remaining)
|
||||
{
|
||||
// Place item in this bin
|
||||
current.Bins[i].Add(item);
|
||||
var prevBinIndex = current.LastBinIndexUsed;
|
||||
current.LastBinIndexUsed = i;
|
||||
Search(items, itemIndex + 1, current, best, request);
|
||||
current.LastBinIndexUsed = prevBinIndex;
|
||||
current.Bins[i].RemoveAt(current.Bins[i].Count - 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Try placing in a new bin (if allowed)
|
||||
if (current.BinCount < request.MaxBinCount && current.BinCount < best.BinCount)
|
||||
{
|
||||
int newBinIndex = current.Bins.Count;
|
||||
current.Bins.Add(new List<BinItem> { item });
|
||||
current.BinCount++;
|
||||
var prevBinIndex = current.LastBinIndexUsed;
|
||||
current.LastBinIndexUsed = newBinIndex;
|
||||
Search(items, itemIndex + 1, current, best, request);
|
||||
current.LastBinIndexUsed = prevBinIndex;
|
||||
current.Bins.RemoveAt(current.Bins.Count - 1);
|
||||
current.BinCount--;
|
||||
}
|
||||
}
|
||||
|
||||
private double GetBinUsedLength(List<BinItem> binItems, double spacing)
|
||||
{
|
||||
if (binItems.Count == 0)
|
||||
return 0;
|
||||
|
||||
return binItems.Sum(i => i.Length) + binItems.Count * spacing;
|
||||
}
|
||||
|
||||
private double GetTotalWaste(SearchState state, PackingRequest request)
|
||||
{
|
||||
double totalWaste = 0;
|
||||
foreach (var bin in state.Bins)
|
||||
{
|
||||
var used = GetBinUsedLength(bin, request.Spacing);
|
||||
totalWaste += request.StockLength - used;
|
||||
}
|
||||
return totalWaste;
|
||||
}
|
||||
|
||||
private class SearchState
|
||||
{
|
||||
public List<List<BinItem>> Bins { get; set; } = new();
|
||||
public int BinCount { get; set; }
|
||||
public int LastBinIndexUsed { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user