style: apply CSharpier formatting to all C# sources
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.
This commit is contained in:
@@ -16,8 +16,11 @@ namespace OpenNest.Engine.BestFit
|
||||
public static Func<ISlideComputer> CreateSlideComputer { get; set; }
|
||||
|
||||
public static List<BestFitResult> GetOrCompute(
|
||||
Drawing drawing, double plateWidth, double plateHeight,
|
||||
double spacing)
|
||||
Drawing drawing,
|
||||
double plateWidth,
|
||||
double plateHeight,
|
||||
double spacing
|
||||
)
|
||||
{
|
||||
var key = new CacheKey(drawing, plateWidth, plateHeight, spacing);
|
||||
|
||||
@@ -34,14 +37,24 @@ namespace OpenNest.Engine.BestFit
|
||||
{
|
||||
if (CreateEvaluator != null)
|
||||
{
|
||||
try { evaluator = CreateEvaluator(canonical, spacing); }
|
||||
catch { /* fall back to default evaluator */ }
|
||||
try
|
||||
{
|
||||
evaluator = CreateEvaluator(canonical, spacing);
|
||||
}
|
||||
catch
|
||||
{ /* fall back to default evaluator */
|
||||
}
|
||||
}
|
||||
|
||||
if (CreateSlideComputer != null)
|
||||
{
|
||||
try { slideComputer = CreateSlideComputer(); }
|
||||
catch { /* fall back to CPU slide computation */ }
|
||||
try
|
||||
{
|
||||
slideComputer = CreateSlideComputer();
|
||||
}
|
||||
catch
|
||||
{ /* fall back to CPU slide computation */
|
||||
}
|
||||
}
|
||||
|
||||
var finder = new BestFitFinder(plateWidth, plateHeight, evaluator, slideComputer);
|
||||
@@ -58,8 +71,10 @@ namespace OpenNest.Engine.BestFit
|
||||
}
|
||||
|
||||
public static void ComputeForSizes(
|
||||
Drawing drawing, double spacing,
|
||||
IEnumerable<(double Width, double Height)> plateSizes)
|
||||
Drawing drawing,
|
||||
double spacing,
|
||||
IEnumerable<(double Width, double Height)> plateSizes
|
||||
)
|
||||
{
|
||||
// Skip sizes that are already cached.
|
||||
var needed = new List<(double Width, double Height)>();
|
||||
@@ -80,8 +95,10 @@ namespace OpenNest.Engine.BestFit
|
||||
var maxHeight = 0.0;
|
||||
foreach (var size in needed)
|
||||
{
|
||||
if (size.Width > maxWidth) maxWidth = size.Width;
|
||||
if (size.Height > maxHeight) maxHeight = size.Height;
|
||||
if (size.Width > maxWidth)
|
||||
maxWidth = size.Width;
|
||||
if (size.Height > maxHeight)
|
||||
maxHeight = size.Height;
|
||||
}
|
||||
|
||||
IPairEvaluator evaluator = null;
|
||||
@@ -94,14 +111,24 @@ namespace OpenNest.Engine.BestFit
|
||||
|
||||
if (CreateEvaluator != null)
|
||||
{
|
||||
try { evaluator = CreateEvaluator(canonical, spacing); }
|
||||
catch { /* fall back to default evaluator */ }
|
||||
try
|
||||
{
|
||||
evaluator = CreateEvaluator(canonical, spacing);
|
||||
}
|
||||
catch
|
||||
{ /* fall back to default evaluator */
|
||||
}
|
||||
}
|
||||
|
||||
if (CreateSlideComputer != null)
|
||||
{
|
||||
try { slideComputer = CreateSlideComputer(); }
|
||||
catch { /* fall back to CPU slide computation */ }
|
||||
try
|
||||
{
|
||||
slideComputer = CreateSlideComputer();
|
||||
}
|
||||
catch
|
||||
{ /* fall back to CPU slide computation */
|
||||
}
|
||||
}
|
||||
|
||||
// Compute candidates and evaluate once with the largest plate.
|
||||
@@ -114,25 +141,27 @@ namespace OpenNest.Engine.BestFit
|
||||
var filter = new BestFitFilter
|
||||
{
|
||||
MaxPlateWidth = size.Width,
|
||||
MaxPlateHeight = size.Height
|
||||
MaxPlateHeight = size.Height,
|
||||
};
|
||||
|
||||
var copy = new List<BestFitResult>(baseResults.Count);
|
||||
for (var i = 0; i < baseResults.Count; i++)
|
||||
{
|
||||
var r = baseResults[i];
|
||||
copy.Add(new BestFitResult
|
||||
{
|
||||
Candidate = r.Candidate,
|
||||
RotatedArea = r.RotatedArea,
|
||||
BoundingWidth = r.BoundingWidth,
|
||||
BoundingHeight = r.BoundingHeight,
|
||||
OptimalRotation = r.OptimalRotation,
|
||||
TrueArea = r.TrueArea,
|
||||
HullAngles = r.HullAngles,
|
||||
Keep = r.Keep,
|
||||
Reason = r.Reason
|
||||
});
|
||||
copy.Add(
|
||||
new BestFitResult
|
||||
{
|
||||
Candidate = r.Candidate,
|
||||
RotatedArea = r.RotatedArea,
|
||||
BoundingWidth = r.BoundingWidth,
|
||||
BoundingHeight = r.BoundingHeight,
|
||||
OptimalRotation = r.OptimalRotation,
|
||||
TrueArea = r.TrueArea,
|
||||
HullAngles = r.HullAngles,
|
||||
Keep = r.Keep,
|
||||
Reason = r.Reason,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
filter.Apply(copy);
|
||||
@@ -156,8 +185,13 @@ namespace OpenNest.Engine.BestFit
|
||||
}
|
||||
}
|
||||
|
||||
public static void Populate(Drawing drawing, double plateWidth, double plateHeight,
|
||||
double spacing, List<BestFitResult> results)
|
||||
public static void Populate(
|
||||
Drawing drawing,
|
||||
double plateWidth,
|
||||
double plateHeight,
|
||||
double spacing,
|
||||
List<BestFitResult> results
|
||||
)
|
||||
{
|
||||
if (results == null || results.Count == 0)
|
||||
return;
|
||||
@@ -166,8 +200,10 @@ namespace OpenNest.Engine.BestFit
|
||||
_cache.TryAdd(key, results);
|
||||
}
|
||||
|
||||
public static Dictionary<(double PlateWidth, double PlateHeight, double Spacing), List<BestFitResult>>
|
||||
GetAllForDrawing(Drawing drawing)
|
||||
public static Dictionary<
|
||||
(double PlateWidth, double PlateHeight, double Spacing),
|
||||
List<BestFitResult>
|
||||
> GetAllForDrawing(Drawing drawing)
|
||||
{
|
||||
var result = new Dictionary<(double, double, double), List<BestFitResult>>();
|
||||
foreach (var kvp in _cache)
|
||||
@@ -200,10 +236,10 @@ namespace OpenNest.Engine.BestFit
|
||||
|
||||
public bool Equals(CacheKey other)
|
||||
{
|
||||
return ReferenceEquals(Drawing, other.Drawing) &&
|
||||
PlateWidth == other.PlateWidth &&
|
||||
PlateHeight == other.PlateHeight &&
|
||||
Spacing == other.Spacing;
|
||||
return ReferenceEquals(Drawing, other.Drawing)
|
||||
&& PlateWidth == other.PlateWidth
|
||||
&& PlateHeight == other.PlateHeight
|
||||
&& Spacing == other.Spacing;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj) => obj is CacheKey other && Equals(other);
|
||||
|
||||
@@ -17,8 +17,10 @@ namespace OpenNest.Engine.BestFit
|
||||
if (!result.Keep)
|
||||
continue;
|
||||
|
||||
if (result.ShortestSide > System.Math.Min(MaxPlateWidth, MaxPlateHeight) ||
|
||||
result.LongestSide > System.Math.Max(MaxPlateWidth, MaxPlateHeight))
|
||||
if (
|
||||
result.ShortestSide > System.Math.Min(MaxPlateWidth, MaxPlateHeight)
|
||||
|| result.LongestSide > System.Math.Max(MaxPlateWidth, MaxPlateHeight)
|
||||
)
|
||||
{
|
||||
result.Keep = false;
|
||||
result.Reason = "Exceeds plate dimensions";
|
||||
@@ -30,14 +32,21 @@ namespace OpenNest.Engine.BestFit
|
||||
if (aspect > MaxAspectRatio && result.Utilization < UtilizationOverride)
|
||||
{
|
||||
result.Keep = false;
|
||||
result.Reason = string.Format("Aspect ratio {0:F1} exceeds max {1}", aspect, MaxAspectRatio);
|
||||
result.Reason = string.Format(
|
||||
"Aspect ratio {0:F1} exceeds max {1}",
|
||||
aspect,
|
||||
MaxAspectRatio
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (result.Utilization < MinUtilization)
|
||||
{
|
||||
result.Keep = false;
|
||||
result.Reason = string.Format("Utilization {0:P0} below minimum", result.Utilization);
|
||||
result.Reason = string.Format(
|
||||
"Utilization {0:P0} below minimum",
|
||||
result.Utilization
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
using OpenNest.Converters;
|
||||
using OpenNest.Engine.BestFit.Tiling;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using OpenNest.Converters;
|
||||
using OpenNest.Engine.BestFit.Tiling;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.Engine.BestFit
|
||||
{
|
||||
@@ -16,20 +16,26 @@ namespace OpenNest.Engine.BestFit
|
||||
private readonly IDistanceComputer _distanceComputer;
|
||||
private readonly BestFitFilter _filter;
|
||||
|
||||
public BestFitFinder(double maxPlateWidth, double maxPlateHeight,
|
||||
IPairEvaluator evaluator = null, ISlideComputer slideComputer = null)
|
||||
public BestFitFinder(
|
||||
double maxPlateWidth,
|
||||
double maxPlateHeight,
|
||||
IPairEvaluator evaluator = null,
|
||||
ISlideComputer slideComputer = null
|
||||
)
|
||||
{
|
||||
_evaluator = evaluator ?? new PairEvaluator();
|
||||
_distanceComputer = slideComputer != null
|
||||
? (IDistanceComputer)new GpuDistanceComputer(slideComputer)
|
||||
: new CpuDistanceComputer();
|
||||
var plateAspect = System.Math.Max(maxPlateWidth, maxPlateHeight) /
|
||||
System.Math.Max(System.Math.Min(maxPlateWidth, maxPlateHeight), 0.001);
|
||||
_distanceComputer =
|
||||
slideComputer != null
|
||||
? (IDistanceComputer)new GpuDistanceComputer(slideComputer)
|
||||
: new CpuDistanceComputer();
|
||||
var plateAspect =
|
||||
System.Math.Max(maxPlateWidth, maxPlateHeight)
|
||||
/ System.Math.Max(System.Math.Min(maxPlateWidth, maxPlateHeight), 0.001);
|
||||
_filter = new BestFitFilter
|
||||
{
|
||||
MaxPlateWidth = maxPlateWidth,
|
||||
MaxPlateHeight = maxPlateHeight,
|
||||
MaxAspectRatio = System.Math.Max(5.0, plateAspect)
|
||||
MaxAspectRatio = System.Math.Max(5.0, plateAspect),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -37,20 +43,26 @@ namespace OpenNest.Engine.BestFit
|
||||
Drawing drawing,
|
||||
double spacing = 0.25,
|
||||
double stepSize = 0.25,
|
||||
BestFitSortField sortBy = BestFitSortField.Area)
|
||||
BestFitSortField sortBy = BestFitSortField.Area
|
||||
)
|
||||
{
|
||||
var strategies = BuildStrategies(drawing, spacing);
|
||||
|
||||
var candidateBags = new ConcurrentBag<List<PairCandidate>>();
|
||||
|
||||
Parallel.ForEach(strategies, strategy =>
|
||||
{
|
||||
candidateBags.Add(strategy.GenerateCandidates(drawing, spacing, stepSize));
|
||||
});
|
||||
Parallel.ForEach(
|
||||
strategies,
|
||||
strategy =>
|
||||
{
|
||||
candidateBags.Add(strategy.GenerateCandidates(drawing, spacing, stepSize));
|
||||
}
|
||||
);
|
||||
|
||||
var allCandidates = candidateBags.SelectMany(c => c).ToList();
|
||||
|
||||
Debug.WriteLine($"[BestFitFinder] {strategies.Count} strategies, {allCandidates.Count} candidates");
|
||||
Debug.WriteLine(
|
||||
$"[BestFitFinder] {strategies.Count} strategies, {allCandidates.Count} candidates"
|
||||
);
|
||||
|
||||
var results = _evaluator.EvaluateAll(allCandidates);
|
||||
|
||||
@@ -65,8 +77,12 @@ namespace OpenNest.Engine.BestFit
|
||||
}
|
||||
|
||||
public List<TileResult> FindAndTile(
|
||||
Drawing drawing, Plate plate,
|
||||
double spacing = 0.25, double stepSize = 0.25, int topN = 10)
|
||||
Drawing drawing,
|
||||
Plate plate,
|
||||
double spacing = 0.25,
|
||||
double stepSize = 0.25,
|
||||
int topN = 10
|
||||
)
|
||||
{
|
||||
var bestFits = FindBestFits(drawing, spacing, stepSize);
|
||||
var tileEvaluator = new TileEvaluator();
|
||||
@@ -88,7 +104,10 @@ namespace OpenNest.Engine.BestFit
|
||||
|
||||
foreach (var angle in angles)
|
||||
{
|
||||
var desc = string.Format("{0:F1} deg rotated, offset slide", Angle.ToDegrees(angle));
|
||||
var desc = string.Format(
|
||||
"{0:F1} deg rotated, offset slide",
|
||||
Angle.ToDegrees(angle)
|
||||
);
|
||||
strategies.Add(new RotationSlideStrategy(angle, index++, desc, _distanceComputer));
|
||||
}
|
||||
|
||||
@@ -97,13 +116,7 @@ namespace OpenNest.Engine.BestFit
|
||||
|
||||
private List<double> GetRotationAngles(Drawing drawing)
|
||||
{
|
||||
var angles = new List<double>
|
||||
{
|
||||
0,
|
||||
Angle.HalfPI,
|
||||
System.Math.PI,
|
||||
Angle.HalfPI * 3
|
||||
};
|
||||
var angles = new List<double> { 0, Angle.HalfPI, System.Math.PI, Angle.HalfPI * 3 };
|
||||
|
||||
var hullAngles = GetHullEdgeAngles(drawing);
|
||||
|
||||
@@ -119,7 +132,8 @@ namespace OpenNest.Engine.BestFit
|
||||
|
||||
private List<double> GetHullEdgeAngles(Drawing drawing)
|
||||
{
|
||||
var entities = ConvertProgram.ToGeometry(drawing.Program)
|
||||
var entities = ConvertProgram
|
||||
.ToGeometry(drawing.Program)
|
||||
.Where(e => e.Layer != SpecialLayers.Rapid);
|
||||
var shapes = ShapeBuilder.GetShapes(entities);
|
||||
|
||||
@@ -220,7 +234,10 @@ namespace OpenNest.Engine.BestFit
|
||||
angles.Add(angle);
|
||||
}
|
||||
|
||||
private List<BestFitResult> SortResults(List<BestFitResult> results, BestFitSortField sortBy)
|
||||
private List<BestFitResult> SortResults(
|
||||
List<BestFitResult> results,
|
||||
BestFitSortField sortBy
|
||||
)
|
||||
{
|
||||
switch (sortBy)
|
||||
{
|
||||
@@ -231,16 +248,19 @@ namespace OpenNest.Engine.BestFit
|
||||
case BestFitSortField.ShortestSide:
|
||||
return results.OrderBy(r => r.ShortestSide).ToList();
|
||||
case BestFitSortField.Type:
|
||||
return results.OrderBy(r => r.Candidate.StrategyIndex)
|
||||
.ThenBy(r => r.Candidate.TestNumber).ToList();
|
||||
return results
|
||||
.OrderBy(r => r.Candidate.StrategyIndex)
|
||||
.ThenBy(r => r.Candidate.TestNumber)
|
||||
.ToList();
|
||||
case BestFitSortField.OriginalSequence:
|
||||
return results.OrderBy(r => r.Candidate.TestNumber).ToList();
|
||||
case BestFitSortField.Keep:
|
||||
return results.OrderByDescending(r => r.Keep)
|
||||
.ThenBy(r => r.RotatedArea).ToList();
|
||||
return results
|
||||
.OrderByDescending(r => r.Keep)
|
||||
.ThenBy(r => r.RotatedArea)
|
||||
.ToList();
|
||||
case BestFitSortField.WhyKeepDrop:
|
||||
return results.OrderBy(r => r.Reason)
|
||||
.ThenBy(r => r.RotatedArea).ToList();
|
||||
return results.OrderBy(r => r.Reason).ThenBy(r => r.RotatedArea).ToList();
|
||||
default:
|
||||
return results;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using OpenNest.Engine;
|
||||
using OpenNest.Converters;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenNest.Converters;
|
||||
using OpenNest.Engine;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.Engine.BestFit
|
||||
{
|
||||
@@ -44,13 +44,17 @@ namespace OpenNest.Engine.BestFit
|
||||
|
||||
if (!OptimalRotation.IsEqualTo(0))
|
||||
{
|
||||
var pairBounds = ((IEnumerable<IBoundable>)new IBoundable[] { part1, part2 }).GetBoundingBox();
|
||||
var pairBounds = (
|
||||
(IEnumerable<IBoundable>)new IBoundable[] { part1, part2 }
|
||||
).GetBoundingBox();
|
||||
var center = pairBounds.Center;
|
||||
part1.Rotate(-OptimalRotation, center);
|
||||
part2.Rotate(-OptimalRotation, center);
|
||||
}
|
||||
|
||||
var finalBounds = ((IEnumerable<IBoundable>)new IBoundable[] { part1, part2 }).GetBoundingBox();
|
||||
var finalBounds = (
|
||||
(IEnumerable<IBoundable>)new IBoundable[] { part1, part2 }
|
||||
).GetBoundingBox();
|
||||
var offset = new Vector(-finalBounds.Left, -finalBounds.Bottom);
|
||||
part1.Offset(offset);
|
||||
part2.Offset(offset);
|
||||
@@ -106,7 +110,8 @@ namespace OpenNest.Engine.BestFit
|
||||
|
||||
foreach (var part in parts)
|
||||
{
|
||||
var partEntities = ConvertProgram.ToGeometry(part.Program)
|
||||
var partEntities = ConvertProgram
|
||||
.ToGeometry(part.Program)
|
||||
.Where(e => e.Layer != SpecialLayers.Rapid)
|
||||
.ToList();
|
||||
|
||||
@@ -129,6 +134,6 @@ namespace OpenNest.Engine.BestFit
|
||||
Type,
|
||||
OriginalSequence,
|
||||
Keep,
|
||||
WhyKeepDrop
|
||||
WhyKeepDrop,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.Engine.BestFit
|
||||
{
|
||||
@@ -10,7 +10,8 @@ namespace OpenNest.Engine.BestFit
|
||||
public double[] ComputeDistances(
|
||||
List<Line> stationaryLines,
|
||||
List<Line> movingTemplateLines,
|
||||
SlideOffset[] offsets)
|
||||
SlideOffset[] offsets
|
||||
)
|
||||
{
|
||||
var count = offsets.Length;
|
||||
var results = new double[count];
|
||||
@@ -18,7 +19,8 @@ namespace OpenNest.Engine.BestFit
|
||||
var allMovingVerts = ExtractUniqueVertices(movingTemplateLines);
|
||||
var allStationaryVerts = ExtractUniqueVertices(stationaryLines);
|
||||
|
||||
var vertexCache = new Dictionary<(double, double), (Vector[] leading, Vector[] facing)>();
|
||||
var vertexCache =
|
||||
new Dictionary<(double, double), (Vector[] leading, Vector[] facing)>();
|
||||
|
||||
foreach (var offset in offsets)
|
||||
{
|
||||
@@ -26,69 +28,101 @@ namespace OpenNest.Engine.BestFit
|
||||
if (vertexCache.ContainsKey(key))
|
||||
continue;
|
||||
|
||||
var leading = FilterVerticesByProjection(allMovingVerts, offset.DirX, offset.DirY, keepHigh: true);
|
||||
var facing = FilterVerticesByProjection(allStationaryVerts, offset.DirX, offset.DirY, keepHigh: false);
|
||||
var leading = FilterVerticesByProjection(
|
||||
allMovingVerts,
|
||||
offset.DirX,
|
||||
offset.DirY,
|
||||
keepHigh: true
|
||||
);
|
||||
var facing = FilterVerticesByProjection(
|
||||
allStationaryVerts,
|
||||
offset.DirX,
|
||||
offset.DirY,
|
||||
keepHigh: false
|
||||
);
|
||||
vertexCache[key] = (leading, facing);
|
||||
}
|
||||
|
||||
System.Threading.Tasks.Parallel.For(0, count, i =>
|
||||
{
|
||||
var offset = offsets[i];
|
||||
var dirX = offset.DirX;
|
||||
var dirY = offset.DirY;
|
||||
var oppX = -dirX;
|
||||
var oppY = -dirY;
|
||||
|
||||
var (leadingMoving, facingStationary) = vertexCache[(dirX, dirY)];
|
||||
|
||||
var minDist = double.MaxValue;
|
||||
|
||||
for (var v = 0; v < leadingMoving.Length; v++)
|
||||
System.Threading.Tasks.Parallel.For(
|
||||
0,
|
||||
count,
|
||||
i =>
|
||||
{
|
||||
var vx = leadingMoving[v].X + offset.Dx;
|
||||
var vy = leadingMoving[v].Y + offset.Dy;
|
||||
var offset = offsets[i];
|
||||
var dirX = offset.DirX;
|
||||
var dirY = offset.DirY;
|
||||
var oppX = -dirX;
|
||||
var oppY = -dirY;
|
||||
|
||||
for (var j = 0; j < stationaryLines.Count; j++)
|
||||
var (leadingMoving, facingStationary) = vertexCache[(dirX, dirY)];
|
||||
|
||||
var minDist = double.MaxValue;
|
||||
|
||||
for (var v = 0; v < leadingMoving.Length; v++)
|
||||
{
|
||||
var e = stationaryLines[j];
|
||||
var d = SpatialQuery.RayEdgeDistance(
|
||||
vx, vy,
|
||||
e.StartPoint.X, e.StartPoint.Y,
|
||||
e.EndPoint.X, e.EndPoint.Y,
|
||||
dirX, dirY);
|
||||
var vx = leadingMoving[v].X + offset.Dx;
|
||||
var vy = leadingMoving[v].Y + offset.Dy;
|
||||
|
||||
if (d < minDist)
|
||||
for (var j = 0; j < stationaryLines.Count; j++)
|
||||
{
|
||||
minDist = d;
|
||||
if (d <= 0) { results[i] = 0; return; }
|
||||
var e = stationaryLines[j];
|
||||
var d = SpatialQuery.RayEdgeDistance(
|
||||
vx,
|
||||
vy,
|
||||
e.StartPoint.X,
|
||||
e.StartPoint.Y,
|
||||
e.EndPoint.X,
|
||||
e.EndPoint.Y,
|
||||
dirX,
|
||||
dirY
|
||||
);
|
||||
|
||||
if (d < minDist)
|
||||
{
|
||||
minDist = d;
|
||||
if (d <= 0)
|
||||
{
|
||||
results[i] = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var v = 0; v < facingStationary.Length; v++)
|
||||
{
|
||||
var svx = facingStationary[v].X;
|
||||
var svy = facingStationary[v].Y;
|
||||
|
||||
for (var j = 0; j < movingTemplateLines.Count; j++)
|
||||
for (var v = 0; v < facingStationary.Length; v++)
|
||||
{
|
||||
var e = movingTemplateLines[j];
|
||||
var d = SpatialQuery.RayEdgeDistance(
|
||||
svx, svy,
|
||||
e.StartPoint.X + offset.Dx, e.StartPoint.Y + offset.Dy,
|
||||
e.EndPoint.X + offset.Dx, e.EndPoint.Y + offset.Dy,
|
||||
oppX, oppY);
|
||||
var svx = facingStationary[v].X;
|
||||
var svy = facingStationary[v].Y;
|
||||
|
||||
if (d < minDist)
|
||||
for (var j = 0; j < movingTemplateLines.Count; j++)
|
||||
{
|
||||
minDist = d;
|
||||
if (d <= 0) { results[i] = 0; return; }
|
||||
var e = movingTemplateLines[j];
|
||||
var d = SpatialQuery.RayEdgeDistance(
|
||||
svx,
|
||||
svy,
|
||||
e.StartPoint.X + offset.Dx,
|
||||
e.StartPoint.Y + offset.Dy,
|
||||
e.EndPoint.X + offset.Dx,
|
||||
e.EndPoint.Y + offset.Dy,
|
||||
oppX,
|
||||
oppY
|
||||
);
|
||||
|
||||
if (d < minDist)
|
||||
{
|
||||
minDist = d;
|
||||
if (d <= 0)
|
||||
{
|
||||
results[i] = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
results[i] = minDist;
|
||||
});
|
||||
results[i] = minDist;
|
||||
}
|
||||
);
|
||||
|
||||
return results;
|
||||
}
|
||||
@@ -96,7 +130,8 @@ namespace OpenNest.Engine.BestFit
|
||||
public double[] ComputeDistances(
|
||||
List<Entity> stationaryEntities,
|
||||
List<Entity> movingEntities,
|
||||
SlideOffset[] offsets)
|
||||
SlideOffset[] offsets
|
||||
)
|
||||
{
|
||||
var count = offsets.Length;
|
||||
var results = new double[count];
|
||||
@@ -107,7 +142,8 @@ namespace OpenNest.Engine.BestFit
|
||||
var movingCurves = ExtractCurveParams(movingEntities);
|
||||
var stationaryCurves = ExtractCurveParams(stationaryEntities);
|
||||
|
||||
var vertexCache = new Dictionary<(double, double), (Vector[] leading, Vector[] facing)>();
|
||||
var vertexCache =
|
||||
new Dictionary<(double, double), (Vector[] leading, Vector[] facing)>();
|
||||
|
||||
foreach (var offset in offsets)
|
||||
{
|
||||
@@ -115,106 +151,169 @@ namespace OpenNest.Engine.BestFit
|
||||
if (vertexCache.ContainsKey(key))
|
||||
continue;
|
||||
|
||||
var leading = FilterVerticesByProjection(allMovingVerts, offset.DirX, offset.DirY, keepHigh: true);
|
||||
var facing = FilterVerticesByProjection(allStationaryVerts, offset.DirX, offset.DirY, keepHigh: false);
|
||||
var leading = FilterVerticesByProjection(
|
||||
allMovingVerts,
|
||||
offset.DirX,
|
||||
offset.DirY,
|
||||
keepHigh: true
|
||||
);
|
||||
var facing = FilterVerticesByProjection(
|
||||
allStationaryVerts,
|
||||
offset.DirX,
|
||||
offset.DirY,
|
||||
keepHigh: false
|
||||
);
|
||||
vertexCache[key] = (leading, facing);
|
||||
}
|
||||
|
||||
System.Threading.Tasks.Parallel.For(0, count, i =>
|
||||
{
|
||||
var offset = offsets[i];
|
||||
var dirX = offset.DirX;
|
||||
var dirY = offset.DirY;
|
||||
var oppX = -dirX;
|
||||
var oppY = -dirY;
|
||||
|
||||
var (leadingMoving, facingStationary) = vertexCache[(dirX, dirY)];
|
||||
|
||||
var minDist = double.MaxValue;
|
||||
|
||||
// Case 1: Leading moving vertices → stationary entities
|
||||
for (var v = 0; v < leadingMoving.Length; v++)
|
||||
System.Threading.Tasks.Parallel.For(
|
||||
0,
|
||||
count,
|
||||
i =>
|
||||
{
|
||||
var vx = leadingMoving[v].X + offset.Dx;
|
||||
var vy = leadingMoving[v].Y + offset.Dy;
|
||||
var offset = offsets[i];
|
||||
var dirX = offset.DirX;
|
||||
var dirY = offset.DirY;
|
||||
var oppX = -dirX;
|
||||
var oppY = -dirY;
|
||||
|
||||
for (var j = 0; j < stationaryEntities.Count; j++)
|
||||
var (leadingMoving, facingStationary) = vertexCache[(dirX, dirY)];
|
||||
|
||||
var minDist = double.MaxValue;
|
||||
|
||||
// Case 1: Leading moving vertices → stationary entities
|
||||
for (var v = 0; v < leadingMoving.Length; v++)
|
||||
{
|
||||
var d = RayEntityDistance(vx, vy, stationaryEntities[j], 0, 0, dirX, dirY);
|
||||
var vx = leadingMoving[v].X + offset.Dx;
|
||||
var vy = leadingMoving[v].Y + offset.Dy;
|
||||
|
||||
if (d < minDist)
|
||||
for (var j = 0; j < stationaryEntities.Count; j++)
|
||||
{
|
||||
minDist = d;
|
||||
if (d <= 0) { results[i] = 0; return; }
|
||||
}
|
||||
}
|
||||
}
|
||||
var d = RayEntityDistance(
|
||||
vx,
|
||||
vy,
|
||||
stationaryEntities[j],
|
||||
0,
|
||||
0,
|
||||
dirX,
|
||||
dirY
|
||||
);
|
||||
|
||||
// Case 2: Facing stationary vertices → moving entities (opposite direction)
|
||||
for (var v = 0; v < facingStationary.Length; v++)
|
||||
{
|
||||
var svx = facingStationary[v].X;
|
||||
var svy = facingStationary[v].Y;
|
||||
|
||||
for (var j = 0; j < movingEntities.Count; j++)
|
||||
{
|
||||
var d = RayEntityDistance(svx, svy, movingEntities[j], offset.Dx, offset.Dy, oppX, oppY);
|
||||
|
||||
if (d < minDist)
|
||||
{
|
||||
minDist = d;
|
||||
if (d <= 0) { results[i] = 0; return; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 3: Curve-to-curve direct distance.
|
||||
// Vertex sampling misses the true contact between two curved entities
|
||||
// when the approach angle doesn't align with a sampled vertex.
|
||||
for (var m = 0; m < movingCurves.Length; m++)
|
||||
{
|
||||
var mc = movingCurves[m];
|
||||
var mcx = mc.Cx + offset.Dx;
|
||||
var mcy = mc.Cy + offset.Dy;
|
||||
|
||||
for (var s = 0; s < stationaryCurves.Length; s++)
|
||||
{
|
||||
var sc = stationaryCurves[s];
|
||||
var d = SpatialQuery.RayCircleDistance(
|
||||
mcx, mcy, sc.Cx, sc.Cy, mc.Radius + sc.Radius, dirX, dirY);
|
||||
|
||||
if (d >= minDist || d == double.MaxValue)
|
||||
continue;
|
||||
|
||||
if (mc.Entity is Arc || sc.Entity is Arc)
|
||||
{
|
||||
var mx = mcx + d * dirX;
|
||||
var my = mcy + d * dirY;
|
||||
var toCx = sc.Cx - mx;
|
||||
var toCy = sc.Cy - my;
|
||||
|
||||
if (mc.Entity is Arc mArc)
|
||||
if (d < minDist)
|
||||
{
|
||||
var angle = Angle.NormalizeRad(System.Math.Atan2(toCy, toCx));
|
||||
if (!Angle.IsBetweenRad(angle, mArc.StartAngle, mArc.EndAngle, mArc.IsReversed))
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sc.Entity is Arc sArc)
|
||||
{
|
||||
var angle = Angle.NormalizeRad(System.Math.Atan2(-toCy, -toCx));
|
||||
if (!Angle.IsBetweenRad(angle, sArc.StartAngle, sArc.EndAngle, sArc.IsReversed))
|
||||
continue;
|
||||
minDist = d;
|
||||
if (d <= 0)
|
||||
{
|
||||
results[i] = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
minDist = d;
|
||||
if (d <= 0) { results[i] = 0; return; }
|
||||
}
|
||||
}
|
||||
|
||||
results[i] = minDist;
|
||||
});
|
||||
// Case 2: Facing stationary vertices → moving entities (opposite direction)
|
||||
for (var v = 0; v < facingStationary.Length; v++)
|
||||
{
|
||||
var svx = facingStationary[v].X;
|
||||
var svy = facingStationary[v].Y;
|
||||
|
||||
for (var j = 0; j < movingEntities.Count; j++)
|
||||
{
|
||||
var d = RayEntityDistance(
|
||||
svx,
|
||||
svy,
|
||||
movingEntities[j],
|
||||
offset.Dx,
|
||||
offset.Dy,
|
||||
oppX,
|
||||
oppY
|
||||
);
|
||||
|
||||
if (d < minDist)
|
||||
{
|
||||
minDist = d;
|
||||
if (d <= 0)
|
||||
{
|
||||
results[i] = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 3: Curve-to-curve direct distance.
|
||||
// Vertex sampling misses the true contact between two curved entities
|
||||
// when the approach angle doesn't align with a sampled vertex.
|
||||
for (var m = 0; m < movingCurves.Length; m++)
|
||||
{
|
||||
var mc = movingCurves[m];
|
||||
var mcx = mc.Cx + offset.Dx;
|
||||
var mcy = mc.Cy + offset.Dy;
|
||||
|
||||
for (var s = 0; s < stationaryCurves.Length; s++)
|
||||
{
|
||||
var sc = stationaryCurves[s];
|
||||
var d = SpatialQuery.RayCircleDistance(
|
||||
mcx,
|
||||
mcy,
|
||||
sc.Cx,
|
||||
sc.Cy,
|
||||
mc.Radius + sc.Radius,
|
||||
dirX,
|
||||
dirY
|
||||
);
|
||||
|
||||
if (d >= minDist || d == double.MaxValue)
|
||||
continue;
|
||||
|
||||
if (mc.Entity is Arc || sc.Entity is Arc)
|
||||
{
|
||||
var mx = mcx + d * dirX;
|
||||
var my = mcy + d * dirY;
|
||||
var toCx = sc.Cx - mx;
|
||||
var toCy = sc.Cy - my;
|
||||
|
||||
if (mc.Entity is Arc mArc)
|
||||
{
|
||||
var angle = Angle.NormalizeRad(System.Math.Atan2(toCy, toCx));
|
||||
if (
|
||||
!Angle.IsBetweenRad(
|
||||
angle,
|
||||
mArc.StartAngle,
|
||||
mArc.EndAngle,
|
||||
mArc.IsReversed
|
||||
)
|
||||
)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sc.Entity is Arc sArc)
|
||||
{
|
||||
var angle = Angle.NormalizeRad(System.Math.Atan2(-toCy, -toCx));
|
||||
if (
|
||||
!Angle.IsBetweenRad(
|
||||
angle,
|
||||
sArc.StartAngle,
|
||||
sArc.EndAngle,
|
||||
sArc.IsReversed
|
||||
)
|
||||
)
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
minDist = d;
|
||||
if (d <= 0)
|
||||
{
|
||||
results[i] = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
results[i] = minDist;
|
||||
}
|
||||
);
|
||||
|
||||
return results;
|
||||
}
|
||||
@@ -222,7 +321,9 @@ namespace OpenNest.Engine.BestFit
|
||||
private readonly struct CurveParams
|
||||
{
|
||||
public readonly Entity Entity;
|
||||
public readonly double Cx, Cy, Radius;
|
||||
public readonly double Cx,
|
||||
Cy,
|
||||
Radius;
|
||||
|
||||
public CurveParams(Entity entity, double cx, double cy, double radius)
|
||||
{
|
||||
@@ -239,7 +340,9 @@ namespace OpenNest.Engine.BestFit
|
||||
for (var i = 0; i < entities.Count; i++)
|
||||
{
|
||||
if (entities[i] is Circle circle)
|
||||
curves.Add(new CurveParams(circle, circle.Center.X, circle.Center.Y, circle.Radius));
|
||||
curves.Add(
|
||||
new CurveParams(circle, circle.Center.X, circle.Center.Y, circle.Radius)
|
||||
);
|
||||
else if (entities[i] is Arc arc)
|
||||
curves.Add(new CurveParams(arc, arc.Center.X, arc.Center.Y, arc.Radius));
|
||||
}
|
||||
@@ -247,36 +350,56 @@ namespace OpenNest.Engine.BestFit
|
||||
}
|
||||
|
||||
private static double RayEntityDistance(
|
||||
double vx, double vy, Entity entity,
|
||||
double entityOffsetX, double entityOffsetY,
|
||||
double dirX, double dirY)
|
||||
double vx,
|
||||
double vy,
|
||||
Entity entity,
|
||||
double entityOffsetX,
|
||||
double entityOffsetY,
|
||||
double dirX,
|
||||
double dirY
|
||||
)
|
||||
{
|
||||
if (entity is Line line)
|
||||
{
|
||||
return SpatialQuery.RayEdgeDistance(
|
||||
vx, vy,
|
||||
line.StartPoint.X + entityOffsetX, line.StartPoint.Y + entityOffsetY,
|
||||
line.EndPoint.X + entityOffsetX, line.EndPoint.Y + entityOffsetY,
|
||||
dirX, dirY);
|
||||
vx,
|
||||
vy,
|
||||
line.StartPoint.X + entityOffsetX,
|
||||
line.StartPoint.Y + entityOffsetY,
|
||||
line.EndPoint.X + entityOffsetX,
|
||||
line.EndPoint.Y + entityOffsetY,
|
||||
dirX,
|
||||
dirY
|
||||
);
|
||||
}
|
||||
|
||||
if (entity is Arc arc)
|
||||
{
|
||||
return SpatialQuery.RayArcDistance(
|
||||
vx, vy,
|
||||
arc.Center.X + entityOffsetX, arc.Center.Y + entityOffsetY,
|
||||
vx,
|
||||
vy,
|
||||
arc.Center.X + entityOffsetX,
|
||||
arc.Center.Y + entityOffsetY,
|
||||
arc.Radius,
|
||||
arc.StartAngle, arc.EndAngle, arc.IsReversed,
|
||||
dirX, dirY);
|
||||
arc.StartAngle,
|
||||
arc.EndAngle,
|
||||
arc.IsReversed,
|
||||
dirX,
|
||||
dirY
|
||||
);
|
||||
}
|
||||
|
||||
if (entity is Circle circle)
|
||||
{
|
||||
return SpatialQuery.RayCircleDistance(
|
||||
vx, vy,
|
||||
circle.Center.X + entityOffsetX, circle.Center.Y + entityOffsetY,
|
||||
vx,
|
||||
vy,
|
||||
circle.Center.X + entityOffsetX,
|
||||
circle.Center.Y + entityOffsetY,
|
||||
circle.Radius,
|
||||
dirX, dirY);
|
||||
dirX,
|
||||
dirY
|
||||
);
|
||||
}
|
||||
|
||||
return double.MaxValue;
|
||||
@@ -352,7 +475,11 @@ namespace OpenNest.Engine.BestFit
|
||||
}
|
||||
|
||||
private static Vector[] FilterVerticesByProjection(
|
||||
Vector[] vertices, double dirX, double dirY, bool keepHigh)
|
||||
Vector[] vertices,
|
||||
double dirX,
|
||||
double dirY,
|
||||
bool keepHigh
|
||||
)
|
||||
{
|
||||
if (vertices.Length == 0)
|
||||
return vertices;
|
||||
@@ -364,8 +491,10 @@ namespace OpenNest.Engine.BestFit
|
||||
for (var i = 0; i < vertices.Length; i++)
|
||||
{
|
||||
projections[i] = vertices[i].X * dirX + vertices[i].Y * dirY;
|
||||
if (projections[i] < min) min = projections[i];
|
||||
if (projections[i] > max) max = projections[i];
|
||||
if (projections[i] < min)
|
||||
min = projections[i];
|
||||
if (projections[i] > max)
|
||||
max = projections[i];
|
||||
}
|
||||
|
||||
var midpoint = (min + max) / 2;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest.Engine.BestFit
|
||||
{
|
||||
@@ -15,7 +15,8 @@ namespace OpenNest.Engine.BestFit
|
||||
public double[] ComputeDistances(
|
||||
List<Line> stationaryLines,
|
||||
List<Line> movingTemplateLines,
|
||||
SlideOffset[] offsets)
|
||||
SlideOffset[] offsets
|
||||
)
|
||||
{
|
||||
var stationarySegments = SpatialQuery.FlattenLines(stationaryLines);
|
||||
var movingSegments = SpatialQuery.FlattenLines(movingTemplateLines);
|
||||
@@ -31,15 +32,21 @@ namespace OpenNest.Engine.BestFit
|
||||
}
|
||||
|
||||
return _slideComputer.ComputeBatchMultiDir(
|
||||
stationarySegments, stationaryLines.Count,
|
||||
movingSegments, movingTemplateLines.Count,
|
||||
flatOffsets, count, directions);
|
||||
stationarySegments,
|
||||
stationaryLines.Count,
|
||||
movingSegments,
|
||||
movingTemplateLines.Count,
|
||||
flatOffsets,
|
||||
count,
|
||||
directions
|
||||
);
|
||||
}
|
||||
|
||||
public double[] ComputeDistances(
|
||||
List<Entity> stationaryEntities,
|
||||
List<Entity> movingEntities,
|
||||
SlideOffset[] offsets)
|
||||
SlideOffset[] offsets
|
||||
)
|
||||
{
|
||||
// GPU path doesn't support native entities yet — fall back to CPU.
|
||||
var cpu = new CpuDistanceComputer();
|
||||
@@ -52,9 +59,12 @@ namespace OpenNest.Engine.BestFit
|
||||
/// </summary>
|
||||
private static int DirectionVectorToInt(double dirX, double dirY)
|
||||
{
|
||||
if (dirX < -0.5) return (int)PushDirection.Left;
|
||||
if (dirX > 0.5) return (int)PushDirection.Right;
|
||||
if (dirY < -0.5) return (int)PushDirection.Down;
|
||||
if (dirX < -0.5)
|
||||
return (int)PushDirection.Left;
|
||||
if (dirX > 0.5)
|
||||
return (int)PushDirection.Right;
|
||||
if (dirY < -0.5)
|
||||
return (int)PushDirection.Down;
|
||||
return (int)PushDirection.Up;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest.Engine.BestFit
|
||||
{
|
||||
@@ -8,11 +8,13 @@ namespace OpenNest.Engine.BestFit
|
||||
double[] ComputeDistances(
|
||||
List<Line> stationaryLines,
|
||||
List<Line> movingTemplateLines,
|
||||
SlideOffset[] offsets);
|
||||
SlideOffset[] offsets
|
||||
);
|
||||
|
||||
double[] ComputeDistances(
|
||||
List<Entity> stationaryEntities,
|
||||
List<Entity> movingEntities,
|
||||
SlideOffset[] offsets);
|
||||
SlideOffset[] offsets
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,19 +20,27 @@ namespace OpenNest.Engine.BestFit
|
||||
/// <param name="direction">Push direction.</param>
|
||||
/// <returns>Array of minimum distances, one per offset position.</returns>
|
||||
double[] ComputeBatch(
|
||||
double[] stationarySegments, int stationaryCount,
|
||||
double[] movingTemplateSegments, int movingCount,
|
||||
double[] offsets, int offsetCount,
|
||||
PushDirection direction);
|
||||
double[] stationarySegments,
|
||||
int stationaryCount,
|
||||
double[] movingTemplateSegments,
|
||||
int movingCount,
|
||||
double[] offsets,
|
||||
int offsetCount,
|
||||
PushDirection direction
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Computes minimum directional distance for offsets with per-offset directions.
|
||||
/// Uploads segment data once for all offsets, reducing GPU round-trips.
|
||||
/// </summary>
|
||||
double[] ComputeBatchMultiDir(
|
||||
double[] stationarySegments, int stationaryCount,
|
||||
double[] movingTemplateSegments, int movingCount,
|
||||
double[] offsets, int offsetCount,
|
||||
int[] directions);
|
||||
double[] stationarySegments,
|
||||
int stationaryCount,
|
||||
double[] movingTemplateSegments,
|
||||
int movingCount,
|
||||
double[] offsets,
|
||||
int offsetCount,
|
||||
int[] directions
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest.Engine.BestFit
|
||||
{
|
||||
@@ -10,8 +10,14 @@ namespace OpenNest.Engine.BestFit
|
||||
private readonly Polygon _stationaryHull;
|
||||
private readonly Vector _correction;
|
||||
|
||||
public NfpSlideStrategy(double part2Rotation, int type, string description,
|
||||
Polygon stationaryPerimeter, Polygon stationaryHull, Vector correction)
|
||||
public NfpSlideStrategy(
|
||||
double part2Rotation,
|
||||
int type,
|
||||
string description,
|
||||
Polygon stationaryPerimeter,
|
||||
Polygon stationaryHull,
|
||||
Vector correction
|
||||
)
|
||||
{
|
||||
_part2Rotation = part2Rotation;
|
||||
StrategyIndex = type;
|
||||
@@ -28,8 +34,13 @@ namespace OpenNest.Engine.BestFit
|
||||
/// Creates an NfpSlideStrategy by extracting polygon data from a drawing.
|
||||
/// Returns null if the drawing has no valid perimeter.
|
||||
/// </summary>
|
||||
public static NfpSlideStrategy Create(Drawing drawing, double part2Rotation,
|
||||
int type, string description, double spacing)
|
||||
public static NfpSlideStrategy Create(
|
||||
Drawing drawing,
|
||||
double part2Rotation,
|
||||
int type,
|
||||
string description,
|
||||
double spacing
|
||||
)
|
||||
{
|
||||
var result = PolygonHelper.ExtractPerimeterPolygon(drawing, spacing / 2);
|
||||
|
||||
@@ -38,18 +49,32 @@ namespace OpenNest.Engine.BestFit
|
||||
|
||||
var hull = ConvexHull.Compute(result.Polygon.Vertices);
|
||||
|
||||
return new NfpSlideStrategy(part2Rotation, type, description,
|
||||
result.Polygon, hull, result.Correction);
|
||||
return new NfpSlideStrategy(
|
||||
part2Rotation,
|
||||
type,
|
||||
description,
|
||||
result.Polygon,
|
||||
hull,
|
||||
result.Correction
|
||||
);
|
||||
}
|
||||
|
||||
public List<PairCandidate> GenerateCandidates(Drawing drawing, double spacing, double stepSize)
|
||||
public List<PairCandidate> GenerateCandidates(
|
||||
Drawing drawing,
|
||||
double spacing,
|
||||
double stepSize
|
||||
)
|
||||
{
|
||||
var candidates = new List<PairCandidate>();
|
||||
|
||||
if (stepSize <= 0)
|
||||
return candidates;
|
||||
|
||||
var orbitingPerimeter = PolygonHelper.RotatePolygon(_stationaryPerimeter, _part2Rotation, reNormalize: true);
|
||||
var orbitingPerimeter = PolygonHelper.RotatePolygon(
|
||||
_stationaryPerimeter,
|
||||
_part2Rotation,
|
||||
reNormalize: true
|
||||
);
|
||||
var orbitingPoly = ConvexHull.Compute(orbitingPerimeter.Vertices);
|
||||
|
||||
var nfp = NoFitPolygon.ComputeConvex(_stationaryHull, orbitingPoly);
|
||||
@@ -79,9 +104,7 @@ namespace OpenNest.Engine.BestFit
|
||||
for (var s = 1; s < steps; s++)
|
||||
{
|
||||
var t = (double)s / steps;
|
||||
var sample = new Vector(
|
||||
verts[i].X + dx * t,
|
||||
verts[i].Y + dy * t);
|
||||
var sample = new Vector(verts[i].X + dx * t, verts[i].Y + dy * t);
|
||||
var sampleOffset = ApplyCorrection(sample, _correction);
|
||||
candidates.Add(MakeCandidate(drawing, sampleOffset, spacing, testNumber++));
|
||||
}
|
||||
@@ -96,7 +119,12 @@ namespace OpenNest.Engine.BestFit
|
||||
return new Vector(nfpVertex.X - correction.X, nfpVertex.Y - correction.Y);
|
||||
}
|
||||
|
||||
private PairCandidate MakeCandidate(Drawing drawing, Vector offset, double spacing, int testNumber)
|
||||
private PairCandidate MakeCandidate(
|
||||
Drawing drawing,
|
||||
Vector offset,
|
||||
double spacing,
|
||||
int testNumber
|
||||
)
|
||||
{
|
||||
return new PairCandidate
|
||||
{
|
||||
@@ -106,7 +134,7 @@ namespace OpenNest.Engine.BestFit
|
||||
Part2Offset = offset,
|
||||
StrategyIndex = StrategyIndex,
|
||||
TestNumber = testNumber,
|
||||
Spacing = spacing
|
||||
Spacing = spacing,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
using OpenNest.Converters;
|
||||
using OpenNest.Engine.Fill;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using OpenNest.Converters;
|
||||
using OpenNest.Engine.Fill;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.Engine.BestFit
|
||||
{
|
||||
@@ -24,10 +24,13 @@ namespace OpenNest.Engine.BestFit
|
||||
|
||||
var resultBag = new ConcurrentBag<BestFitResult>();
|
||||
|
||||
Parallel.ForEach(candidates, c =>
|
||||
{
|
||||
resultBag.Add(Evaluate(c, perimeterDrawing));
|
||||
});
|
||||
Parallel.ForEach(
|
||||
candidates,
|
||||
c =>
|
||||
{
|
||||
resultBag.Add(Evaluate(c, perimeterDrawing));
|
||||
}
|
||||
);
|
||||
|
||||
return resultBag.ToList();
|
||||
}
|
||||
@@ -56,7 +59,10 @@ namespace OpenNest.Engine.BestFit
|
||||
allPoints.AddRange(GetPartVertices(part2));
|
||||
|
||||
// Find optimal bounding rectangle via rotating calipers
|
||||
double bestArea, bestWidth, bestHeight, bestRotation;
|
||||
double bestArea,
|
||||
bestWidth,
|
||||
bestHeight,
|
||||
bestRotation;
|
||||
List<double> hullAngles = null;
|
||||
|
||||
if (allPoints.Count >= 3)
|
||||
@@ -71,7 +77,9 @@ namespace OpenNest.Engine.BestFit
|
||||
}
|
||||
else
|
||||
{
|
||||
var combinedBox = ((IEnumerable<IBoundable>)new IBoundable[] { part1, part2 }).GetBoundingBox();
|
||||
var combinedBox = (
|
||||
(IEnumerable<IBoundable>)new IBoundable[] { part1, part2 }
|
||||
).GetBoundingBox();
|
||||
bestArea = combinedBox.Area();
|
||||
bestWidth = combinedBox.Width;
|
||||
bestHeight = combinedBox.Length;
|
||||
@@ -100,14 +108,16 @@ namespace OpenNest.Engine.BestFit
|
||||
TrueArea = trueArea,
|
||||
HullAngles = hullAngles,
|
||||
Keep = !overlaps,
|
||||
Reason = overlaps ? "Overlap detected" : "Valid"
|
||||
Reason = overlaps ? "Overlap detected" : "Valid",
|
||||
};
|
||||
}
|
||||
|
||||
private static Drawing CreatePerimeterDrawing(Drawing source)
|
||||
{
|
||||
var entities = ConvertProgram.ToGeometry(source.Program)
|
||||
.Where(e => e.Layer != SpecialLayers.Rapid).ToList();
|
||||
var entities = ConvertProgram
|
||||
.ToGeometry(source.Program)
|
||||
.Where(e => e.Layer != SpecialLayers.Rapid)
|
||||
.ToList();
|
||||
var profile = new ShapeProfile(entities);
|
||||
var program = ConvertGeometry.ToProgram(profile.Perimeter);
|
||||
return new Drawing(source.Name, program);
|
||||
@@ -115,18 +125,23 @@ namespace OpenNest.Engine.BestFit
|
||||
|
||||
private static Shape GetPerimeterShape(Part part)
|
||||
{
|
||||
var entities = ConvertProgram.ToGeometry(part.Program)
|
||||
.Where(e => e.Layer != SpecialLayers.Rapid).ToList();
|
||||
var entities = ConvertProgram
|
||||
.ToGeometry(part.Program)
|
||||
.Where(e => e.Layer != SpecialLayers.Rapid)
|
||||
.ToList();
|
||||
var shapes = ShapeBuilder.GetShapes(entities);
|
||||
if (shapes.Count == 0) return null;
|
||||
if (shapes.Count == 0)
|
||||
return null;
|
||||
shapes[0].Offset(part.Location);
|
||||
return shapes[0];
|
||||
}
|
||||
|
||||
private static List<Vector> GetPartVertices(Part part)
|
||||
{
|
||||
var entities = ConvertProgram.ToGeometry(part.Program)
|
||||
.Where(e => e.Layer != SpecialLayers.Rapid).ToList();
|
||||
var entities = ConvertProgram
|
||||
.ToGeometry(part.Program)
|
||||
.Where(e => e.Layer != SpecialLayers.Rapid)
|
||||
.ToList();
|
||||
var shapes = ShapeBuilder.GetShapes(entities);
|
||||
var points = new List<Vector>();
|
||||
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
using System.Linq;
|
||||
using OpenNest.Converters;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenNest.Engine.BestFit
|
||||
{
|
||||
public static class PolygonHelper
|
||||
{
|
||||
public static PolygonExtractionResult ExtractPerimeterPolygon(Drawing drawing, double halfSpacing)
|
||||
public static PolygonExtractionResult ExtractPerimeterPolygon(
|
||||
Drawing drawing,
|
||||
double halfSpacing
|
||||
)
|
||||
{
|
||||
var entities = ConvertProgram.ToGeometry(drawing.Program)
|
||||
var entities = ConvertProgram
|
||||
.ToGeometry(drawing.Program)
|
||||
.Where(e => e.Layer != SpecialLayers.Rapid)
|
||||
.ToList();
|
||||
|
||||
@@ -25,9 +29,8 @@ namespace OpenNest.Engine.BestFit
|
||||
// Ensure CW winding for correct outward offset direction.
|
||||
definedShape.NormalizeWinding();
|
||||
|
||||
var inflated = halfSpacing > 0
|
||||
? (perimeter.OffsetOutward(halfSpacing) ?? perimeter)
|
||||
: perimeter;
|
||||
var inflated =
|
||||
halfSpacing > 0 ? (perimeter.OffsetOutward(halfSpacing) ?? perimeter) : perimeter;
|
||||
|
||||
// Convert to polygon with circumscribed arcs for tight nesting.
|
||||
var polygon = inflated.ToPolygonWithTolerance(0.01, circumscribe: true);
|
||||
@@ -57,9 +60,7 @@ namespace OpenNest.Engine.BestFit
|
||||
|
||||
foreach (var v in polygon.Vertices)
|
||||
{
|
||||
result.Vertices.Add(new Vector(
|
||||
v.X * cos - v.Y * sin,
|
||||
v.X * sin + v.Y * cos));
|
||||
result.Vertices.Add(new Vector(v.X * cos - v.Y * sin, v.X * sin + v.Y * cos));
|
||||
}
|
||||
|
||||
if (reNormalize)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest.Engine.BestFit
|
||||
{
|
||||
@@ -9,14 +9,18 @@ namespace OpenNest.Engine.BestFit
|
||||
|
||||
private static readonly (double DirX, double DirY)[] PushDirections =
|
||||
{
|
||||
(-1, 0), // Left
|
||||
(0, -1), // Down
|
||||
(1, 0), // Right
|
||||
(0, 1) // Up
|
||||
(-1, 0), // Left
|
||||
(0, -1), // Down
|
||||
(1, 0), // Right
|
||||
(0, 1), // Up
|
||||
};
|
||||
|
||||
public RotationSlideStrategy(double part2Rotation, int strategyIndex, string description,
|
||||
IDistanceComputer distanceComputer)
|
||||
public RotationSlideStrategy(
|
||||
double part2Rotation,
|
||||
int strategyIndex,
|
||||
string description,
|
||||
IDistanceComputer distanceComputer
|
||||
)
|
||||
{
|
||||
Part2Rotation = part2Rotation;
|
||||
StrategyIndex = strategyIndex;
|
||||
@@ -28,7 +32,11 @@ namespace OpenNest.Engine.BestFit
|
||||
public int StrategyIndex { get; }
|
||||
public string Description { get; }
|
||||
|
||||
public List<PairCandidate> GenerateCandidates(Drawing drawing, double spacing, double stepSize)
|
||||
public List<PairCandidate> GenerateCandidates(
|
||||
Drawing drawing,
|
||||
double spacing,
|
||||
double stepSize
|
||||
)
|
||||
{
|
||||
var candidates = new List<PairCandidate>();
|
||||
|
||||
@@ -48,7 +56,10 @@ namespace OpenNest.Engine.BestFit
|
||||
return candidates;
|
||||
|
||||
var distances = _distanceComputer.ComputeDistances(
|
||||
part1Entities, part2Entities, offsets);
|
||||
part1Entities,
|
||||
part2Entities,
|
||||
offsets
|
||||
);
|
||||
|
||||
var testNumber = 0;
|
||||
|
||||
@@ -60,24 +71,32 @@ namespace OpenNest.Engine.BestFit
|
||||
|
||||
var finalPosition = new Vector(
|
||||
part2Template.Location.X + offsets[i].Dx + offsets[i].DirX * slideDist,
|
||||
part2Template.Location.Y + offsets[i].Dy + offsets[i].DirY * slideDist);
|
||||
part2Template.Location.Y + offsets[i].Dy + offsets[i].DirY * slideDist
|
||||
);
|
||||
|
||||
candidates.Add(new PairCandidate
|
||||
{
|
||||
Drawing = drawing,
|
||||
Part1Rotation = 0,
|
||||
Part2Rotation = Part2Rotation,
|
||||
Part2Offset = finalPosition,
|
||||
StrategyIndex = StrategyIndex,
|
||||
TestNumber = testNumber++,
|
||||
Spacing = spacing
|
||||
});
|
||||
candidates.Add(
|
||||
new PairCandidate
|
||||
{
|
||||
Drawing = drawing,
|
||||
Part1Rotation = 0,
|
||||
Part2Rotation = Part2Rotation,
|
||||
Part2Offset = finalPosition,
|
||||
StrategyIndex = StrategyIndex,
|
||||
TestNumber = testNumber++,
|
||||
Spacing = spacing,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return candidates;
|
||||
}
|
||||
|
||||
private static SlideOffset[] BuildOffsets(Box bbox1, Box bbox2, double spacing, double stepSize)
|
||||
private static SlideOffset[] BuildOffsets(
|
||||
Box bbox1,
|
||||
Box bbox2,
|
||||
double spacing,
|
||||
double stepSize
|
||||
)
|
||||
{
|
||||
var offsets = new List<SlideOffset>();
|
||||
|
||||
@@ -85,7 +104,9 @@ namespace OpenNest.Engine.BestFit
|
||||
{
|
||||
var isHorizontalPush = System.Math.Abs(dirX) > System.Math.Abs(dirY);
|
||||
|
||||
double perpMin, perpMax, pushStartOffset;
|
||||
double perpMin,
|
||||
perpMax,
|
||||
pushStartOffset;
|
||||
|
||||
if (isHorizontalPush)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.Engine.BestFit.Tiling
|
||||
{
|
||||
@@ -16,7 +16,12 @@ namespace OpenNest.Engine.BestFit.Tiling
|
||||
return result1.PartsNested >= result2.PartsNested ? result1 : result2;
|
||||
}
|
||||
|
||||
private TileResult TryTile(BestFitResult bestFit, double plateWidth, double plateHeight, bool rotatePair)
|
||||
private TileResult TryTile(
|
||||
BestFitResult bestFit,
|
||||
double plateWidth,
|
||||
double plateHeight,
|
||||
bool rotatePair
|
||||
)
|
||||
{
|
||||
var pairWidth = rotatePair ? bestFit.BoundingHeight : bestFit.BoundingWidth;
|
||||
var pairHeight = rotatePair ? bestFit.BoundingWidth : bestFit.BoundingHeight;
|
||||
@@ -36,13 +41,16 @@ namespace OpenNest.Engine.BestFit.Tiling
|
||||
{
|
||||
for (var col = 0; col < cols; col++)
|
||||
{
|
||||
placements.Add(new PairPlacement
|
||||
{
|
||||
Position = new Vector(
|
||||
col * (pairWidth + spacing),
|
||||
row * (pairHeight + spacing)),
|
||||
PairRotation = rotatePair ? Angle.HalfPI : 0
|
||||
});
|
||||
placements.Add(
|
||||
new PairPlacement
|
||||
{
|
||||
Position = new Vector(
|
||||
col * (pairWidth + spacing),
|
||||
row * (pairHeight + spacing)
|
||||
),
|
||||
PairRotation = rotatePair ? Angle.HalfPI : 0,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +63,7 @@ namespace OpenNest.Engine.BestFit.Tiling
|
||||
Columns = cols,
|
||||
Utilization = plateArea > 0 ? usedArea / plateArea : 0,
|
||||
Placements = placements,
|
||||
PairRotated = rotatePair
|
||||
PairRotated = rotatePair,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest.Engine.BestFit.Tiling
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user