Files
OpenNest/OpenNest.Engine.Tests/Jobs/RotationPolicyTests.cs
T
ec5f57171f feat(engine): add RotationPolicy.EnumerateAngles and RotationCandidates
All three plugin engines turned a RotationPolicy into trial angles by hand
(fixed angle, stepped sweep, or right angles plus the minimum-bounding-
rectangle angle for Automatic), each with its own normalization, dedup and
sweep caps. EnumerateAngles gives one deterministic, Allows-checked list;
RotationCandidates.ForShape adds the MBR-aligning angles via the existing
Polygon.FindBestRotation, and DistinctOutlines drops angles where the part
looks identical. A cap of one returns the sweep start rather than throwing,
since engines request a single sample for small orientation budgets.

Co-Authored-By: Codex <noreply@openai.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 08:33:52 -04:00

117 lines
3.8 KiB
C#

using OpenNest.Engine.Jobs;
namespace OpenNest.Engine.Tests.Jobs;
public class RotationPolicyTests
{
[Fact]
public void AutomaticReturnsRightAnglesInOrder()
{
Assert.Equal(new[] { 0, System.Math.PI / 2, System.Math.PI, 3 * System.Math.PI / 2 },
RotationPolicy.Automatic.EnumerateAngles());
}
[Fact]
public void FixedIncludesNormalizedHalfTurnEquivalent()
{
var policy = RotationPolicy.Fixed(-System.Math.PI / 2, true);
Assert.Equal(new[] { 3 * System.Math.PI / 2, System.Math.PI / 2 }, policy.EnumerateAngles(1));
AssertLegal(policy);
}
[Fact]
public void OversizedSweepEvenlySamplesGridIncludingEndpoints()
{
var policy = RotationPolicy.BoundedSweep(0, 1, 0.01);
Assert.Equal(new[] { 0, 0.25, 0.5, 0.75, 1 }, policy.EnumerateAngles(5));
AssertLegal(policy, 5);
}
[Fact]
public void SubsamplingRoundsToLegalGridPoints()
{
var policy = RotationPolicy.BoundedSweep(0, 1, 0.1);
var angles = policy.EnumerateAngles(4);
Assert.Equal(4, angles.Count);
Assert.Equal(0.3, angles[1], 10);
Assert.Equal(0.7, angles[2], 10);
Assert.Equal(1, angles[3]);
AssertLegal(policy, 4);
}
[Fact]
public void OffGridEndUsesLastLegalGridPoint()
{
var policy = RotationPolicy.BoundedSweep(0, 1, 0.3);
Assert.Equal(0.9, policy.EnumerateAngles(2)[1], 10);
AssertLegal(policy, 2);
}
[Fact]
public void SweepEquivalentsFollowEachBaseAngleAndAreDeduplicated()
{
var policy = RotationPolicy.BoundedSweep(0, System.Math.PI, System.Math.PI / 2, true);
Assert.Equal(new[] { 0, System.Math.PI, System.Math.PI / 2, 3 * System.Math.PI / 2 },
policy.EnumerateAngles());
AssertLegal(policy);
}
[Fact]
public void SweepCrossingFullTurnNormalizesWithoutSorting()
{
var policy = RotationPolicy.BoundedSweep(3 * System.Math.PI / 2,
5 * System.Math.PI / 2, System.Math.PI / 2, true);
Assert.Equal(new[] { 3 * System.Math.PI / 2, System.Math.PI / 2, 0, System.Math.PI },
policy.EnumerateAngles());
AssertLegal(policy);
}
[Fact]
public void FullTurnAndNearDuplicateAnglesCollapse()
{
Assert.Equal(4, RotationPolicy.BoundedSweep(0, 2 * System.Math.PI,
System.Math.PI / 2).EnumerateAngles().Count);
Assert.Single(RotationPolicy.BoundedSweep(-1e-8, 1e-8, 1e-8).EnumerateAngles());
}
[Fact]
public void OneSampleReturnsTheSweepStart()
{
Assert.Single(RotationPolicy.BoundedSweep(0.2, 0.3, 1).EnumerateAngles(1));
Assert.Equal(new[] { 0.0 }, RotationPolicy.BoundedSweep(0, 1, 0.5).EnumerateAngles(1));
Assert.Equal(new[] { 0.0, System.Math.PI },
RotationPolicy.BoundedSweep(0, 1, 0.5, true).EnumerateAngles(1));
Assert.Equal(4, RotationPolicy.Automatic.EnumerateAngles(1).Count);
}
[Theory]
[InlineData(0)]
[InlineData(-1)]
public void InvalidSampleCapThrows(int maxSamples)
{
Assert.Throws<ArgumentOutOfRangeException>(() =>
RotationPolicy.Fixed(0).EnumerateAngles(maxSamples));
}
[Fact]
public void DefaultSweepCapIs720BaseSamples()
{
var policy = RotationPolicy.BoundedSweep(0, 1, 0.0001, true);
var angles = policy.EnumerateAngles();
Assert.Equal(1440, angles.Count);
Assert.Equal(1, angles[^2]);
AssertLegal(policy);
}
private static void AssertLegal(RotationPolicy policy, int maxSamples = 720)
{
var angles = policy.EnumerateAngles(maxSamples);
Assert.Equal(angles, policy.EnumerateAngles(maxSamples));
Assert.All(angles, angle =>
{
Assert.True(angle >= 0 && angle < 2 * System.Math.PI);
Assert.True(policy.Allows(angle));
});
}
}