diff --git a/OpenNest.Engine.Tests/Jobs/PlateFillerContractTests.cs b/OpenNest.Engine.Tests/Jobs/PlateFillerContractTests.cs index cfd71629..d83196e8 100644 --- a/OpenNest.Engine.Tests/Jobs/PlateFillerContractTests.cs +++ b/OpenNest.Engine.Tests/Jobs/PlateFillerContractTests.cs @@ -354,8 +354,8 @@ public class PlateFillerContractTests private static PlateFillerBase CreateFiller(string strategy, Plate plate) => strategy switch { "Fill" => new DefaultPlateFiller(plate), - "Vertical Remnant" => new RemnantPlateFiller(plate, RemnantFillPolicy.Vertical), - "Horizontal Remnant" => new RemnantPlateFiller(plate, RemnantFillPolicy.Horizontal), + "Vertical Remnant" => new DefaultPlateFiller(plate, PlateFillPolicy.VerticalRemnant), + "Horizontal Remnant" => new DefaultPlateFiller(plate, PlateFillPolicy.HorizontalRemnant), _ => throw new ArgumentOutOfRangeException(nameof(strategy)), }; diff --git a/OpenNest.Engine/Jobs/Placement/Fillers/DefaultPlateFiller.cs b/OpenNest.Engine/Jobs/Placement/Fillers/DefaultPlateFiller.cs index a8f7115e..d1db0a5f 100644 --- a/OpenNest.Engine/Jobs/Placement/Fillers/DefaultPlateFiller.cs +++ b/OpenNest.Engine/Jobs/Placement/Fillers/DefaultPlateFiller.cs @@ -16,11 +16,19 @@ namespace OpenNest.Engine.Jobs.Placement.Fillers; internal class DefaultPlateFiller : PlateFillerBase { private readonly AngleCandidateBuilder angleBuilder = new(); + private readonly PlateFillPolicy policy; - internal DefaultPlateFiller(Plate plate) - : base(plate) { } + internal DefaultPlateFiller(Plate plate, PlateFillPolicy policy = null) + : base(plate) + { + this.policy = policy ?? PlateFillPolicy.Fill; + } - protected override IFillComparer CreateComparer() => new DefaultFillComparer(); + protected override IFillComparer CreateComparer() => policy.CreateComparer(); + + public override NestDirection? PreferredDirection => policy.PreferredDirection; + + public override ShrinkAxis TrimAxis => policy.TrimAxis; internal bool ForceFullAngleSweep { @@ -39,7 +47,10 @@ internal class DefaultPlateFiller : PlateFillerBase NestItem item, ClassificationResult classification, Box workArea - ) => angleBuilder.Build(item, classification, workArea); + ) => + policy.UsesAngleBuilder + ? angleBuilder.Build(item, classification, workArea) + : policy.BuildFixedAngles(item, classification); protected override void RecordProductiveAngles(List angleResults) { diff --git a/OpenNest.Engine/Jobs/Placement/Fillers/RemnantPlateFiller.cs b/OpenNest.Engine/Jobs/Placement/Fillers/PlateFillPolicy.cs similarity index 54% rename from OpenNest.Engine/Jobs/Placement/Fillers/RemnantPlateFiller.cs rename to OpenNest.Engine/Jobs/Placement/Fillers/PlateFillPolicy.cs index f34b13c9..2d874574 100644 --- a/OpenNest.Engine/Jobs/Placement/Fillers/RemnantPlateFiller.cs +++ b/OpenNest.Engine/Jobs/Placement/Fillers/PlateFillPolicy.cs @@ -2,42 +2,23 @@ using System; using System.Collections.Generic; using OpenNest.Engine; using OpenNest.Engine.Fill; -using OpenNest.Geometry; using OpenNest.Math; namespace OpenNest.Engine.Jobs.Placement.Fillers; -internal class RemnantPlateFiller : DefaultPlateFiller -{ - private readonly RemnantFillPolicy policy; - - internal RemnantPlateFiller(Plate plate, RemnantFillPolicy policy) - : base(plate) - { - this.policy = policy; - } - - protected override IFillComparer CreateComparer() => policy.CreateComparer(); - - public override NestDirection? PreferredDirection => policy.PreferredDirection; - - public override ShrinkAxis TrimAxis => policy.TrimAxis; - - public override List BuildAngles( - NestItem item, - ClassificationResult classification, - Box workArea - ) => policy.BuildAngles(item, classification); -} - -internal sealed class RemnantFillPolicy +/// +/// Filler-only settings that distinguish Fill from the remnant fills: how candidates are ranked, +/// the preferred direction, the axis surplus parts are trimmed from, and how angles are chosen. +/// Strategies see only the the filler builds from it. +/// +internal sealed class PlateFillPolicy { private readonly Func comparerFactory; private readonly Func angleExtent; - private RemnantFillPolicy( + private PlateFillPolicy( Func comparerFactory, - NestDirection preferredDirection, + NestDirection? preferredDirection, ShrinkAxis trimAxis, Func angleExtent ) @@ -48,27 +29,47 @@ internal sealed class RemnantFillPolicy this.angleExtent = angleExtent; } - internal static RemnantFillPolicy Vertical { get; } = new( + /// Plain fill: default ranking and the filler's learned angle candidates. + internal static PlateFillPolicy Fill { get; } = new( + () => new DefaultFillComparer(), + null, + ShrinkAxis.Width, + null + ); + + /// Vertical remnant: minimize X-extent, prefer horizontal placement. + internal static PlateFillPolicy VerticalRemnant { get; } = new( () => new VerticalRemnantComparer(), NestDirection.Horizontal, ShrinkAxis.Width, RotatedWidth ); - internal static RemnantFillPolicy Horizontal { get; } = new( + /// Horizontal remnant: minimize Y-extent, prefer vertical placement. + internal static PlateFillPolicy HorizontalRemnant { get; } = new( () => new HorizontalRemnantComparer(), NestDirection.Vertical, ShrinkAxis.Length, RotatedHeight ); - internal NestDirection PreferredDirection { get; } + internal NestDirection? PreferredDirection { get; } internal ShrinkAxis TrimAxis { get; } + /// + /// True when the filler's chooses the angles; false when + /// replaces it entirely. + /// + internal bool UsesAngleBuilder => angleExtent == null; + internal IFillComparer CreateComparer() => comparerFactory(); - internal List BuildAngles(NestItem item, ClassificationResult classification) + /// + /// The remnant angles: the primary angle and its quarter turn, narrowest extent first. No + /// circle case, sweep or learned angles, and the item's rotation range is ignored. + /// + internal List BuildFixedAngles(NestItem item, ClassificationResult classification) { var baseAngles = new List { diff --git a/OpenNest.Engine/Jobs/Placement/Fillers/StripPlateFiller.cs b/OpenNest.Engine/Jobs/Placement/Fillers/StripPlateFiller.cs index 4a589dd2..d031b6d0 100644 --- a/OpenNest.Engine/Jobs/Placement/Fillers/StripPlateFiller.cs +++ b/OpenNest.Engine/Jobs/Placement/Fillers/StripPlateFiller.cs @@ -68,12 +68,12 @@ internal class StripPlateFiller : PlateFillerBase { Func> heightFillFunc = (item, box) => { - var inner = new RemnantPlateFiller(Plate, RemnantFillPolicy.Horizontal); + var inner = new DefaultPlateFiller(Plate, PlateFillPolicy.HorizontalRemnant); return inner.Fill(item, box, progress, token); }; Func> widthFillFunc = (item, box) => { - var inner = new RemnantPlateFiller(Plate, RemnantFillPolicy.Vertical); + var inner = new DefaultPlateFiller(Plate, PlateFillPolicy.VerticalRemnant); return inner.Fill(item, box, progress, token); }; diff --git a/OpenNest.Engine/Jobs/Placement/PlateFillService.cs b/OpenNest.Engine/Jobs/Placement/PlateFillService.cs index f4ddcb7c..82da30c9 100644 --- a/OpenNest.Engine/Jobs/Placement/PlateFillService.cs +++ b/OpenNest.Engine/Jobs/Placement/PlateFillService.cs @@ -435,8 +435,8 @@ public static class PlateFillService { "Fill" => new DefaultPlateFiller(plate), "Strip" => new StripPlateFiller(plate), - "Vertical Remnant" => new RemnantPlateFiller(plate, RemnantFillPolicy.Vertical), - _ => new RemnantPlateFiller(plate, RemnantFillPolicy.Horizontal), + "Vertical Remnant" => new DefaultPlateFiller(plate, PlateFillPolicy.VerticalRemnant), + _ => new DefaultPlateFiller(plate, PlateFillPolicy.HorizontalRemnant), }; } diff --git a/OpenNest.Engine/Jobs/Placement/RemnantPlateNester.cs b/OpenNest.Engine/Jobs/Placement/RemnantPlateNester.cs index 876eb8c8..3b3bc6ec 100644 --- a/OpenNest.Engine/Jobs/Placement/RemnantPlateNester.cs +++ b/OpenNest.Engine/Jobs/Placement/RemnantPlateNester.cs @@ -6,7 +6,7 @@ namespace OpenNest.Engine.Jobs.Placement; /// /// Built-in remnant placement strategy for the whole-job runner. Fills one candidate per trial with -/// a for the vertical or horizontal remnant policy. Placement is +/// a with the vertical or horizontal remnant . Placement is /// 's with that filler: the same fresh private plate per trial and /// the same run-scoped identity, progress, and accounting boundaries. /// @@ -18,7 +18,7 @@ public sealed class RemnantPlateNester : IPlateNester { private readonly DefaultPlateNester nester; - internal RemnantPlateNester(Func fillerFactory) + internal RemnantPlateNester(Func fillerFactory) { nester = new DefaultPlateNester( fillerFactory ?? throw new ArgumentNullException(nameof(fillerFactory))); @@ -26,11 +26,11 @@ public sealed class RemnantPlateNester : IPlateNester /// Vertical-remnant policy: minimize X-extent, prefer horizontal placement. internal static RemnantPlateNester Vertical() => - new(plate => new RemnantPlateFiller(plate, RemnantFillPolicy.Vertical)); + new(plate => new DefaultPlateFiller(plate, PlateFillPolicy.VerticalRemnant)); /// Horizontal-remnant policy: minimize Y-extent, prefer vertical placement. internal static RemnantPlateNester Horizontal() => - new(plate => new RemnantPlateFiller(plate, RemnantFillPolicy.Horizontal)); + new(plate => new DefaultPlateFiller(plate, PlateFillPolicy.HorizontalRemnant)); public PlateCandidate Place( PlatePlacementRequest request, diff --git a/OpenNest.Tests/Engine/RemnantEngineTests.cs b/OpenNest.Tests/Engine/RemnantEngineTests.cs index dd0167e9..d685cbfa 100644 --- a/OpenNest.Tests/Engine/RemnantEngineTests.cs +++ b/OpenNest.Tests/Engine/RemnantEngineTests.cs @@ -23,7 +23,7 @@ public class RemnantEngineTests public void VerticalRemnantPlateFiller_UsesHorizontalPreferredDirection() { var plate = new Plate(60, 120); - var filler = new RemnantPlateFiller(plate, RemnantFillPolicy.Vertical); + var filler = new DefaultPlateFiller(plate, PlateFillPolicy.VerticalRemnant); Assert.Equal(NestDirection.Horizontal, filler.PreferredDirection); } @@ -31,7 +31,7 @@ public class RemnantEngineTests public void HorizontalRemnantPlateFiller_UsesVerticalPreferredDirection() { var plate = new Plate(60, 120); - var filler = new RemnantPlateFiller(plate, RemnantFillPolicy.Horizontal); + var filler = new DefaultPlateFiller(plate, PlateFillPolicy.HorizontalRemnant); Assert.Equal(NestDirection.Vertical, filler.PreferredDirection); } @@ -39,7 +39,7 @@ public class RemnantEngineTests public void VerticalRemnantPlateFiller_Fill_ProducesResults() { var plate = new Plate(60, 120); - var filler = new RemnantPlateFiller(plate, RemnantFillPolicy.Vertical); + var filler = new DefaultPlateFiller(plate, PlateFillPolicy.VerticalRemnant); var item = new NestItem { Drawing = MakeRectDrawing(20, 10) }; var parts = filler.Fill( @@ -56,7 +56,7 @@ public class RemnantEngineTests public void HorizontalRemnantPlateFiller_Fill_ProducesResults() { var plate = new Plate(60, 120); - var filler = new RemnantPlateFiller(plate, RemnantFillPolicy.Horizontal); + var filler = new DefaultPlateFiller(plate, PlateFillPolicy.HorizontalRemnant); var item = new NestItem { Drawing = MakeRectDrawing(20, 10) }; var parts = filler.Fill( @@ -84,7 +84,7 @@ public class RemnantEngineTests var item = new NestItem { Drawing = drawing }; var defaultFiller = new DefaultPlateFiller(plate); - var remnantFiller = new RemnantPlateFiller(plate, RemnantFillPolicy.Vertical); + var remnantFiller = new DefaultPlateFiller(plate, PlateFillPolicy.VerticalRemnant); var defaultParts = defaultFiller.Fill( item,