diff --git a/OpenNest.Core/PartGeometry.cs b/OpenNest.Core/PartGeometry.cs index 2289c43b..bb36d5d5 100644 --- a/OpenNest.Core/PartGeometry.cs +++ b/OpenNest.Core/PartGeometry.cs @@ -7,12 +7,21 @@ namespace OpenNest { public static class PartGeometry { + /// + /// Nesting material of a program, without lead-ins and lead-outs: a lead is a cut + /// into scrap, not part material, and chaining it into the perimeter leaves an open + /// spur, so the offset contour no longer closes and every slide contact against it + /// blocks. + /// + private static List MaterialEntities(CNC.Program program) => + ConvertProgram.ToGeometry(program) + .Where(e => e.Layer != SpecialLayers.Leadin && e.Layer != SpecialLayers.Leadout) + .ForNesting(); + public static List GetPartLines(Part part, double chordTolerance = 0.001) { - var entities = ConvertProgram.ToGeometry(part.Program); - var shapes = ShapeBuilder.GetShapes( - entities.ForNesting() - ); + var entities = MaterialEntities(part.Program); + var shapes = ShapeBuilder.GetShapes(entities); var lines = new List(); foreach (var shape in shapes) @@ -55,10 +64,8 @@ namespace OpenNest public static List GetOffsetPerimeterEntities(CNC.Program program, double spacing) { PerfCounters.CountOffsetPerimeterEntities(); - var geoEntities = ConvertProgram.ToGeometry(program); - var profile = new ShapeProfile( - geoEntities.ForNesting().ToList() - ); + var geoEntities = MaterialEntities(program); + var profile = new ShapeProfile(geoEntities); var offsetShape = profile.Perimeter.OffsetOutward(spacing); if (offsetShape == null) @@ -73,10 +80,8 @@ namespace OpenNest /// public static List GetOffsetPartEntities(Part part, double spacing) { - var geoEntities = ConvertProgram.ToGeometry(part.Program); - var profile = new ShapeProfile( - geoEntities.ForNesting().ToList() - ); + var geoEntities = MaterialEntities(part.Program); + var profile = new ShapeProfile(geoEntities); var entities = new List(); var perimeter = profile.Perimeter.OffsetOutward(spacing); @@ -106,10 +111,8 @@ namespace OpenNest /// public static List GetPerimeterEntities(Part part) { - var geoEntities = ConvertProgram.ToGeometry(part.Program); - var profile = new ShapeProfile( - geoEntities.ForNesting().ToList() - ); + var geoEntities = MaterialEntities(part.Program); + var profile = new ShapeProfile(geoEntities); return CopyEntitiesAtLocation(profile.Perimeter.Entities, part.Location); } @@ -120,10 +123,8 @@ namespace OpenNest /// public static List GetPartEntities(Part part) { - var geoEntities = ConvertProgram.ToGeometry(part.Program); - var profile = new ShapeProfile( - geoEntities.ForNesting().ToList() - ); + var geoEntities = MaterialEntities(part.Program); + var profile = new ShapeProfile(geoEntities); var entities = CopyEntitiesAtLocation(profile.Perimeter.Entities, part.Location); foreach (var cutout in profile.Cutouts) @@ -165,10 +166,8 @@ namespace OpenNest Vector vectorDirection ) { - var entities = ConvertProgram.ToGeometry(part.Program); - var shapes = ShapeBuilder.GetShapes( - entities.ForNesting() - ); + var entities = MaterialEntities(part.Program); + var shapes = ShapeBuilder.GetShapes(entities); var lines = new List(); foreach (var shape in shapes) diff --git a/OpenNest.Tests/Fill/CompactorTests.cs b/OpenNest.Tests/Fill/CompactorTests.cs index 398dafef..beb824ee 100644 --- a/OpenNest.Tests/Fill/CompactorTests.cs +++ b/OpenNest.Tests/Fill/CompactorTests.cs @@ -312,6 +312,35 @@ namespace OpenNest.Tests.Fill AssertClearance(moving, obstacle, 2); } + [Theory] + [InlineData(PushDirection.Right)] + [InlineData(PushDirection.Up)] + public void Push_WithSpacing_ContactWithLeadInPartDoesNotBlockOtherDirections( + PushDirection next + ) + { + // Leads are cut into scrap, not material: chained into the obstacle's perimeter + // they left its offset contour open, so every contact with it blocked. + var workArea = new Box(0, 0, 100, 100); + var obstacle = MakeRectPart(20, 20, 40, 10); + obstacle.ApplyLeadIns(new OpenNest.CNC.CuttingStrategy.CuttingParameters + { + ExternalLeadIn = new OpenNest.CNC.CuttingStrategy.LineLeadIn { Length = 0.25 }, + ExternalLeadOut = new OpenNest.CNC.CuttingStrategy.LineLeadOut { Length = 0.25 }, + }, new Vector(0, 0)); + var moving = MakeRectPart(30, 60, 5, 5); + var parts = new List { moving }; + var obstacles = new List { obstacle }; + + Assert.True(Compactor.Push(parts, obstacles, workArea, 2, PushDirection.Down) > 0); + Assert.Equal(32, moving.BoundingBox.Bottom, 7); + + var distance = Compactor.Push(parts, obstacles, workArea, 2, next); + + Assert.True(distance > 1, $"Push {next} after contact moved only {distance:R}"); + AssertClearance(moving, obstacle, 2); + } + [Fact] public void Push_WithSpacing_ContactStillBlocksTheSameDirection() { diff --git a/OpenNest.Tests/Geometry/PartGeometryDirectionalPreparationTests.cs b/OpenNest.Tests/Geometry/PartGeometryDirectionalPreparationTests.cs index 679a81b4..5c3e3575 100644 --- a/OpenNest.Tests/Geometry/PartGeometryDirectionalPreparationTests.cs +++ b/OpenNest.Tests/Geometry/PartGeometryDirectionalPreparationTests.cs @@ -45,13 +45,13 @@ public class PartGeometryDirectionalPreparationTests } [Fact] - public void MaterialIncludesDisplayAndLeads_ButExcludesRapidAndScribe() + public void MaterialIncludesDisplay_ButExcludesLeadsRapidAndScribe() { var material = RectangleProgram(false); AddRectangle(material, 15, 2, 3, 2, LayerType.Display); + var clean = MakePart(material); AddRectangle(material, 21, 3, 2, 4, LayerType.Leadin); AddRectangle(material, 26, 1, 4, 3, LayerType.Leadout); - var clean = MakePart(material); AddRectangle(material, 100, 120, 7, 9, LayerType.Scribe); material.MoveTo(-200, -300); material.MoveTo(400, 500); @@ -59,9 +59,9 @@ public class PartGeometryDirectionalPreparationTests foreach (var direction in new[] { PushDirection.Left, PushDirection.Right, PushDirection.Up, PushDirection.Down }) { var expected = FrozenGetPartLines(clean, direction); - Assert.Equal(4, expected.Count); + Assert.Equal(2, expected.Count); AssertLines(expected, PartGeometry.GetPartLines(marked, direction)); - AssertMatches(marked, direction, new Vector(3, -2), 0.001); + AssertMatches(clean, direction, new Vector(3, -2), 0.001); } AssertLines(PartGeometry.GetPartLines(clean), PartGeometry.GetPartLines(marked)); }