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:
aj
2026-09-20 16:41:50 -04:00
parent 8e6fa677fb
commit aec0523062
476 changed files with 16592 additions and 7800 deletions
+71 -35
View File
@@ -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);
+13 -4
View File
@@ -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;
}
+56 -36
View File
@@ -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;
}
+13 -8
View File
@@ -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,
}
}
+286 -157
View File
@@ -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;
+19 -9
View File
@@ -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;
}
}
+5 -3
View File
@@ -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
);
}
}
+16 -8
View File
@@ -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
);
}
}
+42 -14
View File
@@ -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,
};
}
}
+33 -18
View File
@@ -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>();
+10 -9
View File
@@ -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)
{
+18 -10
View File
@@ -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 -1
View File
@@ -1,5 +1,5 @@
using OpenNest.Geometry;
using System.Collections.Generic;
using OpenNest.Geometry;
namespace OpenNest.Engine.BestFit.Tiling
{