Repo-wide sweep with the pinned CSharpier 1.3.0 tool. Whitespace and line-wrapping only; OpenNest.Engine.Tests (109) and OpenNest.IO.Tests pass after reformat, full solution builds 0 errors. Added .csharpierignore so csproj/config XML keeps its existing layout (CSharpier's XML wrapping churns attributes with zero benefit). Formatting is now enforceable: dotnet csharpier check . passes.
154 lines
4.7 KiB
C#
154 lines
4.7 KiB
C#
using System.Collections.Generic;
|
|
using OpenNest.Geometry;
|
|
|
|
namespace OpenNest;
|
|
|
|
/// <summary>
|
|
/// Generates interlocking spike/V-groove pairs along the split edge.
|
|
/// Spikes protrude from the positive side into the negative side.
|
|
/// V-grooves on the negative side receive the spikes for self-alignment during welding.
|
|
/// The weld gap (grooveDepth - spikeDepth) is the clearance at the tip when assembled.
|
|
/// </summary>
|
|
public class SpikeGrooveSplit : ISplitFeature
|
|
{
|
|
public string Name => "Spike / V-Groove";
|
|
|
|
public SplitFeatureResult GenerateFeatures(
|
|
SplitLine line,
|
|
double extentStart,
|
|
double extentEnd,
|
|
SplitParameters parameters
|
|
)
|
|
{
|
|
var extent = extentEnd - extentStart;
|
|
var pairCount = parameters.SpikePairCount;
|
|
var spikeDepth = parameters.SpikeDepth;
|
|
var grooveDepth = parameters.GrooveDepth;
|
|
var angleRad = OpenNest.Math.Angle.ToRadians(parameters.SpikeAngle / 2);
|
|
var spikeHalfWidth = spikeDepth * System.Math.Tan(angleRad);
|
|
var grooveHalfWidth = grooveDepth * System.Math.Tan(angleRad);
|
|
|
|
var isVertical = line.Axis == CutOffAxis.Vertical;
|
|
var pos = line.Position;
|
|
|
|
// Use custom positions if provided, otherwise place evenly with margin
|
|
var pairPositions = new List<double>();
|
|
if (line.FeaturePositions.Count > 0)
|
|
{
|
|
pairPositions.AddRange(line.FeaturePositions);
|
|
}
|
|
else if (pairCount == 1)
|
|
{
|
|
pairPositions.Add(extentStart + extent / 2);
|
|
}
|
|
else
|
|
{
|
|
var margin = extent * 0.15;
|
|
var usable = extent - 2 * margin;
|
|
for (var i = 0; i < pairCount; i++)
|
|
pairPositions.Add(extentStart + margin + usable * i / (pairCount - 1));
|
|
}
|
|
|
|
var negEntities = BuildGrooveSide(
|
|
pairPositions,
|
|
grooveHalfWidth,
|
|
grooveDepth,
|
|
extentStart,
|
|
extentEnd,
|
|
pos,
|
|
isVertical
|
|
);
|
|
var posEntities = BuildSpikeSide(
|
|
pairPositions,
|
|
spikeHalfWidth,
|
|
spikeDepth,
|
|
extentStart,
|
|
extentEnd,
|
|
pos,
|
|
isVertical
|
|
);
|
|
|
|
return new SplitFeatureResult(negEntities, posEntities);
|
|
}
|
|
|
|
private static List<Entity> BuildGrooveSide(
|
|
List<double> pairPositions,
|
|
double halfWidth,
|
|
double depth,
|
|
double extentStart,
|
|
double extentEnd,
|
|
double pos,
|
|
bool isVertical
|
|
)
|
|
{
|
|
var entities = new List<Entity>();
|
|
var cursor = extentStart;
|
|
|
|
foreach (var center in pairPositions)
|
|
{
|
|
var grooveStart = center - halfWidth;
|
|
var grooveEnd = center + halfWidth;
|
|
|
|
if (grooveStart > cursor + OpenNest.Math.Tolerance.Epsilon)
|
|
entities.Add(MakeLine(pos, cursor, pos, grooveStart, isVertical));
|
|
|
|
entities.Add(MakeLine(pos, grooveStart, pos - depth, center, isVertical));
|
|
entities.Add(MakeLine(pos - depth, center, pos, grooveEnd, isVertical));
|
|
|
|
cursor = grooveEnd;
|
|
}
|
|
|
|
if (extentEnd > cursor + OpenNest.Math.Tolerance.Epsilon)
|
|
entities.Add(MakeLine(pos, cursor, pos, extentEnd, isVertical));
|
|
|
|
return entities;
|
|
}
|
|
|
|
private static List<Entity> BuildSpikeSide(
|
|
List<double> pairPositions,
|
|
double halfWidth,
|
|
double depth,
|
|
double extentStart,
|
|
double extentEnd,
|
|
double pos,
|
|
bool isVertical
|
|
)
|
|
{
|
|
var entities = new List<Entity>();
|
|
var cursor = extentEnd;
|
|
|
|
for (var i = pairPositions.Count - 1; i >= 0; i--)
|
|
{
|
|
var center = pairPositions[i];
|
|
var spikeEnd = center + halfWidth;
|
|
var spikeStart = center - halfWidth;
|
|
|
|
if (cursor > spikeEnd + OpenNest.Math.Tolerance.Epsilon)
|
|
entities.Add(MakeLine(pos, cursor, pos, spikeEnd, isVertical));
|
|
|
|
entities.Add(MakeLine(pos, spikeEnd, pos - depth, center, isVertical));
|
|
entities.Add(MakeLine(pos - depth, center, pos, spikeStart, isVertical));
|
|
|
|
cursor = spikeStart;
|
|
}
|
|
|
|
if (cursor > extentStart + OpenNest.Math.Tolerance.Epsilon)
|
|
entities.Add(MakeLine(pos, cursor, pos, extentStart, isVertical));
|
|
|
|
return entities;
|
|
}
|
|
|
|
private static Line MakeLine(
|
|
double splitAxis1,
|
|
double along1,
|
|
double splitAxis2,
|
|
double along2,
|
|
bool isVertical
|
|
)
|
|
{
|
|
return isVertical
|
|
? new Line(new Vector(splitAxis1, along1), new Vector(splitAxis2, along2))
|
|
: new Line(new Vector(along1, splitAxis1), new Vector(along2, splitAxis2));
|
|
}
|
|
}
|