diff --git a/PepApi.sln b/PepApi.sln index 66559be..0564e5e 100644 --- a/PepApi.sln +++ b/PepApi.sln @@ -9,6 +9,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PepLib.Core", "PepLib.Core\ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PepMcp", "PepMcp\PepMcp.csproj", "{28899B7B-2185-4141-B348-E227DFB355E9}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PepLib.Core.Tests", "PepLib.Core.Tests\PepLib.Core.Tests.csproj", "{FF7E4CB2-8825-4A03-9D0C-CC713BD93A18}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution 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|x86.ActiveCfg = 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 GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/PepLib.Core.Tests/IO/DrawingDxfExporterTests.cs b/PepLib.Core.Tests/IO/DrawingDxfExporterTests.cs new file mode 100644 index 0000000..35509a8 --- /dev/null +++ b/PepLib.Core.Tests/IO/DrawingDxfExporterTests.cs @@ -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().ToList(); + var arcs = doc.Entities.OfType().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().ToList(); + var arcs = doc.Entities.OfType().ToList(); + Assert.Single(lines); + Assert.Single(arcs); + } +} diff --git a/PepLib.Core.Tests/PepLib.Core.Tests.csproj b/PepLib.Core.Tests/PepLib.Core.Tests.csproj new file mode 100644 index 0000000..6a4b7dc --- /dev/null +++ b/PepLib.Core.Tests/PepLib.Core.Tests.csproj @@ -0,0 +1,23 @@ + + + + net8.0 + enable + enable + false + + + + + + + all + + + + + + + + + diff --git a/PepLib.Core/IO/DrawingDxfExporter.cs b/PepLib.Core/IO/DrawingDxfExporter.cs new file mode 100644 index 0000000..4eeb0f4 --- /dev/null +++ b/PepLib.Core/IO/DrawingDxfExporter.cs @@ -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); +} diff --git a/PepLib.Core/PepLib.Core.csproj b/PepLib.Core/PepLib.Core.csproj index c2ed5d2..9136a0d 100644 --- a/PepLib.Core/PepLib.Core.csproj +++ b/PepLib.Core/PepLib.Core.csproj @@ -7,6 +7,7 @@ + runtime; build; native; contentfiles; analyzers; buildtransitive all