Restructure project layout to flatten directory structure
Move all projects from Source/ to repository root for simpler navigation. - Remove External/ dependency DLLs (will use NuGet packages) - Remove Installer/ NSIS script - Replace PartCollection/PlateCollection with ObservableList - Add packages.config for NuGet dependencies 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
30
OpenNest.Engine/RectanglePacking/Bin.cs
Normal file
30
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
OpenNest.Engine/RectanglePacking/FillBestFit.cs
Normal file
88
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
OpenNest.Engine/RectanglePacking/FillEngine.cs
Normal file
82
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
OpenNest.Engine/RectanglePacking/FillNoRotation.cs
Normal file
68
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
OpenNest.Engine/RectanglePacking/FillSameRotation.cs
Normal file
65
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
OpenNest.Engine/RectanglePacking/Item.cs
Normal file
52
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
OpenNest.Engine/RectanglePacking/PackBottomLeft.cs
Normal file
115
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
OpenNest.Engine/RectanglePacking/PackEngine.cs
Normal file
16
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);
|
||||
}
|
||||
}
|
||||
121
OpenNest.Engine/RectanglePacking/PackFirstFitDecreasing.cs
Normal file
121
OpenNest.Engine/RectanglePacking/PackFirstFitDecreasing.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user