diff --git a/OpenNest.Engine/BestFit/PairEvaluator.cs b/OpenNest.Engine/BestFit/PairEvaluator.cs new file mode 100644 index 0000000..814a88d --- /dev/null +++ b/OpenNest.Engine/BestFit/PairEvaluator.cs @@ -0,0 +1,122 @@ +using System.Collections.Generic; +using System.Linq; +using OpenNest.Converters; +using OpenNest.Geometry; +using OpenNest.Math; + +namespace OpenNest.Engine.BestFit +{ + public class PairEvaluator + { + private const double ChordTolerance = 0.01; + + public BestFitResult Evaluate(PairCandidate candidate) + { + 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(); + + // 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); + part2.Location = candidate.Part2Offset; + part2.UpdateBounds(); + + // Check overlap via shape intersection + var overlaps = CheckOverlap(part1, part2); + + // Collect all polygon vertices for convex hull / optimal rotation + var allPoints = GetPartVertices(part1); + allPoints.AddRange(GetPartVertices(part2)); + + // Find optimal bounding rectangle via rotating calipers + double bestArea, bestWidth, bestHeight, bestRotation; + + if (allPoints.Count >= 3) + { + var hull = ConvexHull.Compute(allPoints); + var result = RotatingCalipers.MinimumBoundingRectangle(hull); + bestArea = result.Area; + bestWidth = result.Width; + bestHeight = result.Height; + bestRotation = result.Angle; + } + else + { + var combinedBox = ((IEnumerable)new IBoundable[] { part1, part2 }).GetBoundingBox(); + bestArea = combinedBox.Area(); + bestWidth = combinedBox.Width; + bestHeight = combinedBox.Height; + bestRotation = 0; + } + + var trueArea = drawing.Area * 2; + + return new BestFitResult + { + Candidate = candidate, + RotatedArea = bestArea, + BoundingWidth = bestWidth, + BoundingHeight = bestHeight, + OptimalRotation = bestRotation, + TrueArea = trueArea, + Keep = !overlaps, + Reason = overlaps ? "Overlap detected" : "Valid" + }; + } + + private bool CheckOverlap(Part part1, Part part2) + { + var shapes1 = GetPartShapes(part1); + var shapes2 = GetPartShapes(part2); + + for (var i = 0; i < shapes1.Count; i++) + { + for (var j = 0; j < shapes2.Count; j++) + { + List pts; + + if (shapes1[i].Intersects(shapes2[j], out pts)) + return true; + } + } + + return false; + } + + private List GetPartShapes(Part part) + { + var entities = ConvertProgram.ToGeometry(part.Program) + .Where(e => e.Layer != SpecialLayers.Rapid); + var shapes = Helper.GetShapes(entities); + shapes.ForEach(s => s.Offset(part.Location)); + return shapes; + } + + private List GetPartVertices(Part part) + { + var entities = ConvertProgram.ToGeometry(part.Program) + .Where(e => e.Layer != SpecialLayers.Rapid); + var shapes = Helper.GetShapes(entities); + var points = new List(); + + foreach (var shape in shapes) + { + var polygon = shape.ToPolygonWithTolerance(ChordTolerance); + polygon.Offset(part.Location); + + foreach (var vertex in polygon.Vertices) + points.Add(vertex); + } + + return points; + } + } +} diff --git a/OpenNest.Engine/OpenNest.Engine.csproj b/OpenNest.Engine/OpenNest.Engine.csproj index 6b81169..d89ba41 100644 --- a/OpenNest.Engine/OpenNest.Engine.csproj +++ b/OpenNest.Engine/OpenNest.Engine.csproj @@ -40,6 +40,7 @@ +