First commit.

This commit is contained in:
aj
2016-05-16 22:09:19 -04:00
commit f2595d7cba
189 changed files with 26944 additions and 0 deletions

View File

@@ -0,0 +1,80 @@
using System;
namespace OpenNest
{
internal static class BestCombination
{
public static bool FindFrom2(double length1, double length2, double overallLength, out int count1, out int count2)
{
overallLength += Tolerance.Epsilon;
if (length1 > overallLength)
{
if (length2 > overallLength)
{
count1 = 0;
count2 = 0;
return false;
}
count1 = 0;
count2 = (int)Math.Floor(overallLength / length2);
return true;
}
if (length2 > overallLength)
{
count1 = (int)Math.Floor(overallLength / length1);
count2 = 0;
return true;
}
var maxCountLength1 = (int)Math.Floor(overallLength / length1);
count1 = maxCountLength1;
count2 = 0;
var remnant = overallLength - maxCountLength1 * length1;
if (remnant.IsEqualTo(0))
return true;
for (int countLength1 = 0; countLength1 <= maxCountLength1; ++countLength1)
{
var remnant1 = overallLength - countLength1 * length1;
if (remnant1 >= length2)
{
var countLength2 = (int)Math.Floor(remnant1 / length2);
var remnant2 = remnant1 - length2 * countLength2;
if (!(remnant2 < remnant))
continue;
count1 = countLength1;
count2 = countLength2;
if (remnant2.IsEqualTo(0))
break;
remnant = remnant2;
}
else
{
if (!(remnant1 < remnant))
continue;
count1 = countLength1;
count2 = 0;
if (remnant1.IsEqualTo(0))
break;
remnant = remnant1;
}
}
return true;
}
}
}

View File

@@ -0,0 +1,30 @@
using System.Collections.Generic;
using System.Linq;
namespace OpenNest.CirclePacking
{
internal class Bin : Box
{
public Bin()
{
Items = new List<Item>();
}
public List<Item> Items { get; set; }
public double Density()
{
return Items.Sum(i => i.Area()) / Area();
}
public object Clone()
{
return new Bin
{
Location = this.Location,
Size = this.Size,
Items = new List<Item>(Items)
};
}
}
}

View File

@@ -0,0 +1,79 @@
using System;
namespace OpenNest.CirclePacking
{
internal class FillEndEven : FillEngine
{
public FillEndEven(Bin bin)
: base(bin)
{
}
public override void Fill(Item item)
{
var max = new Vector(
Bin.Right - item.BoundingBox.Right + Tolerance.Epsilon,
Bin.Top - item.BoundingBox.Top + Tolerance.Epsilon);
var rows = Math.Floor((Bin.Height + Tolerance.Epsilon) / (item.Diameter));
var diameter = item.Diameter;
var remaining = Bin.Height - diameter * rows;
var radius = diameter * 0.5;
if (remaining < radius)
{
var yodd = Bin.Y + remaining;
var yoffset = diameter;
var xoffset = Trigonometry.Base(remaining, diameter);
int column = 0;
for (var x = Bin.X; x <= max.X; x += xoffset)
{
var y = column.IsOdd() ? yodd : Bin.Y;
for (; y <= max.Y; y += yoffset)
{
Bin.Items.Add(new Item
{
Center = new Vector(x, y)
});
}
column++;
}
}
else
{
var yoffset = (Bin.Height - diameter) / (2 * rows - 1);
var xoffset = Trigonometry.Base(yoffset, diameter);
var yodd = Bin.Y + yoffset;
yoffset *= 2.0;
int column = 0;
for (var x = Bin.X; x <= max.X; x += xoffset)
{
var y = column.IsOdd() ? yodd : Bin.Y;
for (; y <= max.Y; y += yoffset)
{
Bin.Items.Add(new Item
{
Center = new Vector(x, y)
});
}
column++;
}
}
}
public override void Fill(Item item, int maxCount)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,100 @@
using System;
namespace OpenNest.CirclePacking
{
internal class FillEndOdd : FillEngine
{
public FillEndOdd(Bin bin)
: base(bin)
{
}
public override void Fill(Item item)
{
var bin1 = FillHorizontal(item);
var bin2 = FillVertical(item);
if (bin1.Items.Count > bin2.Items.Count)
Bin.Items.AddRange(bin1.Items);
else
Bin.Items.AddRange(bin2.Items);
}
public override void Fill(Item item, int maxCount)
{
throw new NotImplementedException();
}
private Bin FillHorizontal(Item item)
{
var bin = Bin.Clone() as Bin;
var max = new Vector(
bin.Right - item.BoundingBox.Right + Tolerance.Epsilon,
bin.Top - item.BoundingBox.Top + Tolerance.Epsilon);
var count = Math.Floor((bin.Width + Tolerance.Epsilon) / item.Diameter);
if (count == 0)
return bin;
var xoffset = (bin.Width - item.Diameter) / (count - 1);
var yoffset = Trigonometry.Height(xoffset * 0.5, item.Diameter);
int row = 0;
for (var y = bin.Y; y <= max.Y; y += yoffset)
{
var x = row.IsOdd() ? bin.X + xoffset * 0.5 : bin.X;
for (; x <= max.X; x += xoffset)
{
var addedItem = item.Clone() as Item;
addedItem.Center = new Vector(x, y);
bin.Items.Add(addedItem);
}
row++;
}
return bin;
}
private Bin FillVertical(Item item)
{
var bin = Bin.Clone() as Bin;
var max = new Vector(
Bin.Right - item.BoundingBox.Right + Tolerance.Epsilon,
Bin.Top - item.BoundingBox.Top + Tolerance.Epsilon);
var count = Math.Floor((bin.Height + Tolerance.Epsilon) / item.Diameter);
if (count == 0)
return bin;
var yoffset = (bin.Height - item.Diameter) / (count - 1);
var xoffset = Trigonometry.Base(yoffset * 0.5, item.Diameter);
int column = 0;
for (var x = bin.X; x <= max.X; x += xoffset)
{
var y = column.IsOdd() ? bin.Y + yoffset * 0.5 : bin.Y;
for (; y <= max.Y; y += yoffset)
{
var addedItem = item.Clone() as Item;
addedItem.Center = new Vector(x, y);
bin.Items.Add(addedItem);
}
column++;
}
return bin;
}
}
}

View File

@@ -0,0 +1,17 @@

namespace OpenNest.CirclePacking
{
internal abstract class FillEngine
{
public FillEngine(Bin bin)
{
Bin = bin;
}
public Bin Bin { get; set; }
public abstract void Fill(Item item);
public abstract void Fill(Item item, int maxCount);
}
}

View File

@@ -0,0 +1,19 @@
using OpenNest.Geometry;
namespace OpenNest.CirclePacking
{
internal class Item : Circle
{
public int Id { get; set; }
public object Clone()
{
return new Item
{
Radius = this.Radius,
Center = this.Center,
Id = this.Id
};
}
}
}

View File

@@ -0,0 +1,9 @@

namespace OpenNest
{
public enum NestDirection
{
Vertical,
Horizontal
}
}

View File

@@ -0,0 +1,176 @@
using System;
using System.Collections.Generic;
using OpenNest.RectanglePacking;
namespace OpenNest
{
public class NestEngine
{
public NestEngine(Plate plate)
{
Plate = plate;
}
public Plate Plate { get; set; }
public NestDirection NestDirection { get; set; }
public bool Fill(NestItem item)
{
var workArea = Plate.WorkArea();
return FillArea(workArea, item);
}
public bool Fill(NestItem item, int maxCount)
{
var workArea = Plate.WorkArea();
return FillArea(workArea, item, maxCount);
}
public bool FillArea(Box box, NestItem item)
{
var binItem = ConvertToRectangleItem(item);
var bin = new Bin
{
Location = box.Location,
Size = box.Size
};
bin.Width += Plate.PartSpacing;
bin.Height += Plate.PartSpacing;
var engine = new FillBestFit(bin);
engine.Fill(binItem);
var nestItems = new List<NestItem>();
nestItems.Add(item);
var parts = ConvertToParts(bin, nestItems);
Plate.Parts.AddRange(parts);
return parts.Count > 0;
}
public bool FillArea(Box box, NestItem item, int maxCount)
{
var binItem = ConvertToRectangleItem(item);
var bin = new Bin
{
Location = box.Location,
Size = box.Size
};
bin.Width += Plate.PartSpacing;
bin.Height += Plate.PartSpacing;
var engine = new FillBestFit(bin);
engine.Fill(binItem, maxCount);
var nestItems = new List<NestItem>();
nestItems.Add(item);
var parts = ConvertToParts(bin, nestItems);
Plate.Parts.AddRange(parts);
return parts.Count > 0;
}
public bool Pack(List<NestItem> items)
{
var workArea = Plate.WorkArea();
return PackArea(workArea, items);
}
public bool PackArea(Box box, List<NestItem> items)
{
var binItems = ConvertToRectangleItems(items);
var bin = new Bin
{
Location = box.Location,
Size = box.Size
};
bin.Width += Plate.PartSpacing;
bin.Height += Plate.PartSpacing;
var engine = new PackBottomLeft(bin);
engine.Pack(binItems);
var parts = ConvertToParts(bin, items);
Plate.Parts.AddRange(parts);
return parts.Count > 0;
}
private List<Part> ConvertToParts(Bin bin, List<NestItem> items)
{
var parts = new List<Part>();
foreach (var item in bin.Items)
{
var nestItem = items[item.Id];
var part = ConvertToPart(item, nestItem.Drawing);
parts.Add(part);
}
return parts;
}
private Part ConvertToPart(Item item, Drawing dwg)
{
var part = new Part(dwg);
if (item.IsRotated)
part.Rotate(Angle.HalfPI);
var boundingBox = part.Program.BoundingBox();
var offset = item.Location - boundingBox.Location;
part.Offset(offset);
return part;
}
private List<Item> ConvertToRectangleItems(List<NestItem> items)
{
var binItems = new List<Item>();
for (int i = 0; i < items.Count; i++)
{
var item = items[i];
var binItem = ConvertToRectangleItem(item, i);
int maxQty = (int)Math.Floor(Plate.Area() / binItem.Area());
int qty = item.Quantity < maxQty
? item.Quantity
: maxQty;
for (int j = 0; j < qty; j++)
binItems.Add(binItem.Clone() as Item);
}
return binItems;
}
private Item ConvertToRectangleItem(NestItem item, int id = 0)
{
var box = item.Drawing.Program.BoundingBox();
box.Width += Plate.PartSpacing;
box.Height += Plate.PartSpacing;
return new Item
{
Id = id,
Location = box.Location,
Size = box.Size
};
}
}
}

View File

@@ -0,0 +1,35 @@
namespace OpenNest
{
public class NestItem
{
/// <summary>
/// The drawing to be nested.
/// </summary>
public Drawing Drawing { get; set; }
/// <summary>
/// Priority of the part determines nesting order. Highest priority will be nested first.
/// </summary>
public int Priority { get; set; }
/// <summary>
/// The number of parts to be nested.
/// </summary>
public int Quantity { get; set; }
/// <summary>
/// The rotation step in radians.
/// </summary>
public double StepAngle { get; set; }
/// <summary>
/// The rotation start angle in radians.
/// </summary>
public double RotationStart { get; set; }
/// <summary>
/// The rotation end angle in radians.
/// </summary>
public double RotationEnd { get; set; }
}
}

View File

@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{0083B9CC-54AD-4085-A30D-56BC6834B71A}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>OpenNest</RootNamespace>
<AssemblyName>OpenNest.Engine</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
</ItemGroup>
<ItemGroup>
<Compile Include="BestCombination.cs" />
<Compile Include="CirclePacking\Bin.cs" />
<Compile Include="CirclePacking\FillEndEven.cs" />
<Compile Include="CirclePacking\FillEndOdd.cs" />
<Compile Include="CirclePacking\FillEngine.cs" />
<Compile Include="CirclePacking\Item.cs" />
<Compile Include="NestDirection.cs" />
<Compile Include="NestEngine.cs" />
<Compile Include="NestItem.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="RectanglePacking\Bin.cs" />
<Compile Include="RectanglePacking\FillBestFit.cs" />
<Compile Include="RectanglePacking\FillEngine.cs" />
<Compile Include="RectanglePacking\Item.cs" />
<Compile Include="RectanglePacking\PackBottomLeft.cs" />
<Compile Include="RectanglePacking\PackEngine.cs" />
<Compile Include="RectanglePacking\FillNoRotation.cs" />
<Compile Include="RectanglePacking\FillSameRotation.cs" />
<Compile Include="RectanglePacking\PackFirstFitDecreasing.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\OpenNest.Core\OpenNest.Core.csproj">
<Project>{5a5fde8d-f8db-440e-866c-c4807e1686cf}</Project>
<Name>OpenNest.Core</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@@ -0,0 +1,35 @@
using System.Reflection;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("OpenNest.Engine")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("OpenNest.Engine")]
[assembly: AssemblyCopyright("Copyright © AJ Isaacs 2015")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("50013ca9-b047-41f5-b519-72e523902b53")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("0.1.0.0")]
[assembly: AssemblyFileVersion("0.1.0.0")]

View File

@@ -0,0 +1,30 @@
using System.Collections.Generic;
using System.Linq;
namespace OpenNest.RectanglePacking
{
internal class Bin : Box
{
public Bin()
{
Items = new List<Item>();
}
public List<Item> Items { get; set; }
public double Density()
{
return Items.Sum(i => i.Area()) / Area();
}
public object Clone()
{
return new Bin
{
Location = this.Location,
Size = this.Size,
Items = new List<Item>(Items)
};
}
}
}

View File

@@ -0,0 +1,88 @@
using System;
namespace OpenNest.RectanglePacking
{
internal class FillBestFit : FillEngine
{
public FillBestFit(Bin bin)
: base(bin)
{
}
public override void Fill(Item item)
{
var bin1 = BestFitHorizontal(item);
var bin2 = BestFitVertical(item);
if (bin1.Items.Count == bin2.Items.Count)
{
var usedArea1 = bin1.Items.GetBoundingBox().Area();
var usedArea2 = bin2.Items.GetBoundingBox().Area();
if (usedArea2 < usedArea1)
Bin.Items.AddRange(bin2.Items);
else
Bin.Items.AddRange(bin1.Items);
}
else if (bin1.Items.Count > bin2.Items.Count)
Bin.Items.AddRange(bin1.Items);
else
Bin.Items.AddRange(bin2.Items);
}
public override void Fill(Item item, int maxCount)
{
throw new NotImplementedException();
}
private Bin BestFitHorizontal(Item item)
{
var bin = Bin.Clone() as Bin;
int normalColumns = 0;
int rotateColumns = 0;
if (!BestCombination.FindFrom2(item.Width, item.Height, bin.Width, out normalColumns, out rotateColumns))
return bin;
var normalRows = (int)Math.Floor((bin.Height + Tolerance.Epsilon) / item.Height);
var rotateRows = (int)Math.Floor((bin.Height + Tolerance.Epsilon) / item.Width);
item.Location = bin.Location;
bin.Items.AddRange(VPattern(item, normalRows, normalColumns, int.MaxValue));
item.Location.X += item.Width * normalColumns;
item.Rotate();
bin.Items.AddRange(VPattern(item, rotateRows, rotateColumns, int.MaxValue));
return bin;
}
private Bin BestFitVertical(Item item)
{
var bin = Bin.Clone() as Bin;
int normalRows = 0;
int rotateRows = 0;
if (!BestCombination.FindFrom2(item.Height, item.Width, Bin.Height, out normalRows, out rotateRows))
return bin;
var normalColumns = (int)Math.Floor((Bin.Width + Tolerance.Epsilon) / item.Width);
var rotateColumns = (int)Math.Floor((Bin.Width + Tolerance.Epsilon) / item.Height);
item.Location = bin.Location;
bin.Items.AddRange(VPattern(item, normalRows, normalColumns, int.MaxValue));
item.Location.Y += item.Height * normalRows;
item.Rotate();
bin.Items.AddRange(VPattern(item, rotateRows, rotateColumns, int.MaxValue));
return bin;
}
}
}

View File

@@ -0,0 +1,82 @@
using System.Collections.Generic;
namespace OpenNest.RectanglePacking
{
internal abstract class FillEngine
{
public FillEngine(Bin bin)
{
Bin = bin;
}
public Bin Bin { get; set; }
public abstract void Fill(Item item);
public abstract void Fill(Item item, int maxCount);
/// <summary>
/// Vertical pattern.
/// </summary>
/// <param name="item"></param>
/// <param name="rows"></param>
/// <param name="columns"></param>
/// <param name="maxCount"></param>
protected List<Item> VPattern(Item item, int rows, int columns, int maxCount)
{
var items = new List<Item>();
for (int i = 0; i < columns; i++)
{
var x = item.Width * i + item.X;
for (int j = 0; j < rows; j++)
{
var y = item.Height * j + item.Y;
var addedItem = item.Clone() as Item;
addedItem.Location = new Vector(x, y);
items.Add(addedItem);
if (items.Count == maxCount)
return items;
}
}
return items;
}
/// <summary>
/// Horizontal pattern.
/// </summary>
/// <param name="item"></param>
/// <param name="rows"></param>
/// <param name="columns"></param>
/// <param name="maxCount"></param>
protected List<Item> HPattern(Item item, int rows, int columns, int maxCount)
{
var items = new List<Item>();
for (int i = 0; i < rows; i++)
{
var y = item.Height * i + item.Y;
for (int j = 0; j < rows; j++)
{
var x = item.Width * j + item.X;
var addedItem = item.Clone() as Item;
addedItem.Location = new Vector(x, y);
items.Add(addedItem);
if (items.Count == maxCount)
return items;
}
}
return items;
}
}
}

View File

@@ -0,0 +1,68 @@
using System;
namespace OpenNest.RectanglePacking
{
internal class FillNoRotation : FillEngine
{
public FillNoRotation(Bin bin)
: base(bin)
{
}
public NestDirection NestDirection { get; set; }
public override void Fill(Item item)
{
var ycount = (int)Math.Floor((Bin.Height + Tolerance.Epsilon) / item.Height);
var xcount = (int)Math.Floor((Bin.Width + Tolerance.Epsilon) / item.Width);
var count = ycount * xcount;
for (int i = 0; i < xcount; i++)
{
var x = item.Width * i + Bin.X;
for (int j = 0; j < ycount; j++)
{
var y = item.Height * j + Bin.Y;
var addedItem = item.Clone() as Item;
addedItem.Location = new Vector(x, y);
Bin.Items.Add(addedItem);
}
}
}
public override void Fill(Item item, int maxCount)
{
var ycount = (int)Math.Floor((Bin.Height + Tolerance.Epsilon) / item.Height);
var xcount = (int)Math.Floor((Bin.Width + Tolerance.Epsilon) / item.Width);
var count = ycount * xcount;
if (count <= maxCount)
{
Fill(item);
return;
}
var columns = 0;
var rows = 0;
if (NestDirection == NestDirection.Vertical)
{
columns = (int)Math.Ceiling((double)maxCount / ycount);
rows = (int)Math.Ceiling((double)maxCount / columns);
}
else
{
rows = (int)Math.Ceiling((double)maxCount / xcount);
columns = (int)Math.Ceiling((double)maxCount / rows);
}
if (item.Width > item.Height)
VPattern(item, rows, columns, maxCount);
else
HPattern(item, rows, columns, maxCount);
}
}
}

View File

@@ -0,0 +1,65 @@

namespace OpenNest.RectanglePacking
{
internal class FillSameRotation : FillEngine
{
public FillSameRotation(Bin bin)
: base(bin)
{
}
public override void Fill(Item item)
{
var bin1 = Bin.Clone() as Bin;
var bin2 = Bin.Clone() as Bin;
var engine = new FillNoRotation(bin1);
engine.Fill(item);
item.Rotate();
engine.Bin = bin2;
engine.Fill(item);
var density1 = bin1.Density();
var density2 = bin2.Density();
if (density1.IsEqualTo(density2))
{
var bounds1 = bin1.Items.GetBoundingBox();
var bounds2 = bin2.Items.GetBoundingBox();
if (bounds2.Right < bounds1.Right)
Bin.Items.AddRange(bin2.Items);
else
Bin.Items.AddRange(bin1.Items);
}
else if (density1 > density2)
Bin.Items.AddRange(bin1.Items);
else
Bin.Items.AddRange(bin2.Items);
}
public override void Fill(Item item, int maxCount)
{
var bin1 = Bin.Clone() as Bin;
var bin2 = Bin.Clone() as Bin;
var engine = new FillNoRotation(bin1);
engine.Fill(item, maxCount);
item.Rotate();
engine.Bin = bin2;
engine.Fill(item, maxCount);
var bounds1 = bin1.Items.GetBoundingBox();
var bounds2 = bin2.Items.GetBoundingBox();
if (bounds2.Right < bounds1.Right)
Bin.Items.AddRange(bin2.Items);
else
Bin.Items.AddRange(bin1.Items);
}
}
}

View File

@@ -0,0 +1,52 @@
using System.Collections.Generic;
namespace OpenNest.RectanglePacking
{
internal class Item : Box
{
public int Id { get; set; }
public bool IsRotated { get; private set; }
public void Rotate()
{
Generic.Swap(ref Size.Width, ref Size.Height);
IsRotated = !IsRotated;
}
public object Clone()
{
return new Item
{
IsRotated = this.IsRotated,
Location = this.Location,
Size = this.Size,
Id = this.Id
};
}
}
internal static class ItemListExtensions
{
public static Box GetBoundingBox(this IList<Item> items)
{
if (items.Count == 0)
return Box.Empty;
double minX = items[0].X;
double minY = items[0].Y;
double maxX = items[0].X + items[0].Width;
double maxY = items[0].Y + items[0].Height;
foreach (var box in items)
{
if (box.Left < minX) minX = box.Left;
if (box.Right > maxX) maxX = box.Right;
if (box.Bottom < minY) minY = box.Bottom;
if (box.Top > maxY) maxY = box.Top;
}
return new Box(minX, minY, maxX - minX, maxY - minY);
}
}
}

View File

@@ -0,0 +1,115 @@
using System.Collections.Generic;
using System.Linq;
namespace OpenNest.RectanglePacking
{
internal class PackBottomLeft : PackEngine
{
private List<Vector> points;
public PackBottomLeft(Bin bin)
: base(bin)
{
points = new List<Vector>();
}
public override void Pack(List<Item> items)
{
items = items.OrderBy(i => -i.Area()).ToList();
points.Add(Bin.Location);
var skip = new List<int>();
for (int i = 0; i < items.Count; i++)
{
var item = items[i];
if (skip.Contains(item.Id))
continue;
var pt = FindPointVertical(item);
if (pt == null)
{
skip.Add(item.Id);
continue;
}
item.Location = pt.Value;
points.Remove(pt.Value);
points.Add(new Vector(item.Left, item.Top));
points.Add(new Vector(item.Right, item.Bottom));
Bin.Items.Add(item);
}
points.Clear();
}
private Vector? FindPointVertical(Item item)
{
var pt = new Vector(double.MaxValue, double.MaxValue);
for (int i = 0; i < points.Count; i++)
{
var point = points[i];
item.Location = point;
if (!IsValid(item))
continue;
if (point.X < pt.X)
pt = point;
else if (point.X.IsEqualTo(pt.X) && point.Y < pt.Y)
pt = point;
}
if (pt.X != double.MaxValue && pt.Y != double.MaxValue)
return pt;
return null;
}
private Vector? FindPointHorizontal(Item item)
{
var pt = new Vector(double.MaxValue, double.MaxValue);
for (int i = 0; i < points.Count; i++)
{
var point = points[i];
item.Location = point;
if (!IsValid(item))
continue;
if (point.Y < pt.Y)
pt = point;
else if (point.Y.IsEqualTo(pt.Y) && point.X < pt.X)
pt = point;
}
if (pt.X != double.MaxValue && pt.Y != double.MaxValue)
return pt;
return null;
}
private bool IsValid(Item item)
{
if (!Bin.Contains(item))
return false;
foreach (var it in Bin.Items)
{
if (item.Intersects(it))
return false;
}
return true;
}
}
}

View File

@@ -0,0 +1,16 @@
using System.Collections.Generic;
namespace OpenNest.RectanglePacking
{
internal abstract class PackEngine
{
public PackEngine(Bin bin)
{
Bin = bin;
}
public Bin Bin { get; set; }
public abstract void Pack(List<Item> items);
}
}

View File

@@ -0,0 +1,121 @@
using System.Collections.Generic;
using System.Linq;
namespace OpenNest.RectanglePacking
{
internal class FirstFitDecreasing : PackEngine
{
private readonly List<Level> levels;
public FirstFitDecreasing(Bin bin)
: base(bin)
{
levels = new List<Level>();
}
public override void Pack(List<Item> items)
{
items = items.OrderBy(i => -i.Height).ToList();
foreach (var item in items)
{
if (item.Height > Bin.Height)
continue;
var level = FindLevel(item);
if (level == null)
continue;
level.AddItem(item);
}
}
private Level FindLevel(Item item)
{
foreach (var level in levels)
{
if (level.Height < item.Height)
continue;
if (level.RemainingWidth < item.Width)
continue;
return level;
}
return CreateNewLevel(item);
}
private Level CreateNewLevel(Item item)
{
var y = Bin.Y;
var lastLevel = levels.LastOrDefault();
if (lastLevel != null)
y = lastLevel.Y + lastLevel.Height;
var remaining = Bin.Top - y;
if (remaining < item.Height)
return null;
var level = new Level(Bin);
level.Y = y;
level.Height = item.Height;
levels.Add(level);
return level;
}
private class Level
{
public Level(Bin parent)
{
Parent = parent;
NextItemLocation = parent.Location;
}
public Bin Parent { get; set; }
private Vector NextItemLocation;
public double X
{
get { return Parent.X; }
}
public double Y
{
get { return NextItemLocation.Y; }
set { NextItemLocation.Y = value; }
}
public double Width
{
get { return Parent.Width; }
}
public double Height { get; set; }
public double Top
{
get { return Y + Height; }
}
public double RemainingWidth
{
get { return X + Width - NextItemLocation.X; }
}
public void AddItem(Item item)
{
item.Location = NextItemLocation;
Parent.Items.Add(item);
NextItemLocation = new Vector(NextItemLocation.X + item.Width, NextItemLocation.Y);
}
}
}
}