diff --git a/OpenNest.Engine/Fill/Compactor.cs b/OpenNest.Engine/Fill/Compactor.cs index 26cef71..7ce2489 100644 --- a/OpenNest.Engine/Fill/Compactor.cs +++ b/OpenNest.Engine/Fill/Compactor.cs @@ -1,6 +1,7 @@ using OpenNest.Geometry; using System.Collections.Generic; using System.Linq; +using OpenNest.Math; namespace OpenNest.Engine.Fill { @@ -14,7 +15,7 @@ namespace OpenNest.Engine.Fill public static double Push(List movingParts, Plate plate, PushDirection direction) { var obstacleParts = plate.Parts - .Where(p => !movingParts.Contains(p)) + .Where(p => !movingParts.Contains(p) && !IntersectsAny(p, movingParts)) .ToList(); return Push(movingParts, obstacleParts, plate.WorkArea(), plate.PartSpacing, direction); @@ -26,7 +27,7 @@ namespace OpenNest.Engine.Fill public static double Push(List movingParts, Plate plate, double angle) { var obstacleParts = plate.Parts - .Where(p => !movingParts.Contains(p)) + .Where(p => !movingParts.Contains(p) && !IntersectsAny(p, movingParts)) .ToList(); var direction = new Vector(System.Math.Cos(angle), System.Math.Sin(angle)); @@ -99,6 +100,13 @@ namespace OpenNest.Engine.Fill : PartGeometry.GetPerimeterEntities(obstacleParts[i]); var d = SpatialQuery.DirectionalDistance(movingEntities, obstacleEntities[i], direction); + if (d <= Tolerance.Epsilon + && partSpacing <= Tolerance.Epsilon + && CanNudgeWithoutOverlap(moving, obstacleParts[i], direction)) + { + continue; + } + if (d < distance) distance = d; } @@ -115,6 +123,31 @@ namespace OpenNest.Engine.Fill return 0; } + private static bool IntersectsAny(Part candidate, List parts) + { + for (var i = 0; i < parts.Count; i++) + { + if (candidate.Intersects(parts[i], out _)) + return true; + } + return false; + } + + private static bool CanNudgeWithoutOverlap(Part moving, Part obstacle, Vector direction) + { + var nudge = direction * (Tolerance.Epsilon * 10); + + moving.Offset(nudge); + try + { + return !moving.Intersects(obstacle, out _); + } + finally + { + moving.Offset(-nudge); + } + } + public static double Push(List movingParts, List obstacleParts, Box workArea, double partSpacing, PushDirection direction) { @@ -130,7 +163,7 @@ namespace OpenNest.Engine.Fill public static double PushBoundingBox(List movingParts, Plate plate, PushDirection direction) { var obstacleParts = plate.Parts - .Where(p => !movingParts.Contains(p)) + .Where(p => !movingParts.Contains(p) && !IntersectsAny(p, movingParts)) .ToList(); return PushBoundingBox(movingParts, obstacleParts, plate.WorkArea(), plate.PartSpacing, direction); diff --git a/OpenNest.Tests/Fill/CompactorTests.cs b/OpenNest.Tests/Fill/CompactorTests.cs index 4c27df8..e85a84a 100644 --- a/OpenNest.Tests/Fill/CompactorTests.cs +++ b/OpenNest.Tests/Fill/CompactorTests.cs @@ -97,6 +97,33 @@ namespace OpenNest.Tests.Fill return part; } + private static Drawing MakeTriangleDrawing(params Vector[] points) + { + var pgm = new OpenNest.CNC.Program(); + pgm.Codes.Add(new OpenNest.CNC.RapidMove(points[0])); + + for (var i = 1; i < points.Length; i++) + pgm.Codes.Add(new OpenNest.CNC.LinearMove(points[i])); + + pgm.Codes.Add(new OpenNest.CNC.LinearMove(points[0])); + return new Drawing("triangle", pgm); + } + + private static Part MakeTrianglePart(params Vector[] points) + { + var part = new Part(MakeTriangleDrawing(points)); + part.UpdateBounds(); + return part; + } + + private static Part MakeTrianglePart(double x, double y, params Vector[] points) + { + var part = MakeTrianglePart(points); + part.Location = new Vector(x, y); + part.UpdateBounds(); + return part; + } + [Fact] public void Push_Left_MovesPartTowardEdge() { @@ -171,6 +198,86 @@ namespace OpenNest.Tests.Fill Assert.NotEqual(distNoSpacing, distWithSpacing); } + [Fact] + public void Push_Up_AllowsSharedDiagonalEdgeToSeparate() + { + var workArea = new Box(0, 0, 20, 20); + var obstacle = MakeTrianglePart( + new Vector(0, 0), + new Vector(10, 0), + new Vector(0, 10)); + var movingPart = MakeTrianglePart( + new Vector(0, 10), + new Vector(10, 0), + new Vector(10, 10)); + + var distance = Compactor.Push( + new List { movingPart }, + new List { obstacle }, + workArea, + 0, + PushDirection.Up); + + Assert.True(distance > 0); + Assert.True(movingPart.BoundingBox.Top > 19.9); + Assert.False(movingPart.Intersects(obstacle, out _)); + } + + [Fact] + public void Push_Up_MovesAfterRightTriangleIsPushedLeftIntoSharedEdge() + { + var workArea = new Box(0, 0, 24, 24); + var leftTriangle = MakeTrianglePart( + 2, 2, + new Vector(0, 0), + new Vector(8, 0), + new Vector(4, 10)); + var rightTriangle = MakeTrianglePart( + 14, 4, + new Vector(0, 10), + new Vector(8, 10), + new Vector(4, 0)); + + var moving = new List { rightTriangle }; + var obstacles = new List { leftTriangle }; + + var leftDistance = Compactor.Push(moving, obstacles, workArea, 0, PushDirection.Left); + var yBeforePushUp = rightTriangle.Location.Y; + var bottomBeforePushUp = rightTriangle.BoundingBox.Bottom; + + var upDistance = Compactor.Push(moving, obstacles, workArea, 0, PushDirection.Up); + + Assert.True(leftDistance > 0); + Assert.True(upDistance > 0); + Assert.True(rightTriangle.Location.Y > yBeforePushUp); + Assert.True(rightTriangle.BoundingBox.Bottom > bottomBeforePushUp); + Assert.False(rightTriangle.Intersects(leftTriangle, out _)); + } + + [Fact] + public void Push_Left_BlocksWhenSharedDiagonalEdgeWouldOverlap() + { + var workArea = new Box(0, 0, 20, 20); + var obstacle = MakeTrianglePart( + new Vector(0, 0), + new Vector(10, 0), + new Vector(0, 10)); + var movingPart = MakeTrianglePart( + new Vector(0, 10), + new Vector(10, 0), + new Vector(10, 10)); + + var distance = Compactor.Push( + new List { movingPart }, + new List { obstacle }, + workArea, + 0, + PushDirection.Left); + + Assert.Equal(0, distance); + Assert.Equal(0, movingPart.BoundingBox.Left); + } + [Fact] public void Push_AngleLeft_MovesPartTowardEdge() {