diff --git a/OpenNest.Engine/Strategies/FillHelpers.cs b/OpenNest.Engine/Strategies/FillHelpers.cs index 6533568..44eb252 100644 --- a/OpenNest.Engine/Strategies/FillHelpers.cs +++ b/OpenNest.Engine/Strategies/FillHelpers.cs @@ -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; } + + /// + /// 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. + /// + public static List FillWithDirectionPreference( + Func> 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(); + + 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(); + } } } diff --git a/OpenNest.Engine/Strategies/FillPolicy.cs b/OpenNest.Engine/Strategies/FillPolicy.cs new file mode 100644 index 0000000..12c37a9 --- /dev/null +++ b/OpenNest.Engine/Strategies/FillPolicy.cs @@ -0,0 +1,8 @@ +namespace OpenNest.Engine.Strategies +{ + /// + /// Groups engine scoring and direction policy into a single object. + /// Set by the engine, consumed by strategies via FillContext.Policy. + /// + public record FillPolicy(IFillComparer Comparer, NestDirection? PreferredDirection = null); +} diff --git a/OpenNest.Tests/FillPolicyTests.cs b/OpenNest.Tests/FillPolicyTests.cs new file mode 100644 index 0000000..8c49625 --- /dev/null +++ b/OpenNest.Tests/FillPolicyTests.cs @@ -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 { TestHelpers.MakePartAt(0, 0, 10), TestHelpers.MakePartAt(12, 0, 10) }; + var vParts = new List { 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 { TestHelpers.MakePartAt(0, 0, 10), TestHelpers.MakePartAt(12, 0, 10) }; + var vParts = new List { 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 { TestHelpers.MakePartAt(0, 0, 10) }; + + var result = FillHelpers.FillWithDirectionPreference( + dir => dir == NestDirection.Horizontal ? new List() : vParts, + NestDirection.Horizontal, comparer, workArea); + + Assert.Equal(1, result.Count); // Falls back to V + } +}