diff --git a/OpenNest.Engine/BestFit/BestFitFinder.cs b/OpenNest.Engine/BestFit/BestFitFinder.cs new file mode 100644 index 0000000..9aede62 --- /dev/null +++ b/OpenNest.Engine/BestFit/BestFitFinder.cs @@ -0,0 +1,179 @@ +using System.Collections.Generic; +using System.Linq; +using OpenNest.Converters; +using OpenNest.Engine.BestFit.Tiling; +using OpenNest.Geometry; +using OpenNest.Math; + +namespace OpenNest.Engine.BestFit +{ + public class BestFitFinder + { + private readonly PairEvaluator _evaluator; + private readonly BestFitFilter _filter; + + public BestFitFinder(double maxPlateWidth, double maxPlateHeight) + { + _evaluator = new PairEvaluator(); + _filter = new BestFitFilter + { + MaxPlateWidth = maxPlateWidth, + MaxPlateHeight = maxPlateHeight + }; + } + + public List FindBestFits( + Drawing drawing, + double spacing = 0.25, + double stepSize = 0.25, + BestFitSortField sortBy = BestFitSortField.Area) + { + var strategies = BuildStrategies(drawing); + + var allCandidates = new List(); + + foreach (var strategy in strategies) + allCandidates.AddRange(strategy.GenerateCandidates(drawing, spacing, stepSize)); + + var results = allCandidates.Select(c => _evaluator.Evaluate(c)).ToList(); + + _filter.Apply(results); + + results = SortResults(results, sortBy); + + for (var i = 0; i < results.Count; i++) + results[i].Candidate.TestNumber = i; + + return results; + } + + public List FindAndTile( + Drawing drawing, Plate plate, + double spacing = 0.25, double stepSize = 0.25, int topN = 10) + { + var bestFits = FindBestFits(drawing, spacing, stepSize); + var tileEvaluator = new TileEvaluator(); + + return bestFits + .Where(r => r.Keep) + .Take(topN) + .Select(r => tileEvaluator.Evaluate(r, plate)) + .OrderByDescending(t => t.PartsNested) + .ThenByDescending(t => t.Utilization) + .ToList(); + } + + private List BuildStrategies(Drawing drawing) + { + var angles = GetRotationAngles(drawing); + var strategies = new List(); + var type = 1; + + foreach (var angle in angles) + { + var desc = string.Format("{0:F1} deg rotated, offset slide", Angle.ToDegrees(angle)); + strategies.Add(new RotationSlideStrategy(angle, type++, desc)); + } + + return strategies; + } + + private List GetRotationAngles(Drawing drawing) + { + var angles = new List + { + 0, + Angle.HalfPI, + System.Math.PI, + Angle.HalfPI * 3 + }; + + var hullAngles = GetHullEdgeAngles(drawing); + + foreach (var hullAngle in hullAngles) + { + AddUniqueAngle(angles, hullAngle); + AddUniqueAngle(angles, Angle.NormalizeRad(hullAngle + System.Math.PI)); + } + + return angles; + } + + private List GetHullEdgeAngles(Drawing drawing) + { + var entities = ConvertProgram.ToGeometry(drawing.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(0.1); + points.AddRange(polygon.Vertices); + } + + if (points.Count < 3) + return new List(); + + var hull = ConvexHull.Compute(points); + var vertices = hull.Vertices; + var n = hull.IsClosed() ? vertices.Count - 1 : vertices.Count; + var hullAngles = new List(); + + for (var i = 0; i < n; i++) + { + var next = (i + 1) % n; + var dx = vertices[next].X - vertices[i].X; + var dy = vertices[next].Y - vertices[i].Y; + + if (dx * dx + dy * dy < Tolerance.Epsilon) + continue; + + var angle = Angle.NormalizeRad(System.Math.Atan2(dy, dx)); + AddUniqueAngle(hullAngles, angle); + } + + return hullAngles; + } + + private static void AddUniqueAngle(List angles, double angle) + { + angle = Angle.NormalizeRad(angle); + + foreach (var existing in angles) + { + if (existing.IsEqualTo(angle)) + return; + } + + angles.Add(angle); + } + + private List SortResults(List results, BestFitSortField sortBy) + { + switch (sortBy) + { + case BestFitSortField.Area: + return results.OrderBy(r => r.RotatedArea).ToList(); + case BestFitSortField.LongestSide: + return results.OrderBy(r => r.LongestSide).ToList(); + case BestFitSortField.ShortestSide: + return results.OrderBy(r => r.ShortestSide).ToList(); + case BestFitSortField.Type: + return results.OrderBy(r => r.Candidate.StrategyType) + .ThenBy(r => r.Candidate.TestNumber).ToList(); + case BestFitSortField.OriginalSequence: + return results.OrderBy(r => r.Candidate.TestNumber).ToList(); + case BestFitSortField.Keep: + return results.OrderByDescending(r => r.Keep) + .ThenBy(r => r.RotatedArea).ToList(); + case BestFitSortField.WhyKeepDrop: + return results.OrderBy(r => r.Reason) + .ThenBy(r => r.RotatedArea).ToList(); + default: + return results; + } + } + } +} diff --git a/OpenNest.Engine/OpenNest.Engine.csproj b/OpenNest.Engine/OpenNest.Engine.csproj index b427b7c..1c4370c 100644 --- a/OpenNest.Engine/OpenNest.Engine.csproj +++ b/OpenNest.Engine/OpenNest.Engine.csproj @@ -42,6 +42,7 @@ +