diff --git a/OpenNest.Core/Part.cs b/OpenNest.Core/Part.cs
index e88a60c..1c8ef21 100644
--- a/OpenNest.Core/Part.cs
+++ b/OpenNest.Core/Part.cs
@@ -103,6 +103,23 @@ namespace OpenNest
BoundingBox.Offset(voffset);
}
+ ///
+ /// Creates a part normalized to the origin with optional rotation.
+ ///
+ public static Part CreateAtOrigin(Drawing drawing, double rotation = 0)
+ {
+ var part = new Part(drawing);
+
+ if (!Math.Tolerance.IsEqualTo(rotation, 0))
+ part.Rotate(rotation);
+
+ var bbox = part.Program.BoundingBox();
+ part.Offset(-bbox.Location.X, -bbox.Location.Y);
+ part.UpdateBounds();
+
+ return part;
+ }
+
///
/// Updates the bounding box of the part.
///
diff --git a/OpenNest.Engine/BestFit/PairEvaluator.cs b/OpenNest.Engine/BestFit/PairEvaluator.cs
index c82df0c..a91f0c6 100644
--- a/OpenNest.Engine/BestFit/PairEvaluator.cs
+++ b/OpenNest.Engine/BestFit/PairEvaluator.cs
@@ -4,7 +4,6 @@ using System.Linq;
using System.Threading.Tasks;
using OpenNest.Converters;
using OpenNest.Geometry;
-using OpenNest.Math;
namespace OpenNest.Engine.BestFit
{
@@ -28,18 +27,9 @@ namespace OpenNest.Engine.BestFit
{
var drawing = candidate.Drawing;
- // Build part1 at origin
- var part1 = new Part(drawing);
- var bbox1 = part1.Program.BoundingBox();
- part1.Offset(-bbox1.Location.X, -bbox1.Location.Y);
- part1.UpdateBounds();
+ var part1 = Part.CreateAtOrigin(drawing);
- // Build part2 with rotation, normalized to origin, then positioned
- var part2 = new Part(drawing);
- if (!candidate.Part2Rotation.IsEqualTo(0))
- part2.Rotate(candidate.Part2Rotation);
- var bbox2 = part2.Program.BoundingBox();
- part2.Offset(-bbox2.Location.X, -bbox2.Location.Y);
+ var part2 = Part.CreateAtOrigin(drawing, candidate.Part2Rotation);
part2.Location = candidate.Part2Offset;
part2.UpdateBounds();
diff --git a/OpenNest.Engine/BestFit/RotationSlideStrategy.cs b/OpenNest.Engine/BestFit/RotationSlideStrategy.cs
index 8cb6162..6079385 100644
--- a/OpenNest.Engine/BestFit/RotationSlideStrategy.cs
+++ b/OpenNest.Engine/BestFit/RotationSlideStrategy.cs
@@ -1,6 +1,5 @@
using System.Collections.Generic;
using OpenNest.Geometry;
-using OpenNest.Math;
namespace OpenNest.Engine.BestFit
{
@@ -21,19 +20,8 @@ namespace OpenNest.Engine.BestFit
{
var candidates = new List();
- // Build part1 at origin
- var part1 = new Part(drawing);
- var bbox1 = part1.Program.BoundingBox();
- part1.Offset(-bbox1.Location.X, -bbox1.Location.Y);
- part1.UpdateBounds();
-
- // Build part2 template with rotation, normalized to origin
- var part2Template = new Part(drawing);
- if (!Part2Rotation.IsEqualTo(0))
- part2Template.Rotate(Part2Rotation);
- var bbox2 = part2Template.Program.BoundingBox();
- part2Template.Offset(-bbox2.Location.X, -bbox2.Location.Y);
- part2Template.UpdateBounds();
+ var part1 = Part.CreateAtOrigin(drawing);
+ var part2Template = Part.CreateAtOrigin(drawing, Part2Rotation);
var testNumber = 0;
diff --git a/OpenNest.Engine/NestEngine.cs b/OpenNest.Engine/NestEngine.cs
index db809e7..16b3dee 100644
--- a/OpenNest.Engine/NestEngine.cs
+++ b/OpenNest.Engine/NestEngine.cs
@@ -4,7 +4,6 @@ using System.Diagnostics;
using System.Linq;
using OpenNest.Converters;
using OpenNest.Engine.BestFit;
-using OpenNest.Engine.BestFit.Tiling;
using OpenNest.Geometry;
using OpenNest.Math;
using OpenNest.RectanglePacking;
@@ -26,96 +25,12 @@ namespace OpenNest
public bool Fill(NestItem item)
{
- var sw = Stopwatch.StartNew();
-
- var workArea = Plate.WorkArea();
- var bestRotation = FindBestRotation(item);
-
- var engine = new FillLinear(workArea, Plate.PartSpacing);
-
- // Try 4 configurations: 2 rotations x 2 axes.
- var configs = new[]
- {
- engine.Fill(item.Drawing, bestRotation, NestDirection.Horizontal),
- engine.Fill(item.Drawing, bestRotation, NestDirection.Vertical),
- engine.Fill(item.Drawing, bestRotation + Angle.HalfPI, NestDirection.Horizontal),
- engine.Fill(item.Drawing, bestRotation + Angle.HalfPI, NestDirection.Vertical)
- };
-
- // Pick the best linear configuration. FillLinear already ensures
- // geometry-aware spacing, so skip the redundant overlap check that
- // can produce false positives on arcs/curves.
- List linearBest = null;
-
- foreach (var config in configs)
- {
- if (IsBetterFill(config, linearBest))
- linearBest = config;
- }
-
- var linearMs = sw.ElapsedMilliseconds;
-
- // Try rectangle best-fit (mixes orientations to fill remnant strips).
- var rectResult = FillRectangleBestFit(item, workArea);
-
- var rectMs = sw.ElapsedMilliseconds - linearMs;
-
- // Try pair-based approach.
- var pairResult = FillWithPairs(item);
-
- var pairMs = sw.ElapsedMilliseconds - linearMs - rectMs;
-
- // Pick whichever is the better fill.
- Debug.WriteLine($"[NestEngine.Fill] Linear: {linearBest?.Count ?? 0} parts ({linearMs}ms) | Rect: {rectResult?.Count ?? 0} parts ({rectMs}ms) | Pair: {pairResult.Count} parts ({pairMs}ms) | WorkArea: {workArea.Width:F1}x{workArea.Height:F1}");
- var best = linearBest;
-
- if (IsBetterFill(rectResult, best))
- best = rectResult;
-
- if (IsBetterFill(pairResult, best))
- best = pairResult;
-
- if (best == null || best.Count == 0)
- return false;
-
- // Limit to requested quantity if specified.
- if (item.Quantity > 0 && best.Count > item.Quantity)
- best = best.Take(item.Quantity).ToList();
-
- Plate.Parts.AddRange(best);
- return true;
+ return Fill(item, Plate.WorkArea());
}
public bool Fill(List groupParts)
{
- if (groupParts == null || groupParts.Count == 0)
- return false;
-
- var workArea = Plate.WorkArea();
- var engine = new FillLinear(workArea, Plate.PartSpacing);
- var angles = FindHullEdgeAngles(groupParts);
- var best = FillPattern(engine, groupParts, angles);
-
- // For single-part groups, also try rectangle best-fit and pair-based filling.
- if (groupParts.Count == 1)
- {
- var nestItem = new NestItem { Drawing = groupParts[0].BaseDrawing };
- var rectResult = FillRectangleBestFit(nestItem, workArea);
-
- if (IsBetterFill(rectResult, best))
- best = rectResult;
-
- var pairResult = FillWithPairs(nestItem);
-
- if (IsBetterFill(pairResult, best))
- best = pairResult;
- }
-
- if (best == null || best.Count == 0)
- return false;
-
- Plate.Parts.AddRange(best);
- return true;
+ return Fill(groupParts, Plate.WorkArea());
}
public bool Fill(NestItem item, Box workArea)
@@ -204,68 +119,6 @@ namespace OpenNest
return true;
}
- public bool Fill(NestItem item, int maxCount)
- {
- if (maxCount <= 0)
- return false;
-
- var savedQty = item.Quantity;
- item.Quantity = maxCount;
- var result = Fill(item);
- item.Quantity = savedQty;
- return result;
- }
-
- 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();
- 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();
- nestItems.Add(item);
-
- var parts = ConvertToParts(bin, nestItems);
- Plate.Parts.AddRange(parts);
-
- return parts.Count > 0;
- }
-
public bool Pack(List items)
{
var workArea = Plate.WorkArea();
@@ -314,11 +167,6 @@ namespace OpenNest
return ConvertToParts(bin, nestItems);
}
- private List FillWithPairs(NestItem item)
- {
- return FillWithPairs(item, Plate.WorkArea());
- }
-
private List FillWithPairs(NestItem item, Box workArea)
{
IPairEvaluator evaluator = null;
@@ -367,16 +215,9 @@ namespace OpenNest
{
var candidate = bestFit.Candidate;
- var part1 = new Part(drawing);
- var bbox1 = part1.Program.BoundingBox();
- part1.Offset(-bbox1.Location.X, -bbox1.Location.Y);
- part1.UpdateBounds();
+ var part1 = Part.CreateAtOrigin(drawing);
- var part2 = new Part(drawing);
- if (!candidate.Part2Rotation.IsEqualTo(0))
- part2.Rotate(candidate.Part2Rotation);
- var bbox2 = part2.Program.BoundingBox();
- part2.Offset(-bbox2.Location.X, -bbox2.Location.Y);
+ var part2 = Part.CreateAtOrigin(drawing, candidate.Part2Rotation);
part2.Location = candidate.Part2Offset;
part2.UpdateBounds();
@@ -396,68 +237,6 @@ namespace OpenNest
return new List { part1, part2 };
}
- private List ConvertTileResultToParts(TileResult tileResult, Drawing drawing)
- {
- var parts = new List();
- var bestFit = tileResult.BestFit;
- var candidate = bestFit.Candidate;
- var workArea = Plate.WorkArea();
-
- foreach (var placement in tileResult.Placements)
- {
- // Build part1 at origin.
- var part1 = new Part(drawing);
- var bbox1 = part1.Program.BoundingBox();
- part1.Offset(-bbox1.Location.X, -bbox1.Location.Y);
- part1.UpdateBounds();
-
- // Build part2 with rotation, positioned at offset.
- var part2 = new Part(drawing);
-
- if (!candidate.Part2Rotation.IsEqualTo(0))
- part2.Rotate(candidate.Part2Rotation);
-
- var bbox2 = part2.Program.BoundingBox();
- part2.Offset(-bbox2.Location.X, -bbox2.Location.Y);
- part2.Location = candidate.Part2Offset;
- part2.UpdateBounds();
-
- // Apply optimal rotation to align pair to minimum bounding rectangle.
- if (!bestFit.OptimalRotation.IsEqualTo(0))
- {
- var pairBounds = ((IEnumerable)new IBoundable[] { part1, part2 }).GetBoundingBox();
- var center = pairBounds.Center;
- part1.Rotate(-bestFit.OptimalRotation, center);
- part2.Rotate(-bestFit.OptimalRotation, center);
- }
-
- // Apply 90 degree rotation if the tiler chose the rotated orientation.
- if (tileResult.PairRotated)
- {
- var pairBounds = ((IEnumerable)new IBoundable[] { part1, part2 }).GetBoundingBox();
- var center = pairBounds.Center;
- part1.Rotate(Angle.HalfPI, center);
- part2.Rotate(Angle.HalfPI, center);
- }
-
- // Normalize pair to origin.
- var finalBounds = ((IEnumerable)new IBoundable[] { part1, part2 }).GetBoundingBox();
- var normalizeOffset = new Vector(-finalBounds.Left, -finalBounds.Bottom);
- part1.Offset(normalizeOffset);
- part2.Offset(normalizeOffset);
-
- // Offset to grid position plus work area origin.
- var plateOffset = placement.Position + workArea.Location;
- part1.Offset(plateOffset);
- part2.Offset(plateOffset);
-
- parts.Add(part1);
- parts.Add(part2);
- }
-
- return parts;
- }
-
private bool HasOverlaps(List parts, double spacing)
{
if (parts == null || parts.Count <= 1)
@@ -497,23 +276,10 @@ namespace OpenNest
private bool IsBetterValidFill(List candidate, List current)
{
- if (candidate == null || candidate.Count == 0)
+ if (candidate != null && candidate.Count > 0 && HasOverlaps(candidate, Plate.PartSpacing))
return false;
- // Reject candidate if it has overlapping parts.
- if (HasOverlaps(candidate, Plate.PartSpacing))
- return false;
-
- if (current == null || current.Count == 0)
- return true;
-
- if (candidate.Count != current.Count)
- return candidate.Count > current.Count;
-
- var candidateBox = ((IEnumerable)candidate).GetBoundingBox();
- var currentBox = ((IEnumerable)current).GetBoundingBox();
-
- return candidateBox.Area() < currentBox.Area();
+ return IsBetterFill(candidate, current);
}
private List FindHullEdgeAngles(List parts)
diff --git a/OpenNest.Engine/Pattern.cs b/OpenNest.Engine/Pattern.cs
index 1cdb2e4..c5fb8ed 100644
--- a/OpenNest.Engine/Pattern.cs
+++ b/OpenNest.Engine/Pattern.cs
@@ -19,26 +19,6 @@ namespace OpenNest
BoundingBox = Parts.GetBoundingBox();
}
- public List GetLines(PushDirection facingDirection)
- {
- var lines = new List();
-
- foreach (var part in Parts)
- lines.AddRange(Helper.GetPartLines(part, facingDirection));
-
- return lines;
- }
-
- public List GetOffsetLines(double spacing, PushDirection facingDirection)
- {
- var lines = new List();
-
- foreach (var part in Parts)
- lines.AddRange(Helper.GetOffsetPartLines(part, spacing, facingDirection));
-
- return lines;
- }
-
public Pattern Clone(Vector offset)
{
var pattern = new Pattern();
diff --git a/OpenNest.Engine/RectanglePacking/FillNoRotation.cs b/OpenNest.Engine/RectanglePacking/FillNoRotation.cs
index 2f6e73a..f248276 100644
--- a/OpenNest.Engine/RectanglePacking/FillNoRotation.cs
+++ b/OpenNest.Engine/RectanglePacking/FillNoRotation.cs
@@ -17,7 +17,6 @@ namespace OpenNest.RectanglePacking
{
var ycount = (int)System.Math.Floor((Bin.Height + Tolerance.Epsilon) / item.Height);
var xcount = (int)System.Math.Floor((Bin.Width + Tolerance.Epsilon) / item.Width);
- var count = ycount * xcount;
for (int i = 0; i < xcount; i++)
{
diff --git a/OpenNest.Engine/RectanglePacking/PackBottomLeft.cs b/OpenNest.Engine/RectanglePacking/PackBottomLeft.cs
index cfe99a3..7ce8a6a 100644
--- a/OpenNest.Engine/RectanglePacking/PackBottomLeft.cs
+++ b/OpenNest.Engine/RectanglePacking/PackBottomLeft.cs
@@ -75,31 +75,6 @@ namespace OpenNest.RectanglePacking
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))