diff --git a/OpenNest.Core/CNC/CuttingStrategy/ContourCuttingStrategy.cs b/OpenNest.Core/CNC/CuttingStrategy/ContourCuttingStrategy.cs index ca952595..51296827 100644 --- a/OpenNest.Core/CNC/CuttingStrategy/ContourCuttingStrategy.cs +++ b/OpenNest.Core/CNC/CuttingStrategy/ContourCuttingStrategy.cs @@ -20,6 +20,7 @@ namespace OpenNest.CNC.CuttingStrategy public CuttingResult Apply(Program partProgram, Vector approachPoint, Vector nextPartStart) { var entities = partProgram.ToGeometry(); + RefuseDisplayPerimeter(entities); var breakCuts = TakeBreakCuts(entities); entities.RemoveAll(e => e.Layer == SpecialLayers.Rapid); @@ -107,6 +108,14 @@ namespace OpenNest.CNC.CuttingStrategy return new CuttingResult { Program = result, LastCutPoint = perimeterPt }; } + // These tools would lead the outline or mistake the largest cutout for it (an External lead + // pierced on the blank). Only the cutting planner knows every contour of such a part is a hole. + private static void RefuseDisplayPerimeter(List entities) + { + if (entities.Exists(e => e.Layer == SpecialLayers.DisplayPerimeter)) + throw new InvalidOperationException(DisplayPerimeterTail.LeadInRefusal); + } + public CuttingResult ApplySingle( Program partProgram, Vector point, @@ -115,6 +124,7 @@ namespace OpenNest.CNC.CuttingStrategy ) { var entities = partProgram.ToGeometry(); + RefuseDisplayPerimeter(entities); var breakCuts = TakeBreakCuts(entities); entities.RemoveAll(e => e.Layer == SpecialLayers.Rapid); diff --git a/OpenNest.Core/CNC/DisplayPerimeterTail.cs b/OpenNest.Core/CNC/DisplayPerimeterTail.cs index 68c2cb0e..d1fc2e85 100644 --- a/OpenNest.Core/CNC/DisplayPerimeterTail.cs +++ b/OpenNest.Core/CNC/DisplayPerimeterTail.cs @@ -17,6 +17,11 @@ namespace OpenNest.CNC /// The comment that starts the tail; every code after it is display only. public const string Marker = "DISPLAY PERIMETER NEVER CUT"; + /// Why the manual and legacy lead-in tools leave a display-perimeter part alone. + public const string LeadInRefusal = + "This part's outline is a display perimeter: a supplied blank that is never cut. " + + "Lead-ins for its cutouts come from Plan Cutting, which keeps every pierce inside a cutout."; + /// The index of the tail's marker in 's codes, or -1. public static int IndexOf(Program program) { diff --git a/OpenNest.Tests/CuttingPlanning/DisplayPerimeterTests.cs b/OpenNest.Tests/CuttingPlanning/DisplayPerimeterTests.cs index 9db3cad2..c6d836cb 100644 --- a/OpenNest.Tests/CuttingPlanning/DisplayPerimeterTests.cs +++ b/OpenNest.Tests/CuttingPlanning/DisplayPerimeterTests.cs @@ -1,5 +1,6 @@ using OpenNest.CNC; using OpenNest.CNC.CuttingPlanning; +using OpenNest.Converters; using OpenNest.Engine.CuttingPlanning; using OpenNest.Geometry; @@ -144,6 +145,25 @@ public class DisplayPerimeterTests Assert.DoesNotContain(simulation.Steps, step => step.Layer == LayerType.DisplayPerimeter); } + [Fact] + public void ManualAndLegacyLeadIns_RefuseADisplayPerimeterAndLeaveThePartAlone() + { + var part = new Part(new Drawing("door", Door()), Vector.Zero); + var before = ExplicitContourTests.Fingerprint(part.Program); + var edge = ConvertProgram.ToGeometry(part.Program).First(e => e.Layer == SpecialLayers.DisplayPerimeter); + var hole = ConvertProgram.ToGeometry(part.Program).First(e => e.Layer == SpecialLayers.Cut); + + var onEdge = Assert.Throws(() => part.ApplySingleLeadIn( + ExplicitContourTests.Parameters(), new Vector(0, 10), edge, OpenNest.CNC.CuttingStrategy.ContourType.External)); + Assert.Throws(() => part.ApplySingleLeadIn( + ExplicitContourTests.Parameters(), new Vector(5, 7), hole, OpenNest.CNC.CuttingStrategy.ContourType.Internal)); + Assert.Throws(() => part.ApplyLeadIns(ExplicitContourTests.Parameters(), Vector.Zero)); + + Assert.Contains("Plan Cutting", onEdge.Message); + Assert.False(part.HasManualLeadIns); + Assert.Equal(before, ExplicitContourTests.Fingerprint(part.Program)); + } + [Fact] public void Posting_RefusesAnUnplannedDisplayPerimeter() { diff --git a/OpenNest/Actions/ActionLeadIn.cs b/OpenNest/Actions/ActionLeadIn.cs index 79e989ab..69af2a18 100644 --- a/OpenNest/Actions/ActionLeadIn.cs +++ b/OpenNest/Actions/ActionLeadIn.cs @@ -423,6 +423,14 @@ namespace OpenNest.Actions if (part.BaseDrawing.IsCutOff) return; + // A supplied blank's cutouts get their leads from Plan Cutting only. + if (CNC.DisplayPerimeterTail.HasDisplayPerimeterMotion(part.BaseDrawing.Program)) + { + MessageBox.Show(plateView.FindForm(), CNC.DisplayPerimeterTail.LeadInRefusal, "Place Lead-in", + MessageBoxButtons.OK, MessageBoxIcon.Information); + return; + } + selectedLayoutPart = layoutPart; selectedPart = part;