From b5bc03bea12e13c7826d5b42196ca78280a6884f Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Wed, 4 Nov 2026 22:00:28 -0500 Subject: [PATCH] refactor(circle-fill): merge FillEndOdd and FillEndEven into StaggeredFill The two stripe layouts differ only in how far odd stripes shift, so they are now one StaggeredFill with StaggerMode.HalfPitch (was FillEndOdd: half-pitch shift, odd stripes one disc fewer) and StaggerMode.SlackShift (was FillEndEven: shift by the stripe's leftover, every stripe full). The unused FillEngine base and its throwing Fill(item, maxCount) go away. Layouts and orientation tie-breaks are unchanged. Co-Authored-By: Claude Opus 5.5 --- ...dOverlapTests.cs => StaggeredFillTests.cs} | 36 ++-- OpenNest.Engine/CirclePacking/FillEndEven.cs | 84 -------- OpenNest.Engine/CirclePacking/FillEndOdd.cs | 72 ------- OpenNest.Engine/CirclePacking/FillEngine.cs | 106 ---------- .../CirclePacking/StaggeredFill.cs | 191 ++++++++++++++++++ .../Strategies/CircleFillStrategy.cs | 15 +- 6 files changed, 217 insertions(+), 287 deletions(-) rename OpenNest.Engine.Tests/CirclePacking/{FillEndOverlapTests.cs => StaggeredFillTests.cs} (77%) delete mode 100644 OpenNest.Engine/CirclePacking/FillEndEven.cs delete mode 100644 OpenNest.Engine/CirclePacking/FillEndOdd.cs delete mode 100644 OpenNest.Engine/CirclePacking/FillEngine.cs create mode 100644 OpenNest.Engine/CirclePacking/StaggeredFill.cs diff --git a/OpenNest.Engine.Tests/CirclePacking/FillEndOverlapTests.cs b/OpenNest.Engine.Tests/CirclePacking/StaggeredFillTests.cs similarity index 77% rename from OpenNest.Engine.Tests/CirclePacking/FillEndOverlapTests.cs rename to OpenNest.Engine.Tests/CirclePacking/StaggeredFillTests.cs index d9edde63..f68cb0f6 100644 --- a/OpenNest.Engine.Tests/CirclePacking/FillEndOverlapTests.cs +++ b/OpenNest.Engine.Tests/CirclePacking/StaggeredFillTests.cs @@ -3,7 +3,7 @@ using OpenNest.Geometry; namespace OpenNest.Engine.Tests.CirclePacking; -public class FillEndOverlapTests +public class StaggeredFillTests { private const double Diameter = 10; @@ -18,11 +18,11 @@ public class FillEndOverlapTests [Theory] [MemberData(nameof(BinSizes))] - public void FillEndOdd_PlacesNoOverlappingCircles(double width, double length) + public void HalfPitch_PlacesNoOverlappingCircles(double width, double length) { var bin = new Bin { Location = new Vector(0, 0), Size = new Size(width, length) }; - new FillEndOdd(bin).Fill(CreateItem()); + StaggeredFill.Fill(bin, CreateItem(), StaggerMode.HalfPitch); Assert.NotEmpty(bin.Items); AssertNoOverlaps(bin.Items); @@ -30,11 +30,11 @@ public class FillEndOverlapTests [Theory] [MemberData(nameof(BinSizes))] - public void FillEndEven_PlacesNoOverlappingCircles(double width, double length) + public void SlackShift_PlacesNoOverlappingCircles(double width, double length) { var bin = new Bin { Location = new Vector(0, 0), Size = new Size(width, length) }; - new FillEndEven(bin).Fill(CreateItem()); + StaggeredFill.Fill(bin, CreateItem(), StaggerMode.SlackShift); Assert.NotEmpty(bin.Items); Assert.All(bin.Items, i => Assert.Equal(Diameter, i.Diameter, 9)); @@ -42,35 +42,35 @@ public class FillEndOverlapTests } [Fact] - public void FillEndOdd_SingleCircleWidth_StacksAlongTheOtherAxis() + public void HalfPitch_SingleCircleWidth_StacksAlongTheOtherAxis() { var bin = new Bin { Location = new Vector(0, 0), Size = new Size(1.5 * Diameter, 1.5 * Diameter) }; - new FillEndOdd(bin).Fill(CreateItem()); + StaggeredFill.Fill(bin, CreateItem(), StaggerMode.HalfPitch); AssertNoOverlaps(bin.Items); } [Theory] [MemberData(nameof(BinSizes))] - public void FillEnd_KeepsEveryCircleInsideTheBin(double width, double length) + public void StaggeredFill_KeepsEveryCircleInsideTheBin(double width, double length) { var odd = new Bin { Location = new Vector(3, 7), Size = new Size(width, length) }; var even = new Bin { Location = new Vector(3, 7), Size = new Size(width, length) }; - new FillEndOdd(odd).Fill(CreateItem()); - new FillEndEven(even).Fill(CreateItem()); + StaggeredFill.Fill(odd, CreateItem(), StaggerMode.HalfPitch); + StaggeredFill.Fill(even, CreateItem(), StaggerMode.SlackShift); AssertInside(odd); AssertInside(even); } [Fact] - public void FillEndOdd_StretchesStripesToTheBinOnALongSheet() + public void HalfPitch_StretchesStripesToTheBinOnALongSheet() { var bin = new Bin { Location = new Vector(0, 0), Size = new Size(59.375, 119.75) }; - new FillEndOdd(bin).Fill(new Item { Radius = 4.125 }); + StaggeredFill.Fill(bin, new Item { Radius = 4.125 }, StaggerMode.HalfPitch); // Staggered all the way places 108; the last stripes stacked straight fit one more. AssertNoOverlaps(bin.Items, 8.25); @@ -79,24 +79,24 @@ public class FillEndOverlapTests } [Fact] - public void FillEndEven_TriesTheStripesAlongTheLongSide() + public void SlackShift_TriesTheStripesAlongTheLongSide() { var bin = new Bin { Location = new Vector(0, 0), Size = new Size(59.375, 119.75) }; - new FillEndEven(bin).Fill(new Item { Radius = 4.125 }); + StaggeredFill.Fill(bin, new Item { Radius = 4.125 }, StaggerMode.SlackShift); AssertNoOverlaps(bin.Items, 8.25); Assert.Equal(112, bin.Items.Count); } [Fact] - public void FillEndOdd_EndsWithStraightStripesWhenTheyFitMore() + public void HalfPitch_EndsWithStraightStripesWhenTheyFitMore() { // 13 discs to a stripe: seven staggered stripes place 88 and six straight ones 78, but // five staggered and two straight over the fifth place 89. var bin = new Bin { Location = new Vector(0, 0), Size = new Size(59.8, 119.8) }; - new FillEndOdd(bin).Fill(new Item { Radius = 4.6 }); + StaggeredFill.Fill(bin, new Item { Radius = 4.6 }, StaggerMode.HalfPitch); AssertNoOverlaps(bin.Items, 9.2); AssertInside(bin); @@ -112,13 +112,13 @@ public class FillEndOverlapTests } [Fact] - public void FillEndOdd_KeepsStaggeredStripesWhenStraightOnesFitNoMore() + public void HalfPitch_KeepsStaggeredStripesWhenStraightOnesFitNoMore() { // Seven staggered stripes place 88; too short for the mixed 89, straight stripes from // any switch point place at most 78. var bin = new Bin { Location = new Vector(0, 0), Size = new Size(57.2, 119.8) }; - new FillEndOdd(bin).Fill(new Item { Radius = 4.6 }); + StaggeredFill.Fill(bin, new Item { Radius = 4.6 }, StaggerMode.HalfPitch); Assert.Equal(88, bin.Items.Count); var stripes = bin.Items.GroupBy(i => System.Math.Round(i.Center.Y, 6)).Select(g => g.Count()); diff --git a/OpenNest.Engine/CirclePacking/FillEndEven.cs b/OpenNest.Engine/CirclePacking/FillEndEven.cs deleted file mode 100644 index 3a7d87f1..00000000 --- a/OpenNest.Engine/CirclePacking/FillEndEven.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System; -using OpenNest.Geometry; -using OpenNest.Math; - -namespace OpenNest.Engine.CirclePacking -{ - internal class FillEndEven : FillEngine - { - public FillEndEven(Bin bin) - : base(bin) { } - - public override void Fill(Item item) - { - var bin1 = FillHorizontal(item); - var bin2 = FillVertical(item); - - if (bin1.Items.Count > bin2.Items.Count) - Bin.Items.AddRange(bin1.Items); - else - Bin.Items.AddRange(bin2.Items); - } - - public override void Fill(Item item, int maxCount) - { - throw new NotImplementedException(); - } - - private Bin FillHorizontal(Item item) => FillAxis(item, horizontal: true); - - private Bin FillVertical(Item item) => FillAxis(item, horizontal: false); - - /// Stripes run along Y and step along X when horizontal, and the reverse otherwise. - private Bin FillAxis(Item item, bool horizontal) - { - var bin = Bin.Clone() as Bin; - - var max = new Vector( - bin.Right - item.Radius + Tolerance.Epsilon, - bin.Top - item.Radius + Tolerance.Epsilon - ); - - // A box's Length runs along X and its Width along Y. - var stripeSize = horizontal ? bin.Width : bin.Length; - var diameter = item.Diameter; - var radius = item.Radius; - var rows = System.Math.Floor((stripeSize + Tolerance.Epsilon) / diameter); - - if (rows == 0) - return bin; - - var remaining = stripeSize - diameter * rows; - double oddShift; - double innerOffset; - double outerOffset; - - if (remaining < radius) - { - oddShift = remaining; - innerOffset = diameter; - outerOffset = Trigonometry.Base(remaining, diameter); - } - else - { - oddShift = (stripeSize - diameter) / (2 * rows - 1); - innerOffset = oddShift * 2.0; - // Adjacent stripes touch diagonally, but stripes two apart sit directly beside - // each other, so the stripe spacing can never drop below a radius. - outerOffset = System.Math.Max(Trigonometry.Base(oddShift, diameter), radius); - } - - AddStripes(bin, item, new Stripes( - InnerAlongX: !horizontal, - OuterStart: (horizontal ? bin.X : bin.Y) + radius, - OuterMax: horizontal ? max.X : max.Y, - OuterStep: outerOffset, - InnerStart: (horizontal ? bin.Y : bin.X) + radius, - InnerMax: horizontal ? max.Y : max.X, - InnerStep: innerOffset, - Shift: oddShift)); - - return bin; - } - } -} diff --git a/OpenNest.Engine/CirclePacking/FillEndOdd.cs b/OpenNest.Engine/CirclePacking/FillEndOdd.cs deleted file mode 100644 index 8183f06c..00000000 --- a/OpenNest.Engine/CirclePacking/FillEndOdd.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; -using OpenNest.Geometry; -using OpenNest.Math; - -namespace OpenNest.Engine.CirclePacking -{ - internal class FillEndOdd : FillEngine - { - public FillEndOdd(Bin bin) - : base(bin) { } - - public override void Fill(Item item) - { - var bin1 = FillHorizontal(item); - var bin2 = FillVertical(item); - - if (bin1.Items.Count > bin2.Items.Count) - Bin.Items.AddRange(bin1.Items); - else - Bin.Items.AddRange(bin2.Items); - } - - public override void Fill(Item item, int maxCount) - { - throw new NotImplementedException(); - } - - private Bin FillHorizontal(Item item) => FillAxis(item, horizontal: true); - - private Bin FillVertical(Item item) => FillAxis(item, horizontal: false); - - private Bin FillAxis(Item item, bool horizontal) - { - var bin = Bin.Clone() as Bin; - - var max = new Vector( - bin.Right - item.Radius + Tolerance.Epsilon, - bin.Top - item.Radius + Tolerance.Epsilon - ); - - // A box's Length runs along X and its Width along Y. - var primarySize = horizontal ? bin.Length : bin.Width; - var count = System.Math.Floor((primarySize + Tolerance.Epsilon) / item.Diameter); - - if (count == 0) - return bin; - - // A single circle per stripe cannot stagger; stack the stripes a diameter apart. - var primaryOffset = count > 1 - ? (primarySize - item.Diameter) / (count - 1) - : double.PositiveInfinity; - - // Adjacent stripes touch diagonally, but stripes two apart sit directly over each - // other, so the stripe spacing can never drop below a radius. - var secondaryOffset = count > 1 - ? System.Math.Max(Trigonometry.Height(primaryOffset * 0.5, item.Diameter), item.Diameter * 0.5) - : item.Diameter; - - AddStripes(bin, item, new Stripes( - InnerAlongX: horizontal, - OuterStart: (horizontal ? bin.Y : bin.X) + item.Radius, - OuterMax: horizontal ? max.Y : max.X, - OuterStep: secondaryOffset, - InnerStart: (horizontal ? bin.X : bin.Y) + item.Radius, - InnerMax: horizontal ? max.X : max.Y, - InnerStep: primaryOffset, - Shift: count > 1 ? primaryOffset * 0.5 : 0)); - - return bin; - } - } -} diff --git a/OpenNest.Engine/CirclePacking/FillEngine.cs b/OpenNest.Engine/CirclePacking/FillEngine.cs deleted file mode 100644 index 2ffb9259..00000000 --- a/OpenNest.Engine/CirclePacking/FillEngine.cs +++ /dev/null @@ -1,106 +0,0 @@ -using System.Collections.Generic; -using OpenNest.Geometry; -using OpenNest.Math; - -namespace OpenNest.Engine.CirclePacking -{ - internal abstract class FillEngine - { - public FillEngine(Bin bin) - { - Bin = bin; - } - - public Bin Bin { get; set; } - - public abstract void Fill(Item item); - - public abstract void Fill(Item item, int maxCount); - - /// - /// Lays staggered stripes apart into , - /// odd stripes shifted along their length by . The last stripes - /// may instead stack straight: a diameter apart, each repeating the last staggered stripe's - /// positions. Straight stacking climbs faster per stripe than staggering, so where the - /// sheet leaves too little for one more staggered stripe, a few straight ones can fit more. - /// Every switch point is tried, all staggered and all straight included, and the one placing - /// the most is kept; a tie keeps the stripes staggered. - /// - protected static void AddStripes(Bin bin, Item item, Stripes stripes) - { - var staggered = new List(); - for (var outer = stripes.OuterStart; outer <= stripes.OuterMax; outer += stripes.OuterStep) - staggered.Add(outer); - - var counts = new int[staggered.Count]; - var total = 0; - for (var stripe = 0; stripe < counts.Length; stripe++) - { - for (var inner = InnerStart(stripes, stripe); inner <= stripes.InnerMax; inner += stripes.InnerStep) - counts[stripe]++; - total += counts[stripe]; - } - - var switchAt = staggered.Count; - var straightCount = 0; - var best = total; - var placed = 0; - - for (var stripe = 0; stripe < counts.Length; stripe++) - { - placed += counts[stripe]; - var straight = 0; - for (var outer = staggered[stripe] + item.Diameter; outer <= stripes.OuterMax; outer += item.Diameter) - straight++; - - if (placed + straight * counts[stripe] > best) - { - best = placed + straight * counts[stripe]; - switchAt = stripe + 1; - straightCount = straight; - } - } - - for (var stripe = 0; stripe < switchAt; stripe++) - AddStripe(bin, item, stripes, staggered[stripe], InnerStart(stripes, stripe)); - - var last = switchAt - 1; - var straightOuter = last >= 0 ? staggered[last] : 0; - for (var stripe = 0; stripe < straightCount; stripe++) - { - straightOuter += item.Diameter; - AddStripe(bin, item, stripes, straightOuter, InnerStart(stripes, last)); - } - } - - private static double InnerStart(Stripes stripes, int stripe) => - stripe.IsOdd() ? stripes.InnerStart + stripes.Shift : stripes.InnerStart; - - private static void AddStripe(Bin bin, Item item, Stripes stripes, double outer, double innerStart) - { - for (var inner = innerStart; inner <= stripes.InnerMax; inner += stripes.InnerStep) - { - var addedItem = item.Clone(); - addedItem.Center = stripes.InnerAlongX - ? new Vector(inner, outer) - : new Vector(outer, inner); - bin.Items.Add(addedItem); - } - } - - /// - /// Stripes run along the inner axis, X when , and step along the - /// other; starts and maxima are circle centers. - /// - protected readonly record struct Stripes( - bool InnerAlongX, - double OuterStart, - double OuterMax, - double OuterStep, - double InnerStart, - double InnerMax, - double InnerStep, - double Shift - ); - } -} diff --git a/OpenNest.Engine/CirclePacking/StaggeredFill.cs b/OpenNest.Engine/CirclePacking/StaggeredFill.cs new file mode 100644 index 00000000..781494b0 --- /dev/null +++ b/OpenNest.Engine/CirclePacking/StaggeredFill.cs @@ -0,0 +1,191 @@ +using System.Collections.Generic; +using OpenNest.Geometry; +using OpenNest.Math; + +namespace OpenNest.Engine.CirclePacking +{ + /// How far shifts every other stripe along its length. + internal enum StaggerMode + { + /// + /// Half the pitch, so odd stripes nest in the gaps and hold one disc fewer; the pitch is + /// stretched until a stripe's discs span the bin. + /// + HalfPitch, + + /// + /// By the room a full stripe leaves, so every stripe holds as many discs as fit and odd + /// stripes drop only as deep into the gaps as that shift allows. + /// + SlackShift, + } + + /// + /// Fills a bin with stripes of equal discs, every other stripe shifted along its length by the + /// , stripes along X and along Y tried and the one placing more kept. + /// + internal static class StaggeredFill + { + public static void Fill(Bin bin, Item item, StaggerMode mode) + { + var alongX = Layout(bin, item, mode, alongX: true); + var alongY = Layout(bin, item, mode, alongX: false); + + // On a tie, half-pitch stripes run along Y and slack-shifted ones along X. + var keepAlongX = mode == StaggerMode.HalfPitch + ? alongX.Count > alongY.Count + : alongX.Count >= alongY.Count; + + bin.Items.AddRange(keepAlongX ? alongX : alongY); + } + + /// Stripes run along X and step along Y when , and the reverse otherwise. + private static List Layout(Bin bin, Item item, StaggerMode mode, bool alongX) + { + var items = new List(); + var diameter = item.Diameter; + var radius = item.Radius; + + // A box's Length runs along X and its Width along Y. + var stripeLength = alongX ? bin.Length : bin.Width; + var count = System.Math.Floor((stripeLength + Tolerance.Epsilon) / diameter); + + if (count == 0) + return items; + + var (pitch, step, shift) = mode == StaggerMode.HalfPitch + ? HalfPitch(stripeLength, count, diameter) + : SlackShift(stripeLength, count, diameter); + + var max = new Vector( + bin.Right - radius + Tolerance.Epsilon, + bin.Top - radius + Tolerance.Epsilon + ); + + AddStripes(items, item, new Stripes( + AlongX: alongX, + OuterStart: (alongX ? bin.Y : bin.X) + radius, + OuterMax: alongX ? max.Y : max.X, + OuterStep: step, + InnerStart: (alongX ? bin.X : bin.Y) + radius, + InnerMax: alongX ? max.X : max.Y, + InnerStep: pitch, + Shift: shift)); + + return items; + } + + private static (double Pitch, double Step, double Shift) HalfPitch(double stripeLength, double count, double diameter) + { + // A single disc per stripe cannot stagger; stack the stripes a diameter apart. + if (count == 1) + return (double.PositiveInfinity, diameter, 0); + + var pitch = (stripeLength - diameter) / (count - 1); + + // Adjacent stripes touch diagonally, but stripes two apart sit directly over each + // other, so the stripe spacing can never drop below a radius. + return (pitch, System.Math.Max(Trigonometry.Height(pitch * 0.5, diameter), diameter * 0.5), pitch * 0.5); + } + + private static (double Pitch, double Step, double Shift) SlackShift(double stripeLength, double count, double diameter) + { + var remaining = stripeLength - diameter * count; + + if (remaining < diameter * 0.5) + return (diameter, Trigonometry.Base(remaining, diameter), remaining); + + var shift = (stripeLength - diameter) / (2 * count - 1); + + // Adjacent stripes touch diagonally, but stripes two apart sit directly beside + // each other, so the stripe spacing can never drop below a radius. + return (shift * 2.0, System.Math.Max(Trigonometry.Base(shift, diameter), diameter * 0.5), shift); + } + + /// + /// Lays staggered stripes apart, odd stripes shifted along + /// their length by . The last stripes may instead stack + /// straight: a diameter apart, each repeating the last staggered stripe's positions. + /// Straight stacking climbs faster per stripe than staggering, so where the bin leaves + /// too little for one more staggered stripe, a few straight ones can fit more. Every + /// switch point is tried, all staggered and all straight included, and the one placing + /// the most is kept; a tie keeps the stripes staggered. + /// + private static void AddStripes(List items, Item item, Stripes stripes) + { + var staggered = new List(); + for (var outer = stripes.OuterStart; outer <= stripes.OuterMax; outer += stripes.OuterStep) + staggered.Add(outer); + + var counts = new int[staggered.Count]; + var total = 0; + for (var stripe = 0; stripe < counts.Length; stripe++) + { + for (var inner = InnerStart(stripes, stripe); inner <= stripes.InnerMax; inner += stripes.InnerStep) + counts[stripe]++; + total += counts[stripe]; + } + + var switchAt = staggered.Count; + var straightCount = 0; + var best = total; + var placed = 0; + + for (var stripe = 0; stripe < counts.Length; stripe++) + { + placed += counts[stripe]; + var straight = 0; + for (var outer = staggered[stripe] + item.Diameter; outer <= stripes.OuterMax; outer += item.Diameter) + straight++; + + if (placed + straight * counts[stripe] > best) + { + best = placed + straight * counts[stripe]; + switchAt = stripe + 1; + straightCount = straight; + } + } + + for (var stripe = 0; stripe < switchAt; stripe++) + AddStripe(items, item, stripes, staggered[stripe], InnerStart(stripes, stripe)); + + var last = switchAt - 1; + var straightOuter = last >= 0 ? staggered[last] : 0; + for (var stripe = 0; stripe < straightCount; stripe++) + { + straightOuter += item.Diameter; + AddStripe(items, item, stripes, straightOuter, InnerStart(stripes, last)); + } + } + + private static double InnerStart(Stripes stripes, int stripe) => + stripe.IsOdd() ? stripes.InnerStart + stripes.Shift : stripes.InnerStart; + + private static void AddStripe(List items, Item item, Stripes stripes, double outer, double innerStart) + { + for (var inner = innerStart; inner <= stripes.InnerMax; inner += stripes.InnerStep) + { + var addedItem = item.Clone(); + addedItem.Center = stripes.AlongX + ? new Vector(inner, outer) + : new Vector(outer, inner); + items.Add(addedItem); + } + } + + /// + /// Stripes run along the inner axis, X when , and step along the + /// other; starts and maxima are disc centers. + /// + private readonly record struct Stripes( + bool AlongX, + double OuterStart, + double OuterMax, + double OuterStep, + double InnerStart, + double InnerMax, + double InnerStep, + double Shift + ); + } +} diff --git a/OpenNest.Engine/Strategies/CircleFillStrategy.cs b/OpenNest.Engine/Strategies/CircleFillStrategy.cs index 94b98c2e..891d3e64 100644 --- a/OpenNest.Engine/Strategies/CircleFillStrategy.cs +++ b/OpenNest.Engine/Strategies/CircleFillStrategy.cs @@ -9,8 +9,9 @@ using OpenNest.Geometry; namespace OpenNest.Engine.Strategies; /// -/// Staggers round parts edge to edge with and , -/// which stretch a row's pitch to the sheet so the next row drops deeper than a fixed lattice does. +/// Staggers round parts edge to edge with , whose half-pitch rows stretch +/// their pitch to the sheet so the next row drops deeper than a fixed lattice does, and whose +/// slack-shifted rows keep every row full; the better of the two is kept. /// public class CircleFillStrategy : IFillStrategy { @@ -35,22 +36,22 @@ public class CircleFillStrategy : IFillStrategy var area = context.WorkArea.Offset(gap * 0.5); var comparer = context.Policy?.Comparer ?? new DefaultFillComparer(); - var odd = Pack(area, spaced, bin => new FillEndOdd(bin), item, context.Item.Drawing); - var even = Pack(area, spaced, bin => new FillEndEven(bin), item, context.Item.Drawing); + var halfPitch = Pack(area, spaced, StaggerMode.HalfPitch, item, context.Item.Drawing); + var slackShift = Pack(area, spaced, StaggerMode.SlackShift, item, context.Item.Drawing); - return comparer.IsBetter(even, odd, context.WorkArea) ? even : odd; + return comparer.IsBetter(slackShift, halfPitch, context.WorkArea) ? slackShift : halfPitch; } private static List Pack( Box area, Item spaced, - System.Func engine, + StaggerMode mode, Item item, Drawing drawing ) { var bin = new Bin { Location = area.Location, Size = area.Size }; - engine(bin).Fill(spaced); + StaggeredFill.Fill(bin, spaced, mode); return bin.Items.Select(i => new Part(drawing, i.Center + item.PartOffset)).ToList(); } }