Files
OpenNest/OpenNest.Tests/BestFit/BestFitCacheStoreTests.cs
T
ajandClaude Opus 5.5 7a5c06bc99 fix(bestfit): bind loaded pair candidates to a canonical copy
Saved pairs are in the drawing's canonical frame, but loaded candidates
held the source drawing, so BuildCanonicalParts/BuildSourceParts rebuilt
a different pair for any drawing with a non-zero canonical angle (a 30
degree T-shape came back 16.57 x 10.39 instead of its saved 16.92 x 8).
That reached the Irregular engine's pair catalog and the best-fit viewer
after opening a saved nest. Import now binds every candidate to one
canonical copy, as computed candidates are; fills already passed the
canonical drawing explicitly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 15:05:43 -04:00

130 lines
4.9 KiB
C#

using OpenNest.BestFit;
using OpenNest.Engine;
using OpenNest.Engine.BestFit;
using OpenNest.Geometry;
using OpenNest.Shapes;
namespace OpenNest.Tests.BestFit;
[Collection(nameof(FillCacheCollection))]
public class BestFitCacheStoreTests
{
private const double Spacing = 0.25;
private static Drawing MakeDrawing() => new TShape { Width = 10, Height = 8 }.GetDrawing();
private static BestFitResult MakeResult(Drawing drawing, bool keep, string reason = "Valid", List<double> hullAngles = null) =>
new()
{
Candidate = new PairCandidate
{
Drawing = drawing,
Part1Rotation = 0.5,
Part2Rotation = System.Math.PI,
Part2Offset = new Vector(3.25, -1.5),
StrategyIndex = 2,
TestNumber = 7,
Spacing = Spacing,
},
RotatedArea = 90,
BoundingWidth = 12,
BoundingHeight = 7.5,
OptimalRotation = 0.1,
Keep = keep,
Reason = reason,
TrueArea = 70,
HullAngles = hullAngles,
};
[Fact]
public void Export_ReturnsOnlyRequestedPlateKeysAndKeptResults()
{
BestFitCache.Clear();
var drawing = MakeDrawing();
BestFitCache.Populate(drawing, 60, 40, Spacing, new()
{
MakeResult(drawing, keep: true, hullAngles: new() { 0.25 }),
MakeResult(drawing, keep: false, reason: "Too wide"),
});
BestFitCache.Populate(drawing, 14, 30, Spacing, new() { MakeResult(drawing, keep: true) });
var sets = BestFitCacheStore.Shared.Export(
drawing, new HashSet<(double, double, double)> { (60, 40, Spacing), (99, 99, Spacing) });
var set = Assert.Single(sets);
Assert.Equal((60.0, 40.0, Spacing), (set.PlateWidth, set.PlateHeight, set.Spacing));
var result = Assert.Single(set.Results);
Assert.Equal(
new SavedPairResult(0.5, System.Math.PI, 3.25, -1.5, 2, 7, Spacing, 90, 12, 7.5, 0.1, true, "Valid", 70, result.HullAngles),
result);
Assert.Equal(new[] { 0.25 }, result.HullAngles);
}
[Fact]
public void Export_DrawingWithoutCacheEntry_ReturnsNoSets()
{
BestFitCache.Clear();
var sets = BestFitCacheStore.Shared.Export(
MakeDrawing(), new HashSet<(double, double, double)> { (60, 40, Spacing) });
Assert.Empty(sets);
}
[Fact]
public void Import_PopulatesCacheWithCandidatesOnACanonicalCopyOfTheDrawing()
{
BestFitCache.Clear();
var drawing = MakeDrawing();
var saved = new SavedPairResult(0.5, System.Math.PI, 3.25, -1.5, 2, 7, Spacing, 90, 12, 7.5, 0.1, true, "Valid", 70, new[] { 0.25, -0.5 });
BestFitCacheStore.Shared.Import(drawing, new[] { new SavedPairSet(60, 40, Spacing, new[] { saved }) });
var restored = Assert.Single(BestFitCache.GetAllForDrawing(drawing));
Assert.Equal((60.0, 40.0, Spacing), restored.Key);
var result = Assert.Single(restored.Value);
Assert.NotSame(drawing, result.Candidate.Drawing);
Assert.Same(drawing, CanonicalFrame.SourceOf(result.Candidate.Drawing));
Assert.Equal(0.0, result.Candidate.Drawing.Source.Angle);
Assert.Equal(0.5, result.Candidate.Part1Rotation);
Assert.Equal(System.Math.PI, result.Candidate.Part2Rotation);
Assert.Equal(new Vector(3.25, -1.5), result.Candidate.Part2Offset);
Assert.Equal(2, result.Candidate.StrategyIndex);
Assert.Equal(7, result.Candidate.TestNumber);
Assert.Equal(Spacing, result.Candidate.Spacing);
Assert.Equal(90, result.RotatedArea);
Assert.Equal(12, result.BoundingWidth);
Assert.Equal(7.5, result.BoundingHeight);
Assert.Equal(0.1, result.OptimalRotation);
Assert.True(result.Keep);
Assert.Equal("Valid", result.Reason);
Assert.Equal(70, result.TrueArea);
Assert.Equal(new[] { 0.25, -0.5 }, result.HullAngles);
}
[Fact]
public void Import_ExistingSetForTheSameKey_IsKept()
{
BestFitCache.Clear();
var drawing = MakeDrawing();
var existing = new List<BestFitResult> { MakeResult(drawing, keep: true) };
BestFitCache.Populate(drawing, 60, 40, Spacing, existing);
var saved = new SavedPairResult(0, 0, 1, 1, 0, 1, Spacing, 5, 1, 5, 0, true, "Valid", 4, new double[0]);
BestFitCacheStore.Shared.Import(drawing, new[] { new SavedPairSet(60, 40, Spacing, new[] { saved }) });
Assert.Same(existing, BestFitCache.GetAllForDrawing(drawing)[(60, 40, Spacing)]);
}
[Fact]
public void Import_EmptySet_AddsNothing()
{
BestFitCache.Clear();
var drawing = MakeDrawing();
BestFitCacheStore.Shared.Import(drawing, new[] { new SavedPairSet(60, 40, Spacing, new SavedPairResult[0]) });
Assert.Empty(BestFitCache.GetAllForDrawing(drawing));
}
}