feat: add FillPolicy record and FillHelpers.FillWithDirectionPreference

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-21 12:41:07 -04:00
parent 1a41eeb81d
commit 4586a53590
3 changed files with 96 additions and 0 deletions

View File

@@ -1,6 +1,7 @@
using OpenNest.Engine.Fill;
using OpenNest.Geometry;
using OpenNest.Math;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading.Tasks;
@@ -63,5 +64,42 @@ namespace OpenNest.Engine.Strategies
return best;
}
/// <summary>
/// Runs a fill function with direction preference logic.
/// If preferred is null, tries both directions and returns the better result.
/// If preferred is set, tries preferred first; only tries other if preferred yields zero.
/// </summary>
public static List<Part> FillWithDirectionPreference(
Func<NestDirection, List<Part>> fillFunc,
NestDirection? preferred,
IFillComparer comparer,
Box workArea)
{
if (preferred == null)
{
var h = fillFunc(NestDirection.Horizontal);
var v = fillFunc(NestDirection.Vertical);
if ((h == null || h.Count == 0) && (v == null || v.Count == 0))
return new List<Part>();
if (h == null || h.Count == 0) return v;
if (v == null || v.Count == 0) return h;
return comparer.IsBetter(h, v, workArea) ? h : v;
}
var other = preferred == NestDirection.Horizontal
? NestDirection.Vertical
: NestDirection.Horizontal;
var pref = fillFunc(preferred.Value);
if (pref != null && pref.Count > 0)
return pref;
var fallback = fillFunc(other);
return fallback ?? new List<Part>();
}
}
}

View File

@@ -0,0 +1,8 @@
namespace OpenNest.Engine.Strategies
{
/// <summary>
/// Groups engine scoring and direction policy into a single object.
/// Set by the engine, consumed by strategies via FillContext.Policy.
/// </summary>
public record FillPolicy(IFillComparer Comparer, NestDirection? PreferredDirection = null);
}

View File

@@ -0,0 +1,50 @@
using OpenNest.Engine;
using OpenNest.Engine.Fill;
using OpenNest.Engine.Strategies;
using OpenNest.Geometry;
namespace OpenNest.Tests;
public class FillWithDirectionPreferenceTests
{
private readonly IFillComparer comparer = new DefaultFillComparer();
private readonly Box workArea = new(0, 0, 100, 100);
[Fact]
public void NullPreference_TriesBothDirections_ReturnsBetter()
{
var hParts = new List<Part> { TestHelpers.MakePartAt(0, 0, 10), TestHelpers.MakePartAt(12, 0, 10) };
var vParts = new List<Part> { TestHelpers.MakePartAt(0, 0, 10) };
var result = FillHelpers.FillWithDirectionPreference(
dir => dir == NestDirection.Horizontal ? hParts : vParts,
null, comparer, workArea);
Assert.Equal(2, result.Count);
}
[Fact]
public void PreferredDirection_UsedFirst_WhenProducesResults()
{
var hParts = new List<Part> { TestHelpers.MakePartAt(0, 0, 10), TestHelpers.MakePartAt(12, 0, 10) };
var vParts = new List<Part> { TestHelpers.MakePartAt(0, 0, 10), TestHelpers.MakePartAt(0, 12, 10), TestHelpers.MakePartAt(0, 24, 10) };
var result = FillHelpers.FillWithDirectionPreference(
dir => dir == NestDirection.Horizontal ? hParts : vParts,
NestDirection.Horizontal, comparer, workArea);
Assert.Equal(2, result.Count); // H has results, so H is returned (preferred)
}
[Fact]
public void PreferredDirection_FallsBack_WhenPreferredReturnsEmpty()
{
var vParts = new List<Part> { TestHelpers.MakePartAt(0, 0, 10) };
var result = FillHelpers.FillWithDirectionPreference(
dir => dir == NestDirection.Horizontal ? new List<Part>() : vParts,
NestDirection.Horizontal, comparer, workArea);
Assert.Equal(1, result.Count); // Falls back to V
}
}