refactor: extract responsibilities from NestEngine into focused classes
- Move BuildPairParts to BestFitResult.BuildParts() instance method - Extract BinConverter (RectanglePacking) for Part/NestItem/Bin conversions - Extract RotationAnalysis for FindBestRotation and FindHullEdgeAngles NestEngine reduced from 484 to 287 lines — now purely orchestration, strategy selection, and comparison logic. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
106
OpenNest.Engine/RotationAnalysis.cs
Normal file
106
OpenNest.Engine/RotationAnalysis.cs
Normal file
@@ -0,0 +1,106 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenNest.Converters;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
internal static class RotationAnalysis
|
||||
{
|
||||
/// <summary>
|
||||
/// Finds the rotation angle that minimizes the bounding rectangle of a drawing's
|
||||
/// largest shape, constrained by the NestItem's rotation range.
|
||||
/// </summary>
|
||||
public static double FindBestRotation(NestItem item)
|
||||
{
|
||||
var entities = ConvertProgram.ToGeometry(item.Drawing.Program)
|
||||
.Where(e => e.Layer != SpecialLayers.Rapid);
|
||||
|
||||
var shapes = Helper.GetShapes(entities);
|
||||
|
||||
if (shapes.Count == 0)
|
||||
return 0;
|
||||
|
||||
// Find the largest shape (outer profile).
|
||||
var largest = shapes[0];
|
||||
var largestArea = largest.Area();
|
||||
|
||||
for (var i = 1; i < shapes.Count; i++)
|
||||
{
|
||||
var area = shapes[i].Area();
|
||||
if (area > largestArea)
|
||||
{
|
||||
largest = shapes[i];
|
||||
largestArea = area;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert to polygon so arcs are properly represented as line segments.
|
||||
// Shape.FindBestRotation() uses Entity cardinal points which are incorrect
|
||||
// for arcs that don't sweep through all 4 cardinal directions.
|
||||
var polygon = largest.ToPolygonWithTolerance(0.1);
|
||||
|
||||
BoundingRectangleResult result;
|
||||
|
||||
if (item.RotationStart.IsEqualTo(0) && item.RotationEnd.IsEqualTo(0))
|
||||
result = polygon.FindBestRotation();
|
||||
else
|
||||
result = polygon.FindBestRotation(item.RotationStart, item.RotationEnd);
|
||||
|
||||
// Negate the angle to align the minimum bounding rectangle with the axes.
|
||||
return -result.Angle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the convex hull of the parts' geometry and returns the unique
|
||||
/// edge angles, suitable for use as candidate rotation angles.
|
||||
/// </summary>
|
||||
public static List<double> FindHullEdgeAngles(List<Part> parts)
|
||||
{
|
||||
var points = new List<Vector>();
|
||||
|
||||
foreach (var part in parts)
|
||||
{
|
||||
var entities = ConvertProgram.ToGeometry(part.Program)
|
||||
.Where(e => e.Layer != SpecialLayers.Rapid);
|
||||
|
||||
var shapes = Helper.GetShapes(entities);
|
||||
|
||||
foreach (var shape in shapes)
|
||||
{
|
||||
var polygon = shape.ToPolygonWithTolerance(0.1);
|
||||
|
||||
foreach (var vertex in polygon.Vertices)
|
||||
points.Add(vertex + part.Location);
|
||||
}
|
||||
}
|
||||
|
||||
if (points.Count < 3)
|
||||
return new List<double> { 0 };
|
||||
|
||||
var hull = ConvexHull.Compute(points);
|
||||
var vertices = hull.Vertices;
|
||||
var n = hull.IsClosed() ? vertices.Count - 1 : vertices.Count;
|
||||
|
||||
var angles = new List<double> { 0 };
|
||||
|
||||
for (var i = 0; i < n; i++)
|
||||
{
|
||||
var next = (i + 1) % n;
|
||||
var dx = vertices[next].X - vertices[i].X;
|
||||
var dy = vertices[next].Y - vertices[i].Y;
|
||||
|
||||
if (dx * dx + dy * dy < Tolerance.Epsilon)
|
||||
continue;
|
||||
|
||||
var angle = -System.Math.Atan2(dy, dx);
|
||||
|
||||
if (!angles.Any(a => a.IsEqualTo(angle)))
|
||||
angles.Add(angle);
|
||||
}
|
||||
|
||||
return angles;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user