Files
OpenNest/OpenNest.IO/Dxf.cs
AJ Isaacs 250fdefaea refactor: merge DxfImporter and DxfExporter into single static Dxf class
Consolidated two stateless classes into one unified API: Dxf.Import(),
Dxf.GetGeometry(), Dxf.ExportPlate(), Dxf.ExportProgram(). Export
state moved into a private ExportContext. Removed bool+out pattern
from GetGeometry in favor of returning empty list on failure.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 08:17:49 -04:00

379 lines
12 KiB
C#

using ACadSharp;
using ACadSharp.IO;
using CSMath;
using OpenNest.CNC;
using OpenNest.Geometry;
using OpenNest.Math;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
namespace OpenNest.IO
{
using AcadArc = ACadSharp.Entities.Arc;
using AcadCircle = ACadSharp.Entities.Circle;
using AcadLine = ACadSharp.Entities.Line;
using Layer = ACadSharp.Tables.Layer;
public static class Dxf
{
#region Import
/// <summary>
/// Imports a DXF file, returning both converted entities and the raw CadDocument
/// for bend detection. The CadDocument is NOT disposed — caller can use it for
/// additional analysis (e.g., MText extraction for bend notes).
/// </summary>
public static DxfImportResult Import(string path)
{
using var reader = new DxfReader(path);
var doc = reader.Read();
return new DxfImportResult
{
Entities = ConvertEntities(doc),
Document = doc
};
}
public static List<Entity> GetGeometry(string path)
{
try
{
using var reader = new DxfReader(path);
var doc = reader.Read();
return ConvertEntities(doc);
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
return new List<Entity>();
}
}
public static List<Entity> GetGeometry(Stream stream)
{
try
{
using var reader = new DxfReader(stream);
var doc = reader.Read();
return ConvertEntities(doc);
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
return new List<Entity>();
}
}
#endregion
#region Export
public static void ExportProgram(Program program, string path)
{
using var stream = File.Create(path);
ExportProgram(program, stream);
}
public static void ExportProgram(Program program, Stream stream)
{
var ctx = new ExportContext();
ctx.AddProgram(program);
using var writer = new DxfWriter(stream, ctx.Document, false);
writer.Write();
}
public static void ExportPlate(Plate plate, string path)
{
using var stream = File.Create(path);
ExportPlate(plate, stream);
}
public static void ExportPlate(Plate plate, Stream stream)
{
var ctx = new ExportContext();
ctx.AddPlateOutline(plate);
foreach (var part in plate.Parts)
{
var endpt = part.Location.ToAcadXYZ();
ctx.AddLine(ctx.CurPos, endpt, ctx.RapidLayer);
ctx.CurPos = part.Location.ToAcadXYZ();
ctx.AddProgram(part.Program);
}
using var writer = new DxfWriter(stream, ctx.Document, false);
writer.Write();
}
#endregion
#region Private
private static List<Entity> ConvertEntities(CadDocument doc)
{
var entities = new List<Entity>();
var lines = new List<Line>();
var arcs = new List<Arc>();
foreach (var entity in doc.Entities)
{
if (IsNonCutLayer(entity.Layer?.Name))
continue;
switch (entity)
{
case ACadSharp.Entities.Line line:
lines.Add(line.ToOpenNest());
break;
case ACadSharp.Entities.Arc arc:
arcs.Add(arc.ToOpenNest());
break;
case ACadSharp.Entities.Circle circle:
entities.Add(circle.ToOpenNest());
break;
case ACadSharp.Entities.Spline spline:
foreach (var e in spline.ToOpenNest())
{
if (e is Line l) lines.Add(l);
else if (e is Arc a) arcs.Add(a);
}
break;
case ACadSharp.Entities.LwPolyline lwPolyline:
lines.AddRange(lwPolyline.ToOpenNest());
break;
case ACadSharp.Entities.Polyline polyline:
lines.AddRange(polyline.ToOpenNest());
break;
case ACadSharp.Entities.Ellipse ellipse:
foreach (var e in ellipse.ToOpenNest())
{
if (e is Line l) lines.Add(l);
else if (e is Arc a) arcs.Add(a);
}
break;
}
}
GeometryOptimizer.Optimize(lines);
GeometryOptimizer.Optimize(arcs);
entities.AddRange(lines);
entities.AddRange(arcs);
return entities;
}
private static bool IsNonCutLayer(string layerName)
{
return string.Equals(layerName, "BEND", StringComparison.OrdinalIgnoreCase)
|| string.Equals(layerName, "ETCH", StringComparison.OrdinalIgnoreCase);
}
private class ExportContext
{
public CadDocument Document { get; }
public XYZ CurPos { get; set; }
public Layer CutLayer { get; }
public Layer RapidLayer { get; }
public Layer PlateLayer { get; }
private Mode mode;
public ExportContext()
{
Document = new CadDocument();
CutLayer = new Layer("Cut") { Color = new Color(1) };
RapidLayer = new Layer("Rapid") { Color = new Color(5) };
PlateLayer = new Layer("Plate") { Color = new Color(4) };
Document.Layers.Add(CutLayer);
Document.Layers.Add(RapidLayer);
Document.Layers.Add(PlateLayer);
}
public void AddLine(XYZ start, XYZ end, Layer layer)
{
var ln = new AcadLine
{
StartPoint = start,
EndPoint = end,
Layer = layer
};
Document.Entities.Add(ln);
}
public void AddPlateOutline(Plate plate)
{
XYZ pt1, pt2, pt3, pt4;
switch (plate.Quadrant)
{
case 1:
pt1 = new XYZ(0, 0, 0);
pt2 = new XYZ(0, plate.Size.Width, 0);
pt3 = new XYZ(plate.Size.Length, plate.Size.Width, 0);
pt4 = new XYZ(plate.Size.Length, 0, 0);
break;
case 2:
pt1 = new XYZ(0, 0, 0);
pt2 = new XYZ(0, plate.Size.Width, 0);
pt3 = new XYZ(-plate.Size.Length, plate.Size.Width, 0);
pt4 = new XYZ(-plate.Size.Length, 0, 0);
break;
case 3:
pt1 = new XYZ(0, 0, 0);
pt2 = new XYZ(0, -plate.Size.Width, 0);
pt3 = new XYZ(-plate.Size.Length, -plate.Size.Width, 0);
pt4 = new XYZ(-plate.Size.Length, 0, 0);
break;
case 4:
pt1 = new XYZ(0, 0, 0);
pt2 = new XYZ(0, -plate.Size.Width, 0);
pt3 = new XYZ(plate.Size.Length, -plate.Size.Width, 0);
pt4 = new XYZ(plate.Size.Length, 0, 0);
break;
default:
return;
}
AddLine(pt1, pt2, PlateLayer);
AddLine(pt2, pt3, PlateLayer);
AddLine(pt3, pt4, PlateLayer);
AddLine(pt4, pt1, PlateLayer);
var m1 = new XYZ(pt1.X + plate.EdgeSpacing.Left, pt1.Y + plate.EdgeSpacing.Bottom, 0);
var m2 = new XYZ(m1.X, pt2.Y - plate.EdgeSpacing.Top, 0);
var m3 = new XYZ(pt3.X - plate.EdgeSpacing.Right, m2.Y, 0);
var m4 = new XYZ(m3.X, m1.Y, 0);
AddLine(m1, m2, PlateLayer);
AddLine(m2, m3, PlateLayer);
AddLine(m3, m4, PlateLayer);
AddLine(m4, m1, PlateLayer);
}
public void AddProgram(Program program)
{
mode = program.Mode;
for (var i = 0; i < program.Length; ++i)
{
var code = program[i];
switch (code.Type)
{
case CodeType.ArcMove:
AddArcMove((ArcMove)code);
break;
case CodeType.LinearMove:
AddLinearMove((LinearMove)code);
break;
case CodeType.RapidMove:
AddRapidMove((RapidMove)code);
break;
case CodeType.SubProgramCall:
var tmpmode = mode;
AddProgram(((SubProgramCall)code).Program);
mode = tmpmode;
break;
}
}
}
private void AddLinearMove(LinearMove line)
{
var pt = line.EndPoint.ToAcadXYZ();
if (mode == Mode.Incremental)
pt = new XYZ(pt.X + CurPos.X, pt.Y + CurPos.Y, 0);
AddLine(CurPos, pt, CutLayer);
CurPos = pt;
}
private void AddRapidMove(RapidMove rapid)
{
var pt = rapid.EndPoint.ToAcadXYZ();
if (mode == Mode.Incremental)
pt = new XYZ(pt.X + CurPos.X, pt.Y + CurPos.Y, 0);
AddLine(CurPos, pt, RapidLayer);
CurPos = pt;
}
private void AddArcMove(ArcMove arc)
{
var center = arc.CenterPoint.ToAcadXYZ();
var endpt = arc.EndPoint.ToAcadXYZ();
if (mode == Mode.Incremental)
{
endpt = new XYZ(endpt.X + CurPos.X, endpt.Y + CurPos.Y, 0);
center = new XYZ(center.X + CurPos.X, center.Y + CurPos.Y, 0);
}
var startAngle = System.Math.Atan2(
CurPos.Y - center.Y,
CurPos.X - center.X);
var endAngle = System.Math.Atan2(
endpt.Y - center.Y,
endpt.X - center.X);
if (arc.Rotation == RotationType.CW)
Generic.Swap(ref startAngle, ref endAngle);
var dx = endpt.X - center.X;
var dy = endpt.Y - center.Y;
var radius = System.Math.Sqrt(dx * dx + dy * dy);
if (startAngle.IsEqualTo(endAngle))
{
var circle = new AcadCircle
{
Center = center,
Radius = radius,
Layer = CutLayer
};
Document.Entities.Add(circle);
}
else
{
var acadArc = new AcadArc
{
Center = center,
Radius = radius,
StartAngle = startAngle,
EndAngle = endAngle,
Layer = CutLayer
};
Document.Entities.Add(acadArc);
}
CurPos = endpt;
}
}
#endregion
}
}