Sfoglia il codice sorgente

Added automatic bounds calculation.

Kenric Nugteren 5 mesi fa
parent
commit
5543d3bc8f
4 ha cambiato i file con 276 aggiunte e 44 eliminazioni
  1. 48 18
      inabox.dxf/DrawData.cs
  2. 29 4
      inabox.dxf/DxfUtils.cs
  3. 133 21
      inabox.dxf/Objects.cs
  4. 66 1
      inabox.dxf/Utils.cs

+ 48 - 18
inabox.dxf/DrawData.cs

@@ -14,26 +14,24 @@ using Vector4 = System.Numerics.Vector4;
 
 namespace InABox.Dxf;
 
-internal class DrawData
+internal class TransformData
 {
     public DxfData Data { get; set; }
 
-    public Graphics Graphics { get; set; }
-
     private Stack<Matrix4x4> MatrixStack = new();
 
-    private Matrix4x4 Transform = Matrix4x4.Identity;
+    public Matrix4x4 Transform { get; private set; } = Matrix4x4.Identity;
 
-    public DrawData()
+    public void PushTransform()
     {
+        MatrixStack.Push(Transform);
     }
 
-    public void PushTransform()
+    protected virtual void UpdateTransform()
     {
-        MatrixStack.Push(Transform);
     }
 
-    private Matrix ProjectMatrix(Matrix4x4 matrix)
+    protected Matrix ProjectMatrix(Matrix4x4 matrix)
     {
         var elMatrix = new Matrix3x2();
         elMatrix.M11 = (float)matrix.M11;
@@ -50,10 +48,10 @@ internal class DrawData
     public void PopTransform()
     {
         Transform = MatrixStack.Pop();
-        Graphics.Transform = ProjectMatrix(Transform);
+        UpdateTransform();
     }
 
-    public Matrix4x4 ArbitraryAxisMatrix(Vector3 zAxis)
+    public static Matrix4x4 ArbitraryAxisMatrix(Vector3 zAxis)
     {
         if (zAxis.Equals(Vector3.UnitZ))
         {
@@ -71,29 +69,29 @@ internal class DrawData
     public void ArbitraryAxis(netDxf.Vector3 zAxis)
     {
         Transform = ArbitraryAxisMatrix(new((float)zAxis.X, (float)zAxis.Y, (float)zAxis.Z)) * Transform;
-        Graphics.Transform = ProjectMatrix(Transform);
+        UpdateTransform();
     }
     public void ArbitraryAxis(Vector3 zAxis)
     {
         Transform = ArbitraryAxisMatrix(zAxis) * Transform;
-        Graphics.Transform = ProjectMatrix(Transform);
+        UpdateTransform();
     }
 
     public void Translate(float x, float y)
     {
         Transform = Transform.Translate(x, y, 0);
-        Graphics.Transform = ProjectMatrix(Transform);
+        UpdateTransform();
     }
     public void Translate(PointF point)
     {
         Transform = Transform.Translate(point.X, point.Y, 0);
-        Graphics.Transform = ProjectMatrix(Transform);
+        UpdateTransform();
     }
 
     public void Rotate(float angle)
     {
         Transform = Transform.Rotate(0, 0, 1, angle);
-        Graphics.Transform = ProjectMatrix(Transform);
+        UpdateTransform();
     }
 
     public void Scale(float scale)
@@ -103,7 +101,7 @@ internal class DrawData
     public void Scale(float scaleX, float scaleY)
     {
         Transform = Transform.Scale(scaleX, scaleY, 1);
-        Graphics.Transform = ProjectMatrix(Transform);
+        UpdateTransform();
     }
 
     public float ConvertThickness(float thickness)
@@ -111,14 +109,35 @@ internal class DrawData
         return thickness == 0 ? 1f / ScaleFactor() : thickness;
     }
 
-    public PointF TransformVec(PointF vec)
+    public PointF TransformPoint(float x, float y)
+    {
+        var nVec = Vector4.Transform(new Vector4(x, y, 0, 1), Transform);
+        return new(nVec.X, nVec.Y);
+    }
+    public PointF TransformPoint(PointF vec)
     {
         var nVec = Vector4.Transform(new Vector4(vec.X, vec.Y, 0, 1), Transform);
         return new(nVec.X, nVec.Y);
     }
+    public PointF TransformPoint(netDxf.Vector2 vec)
+    {
+        var nVec = Vector4.Transform(new Vector4((float)vec.X, (float)vec.Y, 0, 1), Transform);
+        return new(nVec.X, nVec.Y);
+    }
+    public PointF TransformPoint(netDxf.Vector3 vec)
+    {
+        var nVec = Vector4.Transform(new Vector4((float)vec.X, (float)vec.Y, (float)vec.Z, 1), Transform);
+        return new(nVec.X, nVec.Y);
+    }
+
+    public PointF TransformVec(PointF vec)
+    {
+        var nVec = Vector4.Transform(new Vector4(vec.X, vec.Y, 0, 0), Transform);
+        return new(nVec.X, nVec.Y);
+    }
     public Vector2 TransformVec(Vector2 vec)
     {
-        var nVec = Vector4.Transform(new Vector4(vec.X, vec.Y, 0, 1), Transform);
+        var nVec = Vector4.Transform(new Vector4(vec.X, vec.Y, 0, 0), Transform);
         return new(nVec.X, nVec.Y);
     }
 
@@ -137,3 +156,14 @@ internal class DrawData
         return new PointF((float)vec.X, (float)vec.Y);
     }
 }
+
+internal class DrawData : TransformData
+{
+    public Graphics Graphics { get; set; }
+
+    protected override void UpdateTransform()
+    {
+        base.UpdateTransform();
+        Graphics.Transform = ProjectMatrix(Transform);
+    }
+}

+ 29 - 4
inabox.dxf/DxfUtils.cs

@@ -55,6 +55,11 @@ public class DxfData
         Layers = layers.ToHashSet();
     }
 
+    public bool ShouldDraw(EntityObject obj)
+    {
+        return obj.IsVisible && HasLayer(obj.Layer);
+    }
+
     public string LayoutName
     {
         get => Layout.Name;
@@ -135,10 +140,11 @@ public static class DxfUtils
         Brush _brush = new SolidBrush(Color.White);
         graphics.FillRectangle(_brush, 0, 0, graphics.VisibleClipBounds.Width, graphics.VisibleClipBounds.Height);
 
-        drawData.Translate(graphics.VisibleClipBounds.Width / 2, graphics.VisibleClipBounds.Height / 2);
+        // drawData.Translate(graphics.VisibleClipBounds.Width / 2, graphics.VisibleClipBounds.Height / 2);
 
         drawData.Scale(scale, scale);
-        drawData.Translate(-data.Origin.X - data.Size.Width / 2, -data.Origin.Y - data.Size.Height / 2);
+        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)
         {
@@ -164,6 +170,26 @@ public static class DxfUtils
         return new(document, settings);
     }
 
+    /// <summary>
+    /// Returns <see langword="null"/> if the bounds are completely empty.
+    /// </summary>
+    /// <param name="data"></param>
+    /// <returns></returns>
+    public static RectangleF? CalculateDxfSize(DxfData data)
+    {
+        var transformData = new TransformData { Data = data };
+
+        RectangleF? bounds = null;
+        foreach(var el in data.Document.Entities.All)
+        {
+            if(ConvertEl(el) is IDxfObject obj)
+            {
+                bounds = Utils.CombineBounds(bounds, obj.GetBounds(transformData));
+            }
+        }
+        return bounds;
+    }
+
     public static Bitmap ProcessImage(DxfData data)
     {
         var height = data.Size.Height;
@@ -174,9 +200,8 @@ public static class DxfUtils
         float ratioY = (float)data.Settings.ImageSize.Height / height;
 
         var scale = Math.Min(ratioX, ratioY);
-        var margin = 100;
 
-        var _result = new Bitmap((int)(width * scale) + (margin * 2), (int)(height * scale) + (margin * 2));
+        var _result = new Bitmap((int)(width * scale), (int)(height * scale));
         using (var _graphics = Graphics.FromImage(_result))
         {
             _graphics.SmoothingMode = SmoothingMode.AntiAlias;

+ 133 - 21
inabox.dxf/Objects.cs

@@ -14,6 +14,8 @@ namespace InABox.Dxf;
 internal interface IDxfObject
 {
     void Draw(DrawData data);
+
+    RectangleF? GetBounds(TransformData data);
 }
 
 internal class DxfLine : IDxfObject
@@ -22,8 +24,21 @@ internal class DxfLine : IDxfObject
 
     public void Draw(DrawData data)
     {
-        if (!Line.IsVisible || !data.Data.HasLayer(Line.Layer)) return;
+        if (!data.Data.ShouldDraw(Line)) return;
+
+        data.PushTransform();
+        //data.ArbitraryAxis(Line.Normal);
         data.Graphics.DrawLine(new Pen(Color.Black, data.ConvertThickness((float)Line.Thickness)), DrawData.ConvertPoint(Line.StartPoint), DrawData.ConvertPoint(Line.EndPoint));
+        data.PopTransform();
+    }
+
+    public RectangleF? GetBounds(TransformData data)
+    {
+        if (!data.Data.ShouldDraw(Line)) return null;
+
+        return Utils.RectangleFromPoints(
+            data.TransformPoint(Line.StartPoint),
+            data.TransformPoint(Line.EndPoint));
     }
 }
 
@@ -41,7 +56,7 @@ internal class DxfInsert : IDxfObject
 
     public void Draw(DrawData data)
     {
-        if (!Insert.IsVisible || !data.Data.HasLayer(Insert.Layer)) return;
+        if (!data.Data.ShouldDraw(Insert)) return;
 
         // var transformation = Insert.GetTransformation();
         // var translation = Insert.Position - transformation * Insert.Block.Origin;
@@ -71,6 +86,23 @@ internal class DxfInsert : IDxfObject
         //     DxfUtils.ConvertEl(obj)?.Draw(data);
         // }
     }
+
+    public RectangleF? GetBounds(TransformData data)
+    {
+        if (!data.Data.ShouldDraw(Insert)) return null;
+
+        data.PushTransform();
+        data.Translate(new PointF((float)Insert.Position.X, (float)Insert.Position.Y));
+        data.ArbitraryAxis(Insert.Normal);
+        data.Rotate((float)Insert.Rotation);
+        data.Scale((float)Insert.Scale.X, (float)Insert.Scale.Y);
+
+        var bounds = Utils.CombineBounds(Objects.Select(x => x.GetBounds(data)));
+
+        data.PopTransform();
+
+        return bounds;
+    }
 }
 
 internal class DxfEllipse : IDxfObject
@@ -84,7 +116,7 @@ internal class DxfEllipse : IDxfObject
 
     public void Draw(DrawData data)
     {
-        if (!Ellipse.IsVisible || !data.Data.HasLayer(Ellipse.Layer)) return;
+        if (!data.Data.ShouldDraw(Ellipse)) return;
 
         foreach(var obj in Ellipse.ToPolyline2D(100).Explode())
         {
@@ -98,6 +130,16 @@ internal class DxfEllipse : IDxfObject
 
         // data.Graphics.DrawArc(new Pen(Color.Black, data.ConvertThickness((float)Ellipse.Thickness)), center.X - size.Width / 2, center.Y - size.Height / 2, size.Width, size.Height, startAngle, Utils.Mod(endAngle - startAngle, 360));
     }
+
+    public RectangleF? GetBounds(TransformData data)
+    {
+        if (!data.Data.ShouldDraw(Ellipse)) return null;
+
+        var halfSize = new netDxf.Vector3(Ellipse.MajorAxis / 2, Ellipse.MinorAxis / 2, 0);
+        return Utils.RectangleFromPoints(
+            data.TransformPoint(Ellipse.Center - halfSize),
+            data.TransformPoint(Ellipse.Center + halfSize));
+    }
 }
 
 internal class DxfSolid : IDxfObject
@@ -111,7 +153,7 @@ internal class DxfSolid : IDxfObject
 
     public void Draw(DrawData data)
     {
-        if (!Solid.IsVisible || !data.Data.HasLayer(Solid.Layer)) return;
+        if (!data.Data.ShouldDraw(Solid)) return;
 
         var vertices = new Vector2[]
         {
@@ -122,6 +164,21 @@ internal class DxfSolid : IDxfObject
         };
         data.Graphics.FillPolygon(new SolidBrush(Color.Black), vertices.ToArray(x => DrawData.ConvertPoint(x)));
     }
+
+    public RectangleF? GetBounds(TransformData data)
+    {
+        if (!data.Data.ShouldDraw(Solid)) return null;
+
+        var vertices = new Vector2[]
+        {
+            Solid.FirstVertex,
+            Solid.SecondVertex,
+            Solid.FourthVertex, // Apparently the third and fourth are the wrong way round, so I've mirrored that here.
+            Solid.ThirdVertex
+        };
+        return Utils.RectangleFromPoints(
+            vertices.ToArray(data.TransformPoint));
+    }
 }
 
 internal class DxfPolyline2D : IDxfObject
@@ -135,7 +192,7 @@ internal class DxfPolyline2D : IDxfObject
 
     public void Draw(DrawData data)
     {
-        if (!Polyline.IsVisible || !data.Data.HasLayer(Polyline.Layer)) return;
+        if (!data.Data.ShouldDraw(Polyline)) return;
 
         var entities = Polyline.Explode();
         foreach(var entity in entities)
@@ -164,6 +221,14 @@ internal class DxfPolyline2D : IDxfObject
 
         // }
     }
+
+    public RectangleF? GetBounds(TransformData data)
+    {
+        if (!data.Data.ShouldDraw(Polyline)) return null;
+
+        var entities = Polyline.Explode();
+        return Utils.CombineBounds(entities.Select(x => DxfUtils.ConvertEl(x)?.GetBounds(data)));
+    }
 }
 
 internal class DxfMText : IDxfObject
@@ -175,10 +240,8 @@ internal class DxfMText : IDxfObject
         MText = text;
     }
 
-    public void Draw(DrawData data)
+    private Font GetFont()
     {
-        if (!MText.IsVisible || !data.Data.HasLayer(MText.Layer)) return;
-
         FontFamily fontFamily;
         if (MText.Style.FontFamilyName.IsNullOrWhiteSpace())
         {
@@ -188,21 +251,28 @@ internal class DxfMText : IDxfObject
         {
             fontFamily = new FontFamily(MText.Style.FontFamilyName);
         }
-        var font = new Font(fontFamily, (float)MText.Height, MText.Style.FontStyle switch
+        return new Font(fontFamily, (float)MText.Height, MText.Style.FontStyle switch
         {
             netDxf.Tables.FontStyle.Bold => FontStyle.Bold,
             netDxf.Tables.FontStyle.Italic => FontStyle.Italic,
             netDxf.Tables.FontStyle.Regular or _ => FontStyle.Regular,
         });
+    }
+
+    private string GetText()
+    {
+        return MText.PlainText().Replace("^M", "");
+    }
 
-        var text = MText.PlainText().Replace("^M", "");
+    private static Bitmap _placeholderBitmap = new Bitmap(1, 1);
 
-        data.PushTransform();
+    private void Transform(TransformData data, Font font, string text, Graphics g)
+    {
         data.Translate(new PointF((float)MText.Position.X, (float)MText.Position.Y));
         data.Rotate((float)MText.Rotation);
         data.Scale(1, -1);
 
-        var size = data.Graphics.MeasureString(text, font, new PointF(), StringFormat.GenericTypographic);
+        var size = g.MeasureString(text, font, new PointF(), StringFormat.GenericTypographic);
         switch (MText.AttachmentPoint)
         {
             case MTextAttachmentPoint.MiddleLeft:
@@ -219,7 +289,7 @@ internal class DxfMText : IDxfObject
                 var ascent = font.FontFamily.GetCellAscent(font.Style);
                 var lineSpace = font.FontFamily.GetLineSpacing(font.Style);
                 var baseline = ascent * font.Height / font.FontFamily.GetEmHeight(font.Style);
-                var ratio = font.GetHeight(data.Graphics) / lineSpace;
+                var ratio = font.GetHeight(g) / lineSpace;
                 data.Translate(new PointF(0, -baseline + ascent * ratio));
                 break;
         }
@@ -241,34 +311,76 @@ internal class DxfMText : IDxfObject
                 data.Translate(new PointF(-(float)size.Width, 0));
                 break;
         }
+    }
+
+    public void Draw(DrawData data)
+    {
+        if (!data.Data.ShouldDraw(MText)) return;
 
+        var font = GetFont();
+        var text = GetText();
+
+        data.PushTransform();
+        Transform(data, font, text, data.Graphics);
         data.Graphics.DrawString(text, font, new SolidBrush(Color.Black), new PointF(0, 0), StringFormat.GenericTypographic);
         data.PopTransform();
     }
+
+    public RectangleF? GetBounds(TransformData data)
+    {
+        if (!data.Data.ShouldDraw(MText)) return null;
+
+        var font = GetFont();
+        var text = GetText();
+
+        data.PushTransform();
+        using var g = Graphics.FromImage(_placeholderBitmap);
+        Transform(data, font, text, g);
+
+        var size = g.MeasureString(text, font, new PointF(), StringFormat.GenericTypographic);
+        var bounds = Utils.RectangleFromPoints(
+            data.TransformPoint(0, 0),
+            data.TransformPoint(size.Width, size.Height));
+
+        data.PopTransform();
+        return bounds;
+    }
 }
 
 internal class DxfDimension : IDxfObject
 {
     public Dimension Dimension { get; set; }
 
+    public List<IDxfObject> Objects { get; set; }
+
     public DxfDimension(Dimension dimension)
     {
         Dimension = dimension;
+
+        if(Dimension.Block is null)
+        {
+            Objects = new();
+        }
+        else
+        {
+            Objects = Dimension.Block.Entities.Select(DxfUtils.ConvertEl).NotNull().ToList();
+        }
     }
 
     public void Draw(DrawData data)
     {
-        if (!Dimension.IsVisible || !data.Data.HasLayer(Dimension.Layer)) return;
+        if (!data.Data.ShouldDraw(Dimension)) return;
 
-        if(Dimension.Block is null)
+        foreach(var entity in Objects)
         {
-            return;
+            entity.Draw(data);
         }
+    }
 
-        var entities = Dimension.Block.Entities;
-        foreach(var entity in entities)
-        {
-            DxfUtils.ConvertEl(entity)?.Draw(data);
-        }
+    public RectangleF? GetBounds(TransformData data)
+    {
+        if (!data.Data.ShouldDraw(Dimension)) return null;
+
+        return Utils.CombineBounds(Objects.Select(x => x.GetBounds(data)));
     }
 }

+ 66 - 1
inabox.dxf/Utils.cs

@@ -1,5 +1,7 @@
-using System;
+using InABox.Core;
+using System;
 using System.Collections.Generic;
+using System.ComponentModel;
 using System.Drawing;
 using System.Drawing.Drawing2D;
 using System.Linq;
@@ -64,4 +66,67 @@ internal static class Utils
 
         return rotMatrix * matrix;
     }
+
+    public static RectangleF? CombineBounds(IEnumerable<RectangleF?> bounds)
+    {
+        var e = bounds.NotNull().GetEnumerator();
+        if (!e.MoveNext())
+        {
+            return null;
+        }
+
+        var bound = e.Current;
+        while(e.MoveNext())
+        {
+            var thisBound = e.Current;
+
+            var x = Math.Min(bound.X, thisBound.X);
+            var y = Math.Min(bound.Y, thisBound.Y);
+            var right = Math.Max(bound.Right, thisBound.Right);
+            var bottom = Math.Max(bound.Bottom, thisBound.Bottom);
+
+            bound.X = x;
+            bound.Y = y;
+            bound.Height = bottom - bound.Y;
+            bound.Width = right - bound.X;
+        }
+
+        return bound;
+    }
+    public static RectangleF? CombineBounds(params RectangleF?[] bounds)
+    {
+        return CombineBounds((IEnumerable<RectangleF?>)bounds);
+    }
+
+    public static RectangleF AddPoint(RectangleF rectangle, PointF point)
+    {
+        var x = Math.Min(rectangle.X, point.X);
+        var y = Math.Min(rectangle.Y, point.Y);
+        var right = Math.Max(rectangle.Right, point.X);
+        var bottom = Math.Max(rectangle.Bottom, point.Y);
+        rectangle.X = x;
+        rectangle.Y = y;
+        rectangle.Height = bottom - rectangle.Y;
+        rectangle.Width = right - rectangle.X;
+        return rectangle;
+    }
+    public static RectangleF? RectangleFromPoints(params PointF[] points)
+    {
+        if (points.Length == 0) return null;
+
+        var rectangle = new RectangleF(points[0].X, points[0].Y, 0, 0);
+        for(int i = 1; i < points.Length; ++i)
+        {
+            var point = points[i];
+            var x = Math.Min(rectangle.X, point.X);
+            var y = Math.Min(rectangle.Y, point.Y);
+            var right = Math.Max(rectangle.Right, point.X);
+            var bottom = Math.Max(rectangle.Bottom, point.Y);
+            rectangle.X = x;
+            rectangle.Y = y;
+            rectangle.Height = bottom - rectangle.Y;
+            rectangle.Width = right - rectangle.X;
+        }
+        return rectangle;
+    }
 }