Files
OpenNest/OpenNest.Engine.Tests/NestingEngines/FlatBarNestingEngineTests.cs
T
ajandClaude Opus 5.5 27a03b9caf feat(engine): add FlatBar nesting engine for flat bar stock
Reads each stock as a bar and cuts parts in one row along its length, packing
part lengths with a port of the CutList saw cut-list packer (first-fit
decreasing with swap optimization, exhaustive search for small jobs) plus a
best-fill subset-sum candidate. Each part is packed at its length less its
measured self-nesting gain, and a slide-left compaction pass nests shaped ends
(turning alternate mitred parts) and refills the freed length. Every slid pose
is confirmed with NestLayoutCheck.Clears.

On five synthetic flat-bar jobs every layout is valid and complete; total cost
33696 vs Rectangles 36816 and Default 34080.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 01:54:48 -04:00

102 lines
3.6 KiB
C#

using System.Linq;
using OpenNest.Engine.Jobs;
using OpenNest.Engine.NestingEngines.FlatBar;
using OpenNest.Engine.NestingEngines.Rectangles;
using OpenNest.Geometry;
using static OpenNest.Engine.Tests.NestingEngines.JobBuilder;
using static OpenNest.Engine.Tests.NestingEngines.Shapes;
namespace OpenNest.Engine.Tests.NestingEngines;
public class FlatBarNestingEngineTests
{
[Fact]
public void CutListLengthsUseTheFewestBars()
{
// First fit decreasing needs three bars ([45,40] [35,30,25] [25]); two are exact.
var lengths = new[] { 45.0, 40, 35, 30, 25, 25 };
var job = Job(
lengths.Select((l, i) => Rectangle($"p{i}", l, 3, 1)).ToArray(),
[Stock("bar", 3, 100)]);
var result = new FlatBarNestingEngine().Solve(job);
LayoutAssert.Valid(job, result);
Assert.Equal(NestJobStatus.Complete, result.Status);
Assert.Equal(2, result.Plates.Count);
}
[Fact]
public void PartsLieInOneRowAlongTheBar()
{
var job = Job([Rectangle("p", 20, 3, 6)], [Stock("bar", 3, 144, spacing: 0.125)]);
var result = new FlatBarNestingEngine().Solve(job);
LayoutAssert.Valid(job, result);
var placements = Assert.Single(result.Plates).Placements;
Assert.Equal(6, placements.Count);
Assert.All(placements, p => Assert.Equal(0, p.Y, 9));
}
[Fact]
public void BarRunningAlongYIsCutAlongY()
{
var job = Job([Part("p", Rectangle(3, 20), 6, RotationPolicy.Automatic)], [Stock("bar", 144, 3, spacing: 0.125)]);
var result = new FlatBarNestingEngine().Solve(job);
LayoutAssert.Valid(job, result);
Assert.Equal(6, Assert.Single(result.Plates).Placements.Count);
}
[Fact]
public void MitredEndsInterlockByTurningAlternateParts()
{
// 45-degree mitres: a 23-long box, but turned copies nest 20 apart. Five fit on a
// 105 bar only when every other part is turned and slid into its neighbour.
var trapezoid = Part("mitre", Polyline((0, 0), (23, 0), (20, 3), (3, 3)), 5, RotationPolicy.Automatic);
var job = Job([trapezoid], [Stock("bar", 3, 105, spacing: 0.25)]);
var flatBar = new FlatBarNestingEngine().Solve(job);
var rectangles = new RectanglesNestingEngine().Solve(job);
LayoutAssert.Valid(job, flatBar);
Assert.Equal(NestJobStatus.Complete, flatBar.Status);
Assert.Single(flatBar.Plates);
Assert.Equal(2, rectangles.Plates.Count);
}
[Fact]
public void ClampZoneSplitsTheBar()
{
var clamp = new ClampZone("c", new Box(48, -1, 4, 5));
var job = Job([Rectangle("p", 45, 3, 2)], [Stock("bar", 3, 100, quantity: 1, clampZones: [clamp])]);
var result = new FlatBarNestingEngine().Solve(job);
LayoutAssert.Valid(job, result);
Assert.Equal(NestJobStatus.Complete, result.Status);
}
[Fact]
public void CheaperShortBarFinishesTheJob()
{
// 150 of parts: one long bar plus one short bar is cheaper than two long bars.
var job = Job(
[Rectangle("a", 90, 3, 1), Rectangle("b", 60, 3, 1)],
[
new NestPlateStock("long", new Size(3, 100), null, 0, default, 1, 100.0),
new NestPlateStock("short", new Size(3, 60), null, 0, default, 1, 60.0),
]);
var result = new FlatBarNestingEngine().Solve(job);
LayoutAssert.Valid(job, result);
Assert.Equal(NestJobStatus.Complete, result.Status);
Assert.Equal(["long", "short"], result.Plates.Select(p => p.StockId).Order());
}
}
public sealed class FlatBarContractTests : EngineContractTests<FlatBarNestingEngine> { }