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(); doc.Header.Version = ACadVersion.AC1018; // Collect loop IDs that are called as subprograms so we skip drawing them as top-level var subprogramIds = new HashSet(); foreach (var loop in drawing.Loops) { foreach (var code in loop) { if (code.CodeType() == CodeType.SubProgramCall) subprogramIds.Add(((SubProgramCall)code).LoopId); } } foreach (var loop in drawing.Loops) { var loopNumber = GetLoopNumber(loop.Name); if (subprogramIds.Contains(loopNumber)) continue; // drawn inline at SubProgramCall positions var layer = new Layer(loop.Name); doc.Layers.Add(layer); DrawLoop(doc, drawing, loop, layer, new Vector()); } var buffer = new MemoryStream(); using (var writer = new DxfWriter(buffer, doc, false)) { writer.Write(); } return new MemoryStream(buffer.ToArray()); } private static Vector DrawLoop(CadDocument doc, Drawing drawing, Loop loop, Layer layer, Vector startPos) { var pos = startPos; 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; if (lm.Type == EntityType.Cut) { var lineEnd = Advance(pos, lm.EndPoint, loop.Mode); doc.Entities.Add(new AcadLine { StartPoint = ToXYZ(pos), EndPoint = ToXYZ(lineEnd), Layer = layer }); pos = lineEnd; } else { pos = Advance(pos, lm.EndPoint, loop.Mode); } 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) { 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; case CodeType.SubProgramCall: var call = (SubProgramCall)code; var subLoop = drawing.Loops.FirstOrDefault(l => l.Name == drawing.GetLoopName(call.LoopId)); if (subLoop != null) DrawLoop(doc, drawing, subLoop, layer, pos); // pos unchanged — subprograms are closed shapes that return to start break; } } return pos; } private static int GetLoopNumber(string loopName) { var idx = loopName.LastIndexOf(".loop-", StringComparison.OrdinalIgnoreCase); if (idx < 0) return -1; return int.TryParse(loopName.Substring(idx + 6), out var n) ? n : -1; } 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); }