diff --git a/README.md b/README.md index bb07adf..ac2b4a5 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,7 @@ # 3D bin packing algorithms [![NuGet](https://img.shields.io/nuget/vpre/Sharp3DBinPacking.svg)](http://www.nuget.org/packages/Sharp3DBinPacking) -[![license](https://img.shields.io/github/license/303248153/Sharp3DBinPacking.svg)]() -[![GitHub release](https://img.shields.io/github/release/303248153/Sharp3DBinPacking.svg)]() +[![Codacy Badge](https://api.codacy.com/project/badge/Grade/3cd218299e6d439eb49ace4641ce7bf9)](https://www.codacy.com/app/303248153/Sharp3DBinPacking?utm_source=github.com&utm_medium=referral&utm_content=303248153/Sharp3DBinPacking&utm_campaign=Badge_Grade) [Bin packing problem](https://en.wikipedia.org/wiki/Bin_packing_problem). @@ -65,12 +64,12 @@ Outputs: ``` text Bin: -Cuboid(X: 0, Y: 0, Z:0, Width: 750, Height:850, Depth:650) -Cuboid(X: 0, Y: 0, Z:650, Width: 350, Height:350, Depth:350) -Cuboid(X: 750, Y: 0, Z:0, Width: 150, Height:150, Depth:100) +Cuboid(X: 0, Y: 0, Z:0, Width: 750, Height:850, Depth:650, Weight: 0, Tag: ) +Cuboid(X: 0, Y: 0, Z:650, Width: 350, Height:350, Depth:350, Weight: 0, Tag: ) +Cuboid(X: 750, Y: 0, Z:0, Width: 150, Height:150, Depth:100, Weight: 0, Tag: ) Bin: -Cuboid(X: 0, Y: 0, Z:0, Width: 700, Height:500, Depth:550) -Cuboid(X: 0, Y: 500, Z:0, Width: 500, Height:500, Depth:500) +Cuboid(X: 0, Y: 0, Z:0, Width: 700, Height:500, Depth:550, Weight: 0, Tag: ) +Cuboid(X: 0, Y: 500, Z:0, Width: 500, Height:500, Depth:500, Weight: 0, Tag: ) ``` # License diff --git a/Sharp3DBinPacking.RandomTest/Program.cs b/Sharp3DBinPacking.RandomTest/Program.cs index dfc006a..b3a1580 100644 --- a/Sharp3DBinPacking.RandomTest/Program.cs +++ b/Sharp3DBinPacking.RandomTest/Program.cs @@ -12,45 +12,46 @@ static class Program static void Main(string[] args) { var binPacker = BinPacker.GetDefault(BinPackerVerifyOption.All); - var bestAlgorithmRecords = new Dictionary(); + var averageVolumeRate = 0m; var round = 0; while (true) { - var result = TestBinPacker(binPacker); - if (bestAlgorithmRecords.ContainsKey(result.BestAlgorithmName)) - bestAlgorithmRecords[result.BestAlgorithmName]++; - else - bestAlgorithmRecords[result.BestAlgorithmName] = 1; + var tuple = TestBinPacker(binPacker); + var result = tuple.Item1; + var volumeRate = tuple.Item2; + averageVolumeRate = (averageVolumeRate * round + volumeRate) / (round + 1); round++; var binCount = result.BestResult.Count; var cuboidCount = result.BestResult.Sum(x => x.Count); - Console.WriteLine($"Round {round} finished, {binCount} bins contains {cuboidCount} cuboids"); - Console.WriteLine($"Best algorithm records:"); - foreach (var pair in bestAlgorithmRecords.OrderByDescending(x => x.Value)) - { - Console.WriteLine($"{pair.Key}: {pair.Value}"); - } - Console.WriteLine(); + Console.WriteLine( + $"Round {round} finished, {binCount} bins contains {cuboidCount} cuboids, " + + $"average volume rate {averageVolumeRate.ToString("0.0000")}"); Thread.Sleep(1); } } - static BinPackResult TestBinPacker(IBinPacker binPacker) + static Tuple TestBinPacker(IBinPacker binPacker) { var binWidth = RandomInstance.Next(100, 5001); var binHeight = RandomInstance.Next(100, 5001); var binDepth = RandomInstance.Next(100, 5001); + var binWeight = RandomInstance.Next(100, 5001); var cuboidsCount = RandomInstance.Next(50, 501); + var allowRotateVertically = RandomInstance.Next(0, 2) == 0; var cuboids = new List(); for (var x = 0; x < cuboidsCount; ++x) { var width = RandomInstance.Next(1, binWidth + 1); var height = RandomInstance.Next(1, binHeight + 1); var depth = RandomInstance.Next(1, binDepth + 1); - cuboids.Add(new Cuboid(width, height, depth)); + var weight = RandomInstance.Next(1, binWeight / 20 + 1); + cuboids.Add(new Cuboid(width, height, depth, weight, null)); } - var parameter = new BinPackParameter(binWidth, binHeight, binDepth, cuboids); - return binPacker.Pack(parameter); + var parameter = new BinPackParameter( + binWidth, binHeight, binDepth, binWeight, allowRotateVertically, cuboids); + var result = binPacker.Pack(parameter); + var volumeRate = BinPacker.GetVolumeRate(parameter, result.BestResult); + return Tuple.Create(result, volumeRate); } } } diff --git a/Sharp3DBinPacking.sln b/Sharp3DBinPacking.sln index 4d63efd..054d631 100644 --- a/Sharp3DBinPacking.sln +++ b/Sharp3DBinPacking.sln @@ -5,11 +5,14 @@ VisualStudioVersion = 15.0.27130.2024 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sharp3DBinPacking", "Sharp3DBinPacking\Sharp3DBinPacking.csproj", "{5E43899A-E801-4CE8-9E7D-F396A4BE1988}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sharp3DBinPacking.RandomTest", "Sharp3DBinPacking.RandomTest\Sharp3DBinPacking.RandomTest.csproj", "{0651FAB6-AFEF-45FD-9C5B-1F902CCFE8B2}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sharp3DBinPacking.RandomTest", "Sharp3DBinPacking.RandomTest\Sharp3DBinPacking.RandomTest.csproj", "{0651FAB6-AFEF-45FD-9C5B-1F902CCFE8B2}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sharp3DBinPacking.Example", "Sharp3DBinPacking.Example\Sharp3DBinPacking.Example.csproj", "{4D5DBD00-7687-43DA-8229-C0146F1E2A41}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sharp3DBinPacking.Example", "Sharp3DBinPacking.Example\Sharp3DBinPacking.Example.csproj", "{4D5DBD00-7687-43DA-8229-C0146F1E2A41}" EndProject Global + GlobalSection(Performance) = preSolution + HasPerformanceSessions = true + EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU diff --git a/Sharp3DBinPacking/BinPackGuillotineAlgorithm.cs b/Sharp3DBinPacking/Algorithms/BinPackGuillotineAlgorithm.cs similarity index 86% rename from Sharp3DBinPacking/BinPackGuillotineAlgorithm.cs rename to Sharp3DBinPacking/Algorithms/BinPackGuillotineAlgorithm.cs index e97fbcc..afe9268 100644 --- a/Sharp3DBinPacking/BinPackGuillotineAlgorithm.cs +++ b/Sharp3DBinPacking/Algorithms/BinPackGuillotineAlgorithm.cs @@ -1,35 +1,30 @@ using Sharp3DBinPacking.Internal; using System; using System.Collections.Generic; +using System.Linq; using System.Text; -namespace Sharp3DBinPacking +namespace Sharp3DBinPacking.Algorithms { public class BinPackGuillotineAlgorithm : IBinPackAlgorithm { - private readonly decimal _binWidth; - private readonly decimal _binHeight; - private readonly decimal _binDepth; + private readonly BinPackParameter _parameter; private readonly FreeCuboidChoiceHeuristic _cuboidChoice; private readonly GuillotineSplitHeuristic _splitMethod; private readonly IList _usedCuboids; private readonly IList _freeCuboids; public BinPackGuillotineAlgorithm( - decimal binWidth, - decimal binHeight, - decimal binDepth, + BinPackParameter parameter, FreeCuboidChoiceHeuristic cuboidChoice, GuillotineSplitHeuristic splitMethod) { - _binWidth = binWidth; - _binHeight = binHeight; - _binDepth = binDepth; + _parameter = parameter; _cuboidChoice = cuboidChoice; _splitMethod = splitMethod; _usedCuboids = new List(); _freeCuboids = new List(); - AddFreeCuboid(new Cuboid(_binWidth, _binHeight, _binDepth)); + AddFreeCuboid(new Cuboid(parameter.BinWidth, parameter.BinHeight, parameter.BinDepth)); } public void Insert(IEnumerable cuboids) @@ -45,6 +40,10 @@ private void Insert( FreeCuboidChoiceHeuristic cuboidChoice, GuillotineSplitHeuristic splitMethod) { + // Check is overweight + if (cuboid.Weight + _usedCuboids.Sum(x => x.Weight) > _parameter.BinWeight) + return; + // Find where to put the new cuboid var freeNodeIndex = 0; FindPositionForNewNode(cuboid, cuboidChoice, out freeNodeIndex); @@ -80,7 +79,7 @@ private void FindPositionForNewNode( { var freeCuboid = _freeCuboids[index]; - // Width x Height x Depth + // Width x Height x Depth (no rotate) if (width <= freeCuboid.Width && height <= freeCuboid.Height && depth <= freeCuboid.Depth) @@ -100,10 +99,11 @@ private void FindPositionForNewNode( } } - // Width x Depth x Height + // Width x Depth x Height (rotate vertically) if (width <= freeCuboid.Width && depth <= freeCuboid.Height && - height <= freeCuboid.Depth) + height <= freeCuboid.Depth && + _parameter.AllowRotateVertically) { var score = ScoreByHeuristic(cuboid, freeCuboid, cuboidChoice); if (score < bestScore) @@ -120,7 +120,7 @@ private void FindPositionForNewNode( } } - // Depth x Height x Width + // Depth x Height x Width (rotate horizontally) if (depth <= freeCuboid.Width && height <= freeCuboid.Height && width <= freeCuboid.Depth) @@ -140,10 +140,11 @@ private void FindPositionForNewNode( } } - // Depth x Width x Height + // Depth x Width x Height (rotate horizontally and vertically) if (depth <= freeCuboid.Width && width <= freeCuboid.Height && - height <= freeCuboid.Depth) + height <= freeCuboid.Depth && + _parameter.AllowRotateVertically) { var score = ScoreByHeuristic(cuboid, freeCuboid, cuboidChoice); if (score < bestScore) @@ -160,10 +161,11 @@ private void FindPositionForNewNode( } } - // Height x Width x Depth + // Height x Width x Depth (rotate vertically) if (height <= freeCuboid.Width && width <= freeCuboid.Height && - depth <= freeCuboid.Depth) + depth <= freeCuboid.Depth && + _parameter.AllowRotateVertically) { var score = ScoreByHeuristic(cuboid, freeCuboid, cuboidChoice); if (score < bestScore) @@ -180,10 +182,11 @@ private void FindPositionForNewNode( } } - // Height x Depth x Width + // Height x Depth x Width (rotate horizontally and vertically) if (height <= freeCuboid.Width && depth <= freeCuboid.Height && - width <= freeCuboid.Depth) + width <= freeCuboid.Depth && + _parameter.AllowRotateVertically) { var score = ScoreByHeuristic(cuboid, freeCuboid, cuboidChoice); if (score < bestScore) @@ -202,7 +205,7 @@ private void FindPositionForNewNode( } } - private decimal ScoreByHeuristic( + private static decimal ScoreByHeuristic( Cuboid cuboid, Cuboid freeCuboid, FreeCuboidChoiceHeuristic cuboidChoice) @@ -308,9 +311,9 @@ private void AddFreeCuboid(Cuboid freeCuboid) throw new ArithmeticException( $"add free cuboid failed: negative position, algorithm: {this}, cuboid: {freeCuboid}"); } - if (freeCuboid.X + freeCuboid.Width > _binWidth || - freeCuboid.Y + freeCuboid.Height > _binHeight || - freeCuboid.Z + freeCuboid.Depth > _binDepth) + if (freeCuboid.X + freeCuboid.Width > _parameter.BinWidth || + freeCuboid.Y + freeCuboid.Height > _parameter.BinHeight || + freeCuboid.Z + freeCuboid.Depth > _parameter.BinDepth) { throw new ArithmeticException( $"add free cuboid failed: out of bin, algorithm: {this}, cuboid: {freeCuboid}"); diff --git a/Sharp3DBinPacking/BinPackShelfAlgorithm.cs b/Sharp3DBinPacking/Algorithms/BinPackShelfAlgorithm.cs similarity index 74% rename from Sharp3DBinPacking/BinPackShelfAlgorithm.cs rename to Sharp3DBinPacking/Algorithms/BinPackShelfAlgorithm.cs index c3cfa47..c3f4cbe 100644 --- a/Sharp3DBinPacking/BinPackShelfAlgorithm.cs +++ b/Sharp3DBinPacking/Algorithms/BinPackShelfAlgorithm.cs @@ -4,36 +4,32 @@ using System.Linq; using System.Text; -namespace Sharp3DBinPacking +namespace Sharp3DBinPacking.Algorithms { public class BinPackShelfAlgorithm : IBinPackAlgorithm { - private readonly decimal _binWidth; - private readonly decimal _binHeight; - private readonly decimal _binDepth; + private readonly BinPackParameter _parameter; private readonly FreeRectChoiceHeuristic _rectChoice; private readonly GuillotineSplitHeuristic _splitMethod; private readonly ShelfChoiceHeuristic _shelfChoice; // stores the starting y coordinate of the latest(topmost) shelf private decimal _currentY; private readonly IList _shelves; + private readonly IList _packedCuboids; public BinPackShelfAlgorithm( - decimal binWidth, - decimal binHeight, - decimal binDepth, + BinPackParameter parameter, FreeRectChoiceHeuristic rectChoice, GuillotineSplitHeuristic splitMethod, ShelfChoiceHeuristic shelfChoice) { - _binWidth = binWidth; - _binHeight = binHeight; - _binDepth = binDepth; + _parameter = parameter; _rectChoice = rectChoice; _splitMethod = splitMethod; _shelfChoice = shelfChoice; _currentY = 0; _shelves = new List(); + _packedCuboids = new List(); StartNewShelf(0); } @@ -47,6 +43,10 @@ public void Insert(IEnumerable cuboids) private void Insert(Cuboid cuboid, ShelfChoiceHeuristic method) { + // Check is overweight + if (cuboid.Weight + _packedCuboids.Sum(x => x.Weight) > _parameter.BinWeight) + return; + switch (method) { case ShelfChoiceHeuristic.ShelfNextFit: @@ -69,6 +69,9 @@ private void Insert(Cuboid cuboid, ShelfChoiceHeuristic method) } } break; + + default: + throw new NotSupportedException($"shelf choice is unsupported: {method}"); } // The rectangle did not fit on any of the shelves. Open a new shelf. @@ -88,17 +91,20 @@ private void Insert(Cuboid cuboid, ShelfChoiceHeuristic method) }; foreach (var whd in whdSet) { - cuboid.Width = whd.w; - cuboid.Height = whd.h; - cuboid.Depth = whd.d; - if (CanStartNewShelf(cuboid.Height)) + if (_parameter.AllowRotateVertically || cuboid.Height == whd.h) { - StartNewShelf(cuboid.Height); - PutOnShelf(_shelves.Last(), cuboid); - if (cuboid.IsPlaced) + cuboid.Width = whd.w; + cuboid.Height = whd.h; + cuboid.Depth = whd.d; + if (CanStartNewShelf(cuboid.Height)) { - AddToShelf(_shelves.Last(), cuboid); - return; + StartNewShelf(cuboid.Height); + PutOnShelf(_shelves.Last(), cuboid); + if (cuboid.IsPlaced) + { + AddToShelf(_shelves.Last(), cuboid); + return; + } } } } @@ -120,11 +126,8 @@ private void PutOnShelf(Shelf shelf, Cuboid cuboid) var min = edges[0]; // Set cuboid's longest egde vertically - if (max > shelf.Height) - { - // pass - } - else + if (max <= shelf.Height && + (_parameter.AllowRotateVertically || max == cuboid.Height)) { var maxVerticalRect = new Rectangle(middle, min, 0, 0); var freeRectIndex = 0; @@ -143,11 +146,8 @@ private void PutOnShelf(Shelf shelf, Cuboid cuboid) } // Set cuboid's second longest egde vertically - if (middle > shelf.Height) - { - // pass - } - else + if (middle <= shelf.Height && + (_parameter.AllowRotateVertically || middle == cuboid.Height)) { var middleVerticalRect = new Rectangle(min, max, 0, 0); var freeRectIndex = 0; @@ -166,11 +166,8 @@ private void PutOnShelf(Shelf shelf, Cuboid cuboid) } // Set cuboid's smallest egde vertically - if (min > shelf.Height) - { - // pass - } - else + if (min <= shelf.Height && + (_parameter.AllowRotateVertically || min == cuboid.Height)) { var minVerticalRect = new Rectangle(middle, max, 0, 0); var freeRectIndex = 0; @@ -196,19 +193,23 @@ private void AddToShelf(Shelf shelf, Cuboid newCuboid) if (shelf.Height < newCuboid.Height) throw new ArithmeticException("shelf.Height < newCuboid.Height"); newCuboid.Y = shelf.StartY; + _packedCuboids.Add(newCuboid); } private bool CanStartNewShelf(decimal height) { var lastShelf = _shelves.Last(); - return lastShelf.StartY + lastShelf.Height + height <= _binHeight; + return lastShelf.StartY + lastShelf.Height + height <= _parameter.BinHeight; } private void StartNewShelf(decimal startingHeight) { - if (_shelves.Count > 0) - _currentY += _shelves.Last().Height; - var shelf = new Shelf(_currentY, startingHeight, _binWidth, _binDepth); + var lastShelf = _shelves.LastOrDefault(); + if (lastShelf != null) + _currentY += lastShelf.Height; + var shelf = new Shelf(_currentY, startingHeight, _parameter.BinWidth, _parameter.BinDepth); + if (lastShelf != null && lastShelf.StartY + lastShelf.Height > shelf.StartY) + throw new ArithmeticException($"shelf intersects: {lastShelf}, {shelf}"); _shelves.Add(shelf); } diff --git a/Sharp3DBinPacking/BinPackParameter.cs b/Sharp3DBinPacking/BinPackParameter.cs index 204f0a3..83abd23 100644 --- a/Sharp3DBinPacking/BinPackParameter.cs +++ b/Sharp3DBinPacking/BinPackParameter.cs @@ -9,15 +9,26 @@ public class BinPackParameter public decimal BinWidth { get; private set; } public decimal BinHeight { get; private set; } public decimal BinDepth { get; private set; } + public decimal BinWeight { get; private set; } + public bool AllowRotateVertically { get; private set; } public IEnumerable Cuboids { get; private set; } + public int ShuffleCount { get; set; } public BinPackParameter( - decimal binWidth, decimal binHeight, decimal binDepth, IEnumerable cuboids) + decimal binWidth, decimal binHeight, decimal binDepth, IEnumerable cuboids) : + this(binWidth, binHeight, binDepth, 0, true, cuboids) { } + + public BinPackParameter( + decimal binWidth, decimal binHeight, decimal binDepth, decimal binWeight, + bool allowRotateVertically, IEnumerable cuboids) { BinWidth = binWidth; BinHeight = binHeight; BinDepth = binDepth; + BinWeight = binWeight; + AllowRotateVertically = allowRotateVertically; Cuboids = cuboids; + ShuffleCount = 5; } } } diff --git a/Sharp3DBinPacking/BinPackResult.cs b/Sharp3DBinPacking/BinPackResult.cs index 9f33a83..2dadad3 100644 --- a/Sharp3DBinPacking/BinPackResult.cs +++ b/Sharp3DBinPacking/BinPackResult.cs @@ -7,12 +7,10 @@ namespace Sharp3DBinPacking public class BinPackResult { public IList> BestResult { get; private set; } - public string BestAlgorithmName { get; private set; } - public BinPackResult(IList> bestResult, string bestAlgorithmName) + public BinPackResult(IList> bestResult) { BestResult = bestResult; - BestAlgorithmName = bestAlgorithmName; } } } diff --git a/Sharp3DBinPacking/BinPacker.cs b/Sharp3DBinPacking/BinPacker.cs index 532be58..86386e4 100644 --- a/Sharp3DBinPacking/BinPacker.cs +++ b/Sharp3DBinPacking/BinPacker.cs @@ -1,13 +1,12 @@ -using Sharp3DBinPacking.Internal; +using Sharp3DBinPacking.Algorithms; +using Sharp3DBinPacking.Internal; using System; using System.Collections.Generic; using System.Linq; -using System.Text; namespace Sharp3DBinPacking { - public delegate IBinPackAlgorithm BinPackAlgorithmFactory( - decimal binWidth, decimal binHeight, decimal binDepth); + public delegate IBinPackAlgorithm BinPackAlgorithmFactory(BinPackParameter parameter); public class BinPacker : IBinPacker { @@ -23,60 +22,67 @@ public BinPacker(BinPackerVerifyOption verifyOption, params BinPackAlgorithmFact public BinPackResult Pack(BinPackParameter parameter) { // [ [ cuboid in bin a, cuboid in bin a, ... ], [ cuboid in bin b, ... ] ] - IList> bestResult = null; - string bestAlgorithmName = null; - foreach (var factory in _factories) + var bestResult = new List>(); + IList pendingCuboids = parameter.Cuboids.ToList(); + while (pendingCuboids.Count > 0) { - foreach (var cuboids in GetCuboidsPermutations(parameter.Cuboids)) + // pack a single bin + // find the best volume rate from the combination of algorithms and permutations + IList singleBestResult = null; + IList singleBestRemain = null; + decimal singleBestVolumeRate = 0; + string singleBestAlgorihm = null; + foreach (var factory in _factories) { - // reset cuboids state - var unpackedCuboids = cuboids.ToList(); - foreach (var cuboid in unpackedCuboids) - cuboid.ResetPlacedInformation(); - var result = new List>(); - var algorithmName = ""; - while (unpackedCuboids.Count > 0) + foreach (var cuboids in GetCuboidsPermutations(pendingCuboids, parameter.ShuffleCount)) { - // pack single bin - var algorithm = factory(parameter.BinWidth, parameter.BinHeight, parameter.BinDepth); - algorithmName = algorithm.ToString(); - algorithm.Insert(unpackedCuboids); - // find out which cuboids are placed - var packedCuboids = unpackedCuboids.Where(c => c.IsPlaced).ToList(); + var targetCuboids = cuboids.Select(c => c.CloneWithoutPlaceInformation()).ToList(); + var algorithm = factory(parameter); + var algorithmName = algorithm.ToString(); + algorithm.Insert(targetCuboids); + var packedCuboids = targetCuboids.Where(c => c.IsPlaced).ToList(); if (packedCuboids.Count == 0) + { break; - result.Add(packedCuboids); - // pack remain cuboids - unpackedCuboids = unpackedCuboids.Where(c => !c.IsPlaced).ToList(); - } - // verify this result - if (_verifyOption == BinPackerVerifyOption.All) - Verify(parameter.BinWidth, parameter.BinHeight, parameter.BinDepth, algorithmName, result); - // update best result if all cuboids is placed and uses less bins - if (unpackedCuboids.Count == 0 && - (bestResult == null || result.Count < bestResult.Count)) - { - bestResult = result; - bestAlgorithmName = algorithmName; + } + // verify this result + if (_verifyOption == BinPackerVerifyOption.All) + { + Verify(parameter, algorithmName, packedCuboids); + } + // compare with the best result + var volumeRate = GetVolumeRate(parameter, packedCuboids); + if (singleBestResult == null || volumeRate > singleBestVolumeRate) + { + // update the best result + singleBestResult = packedCuboids; + singleBestRemain = targetCuboids.Where(c => !c.IsPlaced).ToList(); + singleBestVolumeRate = volumeRate; + singleBestAlgorihm = algorithmName; + } } } + if (singleBestResult == null) + { + throw new InvalidOperationException( + "no algorithm can pack these cuboids\n" + + $"binWidth: {parameter.BinWidth}, binHeight: {parameter.BinHeight}, " + + $"binDepth: {parameter.BinDepth}, binWeight: {parameter.BinWeight}\n" + + $"cuboids: {string.Join("\n", parameter.Cuboids.Select(x => x.ToString()))}"); + } + // verify the best result + if (_verifyOption == BinPackerVerifyOption.BestOnly) + { + Verify(parameter, singleBestAlgorihm, singleBestResult); + } + // update the best result of multiple bins + bestResult.Add(singleBestResult); + pendingCuboids = singleBestRemain; } - if (bestResult == null) - { - throw new InvalidOperationException( - "no algorithm can pack these cuboids\n" + - $"binWidth: {parameter.BinWidth}, binHeight: {parameter.BinHeight}, binDepth: {parameter.BinDepth}\n" + - $"cuboids: {string.Join("\n", parameter.Cuboids.Select(x => x.ToString()))}"); - } - // verify the best result - if (_verifyOption == BinPackerVerifyOption.BestOnly) - Verify(parameter.BinWidth, parameter.BinHeight, parameter.BinDepth, bestAlgorithmName, bestResult); - return new BinPackResult(bestResult, bestAlgorithmName); + return new BinPackResult(bestResult); } - private void Verify( - decimal binWidth, decimal binHeight, decimal binDepth, - string algorithmName, IList> result) + public static void Verify(BinPackParameter parameter, string algorithmName, IList cuboids) { // o--------o // /| /| @@ -89,73 +95,106 @@ private void Verify( // t | width d // | x // (0, 0, 0) - foreach (var cuboids in result) + for (int a = 0; a < cuboids.Count; ++a) { - for (int a = 0; a < cuboids.Count; ++a) + // check if cuboid out of bin + var cuboid = cuboids[a]; + if (cuboid.X < 0 || cuboid.Y < 0 || cuboid.Z < 0) { - // check if cuboid out of bin - var cuboid = cuboids[a]; - if (cuboid.X < 0 || cuboid.Y < 0 || cuboid.Z < 0) - { - throw new ArithmeticException( - $"verify cuboid failed: negative position, algorithm: {algorithmName}, cuboid: {cuboid}"); - } - if (cuboid.X + cuboid.Width > binWidth || - cuboid.Y + cuboid.Height > binHeight || - cuboid.Z + cuboid.Depth > binDepth) + throw new ArithmeticException( + $"verify cuboid failed: negative position, algorithm: {algorithmName}, cuboid: {cuboid}"); + } + if (cuboid.X + cuboid.Width > parameter.BinWidth || + cuboid.Y + cuboid.Height > parameter.BinHeight || + cuboid.Z + cuboid.Depth > parameter.BinDepth) + { + throw new ArithmeticException( + $"verify cuboid failed: out of bin, algorithm: {algorithmName}, cuboid: {cuboid}"); + } + // check if this cuboid intersects others + for (int b = a + 1; b < cuboids.Count; ++b) + { + var otherCuboid = cuboids[b]; + if (cuboid.X < otherCuboid.X + otherCuboid.Width && + otherCuboid.X < cuboid.X + cuboid.Width && + cuboid.Y < otherCuboid.Y + otherCuboid.Height && + otherCuboid.Y < cuboid.Y + cuboid.Height && + cuboid.Z < otherCuboid.Z + otherCuboid.Depth && + otherCuboid.Z < cuboid.Z + cuboid.Depth) { throw new ArithmeticException( - $"verify cuboid failed: out of bin, algorithm: {algorithmName}, cuboid: {cuboid}"); - } - // check if this cuboid intersects others - for (int b = a + 1; b < cuboids.Count; ++b) - { - var otherCuboid = cuboids[b]; - if (cuboid.X < otherCuboid.X + otherCuboid.Width && - otherCuboid.X < cuboid.X + cuboid.Width && - cuboid.Y < otherCuboid.Y + otherCuboid.Height && - otherCuboid.Y < cuboid.Y + cuboid.Height && - cuboid.Z < otherCuboid.Z + otherCuboid.Depth && - otherCuboid.Z < cuboid.Z + cuboid.Depth) - { - throw new ArithmeticException( - $"verify cuboid failed: cuboid intersects others, algorithm: {algorithmName}, cuboid a: {cuboid}, cuboid b: {otherCuboid}"); - } + $"verify cuboid failed: cuboid intersects others, algorithm: {algorithmName}, cuboid a: {cuboid}, cuboid b: {otherCuboid}"); } } } + // check is cuboids overweight + if (cuboids.Sum(c => c.Weight) > parameter.BinWeight) + { + throw new ArithmeticException( + $"verify cuboid failed: cuboids overweight, algorithm: {algorithmName}"); + } + } + + public static decimal GetVolumeRate(BinPackParameter parameter, IList result) + { + return result.Sum(x => x.Width * x.Height * x.Depth) / + (parameter.BinWidth * parameter.BinHeight * parameter.BinDepth); + } + + public static decimal GetVolumeRate(BinPackParameter parameter, IList> result) + { + var volumeRates = result.Select(x => GetVolumeRate(parameter, x)).ToList(); + if (volumeRates.Count > 1) { + // ignore last bin + volumeRates.RemoveAt(volumeRates.Count - 1); + } + return volumeRates.Average(); } - public IEnumerable> GetCuboidsPermutations(IEnumerable cuboids) + public static IEnumerable> GetCuboidsPermutations( + IEnumerable cuboids, int shuffleCount) { yield return cuboids; - yield return cuboids.OrderByDescending(x => x.Width); - yield return cuboids.OrderByDescending(x => x.Height); - yield return cuboids.OrderByDescending(x => x.Depth); + yield return cuboids.OrderByDescending(x => Math.Max(Math.Max(x.Width, x.Height), x.Depth)); yield return cuboids.OrderByDescending(x => x.Width * x.Height * x.Depth); + if (shuffleCount > 0) + { + var random = new Random(); + for (var x = 0; x < shuffleCount; ++x) + { + yield return cuboids.OrderBy(_ => random.Next(int.MaxValue)); + } + } } - public static IBinPacker GetDefault(BinPackerVerifyOption verifyOption) + public static BinPackAlgorithmFactory[] GetDefaultAlgorithmFactories() { - return new BinPacker(verifyOption, - (w, h, d) => new BinPackShelfAlgorithm( - w, h, d, + return new BinPackAlgorithmFactory[] + { + parameter => new BinPackShelfAlgorithm( + parameter, FreeRectChoiceHeuristic.RectBestAreaFit, GuillotineSplitHeuristic.SplitLongerLeftoverAxis, ShelfChoiceHeuristic.ShelfFirstFit), - (w, h, d) => new BinPackShelfAlgorithm( - w, h, d, + parameter => new BinPackShelfAlgorithm( + parameter, FreeRectChoiceHeuristic.RectBestAreaFit, GuillotineSplitHeuristic.SplitLongerLeftoverAxis, ShelfChoiceHeuristic.ShelfNextFit), - (w, h, d) => new BinPackGuillotineAlgorithm( - w, h, d, + parameter => new BinPackGuillotineAlgorithm( + parameter, FreeCuboidChoiceHeuristic.CuboidMinHeight, GuillotineSplitHeuristic.SplitLongerLeftoverAxis), - (w, h, d) => new BinPackGuillotineAlgorithm( - w, h, d, + parameter => new BinPackGuillotineAlgorithm( + parameter, FreeCuboidChoiceHeuristic.CuboidMinHeight, - GuillotineSplitHeuristic.SplitShorterLeftoverAxis)); + GuillotineSplitHeuristic.SplitShorterLeftoverAxis) + }; + } + + public static IBinPacker GetDefault(BinPackerVerifyOption verifyOption) + { + return new BinPacker(verifyOption, GetDefaultAlgorithmFactories()); } } } diff --git a/Sharp3DBinPacking/Cuboid.cs b/Sharp3DBinPacking/Cuboid.cs index e73a235..f63967c 100644 --- a/Sharp3DBinPacking/Cuboid.cs +++ b/Sharp3DBinPacking/Cuboid.cs @@ -12,11 +12,21 @@ public class Cuboid public decimal X { get; set; } public decimal Y { get; set; } public decimal Z { get; set; } + public decimal Weight { get; set; } + public object Tag { get; set; } internal bool IsPlaced { get; set; } public Cuboid() { } - public Cuboid(decimal width, decimal height, decimal depth) : this(width, height, depth, 0, 0, 0) { } - public Cuboid(decimal width, decimal height, decimal depth, decimal x, decimal y, decimal z) + public Cuboid(decimal width, decimal height, decimal depth) : + this(width, height, depth, 0, 0, 0, 0, null) + { } + public Cuboid(decimal width, decimal height, decimal depth, decimal weight, object tag) : + this(width, height, depth, 0, 0, 0, weight, tag) + { } + public Cuboid(decimal width, decimal height, decimal depth, decimal x, decimal y, decimal z) : + this(width, height, depth, x, y, z, 0, null) + { } + public Cuboid(decimal width, decimal height, decimal depth, decimal x, decimal y, decimal z, decimal weight, object tag) { Width = width; Height = height; @@ -24,19 +34,18 @@ public Cuboid(decimal width, decimal height, decimal depth, decimal x, decimal y X = x; Y = y; Z = z; + Weight = weight; + Tag = tag; } - public void ResetPlacedInformation() + public Cuboid CloneWithoutPlaceInformation() { - X = 0; - Y = 0; - Z = 0; - IsPlaced = false; + return new Cuboid(Width, Height, Depth, 0, 0, 0, Weight, Tag); } public override string ToString() { - return $"Cuboid(X: {X}, Y: {Y}, Z:{Z}, Width: {Width}, Height:{Height}, Depth:{Depth})"; + return $"Cuboid(X: {X}, Y: {Y}, Z:{Z}, Width: {Width}, Height:{Height}, Depth:{Depth}, Weight: {Weight}, Tag: {Tag})"; } } } diff --git a/Sharp3DBinPacking/Internal/Shelf.cs b/Sharp3DBinPacking/Internal/Shelf.cs index ce747cf..458b7a4 100644 --- a/Sharp3DBinPacking/Internal/Shelf.cs +++ b/Sharp3DBinPacking/Internal/Shelf.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Sharp3DBinPacking.Internal +namespace Sharp3DBinPacking.Internal { public class Shelf { @@ -16,5 +12,10 @@ public Shelf(decimal startY, decimal height, decimal binWidth, decimal binDepth) Height = height; Guillotine = new Guillotine2D(binWidth, binDepth); } + + public override string ToString() + { + return $"Shelf(StartY: {StartY}, Height: {Height})"; + } } } diff --git a/Sharp3DBinPacking/Sharp3DBinPacking.nuspec b/Sharp3DBinPacking/Sharp3DBinPacking.nuspec index ce68efe..e97cd18 100644 --- a/Sharp3DBinPacking/Sharp3DBinPacking.nuspec +++ b/Sharp3DBinPacking/Sharp3DBinPacking.nuspec @@ -2,7 +2,7 @@ Sharp3DBinPacking - 1.0.0 + 2.0.0 303248153 303248153 true