First commit.
This commit is contained in:
80
Source/OpenNest.Engine/BestCombination.cs
Normal file
80
Source/OpenNest.Engine/BestCombination.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
30
Source/OpenNest.Engine/CirclePacking/Bin.cs
Normal file
30
Source/OpenNest.Engine/CirclePacking/Bin.cs
Normal 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)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
79
Source/OpenNest.Engine/CirclePacking/FillEndEven.cs
Normal file
79
Source/OpenNest.Engine/CirclePacking/FillEndEven.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
100
Source/OpenNest.Engine/CirclePacking/FillEndOdd.cs
Normal file
100
Source/OpenNest.Engine/CirclePacking/FillEndOdd.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
17
Source/OpenNest.Engine/CirclePacking/FillEngine.cs
Normal file
17
Source/OpenNest.Engine/CirclePacking/FillEngine.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
19
Source/OpenNest.Engine/CirclePacking/Item.cs
Normal file
19
Source/OpenNest.Engine/CirclePacking/Item.cs
Normal 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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
9
Source/OpenNest.Engine/NestDirection.cs
Normal file
9
Source/OpenNest.Engine/NestDirection.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public enum NestDirection
|
||||
{
|
||||
Vertical,
|
||||
Horizontal
|
||||
}
|
||||
}
|
||||
176
Source/OpenNest.Engine/NestEngine.cs
Normal file
176
Source/OpenNest.Engine/NestEngine.cs
Normal 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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
35
Source/OpenNest.Engine/NestItem.cs
Normal file
35
Source/OpenNest.Engine/NestItem.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
70
Source/OpenNest.Engine/OpenNest.Engine.csproj
Normal file
70
Source/OpenNest.Engine/OpenNest.Engine.csproj
Normal 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>
|
||||
35
Source/OpenNest.Engine/Properties/AssemblyInfo.cs
Normal file
35
Source/OpenNest.Engine/Properties/AssemblyInfo.cs
Normal 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")]
|
||||
30
Source/OpenNest.Engine/RectanglePacking/Bin.cs
Normal file
30
Source/OpenNest.Engine/RectanglePacking/Bin.cs
Normal 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)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
88
Source/OpenNest.Engine/RectanglePacking/FillBestFit.cs
Normal file
88
Source/OpenNest.Engine/RectanglePacking/FillBestFit.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
82
Source/OpenNest.Engine/RectanglePacking/FillEngine.cs
Normal file
82
Source/OpenNest.Engine/RectanglePacking/FillEngine.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
68
Source/OpenNest.Engine/RectanglePacking/FillNoRotation.cs
Normal file
68
Source/OpenNest.Engine/RectanglePacking/FillNoRotation.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
65
Source/OpenNest.Engine/RectanglePacking/FillSameRotation.cs
Normal file
65
Source/OpenNest.Engine/RectanglePacking/FillSameRotation.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
52
Source/OpenNest.Engine/RectanglePacking/Item.cs
Normal file
52
Source/OpenNest.Engine/RectanglePacking/Item.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
115
Source/OpenNest.Engine/RectanglePacking/PackBottomLeft.cs
Normal file
115
Source/OpenNest.Engine/RectanglePacking/PackBottomLeft.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
16
Source/OpenNest.Engine/RectanglePacking/PackEngine.cs
Normal file
16
Source/OpenNest.Engine/RectanglePacking/PackEngine.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user