using System.Collections.Generic; using OpenNest.Geometry; namespace OpenNest; /// /// 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. /// 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(); 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 BuildGrooveSide(List pairPositions, double halfWidth, double depth, double extentStart, double extentEnd, double pos, bool isVertical) { var entities = new List(); 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 BuildSpikeSide(List pairPositions, double halfWidth, double depth, double extentStart, double extentEnd, double pos, bool isVertical) { var entities = new List(); 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)); } }