diff --git a/OpenNest.Core/CNC/CuttingStrategy/ContourCuttingStrategy.cs b/OpenNest.Core/CNC/CuttingStrategy/ContourCuttingStrategy.cs index 8812b49..f056d41 100644 --- a/OpenNest.Core/CNC/CuttingStrategy/ContourCuttingStrategy.cs +++ b/OpenNest.Core/CNC/CuttingStrategy/ContourCuttingStrategy.cs @@ -102,7 +102,7 @@ namespace OpenNest.CNC.CuttingStrategy return ordered; } - private ContourType DetectContourType(Shape cutout) + internal static ContourType DetectContourType(Shape cutout) { if (cutout.Entities.Count == 1 && cutout.Entities[0] is Circle) return ContourType.ArcCircle; @@ -110,7 +110,7 @@ namespace OpenNest.CNC.CuttingStrategy return ContourType.Internal; } - private double ComputeNormal(Vector point, Entity entity, ContourType contourType) + internal static double ComputeNormal(Vector point, Entity entity, ContourType contourType) { double normal; @@ -141,7 +141,7 @@ namespace OpenNest.CNC.CuttingStrategy return Math.Angle.NormalizeRad(normal); } - private RotationType DetermineWinding(Shape shape) + internal static RotationType DetermineWinding(Shape shape) { // Use signed area: positive = CCW, negative = CW var area = shape.Area(); diff --git a/OpenNest.Core/Part.cs b/OpenNest.Core/Part.cs index b45c117..075115e 100644 --- a/OpenNest.Core/Part.cs +++ b/OpenNest.Core/Part.cs @@ -22,6 +22,7 @@ namespace OpenNest { private Vector location; private bool ownsProgram; + private double preLeadInRotation; public readonly Drawing BaseDrawing; @@ -56,6 +57,38 @@ namespace OpenNest public bool HasManualLeadIns { get; set; } + public bool LeadInsLocked { get; set; } + + public CNC.CuttingStrategy.CuttingParameters CuttingParameters { get; set; } + + public void ApplyLeadIns(CNC.CuttingStrategy.CuttingParameters parameters, Vector approachPoint) + { + preLeadInRotation = Rotation; + var strategy = new CNC.CuttingStrategy.ContourCuttingStrategy { Parameters = parameters }; + var result = strategy.Apply(Program, approachPoint); + Program = result.Program; + CuttingParameters = parameters; + HasManualLeadIns = true; + UpdateBounds(); + } + + public void RemoveLeadIns() + { + var rotation = preLeadInRotation; + var location = Location; + Program = BaseDrawing.Program.Clone() as Program; + ownsProgram = true; + + if (!Math.Tolerance.IsEqualTo(rotation, 0)) + Program.Rotate(rotation); + + Location = location; + HasManualLeadIns = false; + LeadInsLocked = false; + CuttingParameters = null; + UpdateBounds(); + } + /// /// Gets the rotation of the part in radians. /// diff --git a/OpenNest.Tests/PartLeadInTests.cs b/OpenNest.Tests/PartLeadInTests.cs new file mode 100644 index 0000000..a4ca78b --- /dev/null +++ b/OpenNest.Tests/PartLeadInTests.cs @@ -0,0 +1,127 @@ +using OpenNest.CNC; +using OpenNest.CNC.CuttingStrategy; +using OpenNest.Geometry; + +namespace OpenNest.Tests; + +public class PartLeadInTests +{ + private static Part MakeSquarePart() + { + var pgm = new Program(); + pgm.Codes.Add(new RapidMove(new Vector(0, 0))); + pgm.Codes.Add(new LinearMove(new Vector(0, 10))); + pgm.Codes.Add(new LinearMove(new Vector(10, 10))); + pgm.Codes.Add(new LinearMove(new Vector(10, 0))); + pgm.Codes.Add(new LinearMove(new Vector(0, 0))); + var drawing = new Drawing("test", pgm); + return new Part(drawing); + } + + [Fact] + public void ApplyLeadIns_SetsHasManualLeadIns() + { + var part = MakeSquarePart(); + var parameters = new CuttingParameters + { + ExternalLeadIn = new LineLeadIn { Length = 0.5, ApproachAngle = 90 }, + InternalLeadIn = new LineLeadIn { Length = 0.25, ApproachAngle = 90 } + }; + + part.ApplyLeadIns(parameters, new Vector(-5, -5)); + + Assert.True(part.HasManualLeadIns); + } + + [Fact] + public void ApplyLeadIns_StoresCuttingParameters() + { + var part = MakeSquarePart(); + var parameters = new CuttingParameters + { + ExternalLeadIn = new LineLeadIn { Length = 0.5, ApproachAngle = 90 }, + InternalLeadIn = new LineLeadIn { Length = 0.25, ApproachAngle = 90 } + }; + + part.ApplyLeadIns(parameters, new Vector(-5, -5)); + + Assert.Same(parameters, part.CuttingParameters); + } + + [Fact] + public void ApplyLeadIns_ProgramContainsLeadinCodes() + { + var part = MakeSquarePart(); + var parameters = new CuttingParameters + { + ExternalLeadIn = new LineLeadIn { Length = 0.5, ApproachAngle = 90 } + }; + + part.ApplyLeadIns(parameters, new Vector(-5, -5)); + + var hasLeadin = part.Program.Codes.OfType().Any(m => m.Layer == LayerType.Leadin); + Assert.True(hasLeadin); + } + + [Fact] + public void RemoveLeadIns_RestoresOriginalProgram() + { + var part = MakeSquarePart(); + var originalCodeCount = part.Program.Codes.Count; + var parameters = new CuttingParameters + { + ExternalLeadIn = new LineLeadIn { Length = 0.5, ApproachAngle = 90 } + }; + + part.ApplyLeadIns(parameters, new Vector(-5, -5)); + part.RemoveLeadIns(); + + Assert.False(part.HasManualLeadIns); + Assert.Null(part.CuttingParameters); + Assert.Equal(originalCodeCount, part.Program.Codes.Count); + } + + [Fact] + public void RemoveLeadIns_PreservesRotation() + { + var part = MakeSquarePart(); + part.Rotate(System.Math.PI / 4); // 45 degrees + var rotation = part.Rotation; + + var parameters = new CuttingParameters + { + ExternalLeadIn = new LineLeadIn { Length = 0.5, ApproachAngle = 90 } + }; + + part.ApplyLeadIns(parameters, new Vector(-5, -5)); + part.RemoveLeadIns(); + + Assert.Equal(rotation, part.Rotation, 6); + } + + [Fact] + public void RemoveLeadIns_PreservesLocation() + { + var part = MakeSquarePart(); + part.Offset(20, 30); + var location = part.Location; + + var parameters = new CuttingParameters + { + ExternalLeadIn = new LineLeadIn { Length = 0.5, ApproachAngle = 90 } + }; + + part.ApplyLeadIns(parameters, new Vector(-5, -5)); + part.RemoveLeadIns(); + + Assert.Equal(location.X, part.Location.X, 6); + Assert.Equal(location.Y, part.Location.Y, 6); + } + + [Fact] + public void LeadInsLocked_DefaultsFalse() + { + var part = MakeSquarePart(); + Assert.False(part.LeadInsLocked); + } +}