mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-11-08 04:25:45 -05:00
A library drawing can say how individual contours must be cut. A `ContourCutPin` holds the entry point in the drawing's source frame plus the lead-in, lead-out and optional tab used there; `Drawing.CutPins` lists them. Nothing plans with pins yet (Slice 5). - Pins accept only the exact built-in lead and tab types, copied through the same owned copiers the planner uses, so a pin cannot carry a type the emitter would not certify. Properties return copies: the pin is immutable. - A tab requires no lead-out, because the planner refuses a lead-out after an open contour. - The source frame is the frame of the source entities. It does not move when BuildDrawing re-origins the program at its first rapid, so pins survive converter edits that change Source.Offset. `CutPinResolver` maps pins to (contour ordinal, entity ordinal, local point) for a placement rotation, using the same `PreparedContours` ordinals Plan Cutting will use. It never guesses: a pin must lie within the contour closure tolerance of exactly one contour that no other pin claims, otherwise it is reported unresolved with a typed reason. A drawing program rotated in place (a canonical copy) is not the source frame and resolves nothing; canonical copies and split pieces carry no pins. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
68 lines
2.4 KiB
C#
68 lines
2.4 KiB
C#
using OpenNest.CNC.CuttingStrategy;
|
|
using OpenNest.Geometry;
|
|
|
|
namespace OpenNest.Tests.CuttingPlanning;
|
|
|
|
public class ContourCutPinTests
|
|
{
|
|
private sealed class DerivedLineLeadIn : LineLeadIn { }
|
|
|
|
[Fact]
|
|
public void Pin_OwnsCopiesOfItsLeadsAndTab()
|
|
{
|
|
var leadIn = new LineLeadIn { Length = 0.25, ApproachAngle = 90 };
|
|
var tab = new NormalTab { Size = 0.03 };
|
|
var pin = new ContourCutPin(new Vector(1, 2), leadIn, new NoLeadOut(), tab);
|
|
|
|
leadIn.Length = 9;
|
|
tab.Size = 9;
|
|
((LineLeadIn)pin.LeadIn).Length = 7;
|
|
pin.Tab.Size = 7;
|
|
|
|
Assert.Equal(new Vector(1, 2), pin.Point);
|
|
Assert.Equal(0.25, Assert.IsType<LineLeadIn>(pin.LeadIn).Length);
|
|
Assert.Equal(0.03, Assert.IsType<NormalTab>(pin.Tab).Size);
|
|
Assert.IsType<NoLeadOut>(pin.LeadOut);
|
|
Assert.True(pin.HasTab);
|
|
}
|
|
|
|
[Fact]
|
|
public void Pin_WithoutTab_KeepsItsLeadOut()
|
|
{
|
|
var pin = new ContourCutPin(Vector.Zero, new ArcLeadIn { Radius = 0.1 }, new LineLeadOut { Length = 0.05 });
|
|
|
|
Assert.False(pin.HasTab);
|
|
Assert.Null(pin.Tab);
|
|
Assert.Equal(0.05, Assert.IsType<LineLeadOut>(pin.LeadOut).Length);
|
|
}
|
|
|
|
[Fact]
|
|
public void Pin_WithTab_RefusesALeadOut()
|
|
{
|
|
var ex = Assert.Throws<ArgumentException>(() => new ContourCutPin(
|
|
Vector.Zero, new LineLeadIn { Length = 0.25 }, new LineLeadOut { Length = 0.05 }, new NormalTab()));
|
|
|
|
Assert.Contains("tab", ex.Message);
|
|
}
|
|
|
|
[Fact]
|
|
public void Pin_RefusesInvalidInput()
|
|
{
|
|
var lead = new LineLeadIn { Length = 0.25 };
|
|
Assert.Throws<ArgumentException>(() => new ContourCutPin(new Vector(double.NaN, 0), lead, new NoLeadOut()));
|
|
Assert.Throws<ArgumentException>(() => new ContourCutPin(Vector.Zero, null, new NoLeadOut()));
|
|
Assert.Throws<ArgumentException>(() => new ContourCutPin(Vector.Zero, lead, null));
|
|
Assert.Throws<ArgumentException>(() => new ContourCutPin(
|
|
Vector.Zero, new LineLeadIn { Length = -0.1 }, new NoLeadOut()));
|
|
Assert.Throws<ArgumentException>(() => new ContourCutPin(
|
|
Vector.Zero, lead, new NoLeadOut(), new NormalTab { Size = double.PositiveInfinity }));
|
|
}
|
|
|
|
[Fact]
|
|
public void Pin_RefusesTypesThatAreNotBuiltIn()
|
|
{
|
|
Assert.Throws<NotSupportedException>(() => new ContourCutPin(
|
|
Vector.Zero, new DerivedLineLeadIn { Length = 0.25 }, new NoLeadOut()));
|
|
}
|
|
}
|