mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-31 23:51:36 -04:00
fix(engine): stop staggered circle fills overlapping
Staggered stripes were spaced only so adjacent stripes touched diagonally. When the in-stripe pitch stretched past sqrt(3)*D, stripes two apart, which sit directly over each other, came closer than a diameter. Clamp the stripe spacing to at least a radius in both fills. FillEndOdd also divided by zero when a single circle fit per stripe; it now stacks those stripes a diameter apart. FillEndEven now clones the template item, so placed circles keep its radius instead of zero. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d87302a189
commit
6e2e27d6fc
3 files changed
+93
-9
No files matched your search
@@ -0,0 +1,69 @@
|
||||
using OpenNest.Engine.CirclePacking;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest.Engine.Tests.CirclePacking;
|
||||
|
||||
public class FillEndOverlapTests
|
||||
{
|
||||
private const double Diameter = 10;
|
||||
|
||||
public static IEnumerable<object[]> BinSizes()
|
||||
{
|
||||
for (var width = 1.0; width <= 6.0; width += 0.05)
|
||||
{
|
||||
foreach (var length in new[] { 1.0, 1.5, 1.95, 2.5, 2.9, 3.7, 6.0 })
|
||||
yield return new object[] { width * Diameter, length * Diameter };
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(BinSizes))]
|
||||
public void FillEndOdd_PlacesNoOverlappingCircles(double width, double length)
|
||||
{
|
||||
var bin = new Bin { Location = new Vector(0, 0), Size = new Size(width, length) };
|
||||
|
||||
new FillEndOdd(bin).Fill(CreateItem());
|
||||
|
||||
Assert.NotEmpty(bin.Items);
|
||||
AssertNoOverlaps(bin.Items);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(BinSizes))]
|
||||
public void FillEndEven_PlacesNoOverlappingCircles(double width, double length)
|
||||
{
|
||||
var bin = new Bin { Location = new Vector(0, 0), Size = new Size(width, length) };
|
||||
|
||||
new FillEndEven(bin).Fill(CreateItem());
|
||||
|
||||
Assert.NotEmpty(bin.Items);
|
||||
Assert.All(bin.Items, i => Assert.Equal(Diameter, i.Diameter, 9));
|
||||
AssertNoOverlaps(bin.Items);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FillEndOdd_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());
|
||||
|
||||
AssertNoOverlaps(bin.Items);
|
||||
}
|
||||
|
||||
private static Item CreateItem() => new Item { Radius = Diameter * 0.5, Center = new Vector(Diameter * 0.5, Diameter * 0.5) };
|
||||
|
||||
private static void AssertNoOverlaps(IReadOnlyList<Item> items)
|
||||
{
|
||||
for (var i = 0; i < items.Count; i++)
|
||||
{
|
||||
for (var j = i + 1; j < items.Count; j++)
|
||||
{
|
||||
var distance = items[i].Center.DistanceTo(items[j].Center);
|
||||
Assert.True(
|
||||
distance >= Diameter - 1e-6,
|
||||
$"Circles {i} at {items[i].Center} and {j} at {items[j].Center} are {distance:F4} apart (< {Diameter}).");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
|
||||
@@ -18,6 +18,9 @@ namespace OpenNest.Engine.CirclePacking
|
||||
|
||||
var rows = System.Math.Floor((Bin.Length + Tolerance.Epsilon) / (item.Diameter));
|
||||
|
||||
if (rows == 0)
|
||||
return;
|
||||
|
||||
var diameter = item.Diameter;
|
||||
var remaining = Bin.Length - diameter * rows;
|
||||
var radius = diameter * 0.5;
|
||||
@@ -35,7 +38,9 @@ namespace OpenNest.Engine.CirclePacking
|
||||
|
||||
for (; y <= max.Y; y += yoffset)
|
||||
{
|
||||
Bin.Items.Add(new Item { Center = new Vector(x, y) });
|
||||
var addedItem = item.Clone();
|
||||
addedItem.Center = new Vector(x, y);
|
||||
Bin.Items.Add(addedItem);
|
||||
}
|
||||
|
||||
column++;
|
||||
@@ -44,7 +49,9 @@ namespace OpenNest.Engine.CirclePacking
|
||||
else
|
||||
{
|
||||
var yoffset = (Bin.Length - diameter) / (2 * rows - 1);
|
||||
var xoffset = Trigonometry.Base(yoffset, diameter);
|
||||
// Adjacent columns touch diagonally, but columns two apart sit directly beside
|
||||
// each other, so the column spacing can never drop below a radius.
|
||||
var xoffset = System.Math.Max(Trigonometry.Base(yoffset, diameter), radius);
|
||||
|
||||
var yodd = Bin.Y + yoffset;
|
||||
|
||||
@@ -58,7 +65,9 @@ namespace OpenNest.Engine.CirclePacking
|
||||
|
||||
for (; y <= max.Y; y += yoffset)
|
||||
{
|
||||
Bin.Items.Add(new Item { Center = new Vector(x, y) });
|
||||
var addedItem = item.Clone();
|
||||
addedItem.Center = new Vector(x, y);
|
||||
Bin.Items.Add(addedItem);
|
||||
}
|
||||
|
||||
column++;
|
||||
|
||||
@@ -44,10 +44,16 @@ namespace OpenNest.Engine.CirclePacking
|
||||
if (count == 0)
|
||||
return bin;
|
||||
|
||||
var primaryOffset = (primarySize - item.Diameter) / (count - 1);
|
||||
var secondaryOffset = horizontal
|
||||
? Trigonometry.Height(primaryOffset * 0.5, item.Diameter)
|
||||
: Trigonometry.Base(primaryOffset * 0.5, item.Diameter);
|
||||
// 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;
|
||||
|
||||
var outerStart = horizontal ? bin.Y : bin.X;
|
||||
var outerMax = horizontal ? max.Y : max.X;
|
||||
@@ -58,7 +64,7 @@ namespace OpenNest.Engine.CirclePacking
|
||||
|
||||
for (var outer = outerStart; outer <= outerMax; outer += secondaryOffset)
|
||||
{
|
||||
var inner = stripe.IsOdd() ? innerStart + primaryOffset * 0.5 : innerStart;
|
||||
var inner = count > 1 && stripe.IsOdd() ? innerStart + primaryOffset * 0.5 : innerStart;
|
||||
|
||||
for (; inner <= innerMax; inner += primaryOffset)
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user