feat: add RotationSlideStrategy with directional push contact algorithm
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
131
OpenNest.Engine/BestFit/RotationSlideStrategy.cs
Normal file
131
OpenNest.Engine/BestFit/RotationSlideStrategy.cs
Normal file
@@ -0,0 +1,131 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.Engine.BestFit
|
||||
{
|
||||
public class RotationSlideStrategy : IBestFitStrategy
|
||||
{
|
||||
public RotationSlideStrategy(double part2Rotation, int type, string description)
|
||||
{
|
||||
Part2Rotation = part2Rotation;
|
||||
Type = type;
|
||||
Description = description;
|
||||
}
|
||||
|
||||
public double Part2Rotation { get; }
|
||||
public int Type { get; }
|
||||
public string Description { get; }
|
||||
|
||||
public List<PairCandidate> GenerateCandidates(Drawing drawing, double spacing, double stepSize)
|
||||
{
|
||||
var candidates = new List<PairCandidate>();
|
||||
|
||||
// Build part1 at origin
|
||||
var part1 = new Part(drawing);
|
||||
var bbox1 = part1.Program.BoundingBox();
|
||||
part1.Offset(-bbox1.Location.X, -bbox1.Location.Y);
|
||||
part1.UpdateBounds();
|
||||
|
||||
// Build part2 template with rotation, normalized to origin
|
||||
var part2Template = new Part(drawing);
|
||||
if (!Part2Rotation.IsEqualTo(0))
|
||||
part2Template.Rotate(Part2Rotation);
|
||||
var bbox2 = part2Template.Program.BoundingBox();
|
||||
part2Template.Offset(-bbox2.Location.X, -bbox2.Location.Y);
|
||||
part2Template.UpdateBounds();
|
||||
|
||||
var testNumber = 0;
|
||||
|
||||
// Try pushing left (horizontal slide)
|
||||
GenerateCandidatesForAxis(
|
||||
part1, part2Template, drawing, spacing, stepSize,
|
||||
PushDirection.Left, candidates, ref testNumber);
|
||||
|
||||
// Try pushing down (vertical slide)
|
||||
GenerateCandidatesForAxis(
|
||||
part1, part2Template, drawing, spacing, stepSize,
|
||||
PushDirection.Down, candidates, ref testNumber);
|
||||
|
||||
return candidates;
|
||||
}
|
||||
|
||||
private void GenerateCandidatesForAxis(
|
||||
Part part1, Part part2Template, Drawing drawing,
|
||||
double spacing, double stepSize, PushDirection pushDir,
|
||||
List<PairCandidate> candidates, ref int testNumber)
|
||||
{
|
||||
var bbox1 = part1.BoundingBox;
|
||||
var bbox2 = part2Template.BoundingBox;
|
||||
var halfSpacing = spacing / 2;
|
||||
|
||||
var isHorizontalPush = pushDir == PushDirection.Left || pushDir == PushDirection.Right;
|
||||
|
||||
// Perpendicular range: part2 slides across the full extent of part1
|
||||
double perpMin, perpMax, pushStartOffset;
|
||||
|
||||
if (isHorizontalPush)
|
||||
{
|
||||
perpMin = -(bbox2.Height + spacing);
|
||||
perpMax = bbox1.Height + bbox2.Height + spacing;
|
||||
pushStartOffset = bbox1.Width + bbox2.Width + spacing * 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
perpMin = -(bbox2.Width + spacing);
|
||||
perpMax = bbox1.Width + bbox2.Width + spacing;
|
||||
pushStartOffset = bbox1.Height + bbox2.Height + spacing * 2;
|
||||
}
|
||||
|
||||
// Pre-compute part1's offset lines (half-spacing outward)
|
||||
var part1Lines = Helper.GetOffsetPartLines(part1, halfSpacing);
|
||||
|
||||
for (var offset = perpMin; offset <= perpMax; offset += stepSize)
|
||||
{
|
||||
var part2 = (Part)part2Template.Clone();
|
||||
|
||||
// Place part2 far away along push axis, at perpendicular offset
|
||||
if (isHorizontalPush)
|
||||
part2.Offset(pushStartOffset, offset);
|
||||
else
|
||||
part2.Offset(offset, pushStartOffset);
|
||||
|
||||
// Get part2's offset lines (half-spacing outward)
|
||||
var part2Lines = Helper.GetOffsetPartLines(part2, halfSpacing);
|
||||
|
||||
// Find contact distance
|
||||
var slideDist = Helper.DirectionalDistance(part2Lines, part1Lines, pushDir);
|
||||
|
||||
if (slideDist >= double.MaxValue || slideDist < 0)
|
||||
continue;
|
||||
|
||||
// Move part2 to contact position
|
||||
var pushVector = GetPushVector(pushDir, slideDist);
|
||||
var finalPosition = part2.Location + pushVector;
|
||||
|
||||
candidates.Add(new PairCandidate
|
||||
{
|
||||
Drawing = drawing,
|
||||
Part1Rotation = 0,
|
||||
Part2Rotation = Part2Rotation,
|
||||
Part2Offset = finalPosition,
|
||||
StrategyType = Type,
|
||||
TestNumber = testNumber++,
|
||||
Spacing = spacing
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static Vector GetPushVector(PushDirection direction, double distance)
|
||||
{
|
||||
switch (direction)
|
||||
{
|
||||
case PushDirection.Left: return new Vector(-distance, 0);
|
||||
case PushDirection.Right: return new Vector(distance, 0);
|
||||
case PushDirection.Down: return new Vector(0, -distance);
|
||||
case PushDirection.Up: return new Vector(0, distance);
|
||||
default: return Vector.Zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,7 @@
|
||||
<Compile Include="BestFit\PairCandidate.cs" />
|
||||
<Compile Include="BestFit\BestFitResult.cs" />
|
||||
<Compile Include="BestFit\IBestFitStrategy.cs" />
|
||||
<Compile Include="BestFit\RotationSlideStrategy.cs" />
|
||||
<Compile Include="CirclePacking\Bin.cs" />
|
||||
<Compile Include="CirclePacking\FillEndEven.cs" />
|
||||
<Compile Include="CirclePacking\FillEndOdd.cs" />
|
||||
|
||||
Reference in New Issue
Block a user