Compare commits

..

7 Commits

Author SHA1 Message Date
aj da4d3228b0 revert: remove degree symbol fix (moved to caller)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 16:31:50 -05:00
aj 3c1700c480 fix: repair double-encoded degree symbol in DXF output
ACadSharp misreads UTF-8 degree symbol (C2 B0) as two ANSI_1252
characters (°) then writes that back out. Post-process the saved
DXF to replace ° with ° so bend notes display correctly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 16:28:38 -05:00
aj f2f50f9914 fix: set bend line entity color to ByLayer
Bend lines retained their original explicit color (white) from
SolidWorks after being moved to the BEND layer. Now set to ByLayer
so they inherit the layer's yellow color.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 15:51:54 -05:00
aj 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
aj 6e131d402e fix: upgrade ACadSharp 3.1.32 → 3.4.9 to fix missing OBJECTS section
The DxfWriter in 3.1.32 wrote root dictionary entries referencing child
dictionary objects (ACAD_GROUP, ACAD_LAYOUT, etc.) but never serialized
the actual objects. This caused AutoCAD to report "GroupTable dictionary
was not defined in NamedObject dictionary". Version 3.4.9 includes the
root dictionary fix from PR #957.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 14:20:45 -05:00
aj f04c75235c fix: enable CreateDefaults on DxfReader to fix missing ACAD_GROUP dictionary
SolidWorks DXF exports don't include the ACAD_GROUP entry in the Named
Object Dictionary. Without it, AutoCAD reports "GroupTable dictionary was
not defined in NamedObject dictionary" when opening the file.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 14:13:06 -05:00
aj 2e8f0e60c5 feat: switch from netDxf to ACadSharp for DXF operations
Replace vendored netDxf git submodule with ACadSharp NuGet package
(v3.1.32). This aligns the DXF dependency with the parent project.

API migration: DxfDocument→CadDocument, Vector3→XYZ, Vector2→XY,
MText.Position→MText.InsertPoint, Line.Linetype→Line.LineType,
DxfDocument.Load()→DxfReader, doc.Save()→DxfWriter.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 12:52:08 -05:00
7 changed files with 76 additions and 73 deletions
-3
View File
@@ -1,3 +0,0 @@
[submodule "netDxf"]
path = netDxf
url = https://github.com/haplokuon/netDxf.git
+2 -4
View File
@@ -1,6 +1,4 @@
using netDxf;
using netDxf.Entities;
using netDxf.Tables;
using ACadSharp.Entities;
using System;
using System.Collections.Generic;
@@ -69,7 +67,7 @@ namespace EtchBendLines
public double? Angle { get; set; }
public override string ToString()
public override string ToString()
=> $"{Direction} {(Angle?.ToString("0.##") ?? "?")}° R{(Radius?.ToString("0.##") ?? "?")}";
}
}
+18 -14
View File
@@ -1,5 +1,7 @@
using netDxf;
using netDxf.Entities;
using ACadSharp;
using ACadSharp.Entities;
using ACadSharp.IO;
using CSMath;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
@@ -11,12 +13,13 @@ namespace EtchBendLines
{
public BendLineExtractor(string dxfFile)
{
DxfDocument = DxfDocument.Load(dxfFile);
using var reader = new DxfReader(dxfFile);
Document = reader.Read();
}
public BendLineExtractor(DxfDocument dxfDocument)
public BendLineExtractor(CadDocument document)
{
DxfDocument = dxfDocument;
Document = document;
}
/// <summary>
@@ -33,11 +36,11 @@ namespace EtchBendLines
/// 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+)?)°?\s*R\s*(?<radius>\d+(\.\d+)?)\b",
@"\b(?<direction>UP|DOWN|DN)\s+(?<angle>\d+(\.\d+)?)[^R\d]*R\s*(?<radius>\d+(\.\d+)?)\b",
RegexOptions.Compiled | RegexOptions.IgnoreCase
);
public DxfDocument DxfDocument { get; private set; }
public CadDocument Document { get; private set; }
public List<Bend> GetBendLines()
{
@@ -47,7 +50,7 @@ namespace EtchBendLines
if (ReplaceSharpRadius)
FixSharpBends();
foreach (var line in DxfDocument.Lines)
foreach (var line in Document.Entities.OfType<Line>())
{
if (!IsBendLine(line))
continue;
@@ -63,16 +66,17 @@ namespace EtchBendLines
AssignBendDirections(bends, bendNotes);
return bends.Where(b => b.Radius <= MaxBendRadius).ToList();
return bends.Where(b => b.Radius == null || b.Radius <= MaxBendRadius).ToList();
}
private bool IsBendLine(Line line)
{
if (line.Linetype.Name != "CENTERX2")
if (line.LineType.Name != "CENTERX2")
return false;
switch (line.Layer.Name.ToUpperInvariant())
{
case "0":
case "BEND":
case "BEND LINES":
case "BENDLINES":
@@ -84,7 +88,7 @@ namespace EtchBendLines
private List<MText> GetBendNotes()
{
return DxfDocument.MTexts
return Document.Entities.OfType<MText>()
.Where(t => GetBendDirection(t) != BendDirection.Unknown)
.ToList();
}
@@ -159,7 +163,7 @@ namespace EtchBendLines
for (int i = bendNotesList.Count - 1; i >= 0; i--)
{
var note = bendNotesList[i];
var notePos = note.Position.ToVector2();
var notePos = note.InsertPoint.ToXY();
var perpendicularPoint = bendLine.PointPerpendicularTo(notePos);
var dist = notePos.DistanceTo(perpendicularPoint);
var maxAcceptableDist = note.Height * 2.0;
@@ -172,14 +176,14 @@ namespace EtchBendLines
return null;
var closestNote = bendNotesList.First();
var p1 = closestNote.Position.ToVector2();
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.Position.ToVector2();
var p3 = note.InsertPoint.ToXY();
var p4 = bendLine.ClosestPointOnLineTo(p3);
var dist = p3.DistanceTo(p4);
+1 -4
View File
@@ -11,11 +11,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ACadSharp" Version="3.4.9" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="8.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\netDxf\netDxf\netDxf.csproj" />
</ItemGroup>
</Project>
+38 -30
View File
@@ -1,6 +1,8 @@
using netDxf;
using netDxf.Entities;
using netDxf.Tables;
using ACadSharp;
using ACadSharp.Entities;
using ACadSharp.Tables;
using ACadSharp.IO;
using CSMath;
using System;
using System.Collections.Generic;
using System.IO;
@@ -12,12 +14,12 @@ namespace EtchBendLines
{
public readonly Layer BendLayer = new Layer("BEND")
{
Color = AciColor.Yellow
Color = Color.Yellow
};
static readonly Layer EtchLayer = new Layer("ETCH")
{
Color = AciColor.Green,
Color = Color.Green,
};
private const double DefaultEtchLength = 1.0;
@@ -27,33 +29,35 @@ namespace EtchBendLines
/// </summary>
public double MaxBendRadius { get; set; } = 4.0;
private DxfDocument LoadDocument(string path)
private CadDocument LoadDocument(string path)
{
try
{
return DxfDocument.Load(path)
using var reader = new DxfReader(path);
reader.Configuration.CreateDefaults = true;
return reader.Read()
?? throw new InvalidOperationException("DXF load returned null");
}
catch (Exception ex)
catch (Exception ex) when (ex is not InvalidOperationException)
{
throw new ApplicationException($"Failed to load DXF '{path}'", ex);
}
}
private List<Bend> ExtractBends(DxfDocument doc)
private List<Bend> ExtractBends(CadDocument doc)
{
var extractor = new BendLineExtractor(doc);
return extractor.GetBendLines();
}
private HashSet<string> BuildExistingKeySet(DxfDocument doc)
private HashSet<string> BuildExistingKeySet(CadDocument doc)
=> new HashSet<string>(
doc.Lines
doc.Entities.OfType<Line>()
.Where(l => IsEtchLayer(l.Layer))
.Select(l => KeyFor(l.StartPoint, l.EndPoint))
);
private void InsertEtchLines(DxfDocument doc, IEnumerable<Bend> bends, HashSet<string> existingKeys, double etchLength)
private void InsertEtchLines(CadDocument doc, IEnumerable<Bend> bends, HashSet<string> existingKeys, double etchLength)
{
foreach (var bend in bends)
{
@@ -63,28 +67,31 @@ namespace EtchBendLines
if (existingKeys.Contains(key))
{
// ensure correct layer
var existing = doc.Lines.First(l => KeyFor(l) == key);
var existing = doc.Entities.OfType<Line>().First(l => KeyFor(l) == key);
existing.Layer = EtchLayer;
}
else
{
etch.Layer = EtchLayer;
doc.AddEntity(etch);
doc.Entities.Add(etch);
existingKeys.Add(key);
}
}
}
}
private void SaveDocument(DxfDocument doc, string path)
private void SaveDocument(CadDocument doc, string path)
{
doc.Save(path);
using (var writer = new DxfWriter(path, doc, false))
{
writer.Write();
}
Console.WriteLine($"→ Saved with etch lines: {path}");
}
private static string KeyFor(Line l) => KeyFor(l.StartPoint, l.EndPoint);
private static string KeyFor(Vector3 a, Vector3 b) => $"{a.X:F3},{a.Y:F3}|{b.X:F3},{b.Y:F3}";
private static string KeyFor(XYZ a, XYZ b) => $"{a.X:F3},{a.Y:F3}|{b.X:F3},{b.Y:F3}";
public void AddEtchLines(string filePath, double etchLength = DefaultEtchLength)
{
@@ -93,10 +100,11 @@ namespace EtchBendLines
var doc = LoadDocument(filePath);
var bends = ExtractBends(doc);
// Ensure all bend lines are on the BEND layer
// Ensure all bend lines are on the BEND layer with ByLayer color
foreach (var bend in bends)
{
bend.Line.Layer = BendLayer;
bend.Line.Color = Color.ByLayer;
}
var upBends = bends.Where(b => b.Direction == BendDirection.Up);
@@ -129,8 +137,8 @@ namespace EtchBendLines
{
var lines = new List<Line>();
var startPoint = new Vector2(bendLine.StartPoint.X, bendLine.StartPoint.Y);
var endPoint = new Vector2(bendLine.EndPoint.X, bendLine.EndPoint.Y);
var startPoint = new XY(bendLine.StartPoint.X, bendLine.StartPoint.Y);
var endPoint = new XY(bendLine.EndPoint.X, bendLine.EndPoint.Y);
var bendLength = startPoint.DistanceTo(endPoint);
if (bendLength < (etchLength * 3.0))
@@ -151,26 +159,26 @@ namespace EtchBendLines
var topY1 = Math.Max(startPoint.Y, endPoint.Y);
var topY2 = topY1 - etchLength;
var p1 = new Vector2(x, bottomY1);
var p2 = new Vector2(x, bottomY2);
var p3 = new Vector2(x, topY1);
var p4 = new Vector2(x, topY2);
var p1 = new XYZ(x, bottomY1, 0);
var p2 = new XYZ(x, bottomY2, 0);
var p3 = new XYZ(x, topY1, 0);
var p4 = new XYZ(x, topY2, 0);
lines.Add(new Line(p1, p2));
lines.Add(new Line(p3, p4));
}
else
{
var start = bendLine.StartPoint.ToVector2();
var end = bendLine.EndPoint.ToVector2();
var start = bendLine.StartPoint.ToXY();
var end = bendLine.EndPoint.ToXY();
var dx = Math.Cos(angle) * etchLength;
var dy = Math.Sin(angle) * etchLength;
var p1 = new Vector2(start.X, start.Y);
var p2 = new Vector2(start.X + dx, start.Y + dy);
var p3 = new Vector2(end.X, end.Y);
var p4 = new Vector2(end.X - dx, end.Y - dy);
var p1 = new XYZ(start.X, start.Y, 0);
var p2 = new XYZ(start.X + dx, start.Y + dy, 0);
var p3 = new XYZ(end.X, end.Y, 0);
var p4 = new XYZ(end.X - dx, end.Y - dy, 0);
lines.Add(new Line(p1, p2));
lines.Add(new Line(p3, p4));
+17 -17
View File
@@ -1,5 +1,5 @@
using netDxf;
using netDxf.Entities;
using ACadSharp.Entities;
using CSMath;
using System;
namespace EtchBendLines
@@ -8,12 +8,12 @@ namespace EtchBendLines
{
const double TwoPI = Math.PI * 2.0;
public static Vector2 ToVector2(this Vector3 pt)
public static XY ToXY(this XYZ pt)
{
return new Vector2(pt.X, pt.Y);
return new XY(pt.X, pt.Y);
}
public static bool IsEqualTo(this Vector3 pt, Vector3 pt1)
public static bool IsEqualTo(this XYZ pt, XYZ pt1)
{
return pt.X.IsEqualTo(pt1.X) && pt.Y.IsEqualTo(pt1.Y) && pt.Z.IsEqualTo(pt1.Z);
}
@@ -50,10 +50,10 @@ namespace EtchBendLines
return Math.Round(p1.Y - slope * p1.X, 4);
}
public static Vector2 PointPerpendicularTo(this Line line, Vector2 pt)
public static XY PointPerpendicularTo(this Line line, XY pt)
{
var startPoint = line.StartPoint.ToVector2();
var endPoint = line.EndPoint.ToVector2();
var startPoint = line.StartPoint.ToXY();
var endPoint = line.EndPoint.ToXY();
var d1 = pt - startPoint;
var d2 = endPoint - startPoint;
@@ -61,20 +61,20 @@ namespace EtchBendLines
var lengthSquared = d2.X * d2.X + d2.Y * d2.Y;
var param = dotProduct / lengthSquared;
return new Vector2(
return new XY(
startPoint.X + param * d2.X,
startPoint.Y + param * d2.Y);
}
public static Vector2 MidPoint(this Line line)
public static XY MidPoint(this Line line)
{
var x = (line.StartPoint.X + line.EndPoint.X) * 0.5;
var y = (line.StartPoint.Y + line.EndPoint.Y) * 0.5;
return new Vector2(x, y);
return new XY(x, y);
}
public static double DistanceTo(this Vector2 startPoint, Vector2 endPoint)
public static double DistanceTo(this XY startPoint, XY endPoint)
{
var x = endPoint.X - startPoint.X;
var y = endPoint.Y - startPoint.Y;
@@ -82,7 +82,7 @@ namespace EtchBendLines
return Math.Sqrt(x * x + y * y);
}
public static double AngleTo(this Vector2 startPoint, Vector2 endPoint)
public static double AngleTo(this XY startPoint, XY endPoint)
{
var x = endPoint.X - startPoint.X;
var y = endPoint.Y - startPoint.Y;
@@ -133,10 +133,10 @@ namespace EtchBendLines
return Math.Abs(b - a) <= tolerance;
}
public static Vector2 ClosestPointOnLineTo(this Line line, Vector2 pt)
public static XY ClosestPointOnLineTo(this Line line, XY pt)
{
var startPoint = line.StartPoint.ToVector2();
var endPoint = line.EndPoint.ToVector2();
var startPoint = line.StartPoint.ToXY();
var endPoint = line.EndPoint.ToXY();
var diff1 = pt - startPoint;
var diff2 = endPoint - startPoint;
@@ -150,7 +150,7 @@ namespace EtchBendLines
return endPoint;
else
{
return new Vector2(
return new XY(
startPoint.X + param * diff2.X,
startPoint.Y + param * diff2.Y);
}
Submodule netDxf deleted from 67789bfe70