diff --git a/OpenNest.Engine/PatternTiler.cs b/OpenNest.Engine/PatternTiler.cs new file mode 100644 index 0000000..779f4f8 --- /dev/null +++ b/OpenNest.Engine/PatternTiler.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using OpenNest.Geometry; + +namespace OpenNest.Engine +{ + public static class PatternTiler + { + public static List Tile(List cell, Size plateSize, double partSpacing) + { + if (cell == null || cell.Count == 0) + return new List(); + + var cellBox = cell.GetBoundingBox(); + var halfSpacing = partSpacing / 2; + + var cellWidth = cellBox.Width + partSpacing; + var cellHeight = cellBox.Length + partSpacing; + + if (cellWidth <= 0 || cellHeight <= 0) + return new List(); + + // Size.Width = X-axis, Size.Length = Y-axis + var cols = (int)System.Math.Floor(plateSize.Width / cellWidth); + var rows = (int)System.Math.Floor(plateSize.Length / cellHeight); + + if (cols <= 0 || rows <= 0) + return new List(); + + var cellOrigin = cellBox.Location; + var baseOffset = new Vector(halfSpacing - cellOrigin.X, halfSpacing - cellOrigin.Y); + + var result = new List(cols * rows * cell.Count); + + for (var row = 0; row < rows; row++) + { + for (var col = 0; col < cols; col++) + { + var tileOffset = baseOffset + new Vector(col * cellWidth, row * cellHeight); + + foreach (var part in cell) + { + result.Add(part.CloneAtOffset(tileOffset)); + } + } + } + + return result; + } + } +} diff --git a/OpenNest.Tests/PatternTilerTests.cs b/OpenNest.Tests/PatternTilerTests.cs new file mode 100644 index 0000000..1a186c0 --- /dev/null +++ b/OpenNest.Tests/PatternTilerTests.cs @@ -0,0 +1,105 @@ +using OpenNest; +using OpenNest.Engine; +using OpenNest.Geometry; + +namespace OpenNest.Tests; + +public class PatternTilerTests +{ + private static Drawing MakeSquareDrawing(double size) + { + var pgm = new CNC.Program(); + pgm.Codes.Add(new CNC.LinearMove(size, 0)); + pgm.Codes.Add(new CNC.LinearMove(size, size)); + pgm.Codes.Add(new CNC.LinearMove(0, size)); + pgm.Codes.Add(new CNC.LinearMove(0, 0)); + return new Drawing("square", pgm); + } + + [Fact] + public void Tile_SinglePart_FillsGrid() + { + var drawing = MakeSquareDrawing(10); + var cell = new List { Part.CreateAtOrigin(drawing) }; + var plateSize = new Size(30, 20); + var partSpacing = 0.0; + + var result = PatternTiler.Tile(cell, plateSize, partSpacing); + + Assert.Equal(6, result.Count); + + foreach (var part in result) + { + Assert.True(part.BoundingBox.Right <= plateSize.Width + 0.001); + Assert.True(part.BoundingBox.Top <= plateSize.Length + 0.001); + Assert.True(part.BoundingBox.Left >= -0.001); + Assert.True(part.BoundingBox.Bottom >= -0.001); + } + } + + [Fact] + public void Tile_TwoParts_TilesUnitCell() + { + var drawing = MakeSquareDrawing(10); + var partA = Part.CreateAtOrigin(drawing); + var partB = Part.CreateAtOrigin(drawing); + partB.Offset(10, 0); + + var cell = new List { partA, partB }; + var plateSize = new Size(40, 20); + var partSpacing = 0.0; + + var result = PatternTiler.Tile(cell, plateSize, partSpacing); + + Assert.Equal(8, result.Count); + } + + [Fact] + public void Tile_WithSpacing_ReducesCount() + { + var drawing = MakeSquareDrawing(10); + var cell = new List { Part.CreateAtOrigin(drawing) }; + var plateSize = new Size(30, 20); + var partSpacing = 2.0; + + var result = PatternTiler.Tile(cell, plateSize, partSpacing); + + Assert.Equal(2, result.Count); + } + + [Fact] + public void Tile_EmptyCell_ReturnsEmpty() + { + var result = PatternTiler.Tile(new List(), new Size(100, 100), 0); + Assert.Empty(result); + } + + [Fact] + public void Tile_NonSquarePlate_CorrectAxes() + { + var drawing = MakeSquareDrawing(10); + var cell = new List { Part.CreateAtOrigin(drawing) }; + var plateSize = new Size(50, 10); + + var result = PatternTiler.Tile(cell, plateSize, 0); + + Assert.Equal(5, result.Count); + + var maxRight = result.Max(p => p.BoundingBox.Right); + var maxTop = result.Max(p => p.BoundingBox.Top); + Assert.True(maxRight <= 50.001); + Assert.True(maxTop <= 10.001); + } + + [Fact] + public void Tile_CellLargerThanPlate_ReturnsSingleCell() + { + var drawing = MakeSquareDrawing(50); + var cell = new List { Part.CreateAtOrigin(drawing) }; + var plateSize = new Size(30, 30); + + var result = PatternTiler.Tile(cell, plateSize, 0); + + Assert.Empty(result); + } +}