feat(engine): add PatternTiler for unit cell tiling across plates
This commit is contained in:
52
OpenNest.Engine/PatternTiler.cs
Normal file
52
OpenNest.Engine/PatternTiler.cs
Normal file
@@ -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<Part> Tile(List<Part> cell, Size plateSize, double partSpacing)
|
||||
{
|
||||
if (cell == null || cell.Count == 0)
|
||||
return new List<Part>();
|
||||
|
||||
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<Part>();
|
||||
|
||||
// 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<Part>();
|
||||
|
||||
var cellOrigin = cellBox.Location;
|
||||
var baseOffset = new Vector(halfSpacing - cellOrigin.X, halfSpacing - cellOrigin.Y);
|
||||
|
||||
var result = new List<Part>(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
105
OpenNest.Tests/PatternTilerTests.cs
Normal file
105
OpenNest.Tests/PatternTilerTests.cs
Normal file
@@ -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> { 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<Part> { 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> { 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<Part>(), new Size(100, 100), 0);
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Tile_NonSquarePlate_CorrectAxes()
|
||||
{
|
||||
var drawing = MakeSquareDrawing(10);
|
||||
var cell = new List<Part> { 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> { Part.CreateAtOrigin(drawing) };
|
||||
var plateSize = new Size(30, 30);
|
||||
|
||||
var result = PatternTiler.Tile(cell, plateSize, 0);
|
||||
|
||||
Assert.Empty(result);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user