mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-11-05 07:51:36 -05:00
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 <noreply@anthropic.com>
This commit is contained in:
1 parent
dffb06a9e3
commit
b5bc03bea1
6 files changed
+217
-287
No files matched your search
+18
-18
@@ -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());
|
||||
@@ -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);
|
||||
|
||||
/// <summary>Stripes run along Y and step along X when horizontal, and the reverse otherwise.</summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
/// <summary>
|
||||
/// Lays staggered stripes <see cref="Stripes.OuterStep"/> apart into <paramref name="bin"/>,
|
||||
/// odd stripes shifted along their length by <see cref="Stripes.Shift"/>. 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.
|
||||
/// </summary>
|
||||
protected static void AddStripes(Bin bin, Item item, Stripes stripes)
|
||||
{
|
||||
var staggered = new List<double>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stripes run along the inner axis, X when <see cref="InnerAlongX"/>, and step along the
|
||||
/// other; starts and maxima are circle centers.
|
||||
/// </summary>
|
||||
protected readonly record struct Stripes(
|
||||
bool InnerAlongX,
|
||||
double OuterStart,
|
||||
double OuterMax,
|
||||
double OuterStep,
|
||||
double InnerStart,
|
||||
double InnerMax,
|
||||
double InnerStep,
|
||||
double Shift
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.Engine.CirclePacking
|
||||
{
|
||||
/// <summary>How far <see cref="StaggeredFill"/> shifts every other stripe along its length.</summary>
|
||||
internal enum StaggerMode
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
HalfPitch,
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
SlackShift,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fills a bin with stripes of equal discs, every other stripe shifted along its length by the
|
||||
/// <see cref="StaggerMode"/>, stripes along X and along Y tried and the one placing more kept.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>Stripes run along X and step along Y when <paramref name="alongX"/>, and the reverse otherwise.</summary>
|
||||
private static List<Item> Layout(Bin bin, Item item, StaggerMode mode, bool alongX)
|
||||
{
|
||||
var items = new List<Item>();
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lays staggered stripes <see cref="Stripes.OuterStep"/> apart, odd stripes shifted along
|
||||
/// their length by <see cref="Stripes.Shift"/>. 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.
|
||||
/// </summary>
|
||||
private static void AddStripes(List<Item> items, Item item, Stripes stripes)
|
||||
{
|
||||
var staggered = new List<double>();
|
||||
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<Item> 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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stripes run along the inner axis, X when <see cref="AlongX"/>, and step along the
|
||||
/// other; starts and maxima are disc centers.
|
||||
/// </summary>
|
||||
private readonly record struct Stripes(
|
||||
bool AlongX,
|
||||
double OuterStart,
|
||||
double OuterMax,
|
||||
double OuterStep,
|
||||
double InnerStart,
|
||||
double InnerMax,
|
||||
double InnerStep,
|
||||
double Shift
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -9,8 +9,9 @@ using OpenNest.Geometry;
|
||||
namespace OpenNest.Engine.Strategies;
|
||||
|
||||
/// <summary>
|
||||
/// Staggers round parts edge to edge with <see cref="FillEndOdd"/> and <see cref="FillEndEven"/>,
|
||||
/// 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 <see cref="StaggeredFill"/>, 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.
|
||||
/// </summary>
|
||||
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<Part> Pack(
|
||||
Box area,
|
||||
Item spaced,
|
||||
System.Func<Bin, FillEngine> 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();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user