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:
2025-11-27 20:29:12 -05:00
parent 8367d9f400
commit 2d956fd3f7
189 changed files with 374 additions and 621 deletions

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);
}
}
}
}