diff --git a/OpenNest.Engine/NestEngine.cs b/OpenNest.Engine/NestEngine.cs index 9d2e9c9..f4dead9 100644 --- a/OpenNest.Engine/NestEngine.cs +++ b/OpenNest.Engine/NestEngine.cs @@ -62,22 +62,28 @@ namespace OpenNest var workArea = Plate.WorkArea(); var engine = new FillLinear(workArea, Plate.PartSpacing); + var angles = FindHullEdgeAngles(groupParts); + var best = FillPattern(engine, groupParts, angles); - // Build a pattern from the group of parts. - var pattern = new Pattern(); - foreach (var part in groupParts) - { - var clone = (Part)part.Clone(); - clone.UpdateBounds(); - pattern.Parts.Add(clone); - } - pattern.UpdateBounds(); + if (best == null || best.Count == 0) + return false; + + Plate.Parts.AddRange(best); + return true; + } + + public bool Fill(NestItem item, Box workArea) + { + var bestRotation = FindBestRotation(item); + + var engine = new FillLinear(workArea, Plate.PartSpacing); - // Try 4 configurations: 2 axes x 2 orientations (horizontal/vertical). var configs = new[] { - engine.Fill(pattern, NestDirection.Horizontal), - engine.Fill(pattern, NestDirection.Vertical) + 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) }; List best = null; @@ -88,6 +94,25 @@ namespace OpenNest best = config; } + if (best == null || best.Count == 0) + return false; + + if (item.Quantity > 0 && best.Count > item.Quantity) + best = best.Take(item.Quantity).ToList(); + + Plate.Parts.AddRange(best); + return true; + } + + public bool Fill(List groupParts, Box workArea) + { + if (groupParts == null || groupParts.Count == 0) + return false; + + var engine = new FillLinear(workArea, Plate.PartSpacing); + var angles = FindHullEdgeAngles(groupParts); + var best = FillPattern(engine, groupParts, angles); + if (best == null || best.Count == 0) return false; @@ -185,6 +210,97 @@ namespace OpenNest return parts.Count > 0; } + private List FindHullEdgeAngles(List parts) + { + var points = new List(); + + foreach (var part in parts) + { + var entities = ConvertProgram.ToGeometry(part.Program) + .Where(e => e.Layer != SpecialLayers.Rapid); + + var shapes = Helper.GetShapes(entities); + + foreach (var shape in shapes) + { + var polygon = shape.ToPolygonWithTolerance(0.1); + + foreach (var vertex in polygon.Vertices) + points.Add(vertex + part.Location); + } + } + + if (points.Count < 3) + return new List { 0 }; + + var hull = ConvexHull.Compute(points); + var vertices = hull.Vertices; + var n = hull.IsClosed() ? vertices.Count - 1 : vertices.Count; + + var angles = new List { 0 }; + + 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 = -System.Math.Atan2(dy, dx); + + if (!angles.Any(a => a.IsEqualTo(angle))) + angles.Add(angle); + } + + return angles; + } + + private Pattern BuildRotatedPattern(List groupParts, double angle) + { + var pattern = new Pattern(); + var center = ((IEnumerable)groupParts).GetBoundingBox().Center; + + foreach (var part in groupParts) + { + var clone = (Part)part.Clone(); + clone.UpdateBounds(); + + if (!angle.IsEqualTo(0)) + clone.Rotate(angle, center); + + pattern.Parts.Add(clone); + } + + pattern.UpdateBounds(); + return pattern; + } + + private List FillPattern(FillLinear engine, List groupParts, List angles) + { + List best = null; + + foreach (var angle in angles) + { + var pattern = BuildRotatedPattern(groupParts, angle); + + if (pattern.Parts.Count == 0) + continue; + + var h = engine.Fill(pattern, NestDirection.Horizontal); + var v = engine.Fill(pattern, NestDirection.Vertical); + + if (best == null || h.Count > best.Count) + best = h; + + if (best == null || v.Count > best.Count) + best = v; + } + + return best; + } + private double FindBestRotation(NestItem item) { var entities = ConvertProgram.ToGeometry(item.Drawing.Program) diff --git a/OpenNest/Actions/ActionClone.cs b/OpenNest/Actions/ActionClone.cs index 534b013..5879048 100644 --- a/OpenNest/Actions/ActionClone.cs +++ b/OpenNest/Actions/ActionClone.cs @@ -106,6 +106,17 @@ namespace OpenNest.Actions plateView.SelectedParts.AddRange(parts); } + public override void ConnectEvents() + { + plateView.KeyDown += plateView_KeyDown; + plateView.MouseMove += plateView_MouseMove; + plateView.MouseDown += plateView_MouseDown; + plateView.Paint += plateView_Paint; + + plateView.SelectedParts.Clear(); + plateView.SelectedParts.AddRange(parts); + } + public override void DisconnectEvents() { plateView.KeyDown -= plateView_KeyDown; @@ -159,9 +170,34 @@ namespace OpenNest.Actions private void Fill() { - var engine = new NestEngine(plateView.Plate); + var plate = plateView.Plate; + var engine = new NestEngine(plate); var groupParts = parts.Select(p => p.BasePart).ToList(); - engine.Fill(groupParts); + + var bounds = plate.WorkArea(); + + if (plate.Parts.Count == 0) + { + engine.Fill(groupParts); + return; + } + + var boxes = new List(); + foreach (var part in plate.Parts) + boxes.Add(part.BoundingBox.Offset(plate.PartSpacing)); + + var pt = plateView.CurrentPoint; + var vertical = Helper.GetLargestBoxVertically(pt, bounds, boxes); + var horizontal = Helper.GetLargestBoxHorizontally(pt, bounds, boxes); + + var bestArea = vertical; + if (horizontal.Area() > vertical.Area()) + bestArea = horizontal; + + if (bestArea == Box.Empty) + return; + + engine.Fill(groupParts, bestArea); } } }