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>
This commit is contained in:
@@ -25,14 +25,13 @@ public static class NestRunner
|
||||
|
||||
// 1. Import DXFs → Drawings
|
||||
var drawings = new List<Drawing>();
|
||||
var importer = new DxfImporter();
|
||||
|
||||
foreach (var part in request.Parts)
|
||||
{
|
||||
if (!File.Exists(part.DxfPath))
|
||||
throw new FileNotFoundException($"DXF file not found: {part.DxfPath}", part.DxfPath);
|
||||
|
||||
if (!importer.GetGeometry(part.DxfPath, out var geometry) || geometry.Count == 0)
|
||||
var geometry = Dxf.GetGeometry(part.DxfPath);
|
||||
if (geometry.Count == 0)
|
||||
throw new InvalidOperationException($"Failed to import DXF: {part.DxfPath}");
|
||||
|
||||
var normalized = ShapeProfile.NormalizeEntities(geometry);
|
||||
|
||||
@@ -241,17 +241,11 @@ static class NestConsole
|
||||
|
||||
static Drawing ImportDxf(string path)
|
||||
{
|
||||
var importer = new DxfImporter();
|
||||
|
||||
if (!importer.GetGeometry(path, out var geometry))
|
||||
{
|
||||
Console.Error.WriteLine($"Error: failed to read DXF file: {path}");
|
||||
return null;
|
||||
}
|
||||
var geometry = Dxf.GetGeometry(path);
|
||||
|
||||
if (geometry.Count == 0)
|
||||
{
|
||||
Console.Error.WriteLine($"Error: no geometry found in DXF file: {path}");
|
||||
Console.Error.WriteLine($"Error: failed to read DXF file or no geometry found: {path}");
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
378
OpenNest.IO/Dxf.cs
Normal file
378
OpenNest.IO/Dxf.cs
Normal file
@@ -0,0 +1,378 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -1,297 +0,0 @@
|
||||
using ACadSharp;
|
||||
using ACadSharp.IO;
|
||||
using CSMath;
|
||||
using OpenNest.CNC;
|
||||
using OpenNest.Math;
|
||||
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 class DxfExporter
|
||||
{
|
||||
private CadDocument doc;
|
||||
private XYZ curpos;
|
||||
private Mode mode;
|
||||
private readonly Layer cutLayer;
|
||||
private readonly Layer rapidLayer;
|
||||
private readonly Layer plateLayer;
|
||||
|
||||
public DxfExporter()
|
||||
{
|
||||
doc = new CadDocument();
|
||||
|
||||
cutLayer = new Layer("Cut");
|
||||
cutLayer.Color = new Color(1);
|
||||
|
||||
rapidLayer = new Layer("Rapid");
|
||||
rapidLayer.Color = new Color(5);
|
||||
|
||||
plateLayer = new Layer("Plate");
|
||||
plateLayer.Color = new Color(4);
|
||||
}
|
||||
|
||||
public void ExportProgram(Program program, Stream stream)
|
||||
{
|
||||
doc = new CadDocument();
|
||||
EnsureLayers();
|
||||
AddProgram(program);
|
||||
using (var writer = new DxfWriter(stream, doc, false))
|
||||
{
|
||||
writer.Write();
|
||||
}
|
||||
}
|
||||
|
||||
public bool ExportProgram(Program program, string path)
|
||||
{
|
||||
Stream stream = null;
|
||||
var success = false;
|
||||
|
||||
try
|
||||
{
|
||||
stream = File.Create(path);
|
||||
ExportProgram(program, stream);
|
||||
success = true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
Debug.Fail("DxfExporter.ExportProgram failed to write program to file: " + path);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (stream != null)
|
||||
stream.Close();
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
public void ExportPlate(Plate plate, Stream stream)
|
||||
{
|
||||
doc = new CadDocument();
|
||||
EnsureLayers();
|
||||
AddPlateOutline(plate);
|
||||
|
||||
foreach (var part in plate.Parts)
|
||||
{
|
||||
var endpt = part.Location.ToAcadXYZ();
|
||||
AddLine(curpos, endpt, rapidLayer);
|
||||
curpos = part.Location.ToAcadXYZ();
|
||||
AddProgram(part.Program);
|
||||
}
|
||||
|
||||
using (var writer = new DxfWriter(stream, doc, false))
|
||||
{
|
||||
writer.Write();
|
||||
}
|
||||
}
|
||||
|
||||
public bool ExportPlate(Plate plate, string path)
|
||||
{
|
||||
Stream stream = null;
|
||||
var success = false;
|
||||
|
||||
try
|
||||
{
|
||||
stream = File.Create(path);
|
||||
ExportPlate(plate, stream);
|
||||
success = true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
Debug.Fail("DxfExporter.ExportPlate failed to write plate to file: " + path);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (stream != null)
|
||||
stream.Close();
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
private void EnsureLayers()
|
||||
{
|
||||
doc.Layers.Add(cutLayer);
|
||||
doc.Layers.Add(rapidLayer);
|
||||
doc.Layers.Add(plateLayer);
|
||||
}
|
||||
|
||||
private void AddLine(XYZ start, XYZ end, Layer layer)
|
||||
{
|
||||
var ln = new AcadLine();
|
||||
ln.StartPoint = start;
|
||||
ln.EndPoint = end;
|
||||
ln.Layer = layer;
|
||||
doc.Entities.Add(ln);
|
||||
}
|
||||
|
||||
private void AddPlateOutline(Plate plate)
|
||||
{
|
||||
XYZ pt1;
|
||||
XYZ pt2;
|
||||
XYZ pt3;
|
||||
XYZ 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);
|
||||
}
|
||||
|
||||
private 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:
|
||||
var arc = (ArcMove)code;
|
||||
AddArcMove(arc);
|
||||
break;
|
||||
|
||||
case CodeType.LinearMove:
|
||||
var line = (LinearMove)code;
|
||||
AddLinearMove(line);
|
||||
break;
|
||||
|
||||
case CodeType.RapidMove:
|
||||
var rapid = (RapidMove)code;
|
||||
AddRapidMove(rapid);
|
||||
break;
|
||||
|
||||
case CodeType.SubProgramCall:
|
||||
var tmpmode = mode;
|
||||
var subpgm = (CNC.SubProgramCall)code;
|
||||
AddProgram(subpgm.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 == OpenNest.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();
|
||||
circle.Center = center;
|
||||
circle.Radius = radius;
|
||||
circle.Layer = cutLayer;
|
||||
doc.Entities.Add(circle);
|
||||
}
|
||||
else
|
||||
{
|
||||
var arc2 = new AcadArc();
|
||||
arc2.Center = center;
|
||||
arc2.Radius = radius;
|
||||
arc2.StartAngle = startAngle;
|
||||
arc2.EndAngle = endAngle;
|
||||
arc2.Layer = cutLayer;
|
||||
doc.Entities.Add(arc2);
|
||||
}
|
||||
|
||||
curpos = endpt;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
using ACadSharp;
|
||||
using ACadSharp.IO;
|
||||
using OpenNest.Geometry;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
|
||||
namespace OpenNest.IO
|
||||
{
|
||||
public class DxfImporter
|
||||
{
|
||||
public DxfImporter()
|
||||
{
|
||||
}
|
||||
|
||||
private List<Entity> GetGeometry(CadDocument doc)
|
||||
{
|
||||
var entities = new List<Entity>();
|
||||
var lines = new List<Line>();
|
||||
var arcs = new List<Arc>();
|
||||
|
||||
foreach (var entity in doc.Entities)
|
||||
{
|
||||
// Skip bend/etch entities — bends are converted to Bend objects
|
||||
// separately via bend detection, and etch marks are generated from
|
||||
// bends during DXF export. Neither should be treated as cut geometry.
|
||||
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;
|
||||
}
|
||||
|
||||
/// <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 DxfImportResult Import(string path)
|
||||
{
|
||||
using var reader = new DxfReader(path);
|
||||
var doc = reader.Read();
|
||||
var entities = GetGeometry(doc);
|
||||
|
||||
return new DxfImportResult
|
||||
{
|
||||
Entities = entities,
|
||||
Document = doc
|
||||
};
|
||||
}
|
||||
|
||||
public bool GetGeometry(Stream stream, out List<Entity> geometry)
|
||||
{
|
||||
var success = false;
|
||||
|
||||
try
|
||||
{
|
||||
using (var reader = new DxfReader(stream))
|
||||
{
|
||||
var doc = reader.Read();
|
||||
geometry = GetGeometry(doc);
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine(ex.Message);
|
||||
geometry = new List<Entity>();
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (stream != null)
|
||||
stream.Close();
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
public bool GetGeometry(string path, out List<Entity> geometry)
|
||||
{
|
||||
var success = false;
|
||||
|
||||
try
|
||||
{
|
||||
using (var reader = new DxfReader(path))
|
||||
{
|
||||
var doc = reader.Read();
|
||||
geometry = GetGeometry(doc);
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine(ex.Message);
|
||||
geometry = new List<Entity>();
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
private static bool IsNonCutLayer(string layerName)
|
||||
{
|
||||
return string.Equals(layerName, "BEND", System.StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(layerName, "ETCH", System.StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -96,13 +96,10 @@ namespace OpenNest.Mcp.Tools
|
||||
if (!File.Exists(path))
|
||||
return $"Error: file not found: {path}";
|
||||
|
||||
var importer = new DxfImporter();
|
||||
|
||||
if (!importer.GetGeometry(path, out var geometry))
|
||||
return "Error: failed to read DXF file";
|
||||
var geometry = Dxf.GetGeometry(path);
|
||||
|
||||
if (geometry.Count == 0)
|
||||
return "Error: no geometry found in DXF file";
|
||||
return "Error: failed to read DXF file or no geometry found";
|
||||
|
||||
var normalized = ShapeProfile.NormalizeEntities(geometry);
|
||||
var pgm = ConvertGeometry.ToProgram(normalized);
|
||||
|
||||
@@ -70,8 +70,7 @@ public class NestRunnerTests
|
||||
var pgm = ConvertGeometry.ToProgram(shape);
|
||||
var path = Path.Combine(Path.GetTempPath(), $"test-{Guid.NewGuid()}.dxf");
|
||||
|
||||
var exporter = new DxfExporter();
|
||||
exporter.ExportProgram(pgm, path);
|
||||
Dxf.ExportProgram(pgm, path);
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
@@ -35,8 +35,7 @@ public class SolidWorksBendDetectorTests
|
||||
var path = Path.Combine(AppContext.BaseDirectory, "Bending", "TestData", "4526 A14 PT11 Test.dxf");
|
||||
Assert.True(File.Exists(path), $"Test DXF not found: {path}");
|
||||
|
||||
var importer = new OpenNest.IO.DxfImporter();
|
||||
var result = importer.Import(path);
|
||||
var result = OpenNest.IO.Dxf.Import(path);
|
||||
|
||||
// EllipseConverter now produces arcs directly during import,
|
||||
// so the imported entities should contain Arc instances from the ellipses
|
||||
@@ -61,8 +60,7 @@ public class SolidWorksBendDetectorTests
|
||||
var path = Path.Combine(AppContext.BaseDirectory, "Bending", "TestData", "4526 A14 PT11.dxf");
|
||||
Assert.True(File.Exists(path), $"Test DXF not found: {path}");
|
||||
|
||||
var importer = new OpenNest.IO.DxfImporter();
|
||||
var result = importer.Import(path);
|
||||
var result = OpenNest.IO.Dxf.Import(path);
|
||||
|
||||
// The DXF has 2 trimmed ellipses forming an oblong slot.
|
||||
// Trimmed ellipses must not generate a closing chord line.
|
||||
|
||||
@@ -32,8 +32,7 @@ public class BestFitOverlapTests
|
||||
if (!File.Exists(DxfPath))
|
||||
return null;
|
||||
|
||||
var importer = new DxfImporter();
|
||||
importer.GetGeometry(DxfPath, out var geometry);
|
||||
var geometry = Dxf.GetGeometry(DxfPath);
|
||||
var pgm = ConvertGeometry.ToProgram(geometry);
|
||||
return new Drawing("PT16", pgm);
|
||||
}
|
||||
|
||||
@@ -20,8 +20,7 @@ public class EngineOverlapTests
|
||||
if (!System.IO.File.Exists(DxfPath))
|
||||
return null;
|
||||
|
||||
var importer = new DxfImporter();
|
||||
importer.GetGeometry(DxfPath, out var geometry);
|
||||
var geometry = Dxf.GetGeometry(DxfPath);
|
||||
var pgm = ConvertGeometry.ToProgram(geometry);
|
||||
return new Drawing("PT15", pgm);
|
||||
}
|
||||
|
||||
@@ -228,8 +228,7 @@ public class EllipseConverterTests
|
||||
using (var writer = new ACadSharp.IO.DxfWriter(stream, doc, false))
|
||||
writer.Write();
|
||||
|
||||
var importer = new OpenNest.IO.DxfImporter();
|
||||
var result = importer.Import(tempPath);
|
||||
var result = OpenNest.IO.Dxf.Import(tempPath);
|
||||
|
||||
var arcCount = result.Entities.Count(e => e is Arc);
|
||||
var lineCount = result.Entities.Count(e => e is Line);
|
||||
|
||||
@@ -138,8 +138,7 @@ public class GeometrySimplifierTests
|
||||
if (!File.Exists(path))
|
||||
return; // skip if file not available
|
||||
|
||||
var importer = new DxfImporter();
|
||||
var result = importer.Import(path);
|
||||
var result = Dxf.Import(path);
|
||||
var shapes = ShapeBuilder.GetShapes(result.Entities);
|
||||
|
||||
var simplifier = new GeometrySimplifier { Tolerance = 0.004 };
|
||||
|
||||
@@ -11,17 +11,14 @@ public class DxfRoundtripTests
|
||||
private static List<Entity> ExportAndReimport(List<Entity> geometry)
|
||||
{
|
||||
var program = ConvertGeometry.ToProgram(geometry);
|
||||
var exporter = new DxfExporter();
|
||||
var importer = new DxfImporter();
|
||||
|
||||
using var exportStream = new MemoryStream();
|
||||
exporter.ExportProgram(program, exportStream);
|
||||
Dxf.ExportProgram(program, exportStream);
|
||||
var bytes = exportStream.ToArray();
|
||||
|
||||
var importStream = new MemoryStream(bytes);
|
||||
var success = importer.GetGeometry(importStream, out var reimported);
|
||||
var reimported = Dxf.GetGeometry(importStream);
|
||||
|
||||
Assert.True(success, "Failed to re-import exported DXF");
|
||||
Assert.NotEmpty(reimported);
|
||||
return reimported;
|
||||
}
|
||||
|
||||
|
||||
@@ -369,8 +369,7 @@ public class DrawingSplitterTests
|
||||
var writer = new OpenNest.IO.SplitDxfWriter();
|
||||
writer.Write(tempPath, results[0]);
|
||||
|
||||
var reimporter = new OpenNest.IO.DxfImporter();
|
||||
var reimportResult = reimporter.Import(tempPath);
|
||||
var reimportResult = OpenNest.IO.Dxf.Import(tempPath);
|
||||
|
||||
var afterArcs = reimportResult.Entities.OfType<Arc>().Count();
|
||||
var afterCircles = reimportResult.Entities.OfType<Circle>().Count();
|
||||
|
||||
@@ -185,8 +185,7 @@ public class SplitDxfWriterEtchLayerTests
|
||||
writer.Write(tempPath, splitDrawing);
|
||||
|
||||
// Re-import via DxfImporter (same path as CadConverterForm)
|
||||
var importer = new DxfImporter();
|
||||
var result = importer.Import(tempPath);
|
||||
var result = Dxf.Import(tempPath);
|
||||
|
||||
// ETCH entities should be filtered during import (like BEND)
|
||||
var etchEntities = result.Entities
|
||||
|
||||
@@ -23,8 +23,7 @@ public class StrategyOverlapTests
|
||||
if (!System.IO.File.Exists(DxfPath))
|
||||
return null;
|
||||
|
||||
var importer = new DxfImporter();
|
||||
importer.GetGeometry(DxfPath, out var geometry);
|
||||
var geometry = Dxf.GetGeometry(DxfPath);
|
||||
var pgm = ConvertGeometry.ToProgram(geometry);
|
||||
return new Drawing("PT15", pgm);
|
||||
}
|
||||
|
||||
@@ -104,7 +104,6 @@ int RunDataCollection(string dir, string dbPath, string saveDir, double s, strin
|
||||
if (backfilled > 0)
|
||||
Console.WriteLine($"Backfilled PerimeterToAreaRatio for {backfilled} existing parts");
|
||||
|
||||
var importer = new DxfImporter();
|
||||
var colorIndex = 0;
|
||||
var processed = 0;
|
||||
var skippedGeometry = 0;
|
||||
@@ -129,7 +128,8 @@ int RunDataCollection(string dir, string dbPath, string saveDir, double s, strin
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!importer.GetGeometry(file, out var entities))
|
||||
var entities = Dxf.GetGeometry(file);
|
||||
if (entities.Count == 0)
|
||||
{
|
||||
Console.WriteLine(" - SKIP (no geometry)");
|
||||
skippedGeometry++;
|
||||
|
||||
@@ -382,7 +382,6 @@ namespace OpenNest.Forms
|
||||
}
|
||||
|
||||
var jobName = txtJobName.Text.Trim();
|
||||
var importer = new DxfImporter();
|
||||
var nestsCreated = 0;
|
||||
var importErrors = new List<string>();
|
||||
|
||||
@@ -416,7 +415,7 @@ namespace OpenNest.Forms
|
||||
|
||||
try
|
||||
{
|
||||
var result = importer.Import(part.DxfPath);
|
||||
var result = Dxf.Import(part.DxfPath);
|
||||
|
||||
var drawingName = Path.GetFileNameWithoutExtension(part.DxfPath);
|
||||
var drawing = new Drawing(drawingName);
|
||||
|
||||
@@ -74,8 +74,7 @@ namespace OpenNest.Forms
|
||||
{
|
||||
try
|
||||
{
|
||||
var importer = new DxfImporter();
|
||||
var result = importer.Import(file);
|
||||
var result = Dxf.Import(file);
|
||||
|
||||
if (result.Entities.Count == 0)
|
||||
return;
|
||||
@@ -383,8 +382,7 @@ namespace OpenNest.Forms
|
||||
newItems.Add(splitPath);
|
||||
|
||||
// Re-import geometry but keep bends from the split drawing
|
||||
var importer = new DxfImporter();
|
||||
var result = importer.Import(splitPath);
|
||||
var result = Dxf.Import(splitPath);
|
||||
|
||||
var splitItem = new FileListItem
|
||||
{
|
||||
|
||||
@@ -362,9 +362,8 @@ namespace OpenNest.Forms
|
||||
{
|
||||
if (dlg.FilterIndex == 1)
|
||||
{
|
||||
var exporter = new DxfExporter();
|
||||
var success = exporter.ExportPlate(PlateView.Plate, dlg.FileName);
|
||||
return success;
|
||||
Dxf.ExportPlate(PlateView.Plate, dlg.FileName);
|
||||
return true;
|
||||
}
|
||||
else if (dlg.FilterIndex == 2)
|
||||
{
|
||||
@@ -540,8 +539,7 @@ namespace OpenNest.Forms
|
||||
var plate = PlateView.Plate;
|
||||
var name = string.Format("{0}-P{1}.dxf", Nest.Name, PlateManager.CurrentIndex + 1);
|
||||
var path = Path.Combine(Path.GetTempPath(), name);
|
||||
var exporter = new DxfExporter();
|
||||
exporter.ExportPlate(plate, path);
|
||||
Dxf.ExportPlate(plate, path);
|
||||
|
||||
Process.Start(path);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user