Files
EtchBendLines/EtchBendLines/BendLineExtractor.cs
AJ Isaacs bf36a56387 fix: detect bend lines from SolidWorks DXF exports
Three issues prevented bend line detection after the ACadSharp migration:

1. IsBendLine required layer "BEND" but SolidWorks exports bend lines
   on layer "0" with CENTERX2 linetype. Added "0" as accepted layer.

2. Bend note regex used literal ° which failed when ACadSharp reads the
   degree symbol as multi-byte "°" due to ANSI/UTF-8 encoding mismatch.
   Changed to [^R\d]* to tolerate any characters between angle and R.

3. Radius filter excluded bends with null Radius (unparsed notes).
   Nullable comparison null <= 4.0 evaluates to false in C#, filtering
   out all bends. Added null check to include unparsed bends.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 14:33:59 -05:00

201 lines
5.9 KiB
C#

using ACadSharp;
using ACadSharp.Entities;
using ACadSharp.IO;
using CSMath;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
namespace EtchBendLines
{
public class BendLineExtractor
{
public BendLineExtractor(string dxfFile)
{
using var reader = new DxfReader(dxfFile);
Document = reader.Read();
}
public BendLineExtractor(CadDocument document)
{
Document = document;
}
/// <summary>
/// Maximum bend radius to be considered. Anything beyond this number
/// is a center line for rolling.
/// </summary>
public double MaxBendRadius { get; set; } = 4;
public double SharpRadius { get; set; } = 0.001;
public bool ReplaceSharpRadius { get; set; } = true;
/// <summary>
/// The regular expression pattern the bend note must match
/// </summary>
static readonly Regex bendNoteRegex = new Regex(
@"\b(?<direction>UP|DOWN|DN)\s+(?<angle>\d+(\.\d+)?)[^R\d]*R\s*(?<radius>\d+(\.\d+)?)\b",
RegexOptions.Compiled | RegexOptions.IgnoreCase
);
public CadDocument Document { get; private set; }
public List<Bend> GetBendLines()
{
var bends = new List<Bend>();
var bendNotes = GetBendNotes();
if (ReplaceSharpRadius)
FixSharpBends();
foreach (var line in Document.Entities.OfType<Line>())
{
if (!IsBendLine(line))
continue;
var bend = new Bend
{
Line = line,
Direction = BendDirection.Unknown
};
bends.Add(bend);
}
AssignBendDirections(bends, bendNotes);
return bends.Where(b => b.Radius == null || b.Radius <= MaxBendRadius).ToList();
}
private bool IsBendLine(Line line)
{
if (line.LineType.Name != "CENTERX2")
return false;
switch (line.Layer.Name.ToUpperInvariant())
{
case "0":
case "BEND":
case "BEND LINES":
case "BENDLINES":
return true;
default:
return false;
}
}
private List<MText> GetBendNotes()
{
return Document.Entities.OfType<MText>()
.Where(t => GetBendDirection(t) != BendDirection.Unknown)
.ToList();
}
private void FixSharpBends()
{
var bendNotes = GetBendNotes();
foreach (var bendNote in bendNotes)
{
var text = bendNote.Value?.ToUpper();
if (text == null)
continue;
var index = text.IndexOf("SHARP");
if (index == -1)
continue;
bendNote.Value = bendNote.Value
.Remove(index, 5)
.Insert(index, $"R{SharpRadius}");
}
}
private static BendDirection GetBendDirection(MText mText)
{
if (mText == null || mText.Value == null)
return BendDirection.Unknown;
var text = mText.Value.ToUpper();
if (text.Contains("UP"))
return BendDirection.Up;
if (text.Contains("DOWN") || text.Contains("DN"))
return BendDirection.Down;
return BendDirection.Unknown;
}
private static void AssignBendDirections(IEnumerable<Bend> bendlines, IEnumerable<MText> bendNotes)
{
foreach (var bendline in bendlines)
{
var bendNote = FindBendNote(bendline.Line, bendNotes);
if (bendNote == null)
continue;
bendline.BendNote = bendNote;
bendline.Direction = GetBendDirection(bendNote);
var note = bendNote.Value.ToUpper().Replace("SHARP", "R0");
var match = bendNoteRegex.Match(note);
if (match.Success)
{
var radius = match.Groups["radius"].Value;
var angle = match.Groups["angle"].Value;
bendline.Radius = double.Parse(radius, CultureInfo.InvariantCulture);
bendline.Angle = double.Parse(angle, CultureInfo.InvariantCulture);
}
}
}
private static MText FindBendNote(Line bendLine, IEnumerable<MText> bendNotes)
{
var bendNotesList = bendNotes.ToList();
for (int i = bendNotesList.Count - 1; i >= 0; i--)
{
var note = bendNotesList[i];
var notePos = note.InsertPoint.ToXY();
var perpendicularPoint = bendLine.PointPerpendicularTo(notePos);
var dist = notePos.DistanceTo(perpendicularPoint);
var maxAcceptableDist = note.Height * 2.0;
if (dist > maxAcceptableDist)
bendNotesList.RemoveAt(i);
}
if (bendNotesList.Count == 0)
return null;
var closestNote = bendNotesList.First();
var p1 = closestNote.InsertPoint.ToXY();
var p2 = bendLine.ClosestPointOnLineTo(p1);
var dist2 = p1.DistanceTo(p2);
for (int i = 1; i < bendNotesList.Count; i++)
{
var note = bendNotesList[i];
var p3 = note.InsertPoint.ToXY();
var p4 = bendLine.ClosestPointOnLineTo(p3);
var dist = p3.DistanceTo(p4);
if (dist < dist2)
{
dist2 = dist;
closestNote = note;
}
}
return closestNote;
}
}
}