feat: add IBendDetector interface, SolidWorks implementation, and registry
Introduces a pluggable bend detection system in OpenNest.IO.Bending: - IBendDetector takes CadDocument directly to preserve MText/layer/linetype info - SolidWorksBendDetector finds lines on BEND layer with CENTER linetype and matches nearby MText notes - BendDetectorRegistry auto-registers SolidWorks detector and supports AutoDetect Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
40
OpenNest.IO/Bending/BendDetectorRegistry.cs
Normal file
40
OpenNest.IO/Bending/BendDetectorRegistry.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using ACadSharp;
|
||||
using OpenNest.Bending;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenNest.IO.Bending
|
||||
{
|
||||
public static class BendDetectorRegistry
|
||||
{
|
||||
private static readonly List<IBendDetector> detectors = new();
|
||||
|
||||
static BendDetectorRegistry()
|
||||
{
|
||||
Register(new SolidWorksBendDetector());
|
||||
}
|
||||
|
||||
public static void Register(IBendDetector detector)
|
||||
{
|
||||
detectors.Add(detector);
|
||||
}
|
||||
|
||||
public static IReadOnlyList<IBendDetector> Detectors => detectors;
|
||||
|
||||
public static IBendDetector GetByName(string name)
|
||||
{
|
||||
return detectors.FirstOrDefault(d => d.Name == name);
|
||||
}
|
||||
|
||||
public static List<Bend> AutoDetect(CadDocument document)
|
||||
{
|
||||
foreach (var detector in detectors)
|
||||
{
|
||||
var bends = detector.DetectBends(document);
|
||||
if (bends.Count > 0)
|
||||
return bends;
|
||||
}
|
||||
return new List<Bend>();
|
||||
}
|
||||
}
|
||||
}
|
||||
12
OpenNest.IO/Bending/IBendDetector.cs
Normal file
12
OpenNest.IO/Bending/IBendDetector.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using ACadSharp;
|
||||
using OpenNest.Bending;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.IO.Bending
|
||||
{
|
||||
public interface IBendDetector
|
||||
{
|
||||
string Name { get; }
|
||||
List<Bend> DetectBends(CadDocument document);
|
||||
}
|
||||
}
|
||||
139
OpenNest.IO/Bending/SolidWorksBendDetector.cs
Normal file
139
OpenNest.IO/Bending/SolidWorksBendDetector.cs
Normal file
@@ -0,0 +1,139 @@
|
||||
using ACadSharp;
|
||||
using ACadSharp.Entities;
|
||||
using OpenNest.Bending;
|
||||
using OpenNest.Geometry;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace OpenNest.IO.Bending
|
||||
{
|
||||
public class SolidWorksBendDetector : IBendDetector
|
||||
{
|
||||
public string Name => "SolidWorks";
|
||||
|
||||
public double MaxBendRadius { get; set; } = 4.0;
|
||||
|
||||
private static readonly Regex BendNoteRegex = new Regex(
|
||||
@"\b(?<direction>UP|DOWN|DN)\s+(?<angle>\d+(\.\d+)?)°?\s*R\s*(?<radius>\d+(\.\d+)?)\b",
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
public List<Bend> DetectBends(CadDocument document)
|
||||
{
|
||||
var bendLines = FindBendLines(document);
|
||||
var bendNotes = FindBendNotes(document);
|
||||
|
||||
if (bendLines.Count == 0)
|
||||
return new List<Bend>();
|
||||
|
||||
var bends = new List<Bend>();
|
||||
|
||||
foreach (var line in bendLines)
|
||||
{
|
||||
var start = new Vector(line.StartPoint.X, line.StartPoint.Y);
|
||||
var end = new Vector(line.EndPoint.X, line.EndPoint.Y);
|
||||
|
||||
var bend = new Bend
|
||||
{
|
||||
StartPoint = start,
|
||||
EndPoint = end,
|
||||
Direction = BendDirection.Unknown
|
||||
};
|
||||
|
||||
var note = FindClosestBendNote(line, bendNotes);
|
||||
if (note != null)
|
||||
{
|
||||
bend.Direction = GetBendDirection(note.Value);
|
||||
bend.NoteText = note.Value;
|
||||
ParseBendNote(note.Value, bend);
|
||||
}
|
||||
|
||||
if (!bend.Radius.HasValue || bend.Radius.Value <= MaxBendRadius)
|
||||
bends.Add(bend);
|
||||
}
|
||||
|
||||
return bends;
|
||||
}
|
||||
|
||||
private List<ACadSharp.Entities.Line> FindBendLines(CadDocument document)
|
||||
{
|
||||
return document.Entities
|
||||
.OfType<ACadSharp.Entities.Line>()
|
||||
.Where(l => l.Layer?.Name == "BEND"
|
||||
&& (l.LineType?.Name?.Contains("CENTER") == true
|
||||
|| l.LineType?.Name == "CENTERX2"))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private List<MText> FindBendNotes(CadDocument document)
|
||||
{
|
||||
return document.Entities
|
||||
.OfType<MText>()
|
||||
.Where(t => GetBendDirection(t.Value) != BendDirection.Unknown)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private static BendDirection GetBendDirection(string text)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text))
|
||||
return BendDirection.Unknown;
|
||||
|
||||
var upper = text.ToUpperInvariant();
|
||||
|
||||
if (upper.Contains("UP"))
|
||||
return BendDirection.Up;
|
||||
|
||||
if (upper.Contains("DOWN") || upper.Contains("DN"))
|
||||
return BendDirection.Down;
|
||||
|
||||
return BendDirection.Unknown;
|
||||
}
|
||||
|
||||
private static void ParseBendNote(string text, Bend bend)
|
||||
{
|
||||
var normalized = text.ToUpperInvariant().Replace("SHARP", "R0");
|
||||
var match = BendNoteRegex.Match(normalized);
|
||||
|
||||
if (match.Success)
|
||||
{
|
||||
if (double.TryParse(match.Groups["radius"].Value, NumberStyles.Any, CultureInfo.InvariantCulture, out var radius))
|
||||
bend.Radius = radius;
|
||||
|
||||
if (double.TryParse(match.Groups["angle"].Value, NumberStyles.Any, CultureInfo.InvariantCulture, out var angle))
|
||||
bend.Angle = angle;
|
||||
}
|
||||
}
|
||||
|
||||
private MText FindClosestBendNote(ACadSharp.Entities.Line bendLine, List<MText> notes)
|
||||
{
|
||||
if (notes.Count == 0) return null;
|
||||
|
||||
MText closest = null;
|
||||
var closestDist = double.MaxValue;
|
||||
|
||||
foreach (var note in notes)
|
||||
{
|
||||
var notePos = new Vector(note.InsertPoint.X, note.InsertPoint.Y);
|
||||
var lineStart = new Vector(bendLine.StartPoint.X, bendLine.StartPoint.Y);
|
||||
var lineEnd = new Vector(bendLine.EndPoint.X, bendLine.EndPoint.Y);
|
||||
|
||||
var geomLine = new OpenNest.Geometry.Line(lineStart, lineEnd);
|
||||
var perpPoint = geomLine.ClosestPointTo(notePos);
|
||||
var dist = notePos.DistanceTo(perpPoint);
|
||||
|
||||
var maxAcceptable = note.Height * 2.0;
|
||||
if (dist > maxAcceptable) continue;
|
||||
|
||||
if (dist < closestDist)
|
||||
{
|
||||
closestDist = dist;
|
||||
closest = note;
|
||||
}
|
||||
}
|
||||
|
||||
return closest;
|
||||
}
|
||||
}
|
||||
}
|
||||
30
OpenNest.Tests/Bending/SolidWorksBendDetectorTests.cs
Normal file
30
OpenNest.Tests/Bending/SolidWorksBendDetectorTests.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using OpenNest.Bending;
|
||||
using OpenNest.IO.Bending;
|
||||
|
||||
namespace OpenNest.Tests.Bending;
|
||||
|
||||
public class SolidWorksBendDetectorTests
|
||||
{
|
||||
[Fact]
|
||||
public void SolidWorksDetector_IsRegistered()
|
||||
{
|
||||
var detector = BendDetectorRegistry.GetByName("SolidWorks");
|
||||
Assert.NotNull(detector);
|
||||
Assert.Equal("SolidWorks", detector.Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Registry_ContainsSolidWorksDetector()
|
||||
{
|
||||
Assert.Contains(BendDetectorRegistry.Detectors,
|
||||
d => d.Name == "SolidWorks");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AutoDetect_EmptyDocument_ReturnsEmptyList()
|
||||
{
|
||||
var doc = new ACadSharp.CadDocument();
|
||||
var bends = BendDetectorRegistry.AutoDetect(doc);
|
||||
Assert.Empty(bends);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user