mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-27 17:21:34 -04:00
Circles packs every part as its minimum enclosing circle at centers tangent to two of the work-area walls, placed discs and clamp keep-outs, bottom-left and left-bottom, with hexagonal and square lattice seeds. It bids only when every part fills at least 0.8 of its enclosing circle and otherwise returns everything unplaced at once. Default runs it as a third candidate. Rectangles' sheet-by-sheet stock loop moves to NestingEngines/SheetByStock (Rectangles output unchanged) and Default's candidate scoring and commit forwarding become shared helpers. Circles also runs the whole job preferring each offered stock and keeps the best result. Contract tests take shapes an engine can override. Synthetic round jobs, salvage-credited: Circles wins or ties all six with valid layouts; Default cost falls about 4% versus Irregular alone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
119 lines
4.2 KiB
C#
119 lines
4.2 KiB
C#
using OpenNest.Engine.Jobs;
|
|
using OpenNest.Geometry;
|
|
using Xunit;
|
|
|
|
namespace OpenNest.Engine.Tests.Jobs;
|
|
|
|
public class NestingEngineRegistryTests
|
|
{
|
|
[Fact]
|
|
public void BuiltInStrategiesAreRegistered()
|
|
{
|
|
var names = NestingEngineRegistry.AvailableEngines.Select(e => e.Name).ToList();
|
|
|
|
Assert.Contains("Rectangles", names);
|
|
Assert.Contains("Irregular", names);
|
|
Assert.Contains("Circles", names);
|
|
Assert.Contains("Fill", names);
|
|
Assert.Contains("Strip", names);
|
|
Assert.Contains("Vertical Remnant", names);
|
|
Assert.Contains("Horizontal Remnant", names);
|
|
}
|
|
|
|
[Fact]
|
|
public void RenamedPlugInNamesResolveToTheirBuiltInReplacements()
|
|
{
|
|
Assert.Equal("Irregular", NestingEngineRegistry.ResolveName("Opus55NestingEngine"));
|
|
Assert.Equal("Rectangles", NestingEngineRegistry.ResolveName("rectanglesnestingengine"));
|
|
Assert.Equal("Rectangles", NestingEngineRegistry.ResolveName(" rectangles "));
|
|
Assert.IsType<OpenNest.Engine.NestingEngines.Irregular.IrregularNestingEngine>(
|
|
NestingEngineRegistry.Create("Opus55NestingEngine"));
|
|
}
|
|
|
|
[Fact]
|
|
public void RetiredEnginesAreNotSilentlyAliased()
|
|
{
|
|
Assert.Null(NestingEngineRegistry.ResolveName("Gpt6AstraNestingEngine"));
|
|
Assert.Null(NestingEngineRegistry.ResolveName("Qwen38FlashNextNestingEngine"));
|
|
Assert.Null(NestingEngineRegistry.ResolveName(" "));
|
|
Assert.Throws<NotSupportedException>(() => NestingEngineRegistry.Create("Qwen38FlashNextNestingEngine"));
|
|
}
|
|
|
|
[Fact]
|
|
public void LeftoverPlugInUnderARenamedNameCannotShadowItsReplacement()
|
|
{
|
|
var before = NestingEngineRegistry.AvailableEngines.Count;
|
|
|
|
NestingEngineRegistry.Register("Opus55NestingEngine", "stale plug-in",
|
|
() => new FixedStrategyNestingEngine("Fill"));
|
|
|
|
Assert.Equal(before, NestingEngineRegistry.AvailableEngines.Count);
|
|
Assert.Equal("Irregular", NestingEngineRegistry.ResolveName("Opus55NestingEngine"));
|
|
}
|
|
|
|
[Fact]
|
|
public void EachBuiltInFactoryProducesAWorkingEngine()
|
|
{
|
|
foreach (var info in NestingEngineRegistry.AvailableEngines)
|
|
{
|
|
var engine = info.Factory();
|
|
// Circles bids only on round parts.
|
|
var job = info.Name == "Circles"
|
|
? new NestJob(new[] { new NestJobPart("p",
|
|
PartGeometrySnapshot.FromProgram(NestingEngines.Shapes.Disc(5)), 3) },
|
|
new[] { new NestPlateStock("s", new Size(100, 200), 1) })
|
|
: FiniteStockJobTests.Job(1);
|
|
var result = engine.Solve(job);
|
|
|
|
Assert.Equal(NestJobStatus.Complete, result.Status);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void DuplicateNameIsSkipped()
|
|
{
|
|
var before = NestingEngineRegistry.AvailableEngines.Count;
|
|
|
|
NestingEngineRegistry.Register(
|
|
"Fill",
|
|
"duplicate",
|
|
() => new FixedStrategyNestingEngine("Fill")
|
|
);
|
|
|
|
Assert.Equal(before, NestingEngineRegistry.AvailableEngines.Count);
|
|
}
|
|
|
|
[Fact]
|
|
public void CreateResolvesKnownNamesCaseInsensitivelyAndRejectsUnknown()
|
|
{
|
|
Assert.NotNull(NestingEngineRegistry.Create("fill"));
|
|
Assert.NotNull(NestingEngineRegistry.Create("Vertical Remnant"));
|
|
Assert.NotNull(NestingEngineRegistry.Create("stockladder"));
|
|
|
|
Assert.Throws<NotSupportedException>(() => NestingEngineRegistry.Create("Mystery Engine"));
|
|
Assert.Throws<ArgumentException>(() => NestingEngineRegistry.Create(" "));
|
|
Assert.Throws<ArgumentNullException>(() => NestingEngineRegistry.Create(null!));
|
|
}
|
|
|
|
[Fact]
|
|
public void CreateProducesAWorkingEngine()
|
|
{
|
|
var engine = NestingEngineRegistry.Create("Strip");
|
|
var result = engine.Solve(FiniteStockJobTests.Job(1));
|
|
|
|
Assert.Equal(NestJobStatus.Complete, result.Status);
|
|
}
|
|
|
|
[Fact]
|
|
public void LoadPluginsAgainstMissingDirectoryIsANoOp()
|
|
{
|
|
var before = NestingEngineRegistry.AvailableEngines.Count;
|
|
|
|
NestingEngineRegistry.LoadPlugins(
|
|
Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString())
|
|
);
|
|
|
|
Assert.Equal(before, NestingEngineRegistry.AvailableEngines.Count);
|
|
}
|
|
}
|