feat: add WeldGapTabSplit implementation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
82
OpenNest.Core/Splitting/WeldGapTabSplit.cs
Normal file
82
OpenNest.Core/Splitting/WeldGapTabSplit.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest;
|
||||
|
||||
/// <summary>
|
||||
/// Generates rectangular tabs on one side of the split edge (negative side).
|
||||
/// The positive side remains a straight line. Tabs act as weld-gap spacers.
|
||||
/// </summary>
|
||||
public class WeldGapTabSplit : ISplitFeature
|
||||
{
|
||||
public string Name => "Weld-Gap Tabs";
|
||||
|
||||
public SplitFeatureResult GenerateFeatures(SplitLine line, double extentStart, double extentEnd, SplitParameters parameters)
|
||||
{
|
||||
var extent = extentEnd - extentStart;
|
||||
var tabCount = parameters.TabCount;
|
||||
var tabWidth = parameters.TabWidth;
|
||||
var tabHeight = parameters.TabHeight;
|
||||
|
||||
// Evenly space tabs along the split line
|
||||
var spacing = extent / (tabCount + 1);
|
||||
|
||||
var negEntities = new List<Entity>();
|
||||
var isVertical = line.Axis == CutOffAxis.Vertical;
|
||||
var pos = line.Position;
|
||||
|
||||
// Tabs protrude toward the negative side (lower coordinate on the split axis)
|
||||
var tabDir = -1.0;
|
||||
|
||||
var cursor = extentStart;
|
||||
|
||||
for (var i = 0; i < tabCount; i++)
|
||||
{
|
||||
var tabCenter = extentStart + spacing * (i + 1);
|
||||
var tabStart = tabCenter - tabWidth / 2;
|
||||
var tabEnd = tabCenter + tabWidth / 2;
|
||||
|
||||
if (isVertical)
|
||||
{
|
||||
if (tabStart > cursor + OpenNest.Math.Tolerance.Epsilon)
|
||||
negEntities.Add(new Line(new Vector(pos, cursor), new Vector(pos, tabStart)));
|
||||
|
||||
negEntities.Add(new Line(new Vector(pos, tabStart), new Vector(pos + tabDir * tabHeight, tabStart)));
|
||||
negEntities.Add(new Line(new Vector(pos + tabDir * tabHeight, tabStart), new Vector(pos + tabDir * tabHeight, tabEnd)));
|
||||
negEntities.Add(new Line(new Vector(pos + tabDir * tabHeight, tabEnd), new Vector(pos, tabEnd)));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (tabStart > cursor + OpenNest.Math.Tolerance.Epsilon)
|
||||
negEntities.Add(new Line(new Vector(cursor, pos), new Vector(tabStart, pos)));
|
||||
|
||||
negEntities.Add(new Line(new Vector(tabStart, pos), new Vector(tabStart, pos + tabDir * tabHeight)));
|
||||
negEntities.Add(new Line(new Vector(tabStart, pos + tabDir * tabHeight), new Vector(tabEnd, pos + tabDir * tabHeight)));
|
||||
negEntities.Add(new Line(new Vector(tabEnd, pos + tabDir * tabHeight), new Vector(tabEnd, pos)));
|
||||
}
|
||||
|
||||
cursor = tabEnd;
|
||||
}
|
||||
|
||||
// Final segment from last tab to extent end
|
||||
if (isVertical)
|
||||
{
|
||||
if (extentEnd > cursor + OpenNest.Math.Tolerance.Epsilon)
|
||||
negEntities.Add(new Line(new Vector(pos, cursor), new Vector(pos, extentEnd)));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (extentEnd > cursor + OpenNest.Math.Tolerance.Epsilon)
|
||||
negEntities.Add(new Line(new Vector(cursor, pos), new Vector(extentEnd, pos)));
|
||||
}
|
||||
|
||||
// Positive side: plain straight line (reversed direction)
|
||||
var posEntities = new List<Entity>();
|
||||
if (isVertical)
|
||||
posEntities.Add(new Line(new Vector(pos, extentEnd), new Vector(pos, extentStart)));
|
||||
else
|
||||
posEntities.Add(new Line(new Vector(extentEnd, pos), new Vector(extentStart, pos)));
|
||||
|
||||
return new SplitFeatureResult(negEntities, posEntities);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,53 @@
|
||||
using System.Linq;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest.Tests.Splitting;
|
||||
|
||||
public class SplitFeatureTests
|
||||
{
|
||||
[Fact]
|
||||
public void WeldGapTabSplit_Vertical_TabsOnNegativeSide()
|
||||
{
|
||||
var feature = new WeldGapTabSplit();
|
||||
var line = new SplitLine(50.0, CutOffAxis.Vertical);
|
||||
var parameters = new SplitParameters
|
||||
{
|
||||
Type = SplitType.WeldGapTabs,
|
||||
TabWidth = 2.0,
|
||||
TabHeight = 0.25,
|
||||
TabCount = 2
|
||||
};
|
||||
|
||||
var result = feature.GenerateFeatures(line, 0.0, 100.0, parameters);
|
||||
|
||||
// Positive side (right): single straight line (no tabs)
|
||||
Assert.Single(result.PositiveSideEdge);
|
||||
Assert.IsType<Line>(result.PositiveSideEdge[0]);
|
||||
|
||||
// Negative side (left): has tab protrusions — more than 1 entity
|
||||
Assert.True(result.NegativeSideEdge.Count > 1);
|
||||
|
||||
// All entities should be lines
|
||||
Assert.All(result.NegativeSideEdge, e => Assert.IsType<Line>(e));
|
||||
|
||||
// First entity starts at extent start, last ends at extent end
|
||||
var first = (Line)result.NegativeSideEdge[0];
|
||||
var last = (Line)result.NegativeSideEdge[^1];
|
||||
Assert.Equal(0.0, first.StartPoint.Y, 6);
|
||||
Assert.Equal(100.0, last.EndPoint.Y, 6);
|
||||
|
||||
// Tabs protrude in the negative-X direction (left of split line)
|
||||
var tabEntities = result.NegativeSideEdge.Cast<Line>().ToList();
|
||||
var minX = tabEntities.Min(l => System.Math.Min(l.StartPoint.X, l.EndPoint.X));
|
||||
Assert.Equal(50.0 - 0.25, minX, 6); // tabHeight = 0.25
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeldGapTabSplit_Name()
|
||||
{
|
||||
Assert.Equal("Weld-Gap Tabs", new WeldGapTabSplit().Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StraightSplit_Vertical_ProducesSingleLineEachSide()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user