Browse Source

Dxf now renders PDF

Kenric Nugteren 5 months ago
parent
commit
40ff3c8c9d
5 changed files with 485 additions and 40 deletions
  1. 314 31
      inabox.dxf/DrawData.cs
  2. 121 2
      inabox.dxf/DxfUtils.cs
  3. 1 0
      inabox.dxf/InABox.Dxf.csproj
  4. 25 7
      inabox.dxf/Objects.cs
  5. 24 0
      inabox.dxf/Utils.cs

+ 314 - 31
inabox.dxf/DrawData.cs

@@ -2,6 +2,9 @@
 using netDxf.Tables;
 using Svg;
 using Svg.Transforms;
+using Syncfusion.Pdf;
+using Syncfusion.Pdf.Graphics;
+using Syncfusion.Pdf.Parsing;
 using System;
 using System.Collections.Generic;
 using System.Drawing;
@@ -14,10 +17,36 @@ using FontStyle = System.Drawing.FontStyle;
 using Vector2 = System.Numerics.Vector2;
 using Vector3 = System.Numerics.Vector3;
 using Vector4 = System.Numerics.Vector4;
+using SPdfGraphics = Syncfusion.Pdf.Graphics.PdfGraphics;
+using Syncfusion.Pdf.Tables;
+using netDxf.Entities;
 
 namespace InABox.Dxf;
 
-internal class TransformData
+public enum TextAlignment
+{
+    Left,
+    Center,
+    Right,
+    Justify
+}
+
+public enum TextLineAlignment
+{
+    Top,
+    Center,
+    Bottom,
+    Baseline
+}
+
+public class TextFormat
+{
+    public TextAlignment Alignment { get; set; } = TextAlignment.Left;
+
+    public TextLineAlignment LineAlignment { get; set; } = TextLineAlignment.Top;
+}
+
+public class TransformData
 {
     public DxfData Data { get; set; }
 
@@ -30,6 +59,11 @@ internal class TransformData
         MatrixStack.Push(Transform);
     }
 
+    public void TransformBy(Matrix4x4 matrix)
+    {
+        Transform = matrix * Transform;
+    }
+
     protected virtual void UpdateTransform()
     {
     }
@@ -107,11 +141,6 @@ internal class TransformData
         UpdateTransform();
     }
 
-    public float ConvertThickness(float thickness)
-    {
-        return thickness == 0 ? 1f / ScaleFactor() : thickness;
-    }
-
     public PointF TransformPoint(float x, float y)
     {
         var nVec = Vector4.Transform(new Vector4(x, y, 0, 1), Transform);
@@ -160,9 +189,61 @@ internal class TransformData
     }
 }
 
-internal class DrawData : TransformData
+public class DrawData : TransformData
 {
-    public IGraphics Graphics { get; set; }
+    private IGraphics _graphics;
+    public IGraphics Graphics
+    {
+        get => _graphics;
+        set
+        {
+            _graphics = value;
+            _graphics.DrawData = this;
+        }
+    }
+
+    private Stack<Color> _blockColours = new();
+
+    public Color BlockColour { get; set; } = Color.Black;
+
+    public void PushBlockColour(AciColor colour, EntityObject? obj)
+    {
+        _blockColours.Push(BlockColour);
+        BlockColour = ResolveColour(colour, obj);
+    }
+
+    public void PushBlockColour(Color colour)
+    {
+        _blockColours.Push(BlockColour);
+        BlockColour = colour;
+    }
+
+    public void PopBlockColour()
+    {
+        BlockColour = _blockColours.Pop();
+    }
+
+    public Color ResolveColour(AciColor colour, EntityObject? obj)
+    {
+        if (colour.IsByBlock)
+        {
+            return _blockColours.Count > 0
+                ? BlockColour
+                : (obj?.Layer is null ? Color.Black : ResolveColour(obj.Layer.Color, null));
+        }
+        else if (colour.IsByLayer)
+        {
+            return obj?.Layer is null ? Color.Black : ResolveColour(obj.Layer.Color, null);
+        }
+        if(colour.Index == 7)
+        {
+            return Color.Black;
+        }
+        else
+        {
+            return colour.ToColor();
+        }
+    }
 
     protected override void UpdateTransform()
     {
@@ -173,6 +254,8 @@ internal class DrawData : TransformData
 
 public interface IGraphics
 {
+    DrawData DrawData { get; set; }
+
     void SetTransform(Matrix transform);
 
     void DrawLine(Color color, float thickness, params PointF[] points);
@@ -187,9 +270,11 @@ public interface IGraphics
 
     void SetFont(string fontName, float fontSize, FontStyle fontStyle = FontStyle.Regular);
 
-    void DrawText(string text, Color color, PointF position);
+    void DrawText(string text, Color color, PointF position, float rotation, TextFormat format);
 
     void Clear(Color color);
+
+    void Finish();
 }
 
 public class GdiGraphics : IGraphics
@@ -198,11 +283,18 @@ public class GdiGraphics : IGraphics
 
     private Font Font { get; set; }
 
+    public DrawData DrawData { get; set; }
+
     public GdiGraphics(Graphics graphics)
     {
         Graphics = graphics;
     }
 
+    // public float ConvertThickness(float thickness)
+    // {
+    //     return thickness == 0 ? 1f / ScaleFactor() : thickness;
+    // }
+
     public void DrawLine(Color color, float thickness, params PointF[] points)
     {
         if(points.Length == 2)
@@ -215,9 +307,56 @@ public class GdiGraphics : IGraphics
         }
     }
 
-    public void DrawText(string text, Color color, PointF position)
+    private void TransformText(string text, Font font, PointF position, float rotation, TextFormat format)
     {
-        Graphics.DrawString(text, Font, new SolidBrush(color), position, StringFormat.GenericTypographic);
+        DrawData.Translate(position);
+        DrawData.Rotate(rotation);
+        DrawData.Scale(1, -1);
+
+        var size = Graphics.MeasureString(text, font, new PointF(), StringFormat.GenericTypographic);
+        switch (format.LineAlignment)
+        {
+            case TextLineAlignment.Center:
+                DrawData.Translate(new PointF(0, -size.Height / 2));
+                break;
+            case TextLineAlignment.Bottom:
+                DrawData.Translate(new PointF(0, -size.Height));
+                break;
+            case TextLineAlignment.Baseline:
+                var ascent = font.FontFamily.GetCellAscent(font.Style);
+                var baseline = ascent * font.Height / font.FontFamily.GetEmHeight(font.Style);
+                DrawData.Translate(new PointF(0, -baseline));
+                break;
+            case TextLineAlignment.Top:
+                ascent = font.FontFamily.GetCellAscent(font.Style);
+                baseline = ascent * font.Height / font.FontFamily.GetEmHeight(font.Style);
+                var lineSpace = font.FontFamily.GetLineSpacing(font.Style);
+                var ratio = font.GetHeight(Graphics) / lineSpace;
+                DrawData.Translate(new PointF(0, -baseline + ascent * ratio));
+                break;
+        }
+
+        switch (format.Alignment)
+        {
+            case TextAlignment.Left:
+                break;
+            case TextAlignment.Center:
+                DrawData.Translate(new PointF(-(float)size.Width / 2, 0));
+                break;
+            case TextAlignment.Right:
+                DrawData.Translate(new PointF(-(float)size.Width, 0));
+                break;
+        }
+    }
+
+    public void DrawText(string text, Color color, PointF position, float rotation, TextFormat format)
+    {
+        DrawData.PushTransform();
+        TransformText(text, Font, position, rotation, format);
+
+        Graphics.DrawString(text, Font, new SolidBrush(color), new PointF(0, 0), StringFormat.GenericTypographic);
+
+        DrawData.PopTransform();
     }
 
     public void FillPolygon(Color color, params PointF[] points)
@@ -248,20 +387,146 @@ public class GdiGraphics : IGraphics
     {
         Graphics.Clear(color);
     }
+
+    public void Finish()
+    {
+    }
+}
+
+public class PdfGraphics : IGraphics
+{
+    public DrawData DrawData { get; set; }
+
+    private SPdfGraphics Graphics { get; set; }
+
+    private Matrix _transform = new();
+
+    private PdfFont _font = new PdfStandardFont(PdfFontFamily.Helvetica, 12, PdfFontStyle.Regular);
+
+    public PdfGraphics(SPdfGraphics graphics)
+    {
+        Graphics = graphics;
+    }
+
+    public void Clear(Color color)
+    {
+    }
+
+    public void SetTransform(Matrix transform)
+    {
+        _transform = transform;
+    }
+
+    public void DrawLine(Color color, float thickness, params PointF[] points)
+    {
+        _transform.TransformPoints(points);
+
+        var pen = new PdfPen(color, thickness);
+        for(int i = 0; i < points.Length - 1; ++i)
+        {
+            Graphics.DrawLine(pen, points[i], points[i + 1]);
+        }
+    }
+
+    public void SetFont(float fontSize, FontStyle fontStyle = FontStyle.Regular)
+    {
+        _font = new PdfTrueTypeFont(new Font(SystemFonts.DefaultFont.FontFamily, fontSize));
+    }
+
+    public void SetFont(string fontName, float fontSize, FontStyle fontStyle = FontStyle.Regular)
+    {
+        _font = new PdfTrueTypeFont(new Font(fontName, fontSize));
+    }
+
+    public void DrawText(string text, Color color, PointF position, float rotation, TextFormat format)
+    {
+        var fmt = new PdfStringFormat();
+        fmt.Alignment = format.Alignment switch
+        {
+            TextAlignment.Center => PdfTextAlignment.Center,
+            TextAlignment.Right => PdfTextAlignment.Right,
+            TextAlignment.Justify => PdfTextAlignment.Justify,
+            _ => PdfTextAlignment.Left,
+        };
+        fmt.LineAlignment = format.LineAlignment switch
+        {
+            TextLineAlignment.Center => PdfVerticalAlignment.Middle,
+            TextLineAlignment.Bottom => PdfVerticalAlignment.Bottom,
+            _ => PdfVerticalAlignment.Top
+        };
+        if(format.LineAlignment == TextLineAlignment.Baseline)
+        {
+            fmt.EnableBaseline = true;
+        }
+
+        Graphics.Save();
+
+        var point = _transform.Transform(position);
+        Graphics.TranslateTransform(point.X, point.Y);
+        Graphics.RotateTransform(-(_transform.GetRotation() + rotation));
+        var scale = (_font.Height / _font.Size) * _transform.GetScale();
+        Graphics.ScaleTransform(scale, scale);
+        Graphics.DrawString(text, _font, new PdfSolidBrush(color), new PointF(), fmt);
+
+        Graphics.Restore();
+    }
+
+    public void FillPolygon(Color color, params PointF[] points)
+    {
+        _transform.TransformPoints(points);
+        var brush = new PdfSolidBrush(color);
+        Graphics.DrawPolygon(brush, points);
+    }
+
+    public void Finish()
+    {
+    }
 }
 
 public class SvgGraphics : IGraphics
 {
+    public DrawData DrawData { get; set; }
+
     public SvgDocument Document { get; set; }
 
-    private SvgMatrix _transform;
+    private SvgMatrix _transform = new(new Matrix().Elements.ToList());
 
     private SvgGroup _group;
+    private Matrix _groupTransform = new();
+
+    private static string[][] _groupProps = new[]
+    {
+        new[] { "stroke-width", "style" },
+        new[] { "vector-effect" }
+    };
 
     public SvgGraphics(SvgDocument document)
     {
         Document = document;
         _group = new SvgGroup();
+        Document.Stroke = new SvgColourServer(Color.Black);
+    }
+
+    private void PushGroup()
+    {
+        if(_group.Children.Count > 0)
+        {
+            Document.Children.Add(_group);
+        }
+    }
+
+    private void AddChild(SvgElement item)
+    {
+        var transMat = _transform.Matrix;
+        if(!Equals(transMat, _groupTransform))
+        {
+            PushGroup();
+
+            _group = new();
+            _group.Transforms = new() { _transform };
+            _groupTransform = transMat;
+        }
+        _group.Children.Add(item);
     }
 
     public void SetTransform(Matrix transform)
@@ -273,38 +538,51 @@ public class SvgGraphics : IGraphics
     {
         if(points.Length == 2)
         {
-            Document.Children.Add(new SvgLine
+            var line = new SvgLine
             {
                 StartX = points[0].X,
                 StartY = points[0].Y,
                 EndX = points[1].X,
-                EndY = points[1].Y,
-                Transforms = new()
-                {
-                    _transform
-                },
-                Stroke = new SvgColourServer(color),
-                StrokeWidth = thickness
-            });
+                EndY = points[1].Y
+            };
+            if(color != Color.Black)
+            {
+                line.Stroke = new SvgColourServer(color);
+            }
+            if(thickness == 0)
+            {
+                line.StrokeWidth = 1;
+                line.CustomAttributes.Add("vector-effect", "non-scaling-stroke");
+            }
+            else
+            {
+                line.StrokeWidth = thickness;
+            }
+            AddChild(line);
         }
         else
         {
-            var line = new SvgPolyline
+            var line = new SvgPolyline();
+            if(color != Color.Black)
             {
-                Transforms = new()
-                {
-                    _transform
-                },
-                Stroke = new SvgColourServer(color),
-                StrokeWidth = thickness
-            };
+                line.Stroke = new SvgColourServer(color);
+            }
             foreach(var point in points)
             {
                 line.Points.Add(point.X);
                 line.Points.Add(point.Y);
             }
+            if(thickness == 0)
+            {
+                line.StrokeWidth = 1;
+                line.CustomAttributes.Add("vector-effect", "non-scaling-stroke");
+            }
+            else
+            {
+                line.StrokeWidth = thickness;
+            }
 
-            Document.Children.Add(line);
+            AddChild(line);
         }
     }
 
@@ -320,11 +598,16 @@ public class SvgGraphics : IGraphics
     {
     }
 
-    public void DrawText(string text, Color color, PointF position)
+    public void DrawText(string text, Color color, PointF position, float rotation, TextFormat format)
     {
     }
 
     public void Clear(Color color)
     {
     }
+
+    public void Finish()
+    {
+        PushGroup();
+    }
 }

+ 121 - 2
inabox.dxf/DxfUtils.cs

@@ -1,11 +1,13 @@
 using System.Drawing;
 using System.Drawing.Drawing2D;
+using System.Numerics;
 using InABox.Core;
 using netDxf;
 using netDxf.Entities;
 using netDxf.Objects;
 using netDxf.Tables;
 using Svg;
+using Syncfusion.Pdf;
 using Point = System.Drawing.Point;
 
 namespace InABox.Dxf;
@@ -122,6 +124,10 @@ public static class DxfUtils
         {
             return null;
         }
+        else if (el is netDxf.Entities.Viewport viewport)
+        {
+            return null;
+        }
         else
         {
             return null;
@@ -146,11 +152,12 @@ public static class DxfUtils
         drawData.Translate(-data.Origin.X, -data.Origin.Y);
         // drawData.Translate(-data.Origin.X - data.Size.Width / 2, -data.Origin.Y - data.Size.Height / 2);
 
-        foreach(var el in data.Document.Entities.All)
+        foreach(var el in data.Layout.AssociatedBlock.Entities)
         {
             var item = ConvertEl(el);
             item?.Draw(drawData);
         }
+        graphics.Finish();
     }
 
     public static DxfData LoadDxf(string filename, DxfImportSettings? settings = null)
@@ -212,6 +219,117 @@ public static class DxfUtils
         return _result;
     }
 
+    public static PdfDocument ProcessPdf(DxfData data)
+    {
+        var doc = new PdfDocument();
+
+        doc.PageSettings.Size = data.Size;
+        doc.PageSettings.SetMargins(0);
+
+        var page = doc.Pages.Add();
+
+        var graphics = page.Graphics;
+
+        var drawData = new DrawData() { Graphics = new PdfGraphics(page.Graphics), Data = data };
+        drawData.PushTransform();
+        drawData.Translate(data.Size.Width / 2, data.Size.Height / 2);
+        drawData.Scale(1, -1);
+        drawData.Translate(-data.Size.Width / 2, -data.Size.Height / 2);
+        drawData.Translate(-data.Origin.X, -data.Origin.Y);
+
+        if (data.Layout.IsPaperSpace)
+        {
+            var modelSpace = data.Document.Layouts.First(x => x.Name == "Model");
+            foreach(var el in modelSpace.AssociatedBlock.Entities)
+            {
+                var item = ConvertEl(el);
+                item?.Draw(drawData);
+            }
+        }
+
+        foreach(var el in data.Layout.AssociatedBlock.Entities)
+        {
+            var item = ConvertEl(el);
+            item?.Draw(drawData);
+        }
+        drawData.PopTransform();
+        drawData.Graphics.Finish();
+
+        return doc;
+    }
+    // public static PdfDocument ProcessPdf(DxfData data)
+    // {
+    //     var doc = new PdfDocument();
+
+    //     var size = data.Size;
+    //     var origin = data.Origin;
+
+    //     foreach(var layout in data.LayoutNames)
+    //     {
+    //         data.LayoutName = layout;
+
+    //         Matrix4x4 transform;
+    //         if (data.Layout.IsPaperSpace)
+    //         {
+    //             var viewport = data.Layout.Viewport;
+
+    //             var scale = (float)(viewport.ViewHeight == 0 ? 0 : viewport.Height / viewport.ViewHeight);
+
+    //             data.Origin = new((float)(viewport.ViewCenter.X - viewport.Width / 2), (float)(viewport.ViewCenter.Y - viewport.Height / 2));
+    //             data.Size = new((float)viewport.Width, (float)viewport.Height);
+
+    //             var normal = -System.Numerics.Vector3.Cross(viewport.UcsXAxis.ToVec3(), viewport.UcsYAxis.ToVec3());
+    //             transform = Matrix4x4.CreateLookAt(viewport.UcsOrigin.ToVec3(), viewport.UcsOrigin.ToVec3() + normal, viewport.UcsYAxis.ToVec3())
+    //                 * Matrix4x4.CreateScale(scale)
+    //                 * Matrix4x4.CreateRotationZ((float)viewport.TwistAngle);
+    //         }
+    //         else
+    //         {
+    //             data.Size = size;
+    //             data.Origin = origin;
+    //             transform = Matrix4x4.Identity;
+    //         }
+
+    //         var section = doc.Sections.Add();
+
+    //         section.PageSettings.Size = data.Size;
+    //         section.PageSettings.SetMargins(0);
+
+    //         var page = section.Pages.Add();
+
+    //         var graphics = page.Graphics;
+
+    //         var drawData = new DrawData() { Graphics = new PdfGraphics(page.Graphics), Data = data };
+    //         drawData.PushTransform();
+    //         drawData.Translate(data.Size.Width / 2, data.Size.Height / 2);
+    //         drawData.Scale(1, -1);
+    //         drawData.Translate(-data.Size.Width / 2, -data.Size.Height / 2);
+    //         drawData.Translate(-data.Origin.X, -data.Origin.Y);
+
+    //         drawData.TransformBy(transform);
+
+    //         if (data.Layout.IsPaperSpace)
+    //         {
+    //             var modelSpace = data.Document.Layouts.First(x => x.Name == "Model");
+    //             foreach(var el in modelSpace.AssociatedBlock.Entities)
+    //             {
+    //                 var item = ConvertEl(el);
+    //                 item?.Draw(drawData);
+    //             }
+    //         }
+
+    //         foreach(var el in data.Layout.AssociatedBlock.Entities)
+    //         {
+    //             var item = ConvertEl(el);
+    //             item?.Draw(drawData);
+    //         }
+    //         drawData.PopTransform();
+    //         drawData.Graphics.Finish();
+    //     }
+
+    //     return doc;
+    // }
+
     public static SvgDocument ProcessSvg(DxfData data)
     {
         var doc = new SvgDocument
@@ -219,11 +337,12 @@ public static class DxfUtils
             ViewBox = new(data.Origin.X, data.Origin.Y, data.Size.Width, data.Size.Height)
         };
         var drawData = new DrawData() { Graphics = new SvgGraphics(doc), Data = data };
-        foreach(var el in data.Document.Entities.All)
+        foreach(var el in data.Layout.AssociatedBlock.Entities)
         {
             var item = ConvertEl(el);
             item?.Draw(drawData);
         }
+        drawData.Graphics.Finish();
         return doc;
     }
 

+ 1 - 0
inabox.dxf/InABox.Dxf.csproj

@@ -9,6 +9,7 @@
     <ItemGroup>
         <PackageReference Include="netDxf.netstandard" Version="3.0.1" />
         <PackageReference Include="Svg" Version="3.4.7" />
+        <PackageReference Include="Syncfusion.Pdf.Wpf" Version="25.2.6" />
         <PackageReference Include="System.Drawing.Common" Version="8.0.6" />
     </ItemGroup>
 

+ 25 - 7
inabox.dxf/Objects.cs

@@ -1,5 +1,6 @@
 using InABox.Core;
 using netDxf;
+using netDxf.Blocks;
 using netDxf.Entities;
 using System;
 using System.Collections.Generic;
@@ -29,7 +30,7 @@ internal class DxfLine : IDxfObject
         data.PushTransform();
         //data.ArbitraryAxis(Line.Normal);
         data.Graphics.DrawLine(
-            Color.Black, data.ConvertThickness((float)Line.Thickness),
+            data.ResolveColour(Line.Color, Line), (float)Line.Thickness,
             DrawData.ConvertPoint(Line.StartPoint), DrawData.ConvertPoint(Line.EndPoint));
         data.PopTransform();
     }
@@ -69,11 +70,15 @@ internal class DxfInsert : IDxfObject
         data.Rotate((float)Insert.Rotation);
         data.Scale((float)Insert.Scale.X, (float)Insert.Scale.Y);
 
+        data.PushBlockColour(Insert.Color, Insert);
+
         foreach(var entity in Objects)
         {
             entity.Draw(data);
         }
 
+        data.PopBlockColour();
+
         data.PopTransform();
 
         // foreach(var entity in Insert.Explode())
@@ -164,7 +169,7 @@ internal class DxfSolid : IDxfObject
             Solid.FourthVertex, // Apparently the third and fourth are the wrong way round, so I've mirrored that here.
             Solid.ThirdVertex
         };
-        data.Graphics.FillPolygon(Color.Black, vertices.ToArray(x => DrawData.ConvertPoint(x)));
+        data.Graphics.FillPolygon(data.ResolveColour(Solid.Color, Solid), vertices.ToArray(x => DrawData.ConvertPoint(x)));
     }
 
     public RectangleF? GetBounds(TransformData data)
@@ -339,11 +344,22 @@ internal class DxfMText : IDxfObject
             });
         }
 
-        data.PushTransform();
-        using var g = Graphics.FromImage(_placeholderBitmap);
-        Transform(data, GetFont(), text, g);
-        data.Graphics.DrawText(text, Color.Black, new PointF(0, 0));
-        data.PopTransform();
+        var format = new TextFormat
+        {
+            LineAlignment = MText.AttachmentPoint switch
+            {
+                MTextAttachmentPoint.MiddleLeft or MTextAttachmentPoint.MiddleCenter or MTextAttachmentPoint.MiddleRight => TextLineAlignment.Center,
+                MTextAttachmentPoint.BottomLeft or MTextAttachmentPoint.BottomCenter or MTextAttachmentPoint.BottomRight => TextLineAlignment.Bottom,
+                _ => TextLineAlignment.Top,
+            },
+            Alignment = MText.AttachmentPoint switch
+            {
+                MTextAttachmentPoint.TopCenter or MTextAttachmentPoint.MiddleCenter or MTextAttachmentPoint.BottomCenter => TextAlignment.Center,
+                MTextAttachmentPoint.TopRight or MTextAttachmentPoint.MiddleRight or MTextAttachmentPoint.BottomRight => TextAlignment.Right,
+                _ => TextAlignment.Left,
+            }
+        };
+        data.Graphics.DrawText(text, data.ResolveColour(MText.Color, MText), DrawData.ConvertPoint(MText.Position), (float)MText.Rotation, format);
     }
 
     public RectangleF? GetBounds(TransformData data)
@@ -391,10 +407,12 @@ internal class DxfDimension : IDxfObject
     {
         if (!data.Data.ShouldDraw(Dimension)) return;
 
+        data.PushBlockColour(Dimension.Color, Dimension);
         foreach(var entity in Objects)
         {
             entity.Draw(data);
         }
+        data.PopBlockColour();
     }
 
     public RectangleF? GetBounds(TransformData data)

+ 24 - 0
inabox.dxf/Utils.cs

@@ -1,4 +1,5 @@
 using InABox.Core;
+using netDxf;
 using System;
 using System.Collections.Generic;
 using System.ComponentModel;
@@ -8,6 +9,7 @@ using System.Linq;
 using System.Numerics;
 using System.Text;
 using System.Threading.Tasks;
+using Vector3 = System.Numerics.Vector3;
 
 namespace InABox.Dxf;
 internal static class Utils
@@ -25,11 +27,33 @@ internal static class Utils
         return arr[0];
     }
 
+    public static float GetRotation(this Matrix matrix)
+    {
+        var vec = matrix.TransformVector(new PointF(1, 0));
+        return (float)Math.Atan2(vec.Y, vec.X);
+    }
+
+    public static float GetScale(this Matrix matrix)
+    {
+        var vec = matrix.TransformVector(new PointF(1, 0));
+        return (float)Math.Sqrt(vec.X * vec.X + vec.Y * vec.Y);
+    }
+
     public static float Mod(float x, float y)
     {
         return (float)(x - y * Math.Floor(x / y));
     }
 
+    public static Vector3 ToVec3(this netDxf.Vector3 vec)
+    {
+        return new((float)vec.X, (float)vec.Y, (float)vec.Z);
+    }
+    public static netDxf.Vector3 ToNetVec3(this Vector3 vec)
+    {
+        return new(vec.X, vec.Y, vec.Z);
+    }
+
+
     public static Matrix4x4 Translate(this Matrix4x4 matrix, float x, float y, float z)
     {
         return Matrix4x4.CreateTranslation(x, y, z) * matrix;