feat: add LeadInAssigner for auto-assigning lead-ins to plate parts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-30 13:26:43 -04:00
parent a2a19938d3
commit f34dce95da
2 changed files with 138 additions and 0 deletions

View File

@@ -0,0 +1,40 @@
using OpenNest.Engine.Sequencing;
using OpenNest.Geometry;
using System.Linq;
namespace OpenNest.Engine
{
public class LeadInAssigner
{
public IPartSequencer Sequencer { get; set; }
public void Assign(Plate plate)
{
var parameters = plate.CuttingParameters;
if (parameters == null)
return;
var sequenced = Sequencer.Sequence(plate.Parts.ToList(), plate);
var currentPoint = PlateHelper.GetExitPoint(plate);
foreach (var sp in sequenced)
{
var part = sp.Part;
if (part.LeadInsLocked)
{
currentPoint = part.Location;
continue;
}
if (part.HasManualLeadIns)
part.RemoveLeadIns();
var localApproach = currentPoint - part.Location;
part.ApplyLeadIns(parameters, localApproach);
currentPoint = part.Location;
}
}
}
}

View File

@@ -0,0 +1,98 @@
using OpenNest.CNC;
using OpenNest.CNC.CuttingStrategy;
using OpenNest.Engine;
using OpenNest.Engine.Sequencing;
using OpenNest.Geometry;
namespace OpenNest.Tests;
public class LeadInAssignerTests
{
private static Part MakeSquarePartAt(double x, double y)
{
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, new Vector(x, y));
}
[Fact]
public void Assign_SetsHasManualLeadInsOnAllParts()
{
var plate = new Plate(60, 120);
plate.Parts.Add(MakeSquarePartAt(10, 10));
plate.Parts.Add(MakeSquarePartAt(30, 30));
plate.CuttingParameters = new CuttingParameters
{
ExternalLeadIn = new LineLeadIn { Length = 0.5, ApproachAngle = 90 }
};
var assigner = new LeadInAssigner { Sequencer = new LeftSideSequencer() };
assigner.Assign(plate);
Assert.All(plate.Parts, p => Assert.True(p.HasManualLeadIns));
}
[Fact]
public void Assign_SkipsLockedParts()
{
var plate = new Plate(60, 120);
var lockedPart = MakeSquarePartAt(10, 10);
lockedPart.LeadInsLocked = true;
lockedPart.HasManualLeadIns = true;
var originalProgram = lockedPart.Program;
plate.Parts.Add(lockedPart);
plate.Parts.Add(MakeSquarePartAt(30, 30));
plate.CuttingParameters = new CuttingParameters
{
ExternalLeadIn = new LineLeadIn { Length = 0.5, ApproachAngle = 90 }
};
var assigner = new LeadInAssigner { Sequencer = new LeftSideSequencer() };
assigner.Assign(plate);
Assert.Same(originalProgram, lockedPart.Program);
}
[Fact]
public void Assign_RemovesExistingLeadInsBeforeReapply()
{
var plate = new Plate(60, 120);
plate.Parts.Add(MakeSquarePartAt(10, 10));
plate.CuttingParameters = new CuttingParameters
{
ExternalLeadIn = new LineLeadIn { Length = 0.5, ApproachAngle = 90 }
};
var assigner = new LeadInAssigner { Sequencer = new LeftSideSequencer() };
assigner.Assign(plate);
var countAfterFirst = plate.Parts[0].Program.Codes.Count;
assigner.Assign(plate);
var countAfterSecond = plate.Parts[0].Program.Codes.Count;
Assert.Equal(countAfterFirst, countAfterSecond);
}
[Fact]
public void Assign_PartsContainLeadinLayerCodes()
{
var plate = new Plate(60, 120);
plate.Parts.Add(MakeSquarePartAt(10, 10));
plate.CuttingParameters = new CuttingParameters
{
ExternalLeadIn = new LineLeadIn { Length = 0.5, ApproachAngle = 90 }
};
var assigner = new LeadInAssigner { Sequencer = new LeftSideSequencer() };
assigner.Assign(plate);
var hasLeadin = plate.Parts[0].Program.Codes.OfType<LinearMove>().Any(m => m.Layer == LayerType.Leadin);
Assert.True(hasLeadin);
}
}