feat: add DrawingDxfExporter to convert PEP Drawing to DXF stream

This commit is contained in:
aj
2026-06-23 06:49:50 -04:00
parent 617de2e854
commit af07946a79
5 changed files with 207 additions and 0 deletions
+14
View File
@@ -9,6 +9,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PepLib.Core", "PepLib.Core\
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PepMcp", "PepMcp\PepMcp.csproj", "{28899B7B-2185-4141-B348-E227DFB355E9}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PepMcp", "PepMcp\PepMcp.csproj", "{28899B7B-2185-4141-B348-E227DFB355E9}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PepLib.Core.Tests", "PepLib.Core.Tests\PepLib.Core.Tests.csproj", "{FF7E4CB2-8825-4A03-9D0C-CC713BD93A18}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@@ -55,6 +57,18 @@ Global
{28899B7B-2185-4141-B348-E227DFB355E9}.Release|x64.Build.0 = Release|Any CPU {28899B7B-2185-4141-B348-E227DFB355E9}.Release|x64.Build.0 = Release|Any CPU
{28899B7B-2185-4141-B348-E227DFB355E9}.Release|x86.ActiveCfg = Release|Any CPU {28899B7B-2185-4141-B348-E227DFB355E9}.Release|x86.ActiveCfg = Release|Any CPU
{28899B7B-2185-4141-B348-E227DFB355E9}.Release|x86.Build.0 = Release|Any CPU {28899B7B-2185-4141-B348-E227DFB355E9}.Release|x86.Build.0 = Release|Any CPU
{FF7E4CB2-8825-4A03-9D0C-CC713BD93A18}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FF7E4CB2-8825-4A03-9D0C-CC713BD93A18}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FF7E4CB2-8825-4A03-9D0C-CC713BD93A18}.Debug|x64.ActiveCfg = Debug|Any CPU
{FF7E4CB2-8825-4A03-9D0C-CC713BD93A18}.Debug|x64.Build.0 = Debug|Any CPU
{FF7E4CB2-8825-4A03-9D0C-CC713BD93A18}.Debug|x86.ActiveCfg = Debug|Any CPU
{FF7E4CB2-8825-4A03-9D0C-CC713BD93A18}.Debug|x86.Build.0 = Debug|Any CPU
{FF7E4CB2-8825-4A03-9D0C-CC713BD93A18}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FF7E4CB2-8825-4A03-9D0C-CC713BD93A18}.Release|Any CPU.Build.0 = Release|Any CPU
{FF7E4CB2-8825-4A03-9D0C-CC713BD93A18}.Release|x64.ActiveCfg = Release|Any CPU
{FF7E4CB2-8825-4A03-9D0C-CC713BD93A18}.Release|x64.Build.0 = Release|Any CPU
{FF7E4CB2-8825-4A03-9D0C-CC713BD93A18}.Release|x86.ActiveCfg = Release|Any CPU
{FF7E4CB2-8825-4A03-9D0C-CC713BD93A18}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@@ -0,0 +1,63 @@
using PepLib.Codes;
using PepLib.Enums;
using PepLib.Geometry;
using PepLib.IO;
using PepLib.Models;
using Xunit;
namespace PepLib.Core.Tests.IO;
public class DrawingDxfExporterTests
{
[Fact]
public void Export_RectangularLoop_ProducesFourLines()
{
// Arrange: a square 10x10 in incremental mode
var drawing = new Drawing();
drawing.Info = new DrawingInfo { Name = "TEST" };
var loop = new Loop { Name = "TEST.loop-000" };
// Mode defaults to Incremental in Loop constructor
loop.Add(new RapidMove(new Vector(0, 0))); // position at (0,0)
loop.Add(new LinearMove(new Vector(10, 0))); // → (10,0)
loop.Add(new LinearMove(new Vector(0, 10))); // → (10,10)
loop.Add(new LinearMove(new Vector(-10, 0))); // → (0,10)
loop.Add(new LinearMove(new Vector(0, -10))); // → (0,0)
drawing.Loops.Add(loop);
// Act
using var stream = DrawingDxfExporter.Export(drawing);
// Assert: stream is non-empty and contains 4 line entities
Assert.True(stream.Length > 0);
stream.Position = 0;
using var reader = new ACadSharp.IO.DxfReader(stream);
var doc = reader.Read();
var lines = doc.Entities.OfType<ACadSharp.Entities.Line>().ToList();
var arcs = doc.Entities.OfType<ACadSharp.Entities.Arc>().ToList();
Assert.Equal(4, lines.Count);
Assert.Empty(arcs);
}
[Fact]
public void Export_LoopWithArc_ProducesLineAndArc()
{
var drawing = new Drawing();
drawing.Info = new DrawingInfo { Name = "TEST" };
var loop = new Loop { Name = "TEST.loop-000" };
loop.Add(new RapidMove(new Vector(5, 0))); // start at (5,0)
loop.Add(new LinearMove(new Vector(5, 0))); // → (10,0)
loop.Add(new CircularMove(new Vector(-5, 5), new Vector(-5, 0), RotationType.CCW)); // arc: end=(5,5), center=(5,0)
drawing.Loops.Add(loop);
using var stream = DrawingDxfExporter.Export(drawing);
stream.Position = 0;
using var reader = new ACadSharp.IO.DxfReader(stream);
var doc = reader.Read();
var lines = doc.Entities.OfType<ACadSharp.Entities.Line>().ToList();
var arcs = doc.Entities.OfType<ACadSharp.Entities.Arc>().ToList();
Assert.Single(lines);
Assert.Single(arcs);
}
}
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="ACadSharp" Version="3.1.32" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\PepLib.Core\PepLib.Core.csproj" />
</ItemGroup>
</Project>
+106
View File
@@ -0,0 +1,106 @@
using ACadSharp;
using ACadSharp.IO;
using ACadSharp.Tables;
using CSMath;
using PepLib.Codes;
using PepLib.Enums;
using PepLib.Geometry;
using PepLib.Models;
using AcadArc = ACadSharp.Entities.Arc;
using AcadCircle = ACadSharp.Entities.Circle;
using AcadLine = ACadSharp.Entities.Line;
namespace PepLib.IO;
public static class DrawingDxfExporter
{
public static Stream Export(Drawing drawing)
{
var doc = new CadDocument();
foreach (var loop in drawing.Loops)
{
var layer = new Layer(loop.Name);
doc.Layers.Add(layer);
var pos = new Vector();
foreach (var code in loop)
{
switch (code.CodeType())
{
case CodeType.RapidMove:
pos = Advance(pos, ((RapidMove)code).EndPoint, loop.Mode);
break;
case CodeType.LinearMove:
var lm = (LinearMove)code;
var lineEnd = Advance(pos, lm.EndPoint, loop.Mode);
doc.Entities.Add(new AcadLine
{
StartPoint = ToXYZ(pos),
EndPoint = ToXYZ(lineEnd),
Layer = layer
});
pos = lineEnd;
break;
case CodeType.CircularMove:
var cm = (CircularMove)code;
var arcEnd = Advance(pos, cm.EndPoint, loop.Mode);
var arcCenter = Advance(pos, cm.CenterPoint, loop.Mode);
var dx = pos.X - arcCenter.X;
var dy = pos.Y - arcCenter.Y;
var radius = Math.Sqrt(dx * dx + dy * dy);
var startAngle = Math.Atan2(pos.Y - arcCenter.Y, pos.X - arcCenter.X);
var endAngle = Math.Atan2(arcEnd.Y - arcCenter.Y, arcEnd.X - arcCenter.X);
if (cm.Rotation == RotationType.CW)
{
(startAngle, endAngle) = (endAngle, startAngle);
}
if (Math.Abs(startAngle - endAngle) < 1e-10) // full circle
{
doc.Entities.Add(new AcadCircle
{
Center = ToXYZ(arcCenter),
Radius = radius,
Layer = layer
});
}
else
{
doc.Entities.Add(new AcadArc
{
Center = ToXYZ(arcCenter),
Radius = radius,
StartAngle = startAngle,
EndAngle = endAngle,
Layer = layer
});
}
pos = arcEnd;
break;
}
}
}
var buffer = new MemoryStream();
using (var writer = new DxfWriter(buffer, doc, false))
{
writer.Write();
}
var stream = new MemoryStream(buffer.ToArray());
return stream;
}
private static Vector Advance(Vector current, Vector offset, ProgrammingMode mode) =>
mode == ProgrammingMode.Incremental ? current + offset : offset;
private static XYZ ToXYZ(Vector v) => new XYZ(v.X, v.Y, 0);
}
+1
View File
@@ -7,6 +7,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ACadSharp" Version="3.1.32" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.10"> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.10">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>