- Fill(NestItem) and Fill(List<Part>) now delegate to their Box overloads - Add Part.CreateAtOrigin() to replace repeated 4-line build-at-origin pattern used in NestEngine, RotationSlideStrategy, and PairEvaluator - Remove dead code: FillArea overloads, Fill(NestItem, int), FillWithPairs(NestItem), ConvertTileResultToParts, PackBottomLeft.FindPointHorizontal, Pattern.GetLines/GetOffsetLines, unused count variable in FillNoRotation - Simplify IsBetterValidFill to delegate to IsBetterFill after overlap check NestEngine reduced from 717 to 484 lines. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
125 lines
4.7 KiB
C#
125 lines
4.7 KiB
C#
using System.Collections.Generic;
|
|
using OpenNest.Geometry;
|
|
|
|
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>();
|
|
|
|
var part1 = Part.CreateAtOrigin(drawing);
|
|
var part2Template = Part.CreateAtOrigin(drawing, Part2Rotation);
|
|
|
|
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);
|
|
|
|
// Align sweep start to a multiple of stepSize so that offset=0 is always
|
|
// included. This ensures perfect grid arrangements (side-by-side, stacked)
|
|
// are generated for rectangular parts.
|
|
var alignedStart = System.Math.Ceiling(perpMin / stepSize) * stepSize;
|
|
|
|
for (var offset = alignedStart; 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;
|
|
}
|
|
}
|
|
}
|
|
}
|