diff --git a/OpenNest.Core/Geometry/Box.cs b/OpenNest.Core/Geometry/Box.cs
index 6d4bcdc..7145bb7 100644
--- a/OpenNest.Core/Geometry/Box.cs
+++ b/OpenNest.Core/Geometry/Box.cs
@@ -74,6 +74,16 @@ namespace OpenNest.Geometry
Location += voffset;
}
+ public Box Translate(double x, double y)
+ {
+ return new Box(X + x, Y + y, Width, Length);
+ }
+
+ public Box Translate(Vector offset)
+ {
+ return new Box(X + offset.X, Y + offset.Y, Width, Length);
+ }
+
public double Left
{
get { return X; }
diff --git a/OpenNest.Core/Geometry/InnerFitPolygon.cs b/OpenNest.Core/Geometry/InnerFitPolygon.cs
index bfa09d4..b4c9f68 100644
--- a/OpenNest.Core/Geometry/InnerFitPolygon.cs
+++ b/OpenNest.Core/Geometry/InnerFitPolygon.cs
@@ -52,6 +52,7 @@ namespace OpenNest.Geometry
result.Vertices.Add(new Vector(ifpRight, ifpTop));
result.Vertices.Add(new Vector(ifpLeft, ifpTop));
result.Close();
+ result.UpdateBounds();
return result;
}
@@ -62,36 +63,20 @@ namespace OpenNest.Geometry
/// Returns the polygon representing valid placement positions, or an empty
/// polygon if no valid position exists.
///
- public static Polygon ComputeFeasibleRegion(Polygon ifp, Polygon[] nfps)
+ public static Polygon ComputeFeasibleRegion(Polygon ifp, PathsD nfpPaths)
{
if (ifp.Vertices.Count < 3)
return new Polygon();
- if (nfps == null || nfps.Length == 0)
+ if (nfpPaths == null || nfpPaths.Count == 0)
return ifp;
var ifpPath = NoFitPolygon.ToClipperPath(ifp);
var ifpPaths = new PathsD { ifpPath };
- // Union all NFPs.
- var nfpPaths = new PathsD();
-
- foreach (var nfp in nfps)
- {
- if (nfp.Vertices.Count >= 3)
- {
- var path = NoFitPolygon.ToClipperPath(nfp);
- nfpPaths.Add(path);
- }
- }
-
- if (nfpPaths.Count == 0)
- return ifp;
-
- var nfpUnion = Clipper.Union(nfpPaths, FillRule.NonZero);
-
- // Subtract the NFP union from the IFP.
- var feasible = Clipper.Difference(ifpPaths, nfpUnion, FillRule.NonZero);
+ // Subtract the NFPs from the IFP.
+ // Clipper2 handles the implicit union of the clip paths.
+ var feasible = Clipper.Difference(ifpPaths, nfpPaths, FillRule.NonZero);
if (feasible.Count == 0)
return new Polygon();
@@ -118,6 +103,25 @@ namespace OpenNest.Geometry
return bestPath != null ? NoFitPolygon.FromClipperPath(bestPath) : new Polygon();
}
+ ///
+ /// Computes the feasible region for placing a part given already-placed parts.
+ /// (Legacy overload for backward compatibility).
+ ///
+ public static Polygon ComputeFeasibleRegion(Polygon ifp, Polygon[] nfps)
+ {
+ if (nfps == null || nfps.Length == 0)
+ return ifp;
+
+ var nfpPaths = new PathsD(nfps.Length);
+ foreach (var nfp in nfps)
+ {
+ if (nfp.Vertices.Count >= 3)
+ nfpPaths.Add(NoFitPolygon.ToClipperPath(nfp));
+ }
+
+ return ComputeFeasibleRegion(ifp, nfpPaths);
+ }
+
///
/// Finds the bottom-left-most point on a polygon boundary.
/// "Bottom-left" means: minimize Y first, then minimize X.
diff --git a/OpenNest.Core/Geometry/NoFitPolygon.cs b/OpenNest.Core/Geometry/NoFitPolygon.cs
index 3e89222..4749517 100644
--- a/OpenNest.Core/Geometry/NoFitPolygon.cs
+++ b/OpenNest.Core/Geometry/NoFitPolygon.cs
@@ -250,9 +250,9 @@ namespace OpenNest.Geometry
}
///
- /// Converts an OpenNest Polygon to a Clipper2 PathD.
+ /// Converts an OpenNest Polygon to a Clipper2 PathD, with an optional offset.
///
- internal static PathD ToClipperPath(Polygon polygon)
+ public static PathD ToClipperPath(Polygon polygon, Vector offset = default)
{
var path = new PathD();
var verts = polygon.Vertices;
@@ -263,7 +263,7 @@ namespace OpenNest.Geometry
n--;
for (var i = 0; i < n; i++)
- path.Add(new PointD(verts[i].X, verts[i].Y));
+ path.Add(new PointD(verts[i].X + offset.X, verts[i].Y + offset.Y));
return path;
}
@@ -271,7 +271,7 @@ namespace OpenNest.Geometry
///
/// Converts a Clipper2 PathD to an OpenNest Polygon.
///
- internal static Polygon FromClipperPath(PathD path)
+ public static Polygon FromClipperPath(PathD path)
{
var polygon = new Polygon();
@@ -279,6 +279,7 @@ namespace OpenNest.Geometry
polygon.Vertices.Add(new Vector(pt.x, pt.y));
polygon.Close();
+ polygon.UpdateBounds();
return polygon;
}
}