fix(leads): keep manual lead-ins off a display perimeter

Place Lead-in built its contours from cut-layer entities only, so on a
blank the largest cutout looked like the outside and took an external
lead pierced on the blank; committing then regenerated the part with the
outline as a cut perimeter. The legacy whole-part strategy did the same.

Both strategies now refuse a program with a display perimeter and leave
the part unchanged, and Place Lead-in explains that such a part's cutouts
get their leads from Plan Cutting instead of selecting it.
This commit is contained in:
aj committed 2026-10-22 20:09:23 -04:00
1 parent 8f568482bc
commit 684207039a
4 files changed
+43

No files matched your search

@@ -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<Entity> 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);
@@ -17,6 +17,11 @@ namespace OpenNest.CNC
/// <summary>The comment that starts the tail; every code after it is display only.</summary>
public const string Marker = "DISPLAY PERIMETER NEVER CUT";
/// <summary>Why the manual and legacy lead-in tools leave a display-perimeter part alone.</summary>
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.";
/// <summary>The index of the tail's marker in <paramref name="program"/>'s codes, or -1.</summary>
public static int IndexOf(Program program)
{
@@ -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<InvalidOperationException>(() => part.ApplySingleLeadIn(
ExplicitContourTests.Parameters(), new Vector(0, 10), edge, OpenNest.CNC.CuttingStrategy.ContourType.External));
Assert.Throws<InvalidOperationException>(() => part.ApplySingleLeadIn(
ExplicitContourTests.Parameters(), new Vector(5, 7), hole, OpenNest.CNC.CuttingStrategy.ContourType.Internal));
Assert.Throws<InvalidOperationException>(() => 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()
{
+8
View File
@@ -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;