Files
OpenNest/OpenNest.Tests/CNC/RapidEnumeratorTests.cs
T

181 lines
7.5 KiB
C#

using OpenNest.CNC;
using OpenNest.Geometry;
using Xunit;
namespace OpenNest.Tests.CNC
{
public class RapidEnumeratorTests
{
[Fact]
public void Enumerate_AbsoluteProgram_OffsetsMotionsByBasePos()
{
var pgm = new Program(Mode.Absolute);
pgm.Codes.Add(new RapidMove(1, 0));
pgm.Codes.Add(new LinearMove(2, 0));
pgm.Codes.Add(new RapidMove(3, 3));
var segments = RapidEnumerator.Enumerate(
pgm,
basePos: new Vector(100, 200),
startPos: new Vector(0, 0)
);
// Origin → first pierce, then interior rapid from contour end to next rapid target.
Assert.Equal(2, segments.Count);
Assert.Equal(new Vector(0, 0), segments[0].From);
Assert.Equal(new Vector(101, 200), segments[0].To);
Assert.Equal(new Vector(102, 200), segments[1].From);
Assert.Equal(new Vector(103, 203), segments[1].To);
}
[Fact]
public void Enumerate_IncrementalProgram_InterpretsDeltasFromBasePos()
{
// Pre-lead-in raw program: first rapid normalized to (0,0), Mode=Incremental
// (matches ConvertGeometry.ToProgram output).
var pgm = new Program(Mode.Incremental);
pgm.Codes.Add(new RapidMove(0, 0));
pgm.Codes.Add(new LinearMove(5, 0));
pgm.Codes.Add(new LinearMove(0, 5));
pgm.Codes.Add(new RapidMove(1, 1));
var segments = RapidEnumerator.Enumerate(
pgm,
basePos: new Vector(100, 200),
startPos: new Vector(0, 0)
);
Assert.Equal(2, segments.Count);
// First rapid: plate origin → part pierce at basePos.
Assert.Equal(new Vector(0, 0), segments[0].From);
Assert.Equal(new Vector(100, 200), segments[0].To);
// Interior rapid: after deltas (5,0) and (0,5) from basePos, rapid delta (1,1).
Assert.Equal(new Vector(105, 205), segments[1].From);
Assert.Equal(new Vector(106, 206), segments[1].To);
}
[Fact]
public void Enumerate_IncrementalProgramWithNonZeroFirstRapid_DoesNotDoubleCountIt()
{
// Lead-in programs (ContourCuttingStrategy output) are Incremental and
// start with a nonzero rapid from the part origin to the first pierce.
var pgm = new Program(Mode.Incremental);
pgm.Codes.Add(new RapidMove(2, 3));
pgm.Codes.Add(new LinearMove(5, 0));
pgm.Codes.Add(new RapidMove(1, 1));
var segments = RapidEnumerator.Enumerate(
pgm,
basePos: new Vector(100, 200),
startPos: new Vector(0, 0)
);
Assert.Equal(2, segments.Count);
Assert.Equal(new Vector(102, 203), segments[0].To);
Assert.Equal(new Vector(107, 203), segments[1].From);
Assert.Equal(new Vector(108, 204), segments[1].To);
}
[Theory]
[InlineData(Mode.Absolute)]
[InlineData(Mode.Incremental)]
public void Enumerate_ProgramStartingWithCut_RetainsFirstInternalRapid(Mode mode)
{
// A saved placed program can start with a lead-in cut rather than a G00.
// The first G00 then travels from that cutout to the next one.
var pgm = new Program(mode);
pgm.Codes.Add(new LinearMove(2, 0));
pgm.Codes.Add(new RapidMove(4, 1));
pgm.Codes.Add(new LinearMove(5, 1));
pgm.Codes.Add(new RapidMove(6, 2));
var segments = RapidEnumerator.Enumerate(
pgm,
basePos: new Vector(100, 200),
startPos: Vector.Zero
);
Assert.Equal(3, segments.Count);
Assert.Equal(new RapidEnumerator.Segment(Vector.Zero, new Vector(100, 200)), segments[0]);
Assert.Equal(new Vector(102, 200), segments[1].From);
Assert.Equal(mode == Mode.Absolute ? new Vector(104, 201) : new Vector(106, 201), segments[1].To);
Assert.Equal(mode == Mode.Absolute ? new Vector(105, 201) : new Vector(111, 202), segments[2].From);
}
[Fact]
public void Enumerate_EveryPartStartingWithCut_RetainsItsFirstRapid()
{
var program = new Program(Mode.Incremental);
program.Codes.Add(new LinearMove(1, 0));
program.Codes.Add(new RapidMove(1, 1));
program.Codes.Add(new LinearMove(0, 1));
var drawing = new Drawing("cut-first", program);
var parts = new[]
{
new Part(drawing, new Vector(10, 20)),
new Part(drawing, new Vector(30, 40)),
};
var segments = RapidEnumerator.Enumerate(parts);
Assert.Equal(4, segments.Count);
Assert.Equal(new RapidEnumerator.Segment(new Vector(11, 20), new Vector(12, 21)), segments[1]);
Assert.Equal(new RapidEnumerator.Segment(new Vector(31, 40), new Vector(32, 41)), segments[3]);
}
[Fact]
public void Enumerate_SubProgramStartingWithCut_RetainsFirstInternalRapid()
{
var hole = new Program(Mode.Incremental);
hole.Codes.Add(new LinearMove(1, 0));
hole.Codes.Add(new RapidMove(2, 1));
hole.Codes.Add(new LinearMove(0, 1));
var pgm = new Program(Mode.Absolute);
pgm.Codes.Add(new SubProgramCall { Id = 1, Program = hole, Offset = new Vector(5, 5) });
var segments = RapidEnumerator.Enumerate(pgm, new Vector(100, 200), Vector.Zero);
Assert.Equal(2, segments.Count);
Assert.Equal(new RapidEnumerator.Segment(Vector.Zero, new Vector(105, 205)), segments[0]);
Assert.Equal(new RapidEnumerator.Segment(new Vector(106, 205), new Vector(108, 206)), segments[1]);
}
[Fact]
public void Enumerate_SubProgramCall_RapidEndsAtAbsoluteHolePierce()
{
// Main program: lead-in rapid, a line, then a SubProgramCall for a hole.
// Sub-program (incremental) starts with RapidMove(radius, 0) to the hole pierce.
var sub = new Program(Mode.Incremental);
sub.Codes.Add(new RapidMove(0.5, 0));
sub.Codes.Add(new LinearMove(0, 0.1));
var pgm = new Program(Mode.Absolute);
pgm.Codes.Add(new RapidMove(0.2, 0.3)); // first pierce (perimeter lead-in)
pgm.Codes.Add(new LinearMove(1.0, 1.0)); // contour move
pgm.Codes.Add(
new SubProgramCall
{
Id = 1,
Program = sub,
Offset = new Vector(2, 2), // hole center (drawing-local)
}
);
var basePos = new Vector(100, 200); // part.Location
var segments = RapidEnumerator.Enumerate(pgm, basePos, startPos: new Vector(0, 0));
// Expected rapids:
// 1. origin → first pierce (0.2+100, 0.3+200) = (100.2, 200.3)
// 2. end of contour (1+100, 1+200) = (101, 201) → hole pierce (2+100+0.5, 2+200) = (102.5, 202)
// The sub's internal first rapid is skipped (already drawn in #2).
Assert.Equal(2, segments.Count);
Assert.Equal(new Vector(0, 0), segments[0].From);
Assert.Equal(new Vector(100.2, 200.3), segments[0].To);
Assert.Equal(new Vector(101, 201), segments[1].From);
Assert.Equal(new Vector(102.5, 202), segments[1].To);
}
}
}