using OpenNest.CNC;
using OpenNest.CNC.CuttingPlanning;
using OpenNest.CNC.CuttingStrategy;
using OpenNest.Diagnostics;
using OpenNest.Engine.CuttingPlanning;
using OpenNest.Geometry;
namespace OpenNest.Tests.CuttingPlanning;
///
/// D1a for drawing pins: a pinned lead that meets material is a named warning on an UNVERIFIED
/// proposal, never relocated or shortened. The rapids between contours stay strict.
///
public class PinnedLeadWarningTests
{
[Fact]
public void PinnedLeadIntoNeighbor_IsUnverifiedWithTheRestPlannedAndTheEntryKept()
{
var (plate, pinned, neighbor) = NeighborFixture(pins: true);
var proposal = CuttingPlanBatch.Capture([plate], PinnedContourTests.NestSettings(), false).Plan();
var planned = Assert.Single(proposal.Plates);
Assert.True(planned.HasOnlyFixedLeadWarnings, string.Join("\n", proposal.Describe("in")));
var warning = Assert.Single(planned.Result.Findings);
Assert.Equal(PostVerificationKind.FixedLead, warning.Kind);
Assert.Same(pinned, warning.SourcePart);
Assert.Same(neighbor, warning.OtherSourcePart);
Assert.Equal(new FixedLeadWarning(1, 1, 1), warning.FixedLead);
Assert.Equal("Drawing pin 1 lead on cutting contour 1 contacts or enters another part's material. "
+ "The pinned contour is cut exactly as its drawing says. Increase spacing or move this part away "
+ "from the part it meets, then replan.", warning.Message);
// The search never relocates the pinned entry, and the neighbor is planned normally.
var proposed = planned.Result.ProposedOrder;
var placement = Assert.Single(proposed, p => ReferenceEquals(p.SourcePart, pinned));
Assert.Equal(new Vector(10, 5), Assert.Single(placement.ContourChoices).Point);
var lead = ExecutionMotionReader.Read(placement.CopyProgram(), pinned.Location, null, default).Motions
.Single(m => m.Layer == LayerType.Leadin);
Assert.Equal(1.0, lead.Length, 9); // Not shortened to fit.
Assert.True(Assert.Single(proposed, p => ReferenceEquals(p.SourcePart, neighbor)).IsRegenerated);
Assert.Equal(CuttingCommitStatus.InvalidInput, proposal.Apply().Status);
Assert.Equal(CuttingCommitStatus.Applied, proposal.Apply(acceptWarnings: true).Status);
}
[Fact]
public void UnpinnedControl_IsReady()
{
var (plate, _, _) = NeighborFixture(pins: false);
var proposal = CuttingPlanBatch.Capture([plate], PinnedContourTests.NestSettings(), false).Plan();
Assert.True(proposal.CanApply, string.Join("\n", proposal.Describe("in")));
}
[Fact]
public void PinsOffOnThePinnedPart_IsReady()
{
var (plate, pinned, _) = NeighborFixture(pins: true);
pinned.DrawingPinsOff = true;
var proposal = CuttingPlanBatch.Capture([plate], PinnedContourTests.NestSettings(), false).Plan();
Assert.True(proposal.CanApply, string.Join("\n", proposal.Describe("in")));
}
[Fact]
public void PinnedHoleLeadIntoItsOwnPartsMaterial_Warns()
{
// Two square holes; hole 2's pinned 2.5 lead runs from beyond its right edge.
var program = new Program();
program.MoveTo(2, 2); program.LineTo(4, 2); program.LineTo(4, 4); program.LineTo(2, 4); program.LineTo(2, 2);
program.MoveTo(4.5, 2); program.LineTo(6.5, 2); program.LineTo(6.5, 4); program.LineTo(4.5, 4); program.LineTo(4.5, 2);
program.MoveTo(0, 0); program.LineTo(0, 10); program.LineTo(10, 10); program.LineTo(10, 0); program.LineTo(0, 0);
var drawing = new Drawing("holes", program);
drawing.CutPins.Add(new ContourCutPin(new Vector(4.5, 3), new LineLeadIn { Length = 2.5, ApproachAngle = 90 },
new NoLeadOut()));
var part = new Part(drawing, new Vector(1, 1));
var result = CuttingPlanService.Plan(new CuttingPlanRequest([part], new Vector(-2, -2),
confirmedParameters: PinnedContourTests.NestSettings()));
Assert.Equal(CuttingPlanStatus.BestEffort, result.Status);
Assert.True(result.IndependentlyReplayed);
var warning = Assert.Single(result.Findings);
Assert.StartsWith("Drawing pin 1 lead on cutting contour", warning.Message);
Assert.Contains("contacts or enters its own part's material", warning.Message);
Assert.Null(warning.OtherSourcePart);
Assert.Equal(1, warning.FixedLead!.PinNumber);
}
[Fact]
public void RapidIntoAPinnedContour_StaysStrict()
{
// Holes(): circles at (3, 3) and (7, 3) in a 10 x 10 square; the outside is pinned at
// (10, 3). Cutting the left hole last and leaving from its far side makes the planner's
// rapid to the pinned pierce cross both finished holes: a refusal, never a warning.
var drawing = new Drawing("holes", PreparedContourTests.Holes());
drawing.CutPins.Add(new ContourCutPin(new Vector(10, 3), new LineLeadIn { Length = 0.25, ApproachAngle = 90 },
new NoLeadOut()));
var part = new Part(drawing, Vector.Zero);
var snapshot = CuttingPlanService.Capture(new CuttingPlanRequest([part], new Vector(-2, -2),
confirmedParameters: PinnedContourTests.NestSettings()));
var source = snapshot.Placements[0];
var prepared = source.Prepared!;
var choices = new[] { prepared.Entry(1, 0, new Vector(6, 3)), prepared.Entry(0, 0, new Vector(2, 3)),
prepared.PinnedEntry(2)! };
var program = prepared.Emit(choices);
var proposal = source.Propose(program, ExecutionMotionReader.Read(program, Vector.Zero, new Vector(-2, -2), default),
choices);
var replay = CuttingPlanService.ReplayPrograms(snapshot, [proposal], 0, default);
Assert.Equal(CuttingPlanStatus.ConstraintConflict, replay.Status);
Assert.Contains(replay.Findings, f => f.Kind == PostVerificationKind.RapidCrossing);
Assert.DoesNotContain(replay.Findings, f => f.Kind == PostVerificationKind.FixedLead);
}
[Fact]
public void FeasibilityProbe_AcceptsAPinnedEntryAsGiven()
{
var (plate, pinned, _) = NeighborFixture(pins: true);
var snapshot = CuttingPlanService.Capture(CuttingPlanRequest.ForPlate(plate,
confirmedParameters: PinnedContourTests.NestSettings()));
var source = snapshot.Placements.Single(p => ReferenceEquals(p.SourcePart, pinned));
var adapter = new ContourEntryFeasibility(source.Prepared!, source.Location, source.Material!,
snapshot.Placements.Select(p => p.Material!).ToArray());
var verdict = adapter.Check(source.Prepared!.PinnedEntry(0)!);
Assert.True(verdict.IsClear);
Assert.Equal(0, adapter.EvaluationCount);
}
///
/// A 10 x 10 square pinned at its right edge midpoint with a 1.0 straight lead, and a
/// 2 x 2 neighbor 0.3 to its right that the lead runs through.
///
private static (Plate Plate, Part Pinned, Part Neighbor) NeighborFixture(bool pins)
{
var drawing = new Drawing("pinned", ExplicitContourTests.Square(false));
if (pins)
drawing.CutPins.Add(new ContourCutPin(new Vector(10, 5), new LineLeadIn { Length = 1.0, ApproachAngle = 90 },
new NoLeadOut()));
var pinned = new Part(drawing, Vector.Zero);
var neighbor = new Part(new Drawing("neighbor", LeadPathValidationTests.Rectangle(0, 0, 2, 2)),
new Vector(10.3, 4));
var plate = new Nest().CreatePlate();
plate.Parts.Add(pinned);
plate.Parts.Add(neighbor);
return (plate, pinned, neighbor);
}
}