diff --git a/Elements.Serialization.SVG/src/BaseDrawing.cs b/Elements.Serialization.SVG/src/BaseDrawing.cs
new file mode 100644
index 000000000..f2fe35586
--- /dev/null
+++ b/Elements.Serialization.SVG/src/BaseDrawing.cs
@@ -0,0 +1,116 @@
+using System.Collections.Generic;
+using System.Linq;
+using Elements.Geometry;
+
+namespace Elements.Serialization.SVG
+{
+ ///
+ /// Base class for SVG documents
+ ///
+ public abstract class SvgBaseDrawing
+ {
+ ///
+ /// Scale applied to the model.
+ /// Usually it's a PageHeight to a ViewBoxHeight ratio (or a PageWidth to a ViewBoxWidth)
+ ///
+ public double Scale { get; protected set; }
+ ///
+ /// The width of the model bounding box.
+ ///
+ public float ViewBoxWidth
+ {
+ get { return _viewBoxWidth; }
+ protected set { _viewBoxWidth = value; }
+ }
+ ///
+ /// The height of the model bounding box.
+ ///
+ public float ViewBoxHeight
+ {
+ get { return _viewBoxHeight; }
+ protected set { _viewBoxHeight = value; }
+ }
+
+ ///
+ /// Get the scene bounds.
+ ///
+ public BBox3 SceneBounds
+ {
+ get { return _sceneBounds; }
+ protected set { _sceneBounds = value; }
+ }
+
+ ///
+ /// Computes scene bounds
+ ///
+ /// The set of the models
+ /// Returns the boundinf box around the input models
+ public static BBox3 ComputeSceneBounds(IList models)
+ {
+ var bounds = new BBox3(Vector3.Max, Vector3.Min);
+ foreach (var model in models)
+ {
+ foreach (var element in model.Elements)
+ {
+ if (element.Value is GeometricElement geo)
+ {
+ geo.UpdateRepresentations();
+ if ((geo.Representation == null || geo.Representation.SolidOperations.All(v => v.IsVoid)) &&
+ (geo.RepresentationInstances == null || !geo.RepresentationInstances.Any()))
+ {
+ continue;
+ }
+ geo.UpdateBoundsAndComputeSolid();
+
+ if (geo.Bounds.Volume.ApproximatelyEquals(0))
+ {
+ continue;
+ }
+
+ var bbMax = geo.Transform.OfPoint(geo.Bounds.Max);
+ var bbMin = geo.Transform.OfPoint(geo.Bounds.Min);
+ bounds.Extend(new[] { bbMax, bbMin });
+ }
+ }
+ }
+
+ return bounds;
+ }
+
+ ///
+ /// Gets the rotation angle that must be applied to the plan.
+ ///
+ /// The set of the models.
+ /// The orientation for a plan relative to the page.
+ /// The angle value that must be applied if rotation is Angle
+ /// The angle in degrees
+ protected static double GetRotationValueForPlan(IList models, PlanRotation rotation, double angle)
+ {
+ if (rotation == PlanRotation.Angle)
+ {
+ return angle;
+ }
+
+ var grids = models.SelectMany(m => m.AllElementsOfType()).Select(gl => gl.Curve).Where(gl => gl is Line).ToList();
+ if (!grids.Any())
+ {
+ return 0.0;
+ }
+
+ var longest = (Line)grids.OrderBy(g => g.Length()).First();
+
+ return rotation switch
+ {
+ PlanRotation.LongestGridHorizontal => -longest.Direction().PlaneAngleTo(Vector3.YAxis),
+ PlanRotation.LongestGridVertical => -longest.Direction().PlaneAngleTo(Vector3.XAxis),
+ PlanRotation.Angle => angle,
+ PlanRotation.None => 0.0,
+ _ => 0.0,
+ };
+ }
+
+ private BBox3 _sceneBounds;
+ private float _viewBoxHeight;
+ private float _viewBoxWidth;
+ }
+}
\ No newline at end of file
diff --git a/Elements.Serialization.SVG/src/DrawingActions/DrawPolygon.cs b/Elements.Serialization.SVG/src/DrawingActions/DrawPolygon.cs
new file mode 100644
index 000000000..689534f89
--- /dev/null
+++ b/Elements.Serialization.SVG/src/DrawingActions/DrawPolygon.cs
@@ -0,0 +1,36 @@
+using Elements.Geometry;
+
+namespace Elements.Serialization.SVG
+{
+ ///
+ /// Draw polygon action
+ ///
+ public class DrawPolygon : DrawingAction
+ {
+ ///
+ /// Initializes a new instance for DrawPolygon class.
+ ///
+ /// The polygon.
+ /// The svg context that can be used to draw the polygon (color, line thickness, etc.)
+ /// The text inside the polygon.
+ public DrawPolygon(Polygon polygon, SvgContext context, string text = "")
+ {
+ _polygon = polygon;
+ _text = text;
+ _context = context;
+ }
+
+ ///
+ /// Draws the polygon.
+ ///
+ /// The canvas where the polygon will be added.
+ public override void Draw(BaseSvgCanvas canvas)
+ {
+ canvas.DrawPolygon(_polygon, _context);
+ }
+
+ private Polygon _polygon;
+ private string _text;
+ private SvgContext _context;
+ }
+}
\ No newline at end of file
diff --git a/Elements.Serialization.SVG/src/DrawingActions/DrawText.cs b/Elements.Serialization.SVG/src/DrawingActions/DrawText.cs
new file mode 100644
index 000000000..b70d027fb
--- /dev/null
+++ b/Elements.Serialization.SVG/src/DrawingActions/DrawText.cs
@@ -0,0 +1,36 @@
+using Elements.Geometry;
+
+namespace Elements.Serialization.SVG
+{
+ ///
+ /// Draw text action
+ ///
+ public class DrawText : DrawingAction
+ {
+ ///
+ /// Initializes a new instance for DrawText class.
+ ///
+ /// The text.
+ /// The transformation of the text.
+ /// The svg context that can be used to draw the text (color, line thickness, etc.)
+ public DrawText(string text, Transform transform, SvgContext context)
+ {
+ _text = text;
+ _transform = transform;
+ _context = context;
+ }
+
+ ///
+ /// Draws the text.
+ ///
+ /// The canvas where the text will be added.
+ public override void Draw(BaseSvgCanvas canvas)
+ {
+ canvas.DrawText(_text, _transform, _context);
+ }
+
+ private string _text;
+ private Transform _transform;
+ private SvgContext _context;
+ }
+}
\ No newline at end of file
diff --git a/Elements.Serialization.SVG/src/DrawingActions/DrawingAction.cs b/Elements.Serialization.SVG/src/DrawingActions/DrawingAction.cs
new file mode 100644
index 000000000..cbc65ae9b
--- /dev/null
+++ b/Elements.Serialization.SVG/src/DrawingActions/DrawingAction.cs
@@ -0,0 +1,14 @@
+namespace Elements.Serialization.SVG
+{
+ ///
+ /// The drawing action (e.g. draw polygon, text)
+ ///
+ public abstract class DrawingAction
+ {
+ ///
+ /// Draw something using input drawing tool
+ ///
+ /// The canvas where the element will be added
+ public abstract void Draw(BaseSvgCanvas canvas);
+ }
+}
\ No newline at end of file
diff --git a/Elements.Serialization.SVG/src/DrawingTools/BaseCanvas.cs b/Elements.Serialization.SVG/src/DrawingTools/BaseCanvas.cs
new file mode 100644
index 000000000..6c814416e
--- /dev/null
+++ b/Elements.Serialization.SVG/src/DrawingTools/BaseCanvas.cs
@@ -0,0 +1,91 @@
+using System.Linq;
+using Elements.Geometry;
+
+namespace Elements.Serialization.SVG
+{
+ ///
+ /// The SVG drawing tool
+ ///
+ public abstract class BaseSvgCanvas
+ {
+ ///
+ /// The bounds of the scene.
+ ///
+ protected BBox3 SceneBounds { get; set; }
+ ///
+ /// The SVG document
+ ///
+ protected SvgBaseDrawing Document { get; }
+
+ ///
+ /// The height of the page.
+ ///
+ public double PageHeight { get; set; }
+ ///
+ /// The width of the page.
+ ///
+ public double PageWidth { get; set; }
+
+ ///
+ /// Initializes a new instance of BaseDrawingTool.
+ ///
+ /// The SVG document.
+ public BaseSvgCanvas(SvgBaseDrawing document)
+ {
+ Document = document;
+ }
+
+ ///
+ /// Sets the scene bounds
+ ///
+ /// The scene bounding box.
+ /// The plan rotation angle (in degrees).
+ public virtual void SetBounds(BBox3 sceneBounds, double rotation)
+ {
+ SceneBounds = sceneBounds;
+ var transform = new Transform(Vector3.Origin);
+ transform.Rotate(rotation);
+ var bounds = new BBox3(sceneBounds.Corners().Select(v => transform.OfPoint(v)));
+ var viewBoxWidth = (float)(bounds.Max.X - bounds.Min.X);
+ var viewBoxHeight = (float)(bounds.Max.Y - bounds.Min.Y);
+
+ PageHeight = viewBoxHeight;
+ PageWidth = viewBoxWidth;
+ }
+
+ ///
+ /// Draw polygon logic.
+ ///
+ /// The polygon.
+ /// The svg context that can be used to draw the polygon (color, line thickness, etc.)
+ public abstract void DrawPolygon(Polygon polygon, SvgContext context);
+
+ ///
+ /// Draw text logic.
+ ///
+ /// The text.
+ /// The text transformation matrix.
+ /// The svg context that can be used to draw the text (color, line thickness, etc.)
+ public abstract void DrawText(string text, Transform transform, SvgContext context);
+
+ ///
+ /// Draw line logic.
+ ///
+ /// The line.
+ ///
+ public abstract void DrawLine(Line line, SvgContext context);
+
+ ///
+ /// Draw circle logic.
+ ///
+ /// The circle center.
+ /// The circle radius.
+ /// The svg context that can be used to draw the circle (color, line thickness, etc.)
+ public abstract void DrawCircle(Vector3 center, double radius, SvgContext context);
+
+ ///
+ /// Close the document.
+ ///
+ public abstract void Close();
+ }
+}
\ No newline at end of file
diff --git a/Elements.Serialization.SVG/src/DrawingTools/SkiaCanvas.cs b/Elements.Serialization.SVG/src/DrawingTools/SkiaCanvas.cs
new file mode 100644
index 000000000..d5559a3af
--- /dev/null
+++ b/Elements.Serialization.SVG/src/DrawingTools/SkiaCanvas.cs
@@ -0,0 +1,179 @@
+using System.Collections.Generic;
+using Elements.Geometry;
+using SkiaSharp;
+using System.Linq;
+using System;
+using System.IO;
+
+namespace Elements.Serialization.SVG
+{
+ ///
+ /// The adapter for Skia library.
+ ///
+ public class SkiaCanvas : BaseSvgCanvas
+ {
+ private SKCanvas _canvas;
+ private SKMatrix _matrix = new SKMatrix();
+
+ ///
+ /// Initializes a new instance of SkDrawingTool
+ ///
+ /// The canvas.
+ /// The SVG document.
+ ///
+ public SkiaCanvas(SKCanvas canvas, SvgBaseDrawing document) : base(document)
+ {
+ _canvas = canvas;
+ }
+
+ ///
+ /// Initializes a new instance of SkDrawingTool.
+ ///
+ /// The document stream.
+ /// The height of the model bounding box.
+ /// The width of the model bounding box.
+ /// The SVG document
+ public SkiaCanvas(Stream stream, double viewBoxHeight, double viewBoxWidth, SvgBaseDrawing document)
+ : base(document)
+ {
+ _canvas = SKSvgCanvas.Create(SKRect.Create((float)(viewBoxWidth * document.Scale),
+ (float)(viewBoxHeight * document.Scale)), stream);
+ }
+
+ ///
+ /// Sets the scene bounds
+ ///
+ /// The scene bounding box.
+ /// The plan rotation angle (in degrees).
+ public override void SetBounds(BBox3 sceneBounds, double rotation)
+ {
+ var wOld = (float)(sceneBounds.Max.X - sceneBounds.Min.X);
+ var hOld = (float)(sceneBounds.Max.Y - sceneBounds.Min.Y);
+
+ base.SetBounds(sceneBounds, rotation);
+ double max = Math.Max(PageWidth, PageHeight);
+ _matrix = SKMatrix.CreateRotationDegrees((float)rotation, (float)(PageWidth * Document.Scale / 2.0),
+ (float)(PageHeight * Document.Scale / 2.0));
+ _matrix.TransX += (float)((wOld - PageWidth) * Document.Scale / 2.0f);
+ _matrix.TransY += (float)((PageHeight - hOld) * Document.Scale / 2.0f);
+ }
+
+ ///
+ /// Draws polygon on canvas.
+ ///
+ /// The polygon.
+ /// The svg context that can be used to draw the polygon (color, line thickness, etc.)
+ public override void DrawPolygon(Polygon polygon, SvgContext context)
+ {
+ var path = new SKPath();
+ var points = new List();
+ foreach (var point in polygon.Vertices)
+ {
+ points.Add(ToSkPoint(point));
+ }
+
+ path.AddPoly(points.ToArray(), true);
+ var paint = new SKPaint() { Style = SKPaintStyle.Fill, StrokeWidth = (float)(context.ElementStroke.Width * Document.Scale) };
+ if (context.Color.HasValue)
+ {
+ paint.Color = ToSkColor(context.Color.Value);
+ _canvas.DrawPath(path, paint);
+ }
+
+ paint.Color = ToSkColor(context.ElementStroke.Color);
+ paint.Style = SKPaintStyle.Stroke;
+ _canvas.DrawPath(path, paint);
+ }
+
+ ///
+ /// Draws text on canvas.
+ ///
+ /// The text.
+ /// The text transformation matrix.
+ /// The svg context that can be used to draw the text (color, line thickness, etc.)
+ public override void DrawText(string text, Transform transform, SvgContext context)
+ {
+ var r = default(SKRect);
+ var t2 = SKTypeface.FromFamilyName(context.Font.FamilyName, SKFontStyleWeight.Normal, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright);
+ var font2 = new SKFont(t2, (float)(context.Font.Size * Document.Scale));
+ var p2 = new SKPaint(font2);
+
+ double textLength = p2.MeasureText(text, ref r);
+
+ var heightVectot = transform.OfVector(Vector3.YAxis);
+ var top = ToSkPoint(transform.Origin + heightVectot * textLength / 2.0);
+ var bottom = ToSkPoint(transform.Origin - heightVectot * textLength / 2.0);
+
+ var path = new SKPath();
+ path.AddPoly(new SKPoint[] { top, bottom });
+
+ _canvas.DrawTextOnPath(text, path, (float)(textLength * Document.Scale / 2.0 - r.MidX) / 2f, r.Height / 2.0f, p2);
+ }
+
+ ///
+ /// Draws line on canvas.
+ ///
+ /// The line.
+ ///
+ public override void DrawLine(Line line, SvgContext context)
+ {
+ var paint = new SKPaint() { Style = SKPaintStyle.Fill, StrokeWidth = (float)(context.ElementStroke.Width * Document.Scale) };
+ if (context.Color.HasValue)
+ {
+ paint.Color = ToSkColor(context.Color.Value);
+ }
+
+ paint.Color = ToSkColor(context.ElementStroke.Color);
+ paint.Style = SKPaintStyle.Stroke;
+ if (context.ElementStroke.HasDash)
+ {
+ paint.PathEffect = SKPathEffect.CreateDash(context.ElementStroke.DashIntervals.Select(i => (float)(i * Document.Scale)).ToArray(), (float)context.ElementStroke.DashPhase);
+ }
+
+ _canvas.DrawLine(ToSkPoint(line.Start), ToSkPoint(line.End), paint);
+ }
+
+ ///
+ /// Draws circle on canvas.
+ ///
+ /// The circle center.
+ /// The circle radius.
+ /// The svg context that can be used to draw the circle (color, line thickness, etc.)
+ public override void DrawCircle(Vector3 center, double radius, SvgContext context)
+ {
+ var location = ToSkPoint(center);
+ var paint = new SKPaint() { Style = SKPaintStyle.Fill, StrokeWidth = (float)(context.ElementStroke.Width * Document.Scale) };
+ if (context.Color.HasValue)
+ {
+ paint.Color = ToSkColor(context.Color.Value);
+ _canvas.DrawCircle(location, (float)(radius * Document.Scale), paint);
+ }
+
+ paint.Color = ToSkColor(context.ElementStroke.Color);
+ paint.Style = SKPaintStyle.Stroke;
+
+ _canvas.DrawCircle(location, (float)(radius * Document.Scale), paint);
+ }
+
+ ///
+ /// Disposes the canvas
+ ///
+ public override void Close()
+ {
+ _canvas.Dispose();
+ }
+
+ private SKPoint ToSkPoint(Vector3 point)
+ {
+ var viewBoxHeight = (float)(SceneBounds.Max.Y - SceneBounds.Min.Y);
+ var skPoint = new SKPoint((float)((point.X - SceneBounds.Min.X) * Document.Scale),
+ (float)((viewBoxHeight + SceneBounds.Min.Y - point.Y) * Document.Scale));
+ return _matrix.MapPoint(skPoint);
+ }
+
+ private static SKColor ToSkColor(Color color)
+ {
+ return new SKColor((byte)(color.Red * 255), (byte)(color.Green * 255), (byte)(color.Blue * 255), (byte)(color.Alpha * 255));
+ }
+ }
+}
\ No newline at end of file
diff --git a/Elements.Serialization.SVG/src/Elements.Serialization.SVG.csproj b/Elements.Serialization.SVG/src/Elements.Serialization.SVG.csproj
index 45e0298cd..c180a63da 100644
--- a/Elements.Serialization.SVG/src/Elements.Serialization.SVG.csproj
+++ b/Elements.Serialization.SVG/src/Elements.Serialization.SVG.csproj
@@ -19,15 +19,10 @@
-
+
-
-
-
-
-
\ No newline at end of file
diff --git a/Elements.Serialization.SVG/src/EventArgs/ElementSerializationEventArgs.cs b/Elements.Serialization.SVG/src/EventArgs/ElementSerializationEventArgs.cs
index 0e37468b5..70c2c6a44 100644
--- a/Elements.Serialization.SVG/src/EventArgs/ElementSerializationEventArgs.cs
+++ b/Elements.Serialization.SVG/src/EventArgs/ElementSerializationEventArgs.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using Svg;
namespace Elements.Serialization.SVG
{
@@ -35,10 +34,12 @@ public enum CreationSequences
/// It can be used to create SvgElements
/// (e.g. polygon.ToSvgPolygon(e.DrawingPlan, e.DrawingPlan.FrontContext) or e.DrawingPlan.CreateText(..))
///
- public ElementSerializationEventArgs(SvgSection drawingPlan, Element element)
+ ///
+ public ElementSerializationEventArgs(SvgSection drawingPlan, Element element, double scale)
{
DrawingPlan = drawingPlan;
Element = element;
+ Scale = scale;
}
///
@@ -55,7 +56,7 @@ public ElementSerializationEventArgs(SvgSection drawingPlan, Element element)
/// The set of elements that will be added to the drawing plan if IsProcessed == true
/// Please add there all new element that you created from the Element
///
- public List SvgElements { get; } = new List();
+ public List Actions { get; } = new List();
///
/// The element which is processed before being added to the drawing plan
@@ -71,5 +72,10 @@ public ElementSerializationEventArgs(SvgSection drawingPlan, Element element)
/// 5. Elements that are marked as AfterGridLines
///
public CreationSequences CreationSequence { get; set; }
+
+ ///
+ /// The drawing scale
+ ///
+ public double Scale { get; }
}
}
\ No newline at end of file
diff --git a/Elements.Serialization.SVG/src/Models/Paint/SvgContext.cs b/Elements.Serialization.SVG/src/Models/Paint/SvgContext.cs
new file mode 100644
index 000000000..f0f20f8bf
--- /dev/null
+++ b/Elements.Serialization.SVG/src/Models/Paint/SvgContext.cs
@@ -0,0 +1,203 @@
+using Elements.Geometry;
+using System;
+using Svg;
+using System.Linq;
+
+namespace Elements.Serialization.SVG
+{
+ ///
+ /// The style and color information about how to draw geometries and text.
+ ///
+ public class SvgContext
+ {
+ ///
+ /// Initializes a new instance of SvgContext class.
+ ///
+ public SvgContext() : this(Colors.White, Colors.Black, 0.01)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of SvgContext class.
+ ///
+ /// The paint's stroke color.
+ /// The paint's stroke width.
+ /// The definition of the dash pattern via an even number of entries.
+ public SvgContext(Color strokeColor, double strokeWidth, double[] dashIntervals) : this(Colors.White, strokeColor, strokeWidth)
+ {
+ ElementStroke.CreateDash(dashIntervals, 0);
+ // TODO: delete
+ _strokeDashArray = new SvgUnitCollection();
+ foreach (var interval in dashIntervals)
+ {
+ _strokeDashArray.Add(new SvgUnit(SvgUnitType.User, (float)interval));
+ }
+ }
+
+ ///
+ /// Initializes a new instance of SvgContext class.
+ ///
+ /// The paint's stroke color.
+ /// The paint's stroke width.
+ public SvgContext(Color strokeColor, double strokeWidth) : this(Colors.White, strokeColor, strokeWidth) { }
+
+ ///
+ /// Initializes a new instance of SvgContext class.
+ ///
+ /// The paint's foreground color.
+ /// The paint's stroke color.
+ /// The paint's stroke width.
+ public SvgContext(Color color, Color strokeColor, double strokeWidth)
+ {
+ _elementStroke = new SvgStroke();
+ _font = new SvgFont("Arial");
+
+ Color = color;
+ ElementStroke.Color = strokeColor;
+ ElementStroke.Width = strokeWidth;
+ // TODO: delete
+ _stroke = new SvgColourServer(ToDrawingColor(strokeColor));
+ _strokeWidth = new SvgUnit(SvgUnitType.User, (float)strokeWidth);
+ }
+
+ ///
+ /// Initializes a new instance of SvgContext class.
+ ///
+ /// The font family name.
+ /// The font size.
+ public SvgContext(string fontFamily, double fontSize) : this(Colors.White, Colors.Black, 0)
+ {
+ Font.FamilyName = fontFamily;
+ Font.Size = fontSize;
+ }
+
+ ///
+ /// Initializes a new instance of SvgContext class.
+ ///
+ /// The paint's foreground color.
+ /// The paint's stroke color.
+ /// The paint's stroke width.
+ public SvgContext(System.Drawing.Color color, System.Drawing.Color strokeColor, double strokeWidth) :
+ this(new Color(color), new Color(strokeColor), strokeWidth)
+ { }
+
+ ///
+ /// Initializes a new instance of SvgContext class.
+ ///
+ /// The paint's stroke color.
+ /// The paint's stroke width.
+ public SvgContext(System.Drawing.Color strokeColor, double strokeWidth) :
+ this(new Color(strokeColor), strokeWidth)
+ { }
+
+ ///
+ /// Initializes a new instance of SvgContext class.
+ ///
+ /// The paint's stroke color.
+ /// The paint's stroke width.
+ /// The definition of the dash pattern via an even number of entries.
+ public SvgContext(System.Drawing.Color strokeColor, double strokeWidth, double[] dashIntervals) :
+ this(new Color(strokeColor), strokeWidth, dashIntervals)
+ { }
+
+
+ [Obsolete("Fill is deprecated, please use Color instead.")]
+ public SvgColourServer? Fill
+ {
+ get { return _colourServer; }
+ set
+ {
+ _colourServer = value;
+ _color = value == null ? null : new Color(value.Colour);
+ }
+ }
+
+ [Obsolete("Stroke is deprecated, please use ElementStroke.Color instead.")]
+ public SvgColourServer? Stroke
+ {
+ get { return _stroke; }
+ set
+ {
+ if (value == null)
+ {
+ _stroke = null;
+ _elementStroke.Color = null;
+ }
+ else
+ {
+ _stroke = value;
+ _elementStroke.Color = new Color(value.Colour);
+ }
+ }
+ }
+
+ [Obsolete("StrokeWidth is deprecated, please use ElementStroke.Width instead.")]
+ public SvgUnit StrokeWidth
+ {
+ get { return _strokeWidth; }
+ set
+ {
+ _strokeWidth = value;
+ _elementStroke.Width = value.Value;
+ }
+ }
+
+ [Obsolete("StrokeDashArray is deprecated, please use ElementStroke.CreateDash instead.")]
+ public SvgUnitCollection? StrokeDashArray
+ {
+ get { return _strokeDashArray; }
+ set
+ {
+ if (value == null)
+ {
+ _strokeDashArray = null;
+ _elementStroke.DeleteDash();
+ }
+ else
+ {
+ _strokeDashArray = value;
+ var intervals = _strokeDashArray.Select(ar => (double)ar.Value);
+ _elementStroke.CreateDash(intervals.ToArray(), 0);
+ }
+ }
+ }
+
+ ///
+ /// The paint's foreground color.
+ ///
+ public Color? Color
+ {
+ get { return _color; }
+ set
+ {
+ _color = value;
+ _colourServer = _color.HasValue ? new SvgColourServer(ToDrawingColor(_color.Value)) : null;
+ }
+ }
+
+ ///
+ /// The stroke style information.
+ ///
+ public SvgStroke ElementStroke => _elementStroke;
+
+ ///
+ /// The font style information.
+ ///
+ public SvgFont Font => _font;
+
+ private static System.Drawing.Color ToDrawingColor(Geometry.Color color)
+ {
+ return System.Drawing.Color.FromArgb((int)(color.Alpha * 255), (int)(color.Red * 255), (int)(color.Green * 255), (int)(color.Blue * 255));
+ }
+
+ private Color? _color;
+ private readonly SvgStroke _elementStroke;
+ private SvgFont _font;
+
+ // TODO: delete
+ private SvgColourServer? _colourServer;
+ private SvgColourServer? _stroke;
+ private SvgUnit _strokeWidth = new SvgUnit(SvgUnitType.User, 0.01f);
+ private SvgUnitCollection? _strokeDashArray;
+ }
+}
\ No newline at end of file
diff --git a/Elements.Serialization.SVG/src/Models/Paint/SvgFont.cs b/Elements.Serialization.SVG/src/Models/Paint/SvgFont.cs
new file mode 100644
index 000000000..6eb87e7c7
--- /dev/null
+++ b/Elements.Serialization.SVG/src/Models/Paint/SvgFont.cs
@@ -0,0 +1,27 @@
+namespace Elements.Serialization.SVG
+{
+ ///
+ /// The font family and size information
+ ///
+ public class SvgFont
+ {
+ ///
+ /// Initializes a new instance of SvgFont.
+ ///
+ /// The family name.
+ public SvgFont(string familyName)
+ {
+ FamilyName = familyName;
+ }
+
+ ///
+ /// The font family name.
+ ///
+ public string FamilyName { get; set; }
+
+ ///
+ /// The font size
+ ///
+ public double Size { get; set; } = 0.7;
+ }
+}
\ No newline at end of file
diff --git a/Elements.Serialization.SVG/src/Models/Paint/SvgStroke.cs b/Elements.Serialization.SVG/src/Models/Paint/SvgStroke.cs
new file mode 100644
index 000000000..65e6a4135
--- /dev/null
+++ b/Elements.Serialization.SVG/src/Models/Paint/SvgStroke.cs
@@ -0,0 +1,62 @@
+using System.Collections.Generic;
+using Elements.Geometry;
+
+namespace Elements.Serialization.SVG
+{
+ ///
+ /// The stroke style and size information
+ ///
+ public class SvgStroke
+ {
+ ///
+ /// The stroke width.
+ ///
+ public double Width { get; set; } = 0.01;
+
+ ///
+ /// The stroke color.
+ ///
+ public Color Color { get; set; }
+
+ ///
+ /// The definition of the dash pattern via an even number of entries.
+ ///
+ public IEnumerable DashIntervals => _dashIntervals;
+
+ ///
+ /// The offset into the intervals array. (mod the sum of all of the intervals).
+ ///
+ public double DashPhase => _dashPhase;
+
+ ///
+ /// A value indicating whether paint has a dash pattern.
+ ///
+ public bool HasDash { get; private set; }
+
+ ///
+ /// Creates a dash path effect by specifying the dash intervals.
+ ///
+ /// The definition of the dash pattern via an even number of entries.
+ /// The offset into the intervals array. (mod the sum of all of the intervals).
+ public void CreateDash(double[] intervals, float phase)
+ {
+ _dashIntervals.Clear();
+ _dashIntervals.AddRange(intervals);
+ _dashPhase = phase;
+ HasDash = true;
+ }
+
+ ///
+ /// Deletes dash pattern
+ ///
+ public void DeleteDash()
+ {
+ _dashIntervals.Clear();
+ _dashPhase = 0;
+ HasDash = false;
+ }
+
+ private readonly List _dashIntervals = new List();
+ private double _dashPhase;
+ }
+}
\ No newline at end of file
diff --git a/Elements.Serialization.SVG/src/SvgContext.cs b/Elements.Serialization.SVG/src/SvgContext.cs
deleted file mode 100644
index 515d9be99..000000000
--- a/Elements.Serialization.SVG/src/SvgContext.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using Svg;
-
-namespace Elements.Serialization.SVG
-{
- ///
- /// Contextual data for the SVG serialization process.
- ///
- public class SvgContext
- {
- ///
- /// The fill color.
- ///
- public SvgColourServer? Fill { get; set; }
-
- ///
- /// The stroke color.
- ///
- public SvgColourServer? Stroke { get; set; }
-
- ///
- /// The stroke width.
- ///
- public SvgUnit StrokeWidth { get; set; } = new SvgUnit(SvgUnitType.User, 0.01f);
-
- ///
- /// The line end cap style.
- ///
- public SvgStrokeLineCap? StrokeLineCap { get; set; } = SvgStrokeLineCap.Butt;
-
- ///
- /// The pattern of dashes and gaps used to stroke paths.
- ///
- public SvgUnitCollection? StrokeDashArray { get; set; }
- }
-}
\ No newline at end of file
diff --git a/Elements.Serialization.SVG/src/SvgDrawingExtensions.cs b/Elements.Serialization.SVG/src/SvgDrawingExtensions.cs
index c9f9803ff..d38394fc9 100644
--- a/Elements.Serialization.SVG/src/SvgDrawingExtensions.cs
+++ b/Elements.Serialization.SVG/src/SvgDrawingExtensions.cs
@@ -34,7 +34,7 @@ public static SvgLine ToSvgLine(this Line line, Vector3 sceneBoundsMin, float vi
string style = string.Empty;
if (context.Fill != null)
{
- style += $"fill:{context.Fill.Colour.Name.ToLower()}";
+ style = $"fill:{SvgColourServerToString(context.Fill)}";
}
if (context.Stroke != null)
@@ -43,7 +43,7 @@ public static SvgLine ToSvgLine(this Line line, Vector3 sceneBoundsMin, float vi
{
style += "; ";
}
- style += $"stroke:{context.Stroke.Colour.Name.ToLower()}";
+ style += $"stroke:{SvgColourServerToString(context.Stroke)}";
}
if (!string.IsNullOrEmpty(style))
@@ -53,6 +53,10 @@ public static SvgLine ToSvgLine(this Line line, Vector3 sceneBoundsMin, float vi
return svgLine;
}
+ private static string SvgColourServerToString(SvgColourServer colorServer)
+ {
+ return $"rgb({colorServer.Colour.R}, {colorServer.Colour.G}, {colorServer.Colour.B})";
+ }
///
/// Convert a geometric line to an SVG using a section and a context.
///
@@ -62,7 +66,7 @@ public static SvgLine ToSvgLine(this Line line, Vector3 sceneBoundsMin, float vi
/// An SVG line.
public static SvgLine ToSvgLine(this Line line, SvgSection drawingPlan, SvgContext context)
{
- return ToSvgLine(line, drawingPlan.GetSceneBounds().Min, drawingPlan.ViewBoxHeight, context);
+ return ToSvgLine(line, drawingPlan.SceneBounds.Min, drawingPlan.ViewBoxHeight, context);
}
///
@@ -75,14 +79,34 @@ public static SvgLine ToSvgLine(this Line line, SvgSection drawingPlan, SvgConte
/// An SVG polygon.
public static SvgPolygon ToSvgPolygon(this Polygon polygon, Vector3 sceneBoundsMin, float viewBoxHeight, SvgContext context)
{
- return new SvgPolygon()
+ var svgPolygon = new SvgPolygon()
{
- Fill = context.Fill,
- Stroke = context.Stroke,
StrokeWidth = context.StrokeWidth,
StrokeDashArray = context.StrokeDashArray,
Points = polygon.Vertices.ToSvgPointCollection(sceneBoundsMin, viewBoxHeight)
};
+
+ string style = string.Empty;
+ if (context.Fill != null)
+ {
+ style += $"fill:{SvgColourServerToString(context.Fill)}";
+ }
+
+ if (context.Stroke != null)
+ {
+ if (!string.IsNullOrEmpty(style))
+ {
+ style += "; ";
+ }
+ style += $"stroke:{SvgColourServerToString(context.Stroke)}";
+ }
+
+ if (!string.IsNullOrEmpty(style))
+ {
+ svgPolygon.CustomAttributes.Add("style", style);
+ }
+
+ return svgPolygon;
}
///
@@ -94,7 +118,7 @@ public static SvgPolygon ToSvgPolygon(this Polygon polygon, Vector3 sceneBoundsM
/// An SVG polygon.
public static SvgPolygon ToSvgPolygon(this Polygon polygon, SvgSection drawingPlan, SvgContext context)
{
- return ToSvgPolygon(polygon, drawingPlan.GetSceneBounds().Min, drawingPlan.ViewBoxHeight, context);
+ return ToSvgPolygon(polygon, drawingPlan.SceneBounds.Min, drawingPlan.ViewBoxHeight, context);
}
///
@@ -116,7 +140,7 @@ public static SvgUnit ToXUserUnit(this double x, Vector3 sceneBoundsMin)
/// An SVG unit.
public static SvgUnit ToXUserUnit(this double x, SvgSection drawingPlan)
{
- return ToXUserUnit(x, drawingPlan.GetSceneBounds().Min);
+ return ToXUserUnit(x, drawingPlan.SceneBounds.Min);
}
///
@@ -140,7 +164,7 @@ public static SvgUnit ToYUserUnit(this double y, float viewBoxHeight, Vector3 sc
/// An SVG unit.
public static SvgUnit ToYUserUnit(this double y, SvgSection drawingPlan)
{
- return ToYUserUnit(y, drawingPlan.ViewBoxHeight, drawingPlan.GetSceneBounds().Min);
+ return ToYUserUnit(y, drawingPlan.ViewBoxHeight, drawingPlan.SceneBounds.Min);
}
///
@@ -169,7 +193,7 @@ public static SvgPointCollection ToSvgPointCollection(this IList points
/// An collection of SVG points.
public static SvgPointCollection ToSvgPointCollection(this IList points, SvgSection drawingPlan)
{
- return ToSvgPointCollection(points, drawingPlan.GetSceneBounds().Min, drawingPlan.ViewBoxHeight);
+ return ToSvgPointCollection(points, drawingPlan.SceneBounds.Min, drawingPlan.ViewBoxHeight);
}
}
-}
\ No newline at end of file
+}
diff --git a/Elements.Serialization.SVG/src/SvgFaceElevation.cs b/Elements.Serialization.SVG/src/SvgFaceElevation.cs
index c0df28971..eb4e9d8ee 100644
--- a/Elements.Serialization.SVG/src/SvgFaceElevation.cs
+++ b/Elements.Serialization.SVG/src/SvgFaceElevation.cs
@@ -65,20 +65,12 @@ public SvgFaceElevation(Face face, Vector3 up)
///
/// The svg context which defines settings for element lines.
///
- public SvgContext ElementLinesContext { get; set; } = new SvgContext()
- {
- Stroke = new SvgColourServer(Colors.Black),
- StrokeWidth = new SvgUnit(SvgUnitType.User, 0.01f)
- };
+ public SvgContext ElementLinesContext { get; set; } = new SvgContext(Colors.Black, 0.01);
///
/// The svg context which defines settings for leader lines .
///
- public SvgContext DimensionLinesContext { get; set; } = new SvgContext()
- {
- Stroke = new SvgColourServer(System.Drawing.Color.DarkBlue),
- StrokeWidth = new SvgUnit(SvgUnitType.User, 0.005f)
- };
+ public SvgContext DimensionLinesContext { get; set; } = new SvgContext(System.Drawing.Color.DarkBlue, 0.005);
///
/// An additional amount to rotate the drawing.
diff --git a/Elements.Serialization.SVG/src/SvgSection.cs b/Elements.Serialization.SVG/src/SvgSection.cs
index 017a2e23d..241371f23 100644
--- a/Elements.Serialization.SVG/src/SvgSection.cs
+++ b/Elements.Serialization.SVG/src/SvgSection.cs
@@ -1,8 +1,5 @@
using Elements.Geometry;
-using SkiaSharp;
using Svg;
-using Svg.Skia;
-using Svg.Transforms;
using System;
using System.Collections.Generic;
using System.IO;
@@ -37,14 +34,20 @@ public enum PlanRotation
///
/// A section of a model serialized to SVG.
///
- public class SvgSection
+ public class SvgSection : SvgBaseDrawing
{
+ #region Constants
+
+ private const double DEFAULT_SCALE = 10;
+
+ #endregion
+
#region Private fields
private readonly List _models = new List();
private Dictionary _gridLines = new Dictionary();
private readonly double _elevation;
- private BBox3 _sceneBounds;
+ private BaseSvgCanvas _canvas = null!;
#endregion
@@ -78,35 +81,23 @@ public SvgSection(IList models, double elevation)
///
/// The svg context which defines settings for elements cut by the cut plane.
///
- public SvgContext FrontContext { get; set; } = new SvgContext()
- {
- Fill = new SvgColourServer(System.Drawing.Color.Black),
- StrokeWidth = new SvgUnit(SvgUnitType.User, 0.01f)
- };
+ public SvgContext FrontContext { get; set; } = new SvgContext(Colors.Black, Colors.Black, 0.01);
///
/// The svg context which defines settings for elements behind the cut plane.
///
- public SvgContext BackContext { get; set; } = new SvgContext()
- {
- Stroke = new SvgColourServer(System.Drawing.Color.Black),
- StrokeWidth = new SvgUnit(SvgUnitType.User, 0.01f)
- };
+ public SvgContext BackContext { get; set; } = new SvgContext(Colors.Black, 0.01);
///
/// The svg context which defines settings for the grid elements.
///
- public SvgContext GridContext { get; set; } = new SvgContext()
- {
- Stroke = new SvgColourServer(Colors.Black),
- StrokeWidth = new SvgUnit(SvgUnitType.User, 0.01f),
- StrokeDashArray = new SvgUnitCollection(){
- new SvgUnit(SvgUnitType.User, 0.3f),
- new SvgUnit(SvgUnitType.User, 0.025f),
- new SvgUnit(SvgUnitType.User, 0.05f),
- new SvgUnit(SvgUnitType.User, 0.025f),
- }
- };
+ public SvgContext GridContext { get; set; } = new SvgContext(Colors.Black, 0.01, new double[] { 0.3 * 5, 0.025 * 5, 0.05 * 5, 0.025 * 5 });
+
+ ///
+ /// The svg context which defines settings for the grid text elements.
+ ///
+ ///
+ public SvgContext GridTextContext { get; set; } = new SvgContext("Arial", 0.7);
///
/// Should grid lines be shown in the section?
@@ -133,9 +124,6 @@ public SvgSection(IList models, double elevation)
///
public double PlanRotationDegrees { get; set; } = 0.0;
- internal float ViewBoxWidth { get; private set; }
- internal float ViewBoxHeight { get; private set; }
-
#endregion
#region Public logic
@@ -220,7 +208,7 @@ public static SvgDocument CreatePlanFromModels(IList models,
PlanRotation planRotation = PlanRotation.Angle,
double planRotationDegrees = 0.0)
{
- var drawingPlan = new SvgSection(models, elevation);
+ var drawingPlan = new SvgSectionOld(models, elevation);
drawingPlan.BackContext = backContext;
drawingPlan.FrontContext = frontContext;
drawingPlan.ShowGrid = showGrid;
@@ -237,11 +225,14 @@ public static SvgDocument CreatePlanFromModels(IList models,
///
/// Create a plan of a model and save the resulting section to the provided stream.
///
- /// The stream to write the SVG data
+ /// The stream to write the SVG data.
public void SaveAsSvg(Stream stream)
{
- var doc = CreateSvgDocument();
- doc.Write(stream);
+ var rotation = CreateSceneViewBox();
+ _canvas = new SkiaCanvas(stream, ViewBoxHeight, ViewBoxWidth, this);
+ _canvas.SetBounds(SceneBounds, rotation);
+ Draw(_canvas);
+ _canvas.Close();
}
///
@@ -258,187 +249,20 @@ public void SaveAsSvg(string path)
}
///
- /// Create a plan of a model and save the resulting section to the provided path.
- ///
- /// The location on disk to write the PDF file
- /// The pdf save options: page size, mergin
- public void SaveAsPdf(string path, PdfSaveOptions saveOptions)
- {
- var pdfPath = System.IO.Path.ChangeExtension(path, ".pdf");
- using (var stream = new FileStream(pdfPath, FileMode.Create, FileAccess.Write))
- {
- SaveAsPdf(stream, saveOptions);
- }
- }
-
- ///
- /// Create a plan of a model and save the resulting section to the provided stream.
- ///
- /// The stream to write the PDF data
- /// The pdf save options: page size, mergin
- public void SaveAsPdf(Stream stream, PdfSaveOptions saveOptions)
- {
- var pageHeight = saveOptions.PageHeight;
- var pageWidth = saveOptions.PageWidth;
- var margin = saveOptions.Margin;
-
- using var document = SKDocument.CreatePdf(stream);
- var path = System.IO.Path.GetTempFileName();
- path = System.IO.Path.ChangeExtension(path, ".svg");
-
- using (var svg = new SKSvg())
- {
- var svgStream = new MemoryStream();
- SaveAsSvg(path);
- if (svg.Load(path) is { } && svg.Picture != null)
- {
- if (svg.Picture.CullRect.Width > svg.Picture.CullRect.Height)
- {
- var tmp = pageHeight;
- pageHeight = pageWidth;
- pageWidth = tmp;
- }
-
- using (var pdfCanvas = document.BeginPage(pageWidth, pageHeight))
- {
- float scale = Math.Min((pageHeight - margin * 2) / svg.Picture.CullRect.Height,
- (pageWidth - margin * 2) / svg.Picture.CullRect.Width);
- var matrix = SKMatrix.CreateScale(scale, scale);
- matrix = SKMatrix.Concat(SKMatrix.CreateTranslation(margin, margin), matrix);
- pdfCanvas.DrawPicture(svg.Picture, ref matrix);
- }
-
- document.EndPage();
- }
- }
-
- document.Close();
- stream.Seek(0, SeekOrigin.Begin);
- }
-
- ///
- /// Create SvgText element.
+ /// Draws the models on the canvas from the input.
///
- /// The content.
- /// The element location at the model.
- /// The angle of the content.
+ /// The drawing tool (adapter for Skia, SVG.Net, etc.)
+ /// The height of the page.
+ /// The width of the page.
+ /// The margin from the left and right of the page.
///
- public SvgText CreateText(string text, Vector3 location, double angle)
- {
- var x = location.X.ToXUserUnit(this);
- var y = location.Y.ToYUserUnit(this);
-
- var svgText = new SvgText(text)
- {
- X = new SvgUnitCollection() { x },
- Y = new SvgUnitCollection() { y },
- TextAnchor = SvgTextAnchor.Middle,
- Transforms = new Svg.Transforms.SvgTransformCollection() { new SvgRotate((float)angle, x.Value, y.Value), new SvgTranslate(0, 0.2f) }
- };
-
- svgText.CustomAttributes.Add("style", "font-family: Arial; font-size: 0.5; fill:black");
- return svgText;
- }
-
- ///
- /// Generate a plan of a model.
- ///
- public SvgDocument CreateSvgDocument()
- {
- _sceneBounds = SvgSection.ComputeSceneBounds(_models);
-
- var doc = new SvgDocument
- {
- Fill = SvgPaintServer.None
- };
-
- var rotation = SvgSection.GetRotationValueForPlan(_models, PlanRotation, PlanRotationDegrees);
-
- _gridLines.Clear();
- if (ShowGrid)
- {
- _gridLines = ExtendSceneWithGridLines();
- }
-
- CreateViewBox(doc, rotation);
- Draw(doc, rotation);
- return doc;
- }
-
- ///
- /// Get the scene bounds.
- ///
- public BBox3 GetSceneBounds()
- {
- return _sceneBounds;
- }
-
- #endregion
-
- #region Private logic
-
- private static double GetRotationValueForPlan(IList models, PlanRotation rotation, double angle)
- {
- if (rotation == PlanRotation.Angle)
- {
- return angle;
- }
-
- var grids = models.SelectMany(m => m.AllElementsOfType()).Select(gl => gl.Curve).Where(gl => gl is Line).ToList();
- if (!grids.Any())
- {
- return 0.0;
- }
-
- var longest = (Line)grids.OrderBy(g => g.Length()).First();
-
- return rotation switch
- {
- PlanRotation.LongestGridHorizontal => -longest.Direction().PlaneAngleTo(Vector3.YAxis),
- PlanRotation.LongestGridVertical => -longest.Direction().PlaneAngleTo(Vector3.XAxis),
- PlanRotation.Angle => angle,
- PlanRotation.None => 0.0,
- _ => 0.0,
- };
- }
-
- private static BBox3 ComputeSceneBounds(IList models)
- {
- var bounds = new BBox3(Vector3.Max, Vector3.Min);
- foreach (var model in models)
- {
- foreach (var element in model.Elements)
- {
- if (element.Value is GeometricElement geo)
- {
- geo.UpdateRepresentations();
- if ((geo.Representation == null || geo.Representation.SolidOperations.All(v => v.IsVoid)) &&
- (geo.RepresentationInstances == null || !geo.RepresentationInstances.Any()))
- {
- continue;
- }
- geo.UpdateBoundsAndComputeSolid();
-
- if (geo.Bounds.Volume.ApproximatelyEquals(0))
- {
- continue;
- }
-
- var bbMax = geo.Transform.OfPoint(geo.Bounds.Max);
- var bbMin = geo.Transform.OfPoint(geo.Bounds.Min);
- bounds.Extend(new[] { bbMax, bbMin });
- }
- }
- }
-
- return bounds;
- }
-
- private void Draw(SvgDocument doc, double rotation)
+ public double Draw(BaseSvgCanvas canvas, float pageHeight = -1, float pageWidth = -1, float margin = 0)
{
+ _canvas = canvas;
+ var rotation = CreateSceneViewBox(pageHeight, pageWidth, margin);
var plane = new Plane(new Vector3(0, 0, _elevation), Vector3.ZAxis);
- var customElementsBeforeGrid = new List();
- var customElementsAfterGrid = new List();
+ var customElementsBeforeGrid = new List();
+ var customElementsAfterGrid = new List();
foreach (var model in _models)
{
@@ -448,20 +272,20 @@ private void Draw(SvgDocument doc, double rotation)
var elements = new Dictionary();
foreach (var element in model.Elements)
{
- var e = new ElementSerializationEventArgs(this, element.Value);
+ var e = new ElementSerializationEventArgs(this, element.Value, Scale);
OnElementDrawing.Invoke(this, e);
if (e.IsProcessed)
{
switch (e.CreationSequence)
{
case ElementSerializationEventArgs.CreationSequences.AfterGridLines:
- customElementsAfterGrid.AddRange(e.SvgElements);
+ customElementsAfterGrid.AddRange(e.Actions);
break;
case ElementSerializationEventArgs.CreationSequences.BeforeGridLines:
- customElementsBeforeGrid.AddRange(e.SvgElements);
+ customElementsBeforeGrid.AddRange(e.Actions);
break;
case ElementSerializationEventArgs.CreationSequences.Immediately:
- e.SvgElements.ForEach(el => doc.Children.Add(el));
+ e.Actions.ForEach(a => a.Draw(_canvas));
break;
}
}
@@ -483,7 +307,7 @@ private void Draw(SvgDocument doc, double rotation)
{
foreach (var p in intersectingPolygon.Value)
{
- doc.Children.Add(p.ToSvgPolygon(_sceneBounds.Min, ViewBoxHeight, FrontContext));
+ _canvas.DrawPolygon(p, FrontContext);
}
}
@@ -491,7 +315,7 @@ private void Draw(SvgDocument doc, double rotation)
{
foreach (var p in backPolygon.Value)
{
- doc.Children.Add(p.ToSvgPolygon(_sceneBounds.Min, ViewBoxHeight, BackContext));
+ _canvas.DrawPolygon(p, BackContext);
}
}
@@ -499,74 +323,76 @@ private void Draw(SvgDocument doc, double rotation)
{
foreach (var l in line.Value)
{
- doc.Children.Add(l.ToSvgLine(_sceneBounds.Min, ViewBoxHeight, FrontContext));
+ _canvas.DrawLine(l, FrontContext);
}
}
}
- customElementsBeforeGrid.ForEach(el => doc.Children.Add(el));
- DrawGridLines(doc, rotation, _gridLines);
- customElementsAfterGrid.ForEach(el => doc.Children.Add(el));
+ customElementsBeforeGrid.ForEach(el => el.Draw(_canvas));
+ DrawGridLines(rotation, _gridLines);
+ customElementsAfterGrid.ForEach(el => el.Draw(_canvas));
+ return rotation;
}
- private void CreateViewBox(SvgDocument doc, double rotation)
+ ///
+ /// Creates scene view box
+ ///
+ /// The page height.
+ /// The pae widthh.
+ /// The margin from the left and right of the page.
+ /// Returns plan rotation in degrees.
+ public double CreateSceneViewBox(float pageHeight = -1, float pageWidth = -1, float margin = 0)
{
- ViewBoxWidth = (float)(_sceneBounds.Max.X - _sceneBounds.Min.X);
- ViewBoxHeight = (float)(_sceneBounds.Max.Y - _sceneBounds.Min.Y);
+ SceneBounds = SvgBaseDrawing.ComputeSceneBounds(_models);
+ var rotation = SvgBaseDrawing.GetRotationValueForPlan(_models, PlanRotation, PlanRotationDegrees);
+ _gridLines.Clear();
+ if (ShowGrid)
+ {
+ _gridLines = ExtendSceneWithGridLines();
+ }
- if (rotation == 0.0)
+ ViewBoxWidth = (float)(SceneBounds.Max.X - SceneBounds.Min.X);
+ ViewBoxHeight = (float)(SceneBounds.Max.Y - SceneBounds.Min.Y);
+ var transform = new Transform(Vector3.Origin);
+ transform.Rotate(rotation);
+ var bounds = new BBox3(SceneBounds.Corners().Select(v => transform.OfPoint(v)));
+ var wOld = ViewBoxWidth;
+ var hOld = ViewBoxHeight;
+ ViewBoxWidth = (float)(bounds.Max.X - bounds.Min.X);
+ ViewBoxHeight = (float)(bounds.Max.Y - bounds.Min.Y);
+
+ if (pageHeight == -1 || pageWidth == -1)
{
- doc.ViewBox = new SvgViewBox(0, 0, ViewBoxWidth, ViewBoxHeight);
+ Scale = DEFAULT_SCALE;
}
else
{
- // Compute a new bounding box around the rotated
- // bounding box, to ensure that we our viewbox isn't too small.
- var t = new Transform(Vector3.Origin);
- t.Rotate(rotation);
- var bounds = new BBox3(_sceneBounds.Corners().Select(v => t.OfPoint(v)));
- var wOld = ViewBoxWidth;
- var hOld = ViewBoxHeight;
- ViewBoxWidth = (float)(bounds.Max.X - bounds.Min.X);
- ViewBoxHeight = (float)(bounds.Max.Y - bounds.Min.Y);
- float max = Math.Max(ViewBoxWidth, ViewBoxHeight);
- doc.ViewBox = new SvgViewBox((wOld - ViewBoxWidth) / 2.0f, (ViewBoxHeight - hOld) / 2.0f, ViewBoxWidth, ViewBoxHeight);
- doc.CustomAttributes.Add("transform", $"rotate({rotation} {ViewBoxWidth / 2.0} {ViewBoxHeight / 2.0})");
+ Scale = Math.Min((pageHeight - margin * 4) / ViewBoxHeight,
+ (pageWidth - margin * 4) / ViewBoxWidth);
+ }
+
+ float max = Math.Max(ViewBoxWidth, ViewBoxHeight);
+
+ if (_canvas != null)
+ {
+ _canvas.SetBounds(SceneBounds, rotation);
}
+ return rotation;
}
- private void DrawGridLines(SvgDocument doc, double rotation, Dictionary gridLines)
+ #endregion
+
+ #region Private logic
+
+ private void DrawGridLines(double rotation, Dictionary gridLines)
{
foreach (var line in gridLines)
{
- doc.Children.Add(line.Value.ToSvgLine(_sceneBounds.Min, ViewBoxHeight, GridContext));
- doc.Children.Add(new SvgCircle()
- {
- CenterX = line.Value.Start.X.ToXUserUnit(_sceneBounds.Min),
- CenterY = line.Value.Start.Y.ToYUserUnit(ViewBoxHeight, _sceneBounds.Min),
- Radius = new SvgUnit(SvgUnitType.User, (float)GridHeadRadius),
- Stroke = new SvgColourServer(Colors.Black),
- Fill = new SvgColourServer(Colors.White),
- StrokeWidth = GridContext.StrokeWidth
- });
-
- var x = line.Value.Start.X.ToXUserUnit(_sceneBounds.Min);
- var y = line.Value.Start.Y.ToYUserUnit(ViewBoxHeight, _sceneBounds.Min);
-
- var text = new SvgText(line.Key.ToString())
- {
- X = new SvgUnitCollection() { x },
- Y = new SvgUnitCollection() { y },
- FontStyle = SvgFontStyle.Normal,
- FontSize = new SvgUnit(SvgUnitType.User, 0.5f),
- FontFamily = "Arial",
- Fill = new SvgColourServer(Colors.Black),
- TextAnchor = SvgTextAnchor.Middle,
- };
- text.CustomAttributes.Add("transform", $"rotate({-1 * rotation} {x} {y}), translate(0, 0.2)");
- doc.Children.Add(text);
+ _canvas.DrawLine(line.Value, GridContext);
+ _canvas.DrawCircle(line.Value.Start, GridHeadRadius, GridContext);
+ _canvas.DrawText(line.Key.ToString(), new Transform(line.Value.Start), GridTextContext);
}
}
@@ -589,7 +415,7 @@ private Dictionary ExtendSceneWithGridLines()
var t = start + new Vector3(0, GridHeadRadius);
var b = start + new Vector3(0, -GridHeadRadius);
- _sceneBounds.Extend(start, end, l, r, t, b);
+ SceneBounds.Extend(start, end, l, r, t, b);
}
return gridLines;
}
diff --git a/Elements.Serialization.SVG/src/SvgSectionOld.cs b/Elements.Serialization.SVG/src/SvgSectionOld.cs
new file mode 100644
index 000000000..0eecb4f12
--- /dev/null
+++ b/Elements.Serialization.SVG/src/SvgSectionOld.cs
@@ -0,0 +1,403 @@
+using Elements.Geometry;
+using Svg;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using Colors = System.Drawing.Color;
+
+namespace Elements.Serialization.SVG
+{
+ ///
+ /// A section of a model serialized to SVG.
+ ///
+ internal class SvgSectionOld
+ {
+ #region Private fields
+
+ private readonly List _models = new List();
+ private Dictionary _gridLines = new Dictionary();
+ private readonly double _elevation;
+ private BBox3 _sceneBounds;
+
+ #endregion
+
+ #region Constructors
+
+ ///
+ /// Initializes a new instance of SvgDrawingPlan class.
+ ///
+ /// A collection of models to include in the plan.
+ /// The elevation at which the plan will be cut.
+ public SvgSectionOld(IList models, double elevation)
+ {
+ this._models.AddRange(models);
+ this._elevation = elevation;
+ }
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// The svg context which defines settings for elements cut by the cut plane.
+ ///
+ public SvgContext FrontContext { get; set; } = new SvgContext(Colors.Black, System.Drawing.Color.Black, 0.01);
+
+ ///
+ /// The svg context which defines settings for elements behind the cut plane.
+ ///
+ public SvgContext BackContext { get; set; } = new SvgContext(System.Drawing.Color.Black, 0.01);
+
+ ///
+ /// The svg context which defines settings for the grid elements.
+ ///
+ public SvgContext GridContext { get; set; } = new SvgContext(Colors.Black, 0.01, new double[] { 0.3, 0.025, 0.05, 0.025 });
+
+ ///
+ /// Should grid lines be shown in the section?
+ ///
+ public bool ShowGrid { get; set; } = true;
+
+ ///
+ /// The extension of the grid head past the bounds of the drawing in the created plan.
+ ///
+ public double GridHeadExtension { get; set; } = 2.0;
+
+ ///
+ /// The radius of grid heads in the created plan.
+ ///
+ public double GridHeadRadius { get; set; } = 0.5;
+
+ ///
+ /// How should the plan be rotated relative to the page.
+ ///
+ public PlanRotation PlanRotation { get; set; } = PlanRotation.Angle;
+
+ ///
+ /// An additional amount to rotate the plan.
+ ///
+ public double PlanRotationDegrees { get; set; } = 0.0;
+
+ internal float ViewBoxWidth { get; private set; }
+ internal float ViewBoxHeight { get; private set; }
+
+ #endregion
+
+ #region Public logic
+
+ ///
+ /// Create a plan of a model at the provided elevation and save the
+ /// resulting section to the provided path.
+ ///
+ /// A collection of models to include in the plan.
+ /// The elevation at which the plan will be cut.
+ /// An svg context which defines settings for elements cut by the cut plane.
+ /// An svg context which defines settings for elements behind the cut plane.
+ /// The location on disk to write the SVG file.
+ /// If gridlines exist, should they be shown in the plan?
+ /// The extension of the grid head past the bounds of the drawing in the created plan.
+ /// The radius of grid heads in the created plan.
+ /// How should the plan be rotated relative to the page?
+ /// An additional amount to rotate the plan.
+ public static void CreateAndSavePlanFromModels(IList models,
+ double elevation,
+ SvgContext frontContext,
+ SvgContext backContext,
+ string path,
+ bool showGrid = true,
+ double gridHeadExtension = 2.0,
+ double gridHeadRadius = 0.5,
+ PlanRotation planRotation = PlanRotation.Angle,
+ double planRotationDegrees = 0.0)
+ {
+ var doc = CreatePlanFromModels(models, elevation, frontContext, backContext, showGrid, gridHeadExtension, gridHeadRadius, planRotation, planRotationDegrees);
+ using var stream = new FileStream(path, FileMode.Create, FileAccess.Write);
+ doc.Write(stream);
+ }
+
+ ///
+ /// Generate a plan of a model at the provided elevation.
+ ///
+ /// A collection of models to include in the plan.
+ /// The elevation at which the plan will be cut.
+ /// An svg context which defines settings for elements cut by the cut plane.
+ /// An svg context which defines settings for elements behind the cut plane.
+ /// If gridlines exist, should they be shown in the plan?
+ /// The extension of the grid head past the bounds of the drawing in the created plan.
+ /// The radius of grid heads in the created plan.
+ /// How should the plan be rotated relative to the page?
+ /// An additional amount to rotate the plan.
+ public static SvgDocument CreatePlanFromModels(IList models,
+ double elevation,
+ SvgContext frontContext,
+ SvgContext backContext,
+ bool showGrid = true,
+ double gridHeadExtension = 2.0,
+ double gridHeadRadius = 0.5,
+ PlanRotation planRotation = PlanRotation.Angle,
+ double planRotationDegrees = 0.0)
+ {
+ return CreatePlanFromModels(models, elevation, frontContext, backContext, out _, showGrid, gridHeadExtension, gridHeadRadius,
+ planRotation, planRotationDegrees);
+ }
+
+ ///
+ /// Generate a plan of a model at the provided elevation.
+ ///
+ /// A collection of models to include in the plan.
+ /// The elevation at which the plan will be cut.
+ /// An svg context which defines settings for elements cut by the cut plane.
+ /// An svg context which defines settings for elements behind the cut plane.
+ /// The scene bounds. It can be used to add elements to the output document
+ /// If gridlines exist, should they be shown in the plan?
+ /// The extension of the grid head past the bounds of the drawing in the created plan.
+ /// The radius of grid heads in the created plan.
+ /// How should the plan be rotated relative to the page?
+ /// An additional amount to rotate the plan.
+ public static SvgDocument CreatePlanFromModels(IList models,
+ double elevation,
+ SvgContext frontContext,
+ SvgContext backContext,
+ out BBox3 sceneBounds,
+ bool showGrid = true,
+ double gridHeadExtension = 2.0,
+ double gridHeadRadius = 0.5,
+ PlanRotation planRotation = PlanRotation.Angle,
+ double planRotationDegrees = 0.0)
+ {
+ var drawingPlan = new SvgSectionOld(models, elevation);
+ drawingPlan.BackContext = backContext;
+ drawingPlan.FrontContext = frontContext;
+ drawingPlan.ShowGrid = showGrid;
+ drawingPlan.GridHeadExtension = gridHeadExtension;
+ drawingPlan.GridHeadRadius = gridHeadRadius;
+ drawingPlan.PlanRotation = planRotation;
+ drawingPlan.PlanRotationDegrees = planRotationDegrees;
+
+ var doc = drawingPlan.CreateSvgDocument();
+ sceneBounds = drawingPlan.GetSceneBounds();
+ return doc;
+ }
+
+ ///
+ /// Generate a plan of a model.
+ ///
+ public SvgDocument CreateSvgDocument()
+ {
+ _sceneBounds = SvgSectionOld.ComputeSceneBounds(_models);
+
+ var doc = new SvgDocument
+ {
+ Fill = SvgPaintServer.None
+ };
+
+ var rotation = SvgSectionOld.GetRotationValueForPlan(_models, PlanRotation, PlanRotationDegrees);
+
+ _gridLines.Clear();
+ if (ShowGrid)
+ {
+ _gridLines = ExtendSceneWithGridLines();
+ }
+
+ CreateViewBox(doc, rotation);
+ Draw(doc, rotation);
+ return doc;
+ }
+
+ ///
+ /// Get the scene bounds.
+ ///
+ public BBox3 GetSceneBounds()
+ {
+ return _sceneBounds;
+ }
+
+ #endregion
+
+ #region Private logic
+
+ private static double GetRotationValueForPlan(IList models, PlanRotation rotation, double angle)
+ {
+ if (rotation == PlanRotation.Angle)
+ {
+ return angle;
+ }
+
+ var grids = models.SelectMany(m => m.AllElementsOfType()).Select(gl => gl.Curve).Where(gl => gl is Line).ToList();
+ if (!grids.Any())
+ {
+ return 0.0;
+ }
+
+ var longest = (Line)grids.OrderBy(g => g.Length()).First();
+
+ return rotation switch
+ {
+ PlanRotation.LongestGridHorizontal => -longest.Direction().PlaneAngleTo(Vector3.YAxis),
+ PlanRotation.LongestGridVertical => -longest.Direction().PlaneAngleTo(Vector3.XAxis),
+ PlanRotation.Angle => angle,
+ PlanRotation.None => 0.0,
+ _ => 0.0,
+ };
+ }
+
+ private static BBox3 ComputeSceneBounds(IList models)
+ {
+ var bounds = new BBox3(Vector3.Max, Vector3.Min);
+ foreach (var model in models)
+ {
+ foreach (var element in model.Elements)
+ {
+ if (element.Value is GeometricElement geo)
+ {
+ geo.UpdateRepresentations();
+ if (geo.Representation == null || geo.Representation.SolidOperations.All(v => v.IsVoid))
+ {
+ continue;
+ }
+ geo.UpdateBoundsAndComputeSolid();
+
+ var bbMax = geo.Transform.OfPoint(geo.Bounds.Max);
+ var bbMin = geo.Transform.OfPoint(geo.Bounds.Min);
+ bounds.Extend(new[] { bbMax, bbMin });
+ }
+ }
+ }
+
+ return bounds;
+ }
+
+ private void Draw(SvgDocument doc, double rotation)
+ {
+ var plane = new Plane(new Vector3(0, 0, _elevation), Vector3.ZAxis);
+ var customElementsBeforeGrid = new List();
+ var customElementsAfterGrid = new List();
+
+ foreach (var model in _models)
+ {
+ var modelWithoutCustomElements = model;
+
+ modelWithoutCustomElements.Intersect(plane,
+ out Dictionary> intersecting,
+ out Dictionary> back,
+ out Dictionary> lines);
+
+ foreach (var intersectingPolygon in intersecting)
+ {
+ foreach (var p in intersectingPolygon.Value)
+ {
+ doc.Children.Add(p.ToSvgPolygon(_sceneBounds.Min, ViewBoxHeight, FrontContext));
+ }
+ }
+
+ foreach (var backPolygon in back)
+ {
+ foreach (var p in backPolygon.Value)
+ {
+ doc.Children.Add(p.ToSvgPolygon(_sceneBounds.Min, ViewBoxHeight, BackContext));
+ }
+ }
+
+ foreach (var line in lines)
+ {
+ foreach (var l in line.Value)
+ {
+ doc.Children.Add(l.ToSvgLine(_sceneBounds.Min, ViewBoxHeight, FrontContext));
+ }
+ }
+
+ }
+
+ customElementsBeforeGrid.ForEach(el => doc.Children.Add(el));
+ DrawGridLines(doc, rotation, _gridLines);
+ customElementsAfterGrid.ForEach(el => doc.Children.Add(el));
+ }
+
+ private void CreateViewBox(SvgDocument doc, double rotation)
+ {
+ ViewBoxWidth = (float)(_sceneBounds.Max.X - _sceneBounds.Min.X);
+ ViewBoxHeight = (float)(_sceneBounds.Max.Y - _sceneBounds.Min.Y);
+
+
+ if (rotation == 0.0)
+ {
+ doc.ViewBox = new SvgViewBox(0, 0, ViewBoxWidth, ViewBoxHeight);
+ }
+ else
+ {
+ // Compute a new bounding box around the rotated
+ // bounding box, to ensure that we our viewbox isn't too small.
+ var t = new Transform(Vector3.Origin);
+ t.Rotate(rotation);
+ var bounds = new BBox3(_sceneBounds.Corners().Select(v => t.OfPoint(v)));
+ var wOld = ViewBoxWidth;
+ var hOld = ViewBoxHeight;
+ ViewBoxWidth = (float)(bounds.Max.X - bounds.Min.X);
+ ViewBoxHeight = (float)(bounds.Max.Y - bounds.Min.Y);
+ float max = Math.Max(ViewBoxWidth, ViewBoxHeight);
+ doc.ViewBox = new SvgViewBox((wOld - ViewBoxWidth) / 2.0f, (ViewBoxHeight - hOld) / 2.0f, ViewBoxWidth, ViewBoxHeight);
+ doc.CustomAttributes.Add("transform", $"rotate({rotation} {ViewBoxWidth / 2.0} {ViewBoxHeight / 2.0})");
+ }
+ }
+
+ private void DrawGridLines(SvgDocument doc, double rotation, Dictionary gridLines)
+ {
+ foreach (var line in gridLines)
+ {
+ doc.Children.Add(line.Value.ToSvgLine(_sceneBounds.Min, ViewBoxHeight, GridContext));
+ doc.Children.Add(new SvgCircle()
+ {
+ CenterX = line.Value.Start.X.ToXUserUnit(_sceneBounds.Min),
+ CenterY = line.Value.Start.Y.ToYUserUnit(ViewBoxHeight, _sceneBounds.Min),
+ Radius = new SvgUnit(SvgUnitType.User, (float)GridHeadRadius),
+ Stroke = new SvgColourServer(Colors.Black),
+ Fill = new SvgColourServer(Colors.White),
+ StrokeWidth = GridContext.StrokeWidth
+ });
+
+ var x = line.Value.Start.X.ToXUserUnit(_sceneBounds.Min);
+ var y = line.Value.Start.Y.ToYUserUnit(ViewBoxHeight, _sceneBounds.Min);
+
+ var text = new SvgText(line.Key.ToString())
+ {
+ X = new SvgUnitCollection() { x },
+ Y = new SvgUnitCollection() { y },
+ FontStyle = SvgFontStyle.Normal,
+ FontSize = new SvgUnit(SvgUnitType.User, 0.5f),
+ FontFamily = "Arial",
+ Fill = new SvgColourServer(Colors.Black),
+ TextAnchor = SvgTextAnchor.Middle,
+ };
+ text.CustomAttributes.Add("transform", $"rotate({-1 * rotation} {x} {y}), translate(0, 0.2)");
+ doc.Children.Add(text);
+ }
+ }
+
+ ///
+ /// Grow the bounds by the size of the grid and grid heads
+ ///
+ private Dictionary ExtendSceneWithGridLines()
+ {
+ var gridLines = new Dictionary();
+ foreach (var g in _models.SelectMany(m => m.AllElementsOfType()).ToList())
+ {
+ var gl = (Line)g.Curve;
+ var d = gl.Direction();
+ var start = gl.Start + d.Negate() * GridHeadExtension;
+ var end = gl.End + d * GridHeadExtension;
+ gridLines.Add(g.Name, new Line(start, end));
+
+ var l = start + new Vector3(-GridHeadRadius, 0);
+ var r = start + new Vector3(GridHeadRadius, 0);
+ var t = start + new Vector3(0, GridHeadRadius);
+ var b = start + new Vector3(0, -GridHeadRadius);
+
+ _sceneBounds.Extend(start, end, l, r, t, b);
+ }
+ return gridLines;
+ }
+
+ #endregion
+ }
+}
diff --git a/Elements/src/GeometricElement.cs b/Elements/src/GeometricElement.cs
index d986a9761..d05676ad2 100644
--- a/Elements/src/GeometricElement.cs
+++ b/Elements/src/GeometricElement.cs
@@ -51,7 +51,7 @@ public class GeometricElement : Element
public Representation Representation { get; set; }
///
- /// The list of element representations.
+ /// The list of element representations.
///
[JsonIgnore]
public List RepresentationInstances { get; set; } = new List();
@@ -196,7 +196,7 @@ public bool HasGeometry()
/// The plane of intersection.
/// A collection of polygons representing
/// the intersections of the plane and the element's solid geometry.
- /// A collection of polygons representing coplanar
+ /// A collection of polygons representing coplanar
/// faces beyond the plane of intersection.
/// A collection of lines representing intersections
/// of zero-thickness elements with the plane.