Files
OpenNest/OpenNest.Core/Geometry/GeometryOptimizer.cs
T
ajandClaude Opus 5.5 b550640ef0 refactor: merge duplicated geometry, settings-file and form helpers
Roslyn Bridge flagged these as copy-paste clusters; each pair now shares
one implementation so a fix lands everywhere at once:

- Shape.OffsetInward/OffsetOutward differed only in the target winding.
- BestFitFinder kept its own copy of SpatialQuery's arc-extreme points.
- SplitDrawingForm rebuilt DrawingSplitter's clip-region grid for preview.
- GeometryOptimizer's collinear/coradial finders (one misspelled
  GetCoradialArs) become one generic FindRelated.
- Five settings files repeated the write-with-retry loop, and two also
  repeated the unit parser and unreadable-file filter (SettingsFile).
- ClampsForm and SupportGridForm repeated their row/box builders.
- Vertical/HorizontalRemnantComparer share a RemnantComparer base.

Behavior is unchanged except that Engine/NestStorage settings loads now
also tolerate NotSupported/Security exceptions, matching the other
settings files' never-throw contract.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 12:59:47 -04:00

255 lines
8.0 KiB
C#

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using OpenNest.Math;
namespace OpenNest.Geometry
{
public static class GeometryOptimizer
{
public static void Optimize(IList<Arc> arcs) =>
MergePass(
arcs,
(a, b) => a.IsCoradialTo(b),
(Arc a, Arc b, out Arc joined) => TryJoinArcs(a, b, out joined)
);
public static void Optimize(IList<Line> lines) =>
MergePass(
lines,
(a, b) => a.IsCollinearTo(b),
(Line a, Line b, out Line joined) => TryJoinLines(a, b, out joined)
);
public static void Deduplicate(IList<Circle> circles)
{
for (var i = circles.Count - 1; i >= 1; i--)
{
for (var j = i - 1; j >= 0; j--)
{
if (
circles[i].Center.DistanceTo(circles[j].Center) <= Tolerance.Epsilon
&& circles[i].Radius.IsEqualTo(circles[j].Radius)
)
{
circles.RemoveAt(i);
break;
}
}
}
}
public static void Deduplicate(IList<Circle> circles, IList<Arc> arcs)
{
for (var i = circles.Count - 1; i >= 0; i--)
{
for (var j = arcs.Count - 1; j >= 0; j--)
{
if (
arcs[j].Center.DistanceTo(circles[i].Center) <= Tolerance.Epsilon
&& arcs[j].Radius.IsEqualTo(circles[i].Radius)
&& arcs[j].IsFullCircle()
)
{
arcs.RemoveAt(j);
}
}
}
}
private delegate bool TryJoin<T>(T a, T b, out T joined);
private static void MergePass<T>(
IList<T> items,
Func<T, T, bool> related,
TryJoin<T> tryJoin
)
where T : class
{
for (var i = 0; i < items.Count; ++i)
{
var item = items[i];
var candidates = FindRelated(items, item, i, related);
var index = 0;
while (index < candidates.Count)
{
var candidate = candidates[index];
if (!tryJoin(item, candidate, out var joined))
{
index++;
continue;
}
candidates.Remove(candidate);
items.Remove(candidate);
item = joined;
index = 0;
}
items[i] = item;
}
}
public static bool TryJoinLines(Line line1, Line line2, out Line lineOut)
{
lineOut = null;
if (line1 == line2)
return false;
if (line1.Layer?.Name != line2.Layer?.Name)
return false;
if (!line1.IsCollinearTo(line2))
return false;
bool onPoint = false;
if (line1.StartPoint == line2.StartPoint)
onPoint = true;
else if (line1.StartPoint == line2.EndPoint)
onPoint = true;
else if (line1.EndPoint == line2.StartPoint)
onPoint = true;
else if (line1.EndPoint == line2.EndPoint)
onPoint = true;
var t1 = line1.StartPoint.Y > line1.EndPoint.Y ? line1.StartPoint.Y : line1.EndPoint.Y;
var t2 = line2.StartPoint.Y > line2.EndPoint.Y ? line2.StartPoint.Y : line2.EndPoint.Y;
var b1 = line1.StartPoint.Y < line1.EndPoint.Y ? line1.StartPoint.Y : line1.EndPoint.Y;
var b2 = line2.StartPoint.Y < line2.EndPoint.Y ? line2.StartPoint.Y : line2.EndPoint.Y;
var l1 = line1.StartPoint.X < line1.EndPoint.X ? line1.StartPoint.X : line1.EndPoint.X;
var l2 = line2.StartPoint.X < line2.EndPoint.X ? line2.StartPoint.X : line2.EndPoint.X;
var r1 = line1.StartPoint.X > line1.EndPoint.X ? line1.StartPoint.X : line1.EndPoint.X;
var r2 = line2.StartPoint.X > line2.EndPoint.X ? line2.StartPoint.X : line2.EndPoint.X;
if (!onPoint)
{
if (t1 < b2 - Tolerance.Epsilon)
return false;
if (b1 > t2 + Tolerance.Epsilon)
return false;
if (l1 > r2 + Tolerance.Epsilon)
return false;
if (r1 < l2 - Tolerance.Epsilon)
return false;
}
var l = l1 < l2 ? l1 : l2;
var r = r1 > r2 ? r1 : r2;
var t = t1 > t2 ? t1 : t2;
var b = b1 < b2 ? b1 : b2;
if (!line1.IsVertical() && line1.Slope() < 0)
lineOut = new Line(new Vector(l, t), new Vector(r, b))
{
Layer = line1.Layer,
Color = line1.Color,
};
else
lineOut = new Line(new Vector(l, b), new Vector(r, t))
{
Layer = line1.Layer,
Color = line1.Color,
};
return true;
}
public static bool TryJoinArcs(Arc arc1, Arc arc2, out Arc arcOut)
{
arcOut = null;
if (arc1 == arc2)
return false;
if (arc1.Layer?.Name != arc2.Layer?.Name)
return false;
if (arc1.Center != arc2.Center)
return false;
if (!arc1.Radius.IsEqualTo(arc2.Radius))
return false;
var start1 = arc1.StartAngle;
var end1 = arc1.EndAngle;
var start2 = arc2.StartAngle;
var end2 = arc2.EndAngle;
if (start1 > end1)
start1 -= Angle.TwoPI;
if (start2 > end2)
start2 -= Angle.TwoPI;
// Check that arcs are adjacent (endpoints touch), not overlapping
var touch1 = end1.IsEqualTo(start2) || (end1 + Angle.TwoPI).IsEqualTo(start2);
var touch2 = end2.IsEqualTo(start1) || (end2 + Angle.TwoPI).IsEqualTo(start1);
if (!touch1 && !touch2)
return false;
var startAngle = start1 < start2 ? start1 : start2;
var endAngle = end1 > end2 ? end1 : end2;
// Don't merge if the result would be a full circle (start == end)
var sweep = endAngle - startAngle;
if (sweep >= Angle.TwoPI - Tolerance.Epsilon)
return false;
if (startAngle < 0)
startAngle += Angle.TwoPI;
if (endAngle < 0)
endAngle += Angle.TwoPI;
arcOut = new Arc(arc1.Center, arc1.Radius, startAngle, endAngle)
{
Layer = arc1.Layer,
Color = arc1.Color,
};
return true;
}
/// <summary>
/// The items from <paramref name="startIndex"/> on, other than <paramref name="item"/>
/// itself, that are <paramref name="related"/> to it.
/// </summary>
private static List<T> FindRelated<T>(
IList<T> items,
T item,
int startIndex,
Func<T, T, bool> related
)
where T : class
{
var matches = new List<T>();
Parallel.For(
startIndex,
items.Count,
index =>
{
var candidate = items[index];
if (Object.ReferenceEquals(item, candidate))
return;
if (!related(item, candidate))
return;
lock (matches)
{
matches.Add(candidate);
}
}
);
return matches;
}
}
}